From 443535bde3b3b8f23d5da9cd5acb1e69a13e7314 Mon Sep 17 00:00:00 2001 From: mingmingmon Date: Sat, 14 Dec 2024 12:12:48 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat(Hashtag):=20Hashtag=EC=99=80=20BoardHa?= =?UTF-8?q?shtag=20=EA=B4=80=EB=A0=A8=20entity=EC=99=80=20domain=20?= =?UTF-8?q?=EA=B0=9D=EC=B2=B4=20=EC=84=A4=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/BoardHashtagJpaEntity.java | 40 +++++++++++++++++++ .../out/persistence/BoardHashtagMapper.java | 12 ++++++ .../community/board/domain/BoardHashtag.java | 21 ++++++++++ .../out/persistence/HashtagJpaEntity.java | 35 ++++++++++++++++ .../out/persistence/HashtagMapper.java | 12 ++++++ .../community/hashtag/domain/Hashtag.java | 20 ++++++++++ 6 files changed, 140 insertions(+) create mode 100644 src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagJpaEntity.java create mode 100644 src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagMapper.java create mode 100644 src/main/java/page/clab/api/domain/community/board/domain/BoardHashtag.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagJpaEntity.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagMapper.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/domain/Hashtag.java diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagJpaEntity.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagJpaEntity.java new file mode 100644 index 000000000..cd33ec8dc --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagJpaEntity.java @@ -0,0 +1,40 @@ +package page.clab.api.domain.community.board.adapter.out.persistence; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; +import page.clab.api.global.common.domain.BaseEntity; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE board_hashtag SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") +public class BoardHashtagJpaEntity extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "board_id", nullable = false) + private Long boardId; + + @Column(name = "hashtag_id", nullable = false) + private Long hashtagId; + + @Column(name = "is_deleted", nullable = false) + private Boolean isDeleted; +} diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagMapper.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagMapper.java new file mode 100644 index 000000000..f5fecf8ed --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagMapper.java @@ -0,0 +1,12 @@ +package page.clab.api.domain.community.board.adapter.out.persistence; + +import org.mapstruct.Mapper; +import page.clab.api.domain.community.board.domain.BoardHashtag; + +@Mapper(componentModel = "spring") +public interface BoardHashtagMapper { + + BoardHashtagJpaEntity toEntity(BoardHashtag boardHashTag); + + BoardHashtag toDomain(BoardHashtagJpaEntity entity); +} diff --git a/src/main/java/page/clab/api/domain/community/board/domain/BoardHashtag.java b/src/main/java/page/clab/api/domain/community/board/domain/BoardHashtag.java new file mode 100644 index 000000000..7e9331a6b --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/domain/BoardHashtag.java @@ -0,0 +1,21 @@ +package page.clab.api.domain.community.board.domain; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class BoardHashtag { + + Long id; + Long boardId; + Long hashtagId; + Boolean isDeleted; +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagJpaEntity.java b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagJpaEntity.java new file mode 100644 index 000000000..8d156152b --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagJpaEntity.java @@ -0,0 +1,35 @@ +package page.clab.api.domain.community.hashtag.adapter.out.persistence; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; +import page.clab.api.global.common.domain.BaseEntity; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@SQLDelete(sql = "UPDATE hashtag SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted = false") +public class HashtagJpaEntity extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "name", nullable = false) + private String name; +} + diff --git a/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagMapper.java b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagMapper.java new file mode 100644 index 000000000..a04d7def7 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagMapper.java @@ -0,0 +1,12 @@ +package page.clab.api.domain.community.hashtag.adapter.out.persistence; + +import org.mapstruct.Mapper; +import page.clab.api.domain.community.hashtag.domain.Hashtag; + +@Mapper(componentModel = "spring") +public interface HashtagMapper { + + HashtagJpaEntity toEntity(Hashtag hashTag); + + Hashtag toDomain(HashtagJpaEntity entity); +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/domain/Hashtag.java b/src/main/java/page/clab/api/domain/community/hashtag/domain/Hashtag.java new file mode 100644 index 000000000..0c7929c34 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/domain/Hashtag.java @@ -0,0 +1,20 @@ +package page.clab.api.domain.community.hashtag.domain; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class Hashtag { + + Long id; + String name; +} + From 2c83f4530f98a559bf9f349199ee781faf3eb955 Mon Sep 17 00:00:00 2001 From: mingmingmon Date: Sat, 14 Dec 2024 13:35:59 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat(HashtagRegister):=20=ED=95=B4=EC=8B=9C?= =?UTF-8?q?=ED=83=9C=EA=B7=B8=20=EB=93=B1=EB=A1=9D=20api=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/BoardHashtagJpaEntity.java | 2 ++ .../in/web/HashtagRegisterController.java | 34 ++++++++++++++++++ .../out/persistence/HashtagJpaEntity.java | 8 ++++- .../HashtagPersistenceAdapter.java | 35 ++++++++++++++++++ .../out/persistence/HashtagRepository.java | 10 ++++++ .../dto/mapper/HashtagDtoMapper.java | 17 +++++++++ .../dto/request/HashtagRequestDto.java | 13 +++++++ .../port/in/RegisterHashtagUseCase.java | 8 +++++ .../port/in/RetrieveHashtagUseCase.java | 9 +++++ .../port/out/RegisterHashtagPort.java | 8 +++++ .../port/out/RetrieveHashtagPort.java | 11 ++++++ .../service/HashtagRegisterService.java | 36 +++++++++++++++++++ .../service/HashtagRetrieveService.java | 24 +++++++++++++ .../community/hashtag/domain/Hashtag.java | 1 + 14 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/adapter/in/web/HashtagRegisterController.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagPersistenceAdapter.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagRepository.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/application/dto/mapper/HashtagDtoMapper.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/application/dto/request/HashtagRequestDto.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/application/port/in/RegisterHashtagUseCase.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/application/port/in/RetrieveHashtagUseCase.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RegisterHashtagPort.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RetrieveHashtagPort.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/application/service/HashtagRegisterService.java create mode 100644 src/main/java/page/clab/api/domain/community/hashtag/application/service/HashtagRetrieveService.java diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagJpaEntity.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagJpaEntity.java index cd33ec8dc..294aec01a 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagJpaEntity.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagJpaEntity.java @@ -5,6 +5,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -23,6 +24,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @SQLDelete(sql = "UPDATE board_hashtag SET is_deleted = true WHERE id = ?") @SQLRestriction("is_deleted = false") +@Table(name = "board_hashtag") public class BoardHashtagJpaEntity extends BaseEntity { @Id diff --git a/src/main/java/page/clab/api/domain/community/hashtag/adapter/in/web/HashtagRegisterController.java b/src/main/java/page/clab/api/domain/community/hashtag/adapter/in/web/HashtagRegisterController.java new file mode 100644 index 000000000..22c21b4c8 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/adapter/in/web/HashtagRegisterController.java @@ -0,0 +1,34 @@ +package page.clab.api.domain.community.hashtag.adapter.in.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import page.clab.api.domain.community.hashtag.application.dto.request.HashtagRequestDto; +import page.clab.api.domain.community.hashtag.application.port.in.RegisterHashtagUseCase; +import page.clab.api.global.common.dto.ApiResponse; + +@RestController +@RequestMapping("/api/v1/hashtags") +@RequiredArgsConstructor +@Tag(name = "Community - Hashtag", description = "해시태그") +public class HashtagRegisterController { + + private final RegisterHashtagUseCase registerHashtagUseCase; + + @Operation(summary = "[U] 해시태그 생성", description = "ROLE_USER 이상의 권한이 필요함") + @PreAuthorize("hasRole('USER')") + @PostMapping("") + public ApiResponse> registerHashtag( + @Valid @RequestBody HashtagRequestDto requestDto + ) { + List hashTagIds = registerHashtagUseCase.registerHashtag(requestDto); + return ApiResponse.success(hashTagIds); + } +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagJpaEntity.java b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagJpaEntity.java index 8d156152b..00b1a511f 100644 --- a/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagJpaEntity.java +++ b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagJpaEntity.java @@ -5,6 +5,8 @@ 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; @@ -23,13 +25,17 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) @SQLDelete(sql = "UPDATE hashtag SET is_deleted = true WHERE id = ?") @SQLRestriction("is_deleted = false") +@Table(name = "hashtag") public class HashtagJpaEntity extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "name", nullable = false) + @Column(name = "name", unique = true, nullable = false) private String name; + + @Column(name = "is_deleted", nullable = false) + private Boolean isDeleted; } diff --git a/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagPersistenceAdapter.java b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagPersistenceAdapter.java new file mode 100644 index 000000000..390bd6c30 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagPersistenceAdapter.java @@ -0,0 +1,35 @@ +package page.clab.api.domain.community.hashtag.adapter.out.persistence; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import page.clab.api.domain.community.hashtag.application.port.out.RegisterHashtagPort; +import page.clab.api.domain.community.hashtag.application.port.out.RetrieveHashtagPort; +import page.clab.api.domain.community.hashtag.domain.Hashtag; + +@Component +@RequiredArgsConstructor +public class HashtagPersistenceAdapter implements + RegisterHashtagPort, + RetrieveHashtagPort { + + private final HashtagRepository hashtagRepository; + private final HashtagMapper hashtagMapper; + + @Override + public Hashtag save(Hashtag hashtag) { + HashtagJpaEntity entity = hashtagMapper.toEntity(hashtag); + HashtagJpaEntity savedEntity = hashtagRepository.save(entity); + return hashtagMapper.toDomain(savedEntity); + } + + @Override + public Boolean existsByName(String name) { + return hashtagRepository.existsByName(name); + } + + @Override + public Hashtag findByName(String name) { + HashtagJpaEntity entity = hashtagRepository.findByName(name); + return hashtagMapper.toDomain(entity); + } +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagRepository.java b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagRepository.java new file mode 100644 index 000000000..4cb82b50e --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagRepository.java @@ -0,0 +1,10 @@ +package page.clab.api.domain.community.hashtag.adapter.out.persistence; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HashtagRepository extends JpaRepository { + + boolean existsByName(String name); + + HashtagJpaEntity findByName(String name); +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/application/dto/mapper/HashtagDtoMapper.java b/src/main/java/page/clab/api/domain/community/hashtag/application/dto/mapper/HashtagDtoMapper.java new file mode 100644 index 000000000..359205024 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/application/dto/mapper/HashtagDtoMapper.java @@ -0,0 +1,17 @@ +package page.clab.api.domain.community.hashtag.application.dto.mapper; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import page.clab.api.domain.community.hashtag.domain.Hashtag; + +@Component +@RequiredArgsConstructor +public class HashtagDtoMapper { + + public Hashtag fromDto(String name) { + return Hashtag.builder() + .name(name) + .isDeleted(false) + .build(); + } +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/application/dto/request/HashtagRequestDto.java b/src/main/java/page/clab/api/domain/community/hashtag/application/dto/request/HashtagRequestDto.java new file mode 100644 index 000000000..e5f2ea1ee --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/application/dto/request/HashtagRequestDto.java @@ -0,0 +1,13 @@ +package page.clab.api.domain.community.hashtag.application.dto.request; + +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class HashtagRequestDto { + + List hashtagNames = new ArrayList<>(); +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/application/port/in/RegisterHashtagUseCase.java b/src/main/java/page/clab/api/domain/community/hashtag/application/port/in/RegisterHashtagUseCase.java new file mode 100644 index 000000000..6b7607da2 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/application/port/in/RegisterHashtagUseCase.java @@ -0,0 +1,8 @@ +package page.clab.api.domain.community.hashtag.application.port.in; + +import java.util.List; +import page.clab.api.domain.community.hashtag.application.dto.request.HashtagRequestDto; + +public interface RegisterHashtagUseCase { + List registerHashtag(HashtagRequestDto requestDto); +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/application/port/in/RetrieveHashtagUseCase.java b/src/main/java/page/clab/api/domain/community/hashtag/application/port/in/RetrieveHashtagUseCase.java new file mode 100644 index 000000000..05a509b6c --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/application/port/in/RetrieveHashtagUseCase.java @@ -0,0 +1,9 @@ +package page.clab.api.domain.community.hashtag.application.port.in; + +import page.clab.api.domain.community.hashtag.domain.Hashtag; + +public interface RetrieveHashtagUseCase { + Boolean existsByName(String name); + + Hashtag getByName(String name); +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RegisterHashtagPort.java b/src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RegisterHashtagPort.java new file mode 100644 index 000000000..05605727a --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RegisterHashtagPort.java @@ -0,0 +1,8 @@ +package page.clab.api.domain.community.hashtag.application.port.out; + +import page.clab.api.domain.community.hashtag.domain.Hashtag; + +public interface RegisterHashtagPort { + Hashtag save(Hashtag hashtag); + +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RetrieveHashtagPort.java b/src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RetrieveHashtagPort.java new file mode 100644 index 000000000..0b88c7c25 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RetrieveHashtagPort.java @@ -0,0 +1,11 @@ +package page.clab.api.domain.community.hashtag.application.port.out; + +import page.clab.api.domain.community.hashtag.domain.Hashtag; + +public interface RetrieveHashtagPort { + + Boolean existsByName(String name); + + Hashtag findByName(String name); + +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/application/service/HashtagRegisterService.java b/src/main/java/page/clab/api/domain/community/hashtag/application/service/HashtagRegisterService.java new file mode 100644 index 000000000..dbc72e63e --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/application/service/HashtagRegisterService.java @@ -0,0 +1,36 @@ +package page.clab.api.domain.community.hashtag.application.service; + +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import page.clab.api.domain.community.hashtag.application.dto.mapper.HashtagDtoMapper; +import page.clab.api.domain.community.hashtag.application.dto.request.HashtagRequestDto; +import page.clab.api.domain.community.hashtag.application.port.in.RegisterHashtagUseCase; +import page.clab.api.domain.community.hashtag.application.port.in.RetrieveHashtagUseCase; +import page.clab.api.domain.community.hashtag.application.port.out.RegisterHashtagPort; +import page.clab.api.domain.community.hashtag.domain.Hashtag; + +@Service +@RequiredArgsConstructor +public class HashtagRegisterService implements RegisterHashtagUseCase { + + private final RegisterHashtagPort registerHashtagPort; + private final RetrieveHashtagUseCase retrieveHashtagUseCase; + private final HashtagDtoMapper mapper; + + @Override + public List registerHashtag(HashtagRequestDto requestDto) { + List savedHashtagId = new ArrayList<>(); + for (String name : requestDto.getHashtagNames()) { + if (retrieveHashtagUseCase.existsByName(name)) { + savedHashtagId.add(retrieveHashtagUseCase.getByName(name).getId()); + } else { + Hashtag hashtag = mapper.fromDto(name); + Long id = registerHashtagPort.save(hashtag).getId(); + savedHashtagId.add(id); + } + } + return savedHashtagId; + } +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/application/service/HashtagRetrieveService.java b/src/main/java/page/clab/api/domain/community/hashtag/application/service/HashtagRetrieveService.java new file mode 100644 index 000000000..56f0612f7 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/hashtag/application/service/HashtagRetrieveService.java @@ -0,0 +1,24 @@ +package page.clab.api.domain.community.hashtag.application.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import page.clab.api.domain.community.hashtag.application.port.in.RetrieveHashtagUseCase; +import page.clab.api.domain.community.hashtag.application.port.out.RetrieveHashtagPort; +import page.clab.api.domain.community.hashtag.domain.Hashtag; + +@Service +@RequiredArgsConstructor +public class HashtagRetrieveService implements RetrieveHashtagUseCase { + + private final RetrieveHashtagPort retrieveHashtagPort; + + @Override + public Boolean existsByName(String name) { + return retrieveHashtagPort.existsByName(name); + } + + @Override + public Hashtag getByName(String name) { + return retrieveHashtagPort.findByName(name); + } +} diff --git a/src/main/java/page/clab/api/domain/community/hashtag/domain/Hashtag.java b/src/main/java/page/clab/api/domain/community/hashtag/domain/Hashtag.java index 0c7929c34..032459362 100644 --- a/src/main/java/page/clab/api/domain/community/hashtag/domain/Hashtag.java +++ b/src/main/java/page/clab/api/domain/community/hashtag/domain/Hashtag.java @@ -16,5 +16,6 @@ public class Hashtag { Long id; String name; + Boolean isDeleted; } From 64804480078afc9c81920394f9deb17d83aff3e9 Mon Sep 17 00:00:00 2001 From: mingmingmon Date: Sat, 14 Dec 2024 17:26:19 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat(BoardRegister):=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EA=B0=9C=EB=B0=9C?= =?UTF-8?q?=EC=A7=88=EB=AC=B8=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC?= =?UTF-8?q?=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=ED=95=B4=EC=8B=9C=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=A0=80=EC=9E=A5=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BoardHashtagPersistenceAdapter.java | 21 +++++++++++ .../persistence/BoardHashtagRepository.java | 8 +++++ .../dto/mapper/BoardHashtagDtoMapper.java | 27 ++++++++++++++ .../dto/request/BoardHashtagRequestDto.java | 15 ++++++++ .../dto/request/BoardRequestDto.java | 3 ++ .../dto/request/BoardUpdateRequestDto.java | 4 +++ .../response/BoardCategoryResponseDto.java | 3 ++ .../dto/response/BoardDetailsResponseDto.java | 1 + .../dto/response/BoardListResponseDto.java | 2 ++ .../dto/response/BoardMyResponseDto.java | 2 ++ .../port/out/RegisterBoardHashtagPort.java | 7 ++++ .../port/out/RetrieveBoardHashtagPort.java | 7 ++++ .../service/BoardRegisterService.java | 19 +++++++--- .../domain/community/board/domain/Board.java | 4 +++ .../HashtagPersistenceAdapter.java | 14 ++++++++ .../port/out/RetrieveHashtagPort.java | 3 ++ .../ExternalRegisterBoardHashtagUseCase.java | 7 ++++ .../ExternalBoardHashtagRegisterService.java | 35 +++++++++++++++++++ .../port/ExternalRetrieveHashtagUseCase.java | 9 +++++ .../ExternalHashtagRetrievalService.java | 24 +++++++++++++ 20 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java create mode 100644 src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardHashtagDtoMapper.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardHashtagRequestDto.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterBoardHashtagPort.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java create mode 100644 src/main/java/page/clab/api/external/community/board/application/port/ExternalRegisterBoardHashtagUseCase.java create mode 100644 src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRegisterService.java create mode 100644 src/main/java/page/clab/api/external/hashtag/application/port/ExternalRetrieveHashtagUseCase.java create mode 100644 src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java new file mode 100644 index 000000000..3ab4e9f54 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java @@ -0,0 +1,21 @@ +package page.clab.api.domain.community.board.adapter.out.persistence; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import page.clab.api.domain.community.board.application.port.out.RegisterBoardHashtagPort; +import page.clab.api.domain.community.board.domain.BoardHashtag; + +@Component +@RequiredArgsConstructor +public class BoardHashtagPersistenceAdapter implements + RegisterBoardHashtagPort { + + private final BoardHashtagRepository boardHashtagRepository; + private final BoardHashtagMapper mapper; + @Override + public BoardHashtag save(BoardHashtag boardHashtag) { + BoardHashtagJpaEntity entity = mapper.toEntity(boardHashtag); + BoardHashtagJpaEntity savedEntity = boardHashtagRepository.save(entity); + return mapper.toDomain(savedEntity); + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java new file mode 100644 index 000000000..641e09ff9 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java @@ -0,0 +1,8 @@ +package page.clab.api.domain.community.board.adapter.out.persistence; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BoardHashtagRepository extends JpaRepository { +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardHashtagDtoMapper.java b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardHashtagDtoMapper.java new file mode 100644 index 000000000..35cc6d9a8 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardHashtagDtoMapper.java @@ -0,0 +1,27 @@ +package page.clab.api.domain.community.board.application.dto.mapper; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import page.clab.api.domain.community.board.application.dto.request.BoardHashtagRequestDto; +import page.clab.api.domain.community.board.domain.BoardHashtag; + +@Component +@RequiredArgsConstructor +public class BoardHashtagDtoMapper { + + public BoardHashtag fromDto(Long boardId, Long hashtagId){ + return BoardHashtag.builder() + .boardId(boardId) + .hashtagId(hashtagId) + .isDeleted(false) + .build(); + } + + public BoardHashtagRequestDto toDto(Long boardId, List hashtagIdList) { + return BoardHashtagRequestDto.builder() + .boardId(boardId) + .hashtagIdList(hashtagIdList) + .build(); + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardHashtagRequestDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardHashtagRequestDto.java new file mode 100644 index 000000000..69a7dc4c7 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardHashtagRequestDto.java @@ -0,0 +1,15 @@ +package page.clab.api.domain.community.board.application.dto.request; + +import java.util.List; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Builder +public class BoardHashtagRequestDto { + + private Long boardId; + private List hashtagIdList; +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardRequestDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardRequestDto.java index 88481b690..e9fcdf2fc 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardRequestDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardRequestDto.java @@ -33,4 +33,7 @@ public class BoardRequestDto { @NotNull(message = "{notNull.board.wantAnonymous}") @Schema(description = "익명 사용 여부", example = "false", required = true) private boolean wantAnonymous; + + @Schema(description = "해시태그 id 리스트", example = "[1, 2]") + private List hashtagIdList; } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardUpdateRequestDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardUpdateRequestDto.java index 4a96a5f3b..57a962276 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardUpdateRequestDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/request/BoardUpdateRequestDto.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.util.List; import lombok.Getter; import lombok.Setter; import page.clab.api.domain.community.board.domain.BoardCategory; @@ -25,4 +26,7 @@ public class BoardUpdateRequestDto { @NotNull(message = "{notNull.board.wantAnonymous}") @Schema(description = "익명 사용 여부", example = "false") private boolean wantAnonymous; + + @Schema(description = "해시태그 id 리스트", example = "[1, 2]") + private List hashtagIdList; } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardCategoryResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardCategoryResponseDto.java index a817a5c52..b185dccf9 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardCategoryResponseDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardCategoryResponseDto.java @@ -1,5 +1,7 @@ package page.clab.api.domain.community.board.application.dto.response; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; import lombok.Builder; import lombok.Getter; @@ -17,4 +19,5 @@ public class BoardCategoryResponseDto { private Long commentCount; private String imageUrl; private LocalDateTime createdAt; + private List hashtagList; } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardDetailsResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardDetailsResponseDto.java index 6baa5ee30..1611738e4 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardDetailsResponseDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardDetailsResponseDto.java @@ -26,5 +26,6 @@ public class BoardDetailsResponseDto { @JsonProperty("isOwner") private Boolean isOwner; private List emojiInfos; + private List hashtagList; private LocalDateTime createdAt; } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardListResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardListResponseDto.java index da33f5560..92ec0db77 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardListResponseDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardListResponseDto.java @@ -1,5 +1,6 @@ package page.clab.api.domain.community.board.application.dto.response; +import java.util.List; import lombok.Builder; import lombok.Getter; @@ -17,5 +18,6 @@ public class BoardListResponseDto { private String content; private Long commentCount; private String imageUrl; + private List hashtagList; private LocalDateTime createdAt; } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardMyResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardMyResponseDto.java index b4b88ac0a..debaa22a7 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardMyResponseDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardMyResponseDto.java @@ -1,5 +1,6 @@ package page.clab.api.domain.community.board.application.dto.response; +import java.util.List; import lombok.Builder; import lombok.Getter; @@ -14,5 +15,6 @@ public class BoardMyResponseDto { private String writerName; private String title; private String imageUrl; + private List hashtagList; private LocalDateTime createdAt; } diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterBoardHashtagPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterBoardHashtagPort.java new file mode 100644 index 000000000..840200b6e --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RegisterBoardHashtagPort.java @@ -0,0 +1,7 @@ +package page.clab.api.domain.community.board.application.port.out; + +import page.clab.api.domain.community.board.domain.BoardHashtag; + +public interface RegisterBoardHashtagPort { + BoardHashtag save(BoardHashtag boardHashtag); +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java new file mode 100644 index 000000000..b448739d8 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java @@ -0,0 +1,7 @@ +package page.clab.api.domain.community.board.application.port.out; + +import page.clab.api.domain.community.board.domain.BoardHashtag; + +public interface RetrieveBoardHashtagPort { + BoardHashtag getById(Long id); +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java index cdcd2dee8..c09536742 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java @@ -6,11 +6,13 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; +import page.clab.api.domain.community.board.application.dto.mapper.BoardHashtagDtoMapper; import page.clab.api.domain.community.board.application.dto.request.BoardRequestDto; import page.clab.api.domain.community.board.application.port.in.RegisterBoardUseCase; import page.clab.api.domain.community.board.application.port.out.RegisterBoardPort; import page.clab.api.domain.community.board.domain.Board; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; +import page.clab.api.external.community.board.application.port.ExternalRegisterBoardHashtagUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.external.memberManagement.notification.application.port.ExternalSendNotificationUseCase; import page.clab.api.global.common.file.application.UploadedFileService; @@ -27,15 +29,19 @@ public class BoardRegisterService implements RegisterBoardUseCase { private final RegisterBoardPort registerBoardPort; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; private final ExternalSendNotificationUseCase externalSendNotificationUseCase; + private final ExternalRegisterBoardHashtagUseCase externalRegisterBoardHashtagUseCase; private final UploadedFileService uploadedFileService; private final ApplicationEventPublisher eventPublisher; - private final BoardDtoMapper mapper; + private final BoardDtoMapper boardDtoMapper; + private final BoardHashtagDtoMapper boardHashtagDtoMapper; /** * 새로운 게시글을 등록합니다. * *

현재 로그인한 멤버의 정보를 가져와 게시글을 생성하고, 필요한 경우 알림을 전송합니다. - * 게시글 작성 권한을 검증하며, 공지사항일 경우 Slack과 사용자에게 알림을 보냅니다.

+ * 게시글 작성 권한을 검증하며, 공지사항일 경우 Slack과 사용자에게 알림을 보냅니다. + * 개발 질문일 경우 해시태그의 유무를 확인해 등록합니다. + *

* * @param requestDto 게시글 요청 정보 DTO * @return 등록된 게시글의 카테고리 키 @@ -46,7 +52,7 @@ public class BoardRegisterService implements RegisterBoardUseCase { public String registerBoard(BoardRequestDto requestDto) throws PermissionDeniedException { MemberDetailedInfoDto currentMemberInfo = externalRetrieveMemberUseCase.getCurrentMemberDetailedInfo(); List uploadedFiles = uploadedFileService.getUploadedFilesByUrls(requestDto.getFileUrlList()); - Board board = mapper.fromDto(requestDto, currentMemberInfo.getMemberId(), uploadedFiles); + Board board = boardDtoMapper.fromDto(requestDto, currentMemberInfo.getMemberId(), uploadedFiles); board.validateAccessPermissionForCreation(currentMemberInfo); if (board.shouldNotifyForNewBoard(currentMemberInfo)) { externalSendNotificationUseCase.sendNotificationToMember(currentMemberInfo.getMemberId(), @@ -57,6 +63,11 @@ public String registerBoard(BoardRequestDto requestDto) throws PermissionDeniedE eventPublisher.publishEvent(new NotificationEvent(this, ExecutivesAlertType.NEW_BOARD, null, boardInfo)); - return registerBoardPort.save(board).getCategory().getKey(); + Board savedBoard = registerBoardPort.save(board); + + if (savedBoard.isDevelopmentQna() && requestDto.getHashtagIdList() != null) { + externalRegisterBoardHashtagUseCase.registerBoardHashtag(boardHashtagDtoMapper.toDto(savedBoard.getId(), requestDto.getHashtagIdList())); + } + return savedBoard.getCategory().getKey(); } } diff --git a/src/main/java/page/clab/api/domain/community/board/domain/Board.java b/src/main/java/page/clab/api/domain/community/board/domain/Board.java index c7ab80c84..458ea1c2b 100644 --- a/src/main/java/page/clab/api/domain/community/board/domain/Board.java +++ b/src/main/java/page/clab/api/domain/community/board/domain/Board.java @@ -50,6 +50,10 @@ public boolean isNotice() { return this.category.equals(BoardCategory.NOTICE); } + public boolean isDevelopmentQna() { + return this.category.equals(BoardCategory.DEVELOPMENT_QNA); + } + public boolean shouldNotifyForNewBoard(MemberDetailedInfoDto memberInfo) { return memberInfo.isAdminRole() && this.category.equals(BoardCategory.NOTICE); // Assuming 2 is Admin role level } diff --git a/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagPersistenceAdapter.java b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagPersistenceAdapter.java index 390bd6c30..3234eada1 100644 --- a/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagPersistenceAdapter.java +++ b/src/main/java/page/clab/api/domain/community/hashtag/adapter/out/persistence/HashtagPersistenceAdapter.java @@ -5,6 +5,7 @@ import page.clab.api.domain.community.hashtag.application.port.out.RegisterHashtagPort; import page.clab.api.domain.community.hashtag.application.port.out.RetrieveHashtagPort; import page.clab.api.domain.community.hashtag.domain.Hashtag; +import page.clab.api.global.exception.NotFoundException; @Component @RequiredArgsConstructor @@ -32,4 +33,17 @@ public Hashtag findByName(String name) { HashtagJpaEntity entity = hashtagRepository.findByName(name); return hashtagMapper.toDomain(entity); } + + @Override + public Hashtag getById(Long id) { + return hashtagRepository.findById(id) + .map(hashtagMapper::toDomain) + .orElseThrow(() -> new NotFoundException("[Hashtag] id: " + id + "에 해당하는 해시태그가 존재하지 않습니다.")); + + } + + @Override + public Boolean existsById(Long id) { + return hashtagRepository.existsById(id); + } } diff --git a/src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RetrieveHashtagPort.java b/src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RetrieveHashtagPort.java index 0b88c7c25..3a505d73e 100644 --- a/src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RetrieveHashtagPort.java +++ b/src/main/java/page/clab/api/domain/community/hashtag/application/port/out/RetrieveHashtagPort.java @@ -8,4 +8,7 @@ public interface RetrieveHashtagPort { Hashtag findByName(String name); + Hashtag getById(Long id); + + Boolean existsById(Long id); } diff --git a/src/main/java/page/clab/api/external/community/board/application/port/ExternalRegisterBoardHashtagUseCase.java b/src/main/java/page/clab/api/external/community/board/application/port/ExternalRegisterBoardHashtagUseCase.java new file mode 100644 index 000000000..fb9cd2c4a --- /dev/null +++ b/src/main/java/page/clab/api/external/community/board/application/port/ExternalRegisterBoardHashtagUseCase.java @@ -0,0 +1,7 @@ +package page.clab.api.external.community.board.application.port; + +import page.clab.api.domain.community.board.application.dto.request.BoardHashtagRequestDto; + +public interface ExternalRegisterBoardHashtagUseCase { + Long registerBoardHashtag(BoardHashtagRequestDto requestDto); +} diff --git a/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRegisterService.java b/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRegisterService.java new file mode 100644 index 000000000..354c1eacc --- /dev/null +++ b/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRegisterService.java @@ -0,0 +1,35 @@ +package page.clab.api.external.community.board.application.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import page.clab.api.domain.community.board.application.dto.mapper.BoardHashtagDtoMapper; +import page.clab.api.domain.community.board.application.dto.request.BoardHashtagRequestDto; +import page.clab.api.external.community.board.application.port.ExternalRegisterBoardHashtagUseCase; +import page.clab.api.domain.community.board.application.port.out.RegisterBoardHashtagPort; +import page.clab.api.domain.community.board.domain.BoardHashtag; +import page.clab.api.external.hashtag.application.port.ExternalRetrieveHashtagUseCase; +import page.clab.api.global.exception.NotFoundException; + +@Service +@RequiredArgsConstructor +public class ExternalBoardHashtagRegisterService implements ExternalRegisterBoardHashtagUseCase { + + private final RegisterBoardHashtagPort registerBoardHashtagPort; + private final ExternalRetrieveHashtagUseCase externalRetrieveHashtagUseCase; + private final BoardHashtagDtoMapper mapper; + + @Transactional + @Override + public Long registerBoardHashtag(BoardHashtagRequestDto requestDto) { + Long boardId = requestDto.getBoardId(); + for (Long hashtagId : requestDto.getHashtagIdList()) { + if (!externalRetrieveHashtagUseCase.existById(hashtagId)) { + throw new NotFoundException("[Hashtag] id: " + hashtagId + "에 해당하는 해시태그가 존재하지 않습니다."); + }; + BoardHashtag boardHashtag = mapper.fromDto(boardId, hashtagId); + registerBoardHashtagPort.save(boardHashtag); + } + return boardId; + } +} diff --git a/src/main/java/page/clab/api/external/hashtag/application/port/ExternalRetrieveHashtagUseCase.java b/src/main/java/page/clab/api/external/hashtag/application/port/ExternalRetrieveHashtagUseCase.java new file mode 100644 index 000000000..fa25c7c8a --- /dev/null +++ b/src/main/java/page/clab/api/external/hashtag/application/port/ExternalRetrieveHashtagUseCase.java @@ -0,0 +1,9 @@ +package page.clab.api.external.hashtag.application.port; + +import page.clab.api.domain.community.hashtag.domain.Hashtag; + +public interface ExternalRetrieveHashtagUseCase { + Hashtag getById(Long id); + + Boolean existById(Long id); +} diff --git a/src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java b/src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java new file mode 100644 index 000000000..52e12250c --- /dev/null +++ b/src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java @@ -0,0 +1,24 @@ +package page.clab.api.external.hashtag.application.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import page.clab.api.domain.community.hashtag.application.port.out.RetrieveHashtagPort; +import page.clab.api.domain.community.hashtag.domain.Hashtag; +import page.clab.api.external.hashtag.application.port.ExternalRetrieveHashtagUseCase; + +@Service +@RequiredArgsConstructor +public class ExternalHashtagRetrievalService implements ExternalRetrieveHashtagUseCase { + + private final RetrieveHashtagPort retrieveHashtagPort; + + @Override + public Hashtag getById(Long id) { + return retrieveHashtagPort.getById(id); + } + + @Override + public Boolean existById(Long id) { + return retrieveHashtagPort.existsById(id); + } +} From 3f32633a3994051c99f383a00a53a862d0f1dbf5 Mon Sep 17 00:00:00 2001 From: mingmingmon Date: Sat, 14 Dec 2024 18:26:16 +0900 Subject: [PATCH 4/9] =?UTF-8?q?refactor(BoardDtoMapper):=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=ED=8C=90=20=EA=B4=80=EB=A0=A8=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EC=8B=9C=20=ED=95=B4=EC=8B=9C=ED=83=9C=EA=B7=B8=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=ED=8F=AC=ED=95=A8=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BoardHashtagPersistenceAdapter.java | 12 ++++++- .../persistence/BoardHashtagRepository.java | 4 +++ .../dto/mapper/BoardDtoMapper.java | 13 +++++--- .../dto/mapper/BoardHashtagDtoMapper.java | 10 ++++++ .../response/BoardCategoryResponseDto.java | 2 +- .../dto/response/BoardDetailsResponseDto.java | 2 +- .../dto/response/BoardHashtagResponseDto.java | 14 +++++++++ .../dto/response/BoardListResponseDto.java | 2 +- .../dto/response/BoardMyResponseDto.java | 2 +- .../port/out/RetrieveBoardHashtagPort.java | 3 +- .../service/BoardDetailsRetrievalService.java | 12 +++++-- .../service/BoardRetrievalService.java | 7 ++++- .../BoardsByCategoryRetrievalService.java | 7 ++++- .../DeletedBoardsRetrievalService.java | 7 ++++- .../service/MyBoardsRetrievalService.java | 10 +++++- .../ExternalRetrieveBoardHashtagUseCase.java | 8 +++++ .../ExternalBoardHashtagRetrieveService.java | 31 +++++++++++++++++++ .../ExternalHashtagRetrievalService.java | 2 ++ 18 files changed, 132 insertions(+), 16 deletions(-) create mode 100644 src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardHashtagResponseDto.java create mode 100644 src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java create mode 100644 src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java index 3ab4e9f54..05e986894 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java @@ -1,14 +1,17 @@ package page.clab.api.domain.community.board.adapter.out.persistence; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import page.clab.api.domain.community.board.application.port.out.RegisterBoardHashtagPort; +import page.clab.api.domain.community.board.application.port.out.RetrieveBoardHashtagPort; import page.clab.api.domain.community.board.domain.BoardHashtag; @Component @RequiredArgsConstructor public class BoardHashtagPersistenceAdapter implements - RegisterBoardHashtagPort { + RegisterBoardHashtagPort, RetrieveBoardHashtagPort { private final BoardHashtagRepository boardHashtagRepository; private final BoardHashtagMapper mapper; @@ -18,4 +21,11 @@ public BoardHashtag save(BoardHashtag boardHashtag) { BoardHashtagJpaEntity savedEntity = boardHashtagRepository.save(entity); return mapper.toDomain(savedEntity); } + + @Override + public List getAllByBoardId(Long boardId) { + return boardHashtagRepository.findAllByBoardId(boardId).stream() + .map(mapper::toDomain) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java index 641e09ff9..12b120d56 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java @@ -1,8 +1,12 @@ package page.clab.api.domain.community.board.adapter.out.persistence; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface BoardHashtagRepository extends JpaRepository { + + List findAllByBoardId(Long boardId); + } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java index 8ba304cd7..79466421c 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java @@ -6,6 +6,7 @@ import page.clab.api.domain.community.board.application.dto.response.BoardCategoryResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardDetailsResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardEmojiCountResponseDto; +import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardMyResponseDto; import page.clab.api.domain.community.board.application.dto.response.WriterInfo; @@ -39,7 +40,7 @@ public Board fromDto(BoardRequestDto requestDto, String memberId, List emojiInfos) { + public BoardDetailsResponseDto toDto(Board board, MemberDetailedInfoDto memberInfo, boolean isOwner, List emojiInfos, List boardHashtagInfos) { WriterInfo writerInfo = createDetail(board, memberInfo); return BoardDetailsResponseDto.builder() .id(board.getId()) @@ -54,17 +55,19 @@ public BoardDetailsResponseDto toDto(Board board, MemberDetailedInfoDto memberIn .imageUrl(board.getImageUrl()) .isOwner(isOwner) .emojiInfos(emojiInfos) + .boardHashtagInfos(boardHashtagInfos) .createdAt(board.getCreatedAt()) .build(); } - public BoardMyResponseDto toDto(Board board, MemberBasicInfoDto memberInfo) { + public BoardMyResponseDto toDto(Board board, MemberBasicInfoDto memberInfo, List boardHashtagInfos) { return BoardMyResponseDto.builder() .id(board.getId()) .category(board.getCategory().getKey()) .writerName(board.isWantAnonymous() ? board.getNickname() : memberInfo.getMemberName()) .title(board.getTitle()) .imageUrl(board.getImageUrl()) + .boardHashtagInfos(boardHashtagInfos) .createdAt(board.getCreatedAt()) .build(); } @@ -78,7 +81,7 @@ public BoardCommentInfoDto toDto(Board board) { .build(); } - public BoardCategoryResponseDto toCategoryDto(Board board, MemberDetailedInfoDto memberInfo, Long commentCount) { + public BoardCategoryResponseDto toCategoryDto(Board board, MemberDetailedInfoDto memberInfo, Long commentCount, List boardHashtagInfos) { WriterInfo writerInfo = create(board, memberInfo); return BoardCategoryResponseDto.builder() .id(board.getId()) @@ -88,11 +91,12 @@ public BoardCategoryResponseDto toCategoryDto(Board board, MemberDetailedInfoDto .title(board.getTitle()) .commentCount(commentCount) .imageUrl(board.getImageUrl()) + .boardHashtagInfos(boardHashtagInfos) .createdAt(board.getCreatedAt()) .build(); } - public BoardListResponseDto toListDto(Board board, MemberDetailedInfoDto memberInfo, Long commentCount) { + public BoardListResponseDto toListDto(Board board, MemberDetailedInfoDto memberInfo, Long commentCount, List boardHashtagInfos) { WriterInfo writerInfo = create(board, memberInfo); return BoardListResponseDto.builder() .id(board.getId()) @@ -103,6 +107,7 @@ public BoardListResponseDto toListDto(Board board, MemberDetailedInfoDto memberI .content(board.getContent()) .commentCount(commentCount) .imageUrl(board.getImageUrl()) + .boardHashtagInfos(boardHashtagInfos) .createdAt(board.getCreatedAt()) .build(); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardHashtagDtoMapper.java b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardHashtagDtoMapper.java index 35cc6d9a8..1f5d5cc3b 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardHashtagDtoMapper.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardHashtagDtoMapper.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import page.clab.api.domain.community.board.application.dto.request.BoardHashtagRequestDto; +import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; import page.clab.api.domain.community.board.domain.BoardHashtag; @Component @@ -24,4 +25,13 @@ public BoardHashtagRequestDto toDto(Long boardId, List hashtagIdList) { .hashtagIdList(hashtagIdList) .build(); } + + public BoardHashtagResponseDto toDto(BoardHashtag boardHashtag, String name) { + return BoardHashtagResponseDto.builder() + .id(boardHashtag.getId()) + .boardId(boardHashtag.getBoardId()) + .name(name) + .hashtagId(boardHashtag.getHashtagId()) + .build(); + } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardCategoryResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardCategoryResponseDto.java index b185dccf9..d05a22eb6 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardCategoryResponseDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardCategoryResponseDto.java @@ -19,5 +19,5 @@ public class BoardCategoryResponseDto { private Long commentCount; private String imageUrl; private LocalDateTime createdAt; - private List hashtagList; + private List boardHashtagInfos; } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardDetailsResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardDetailsResponseDto.java index 1611738e4..3b93eee28 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardDetailsResponseDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardDetailsResponseDto.java @@ -26,6 +26,6 @@ public class BoardDetailsResponseDto { @JsonProperty("isOwner") private Boolean isOwner; private List emojiInfos; - private List hashtagList; + private List boardHashtagInfos; private LocalDateTime createdAt; } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardHashtagResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardHashtagResponseDto.java new file mode 100644 index 000000000..b08978a64 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardHashtagResponseDto.java @@ -0,0 +1,14 @@ +package page.clab.api.domain.community.board.application.dto.response; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class BoardHashtagResponseDto { + + private Long id; + private Long boardId; + private Long hashtagId; + private String name; +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardListResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardListResponseDto.java index 92ec0db77..e62042092 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardListResponseDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardListResponseDto.java @@ -18,6 +18,6 @@ public class BoardListResponseDto { private String content; private Long commentCount; private String imageUrl; - private List hashtagList; + private List boardHashtagInfos; private LocalDateTime createdAt; } diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardMyResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardMyResponseDto.java index debaa22a7..8675ad49e 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardMyResponseDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardMyResponseDto.java @@ -15,6 +15,6 @@ public class BoardMyResponseDto { private String writerName; private String title; private String imageUrl; - private List hashtagList; + private List boardHashtagInfos; private LocalDateTime createdAt; } diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java index b448739d8..908ac7a51 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java @@ -1,7 +1,8 @@ package page.clab.api.domain.community.board.application.port.out; +import java.util.List; import page.clab.api.domain.community.board.domain.BoardHashtag; public interface RetrieveBoardHashtagPort { - BoardHashtag getById(Long id); + List getAllByBoardId(Long boardId); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardDetailsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardDetailsRetrievalService.java index fc3831ce7..38749b3fe 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardDetailsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardDetailsRetrievalService.java @@ -4,14 +4,20 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import page.clab.api.domain.community.board.adapter.out.persistence.BoardHashtagJpaEntity; import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; +import page.clab.api.domain.community.board.application.dto.mapper.BoardHashtagDtoMapper; import page.clab.api.domain.community.board.application.dto.response.BoardDetailsResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardEmojiCountResponseDto; +import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; import page.clab.api.domain.community.board.application.port.in.RetrieveBoardDetailsUseCase; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardEmojiPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; import page.clab.api.domain.community.board.domain.Board; +import page.clab.api.domain.community.board.domain.BoardHashtag; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; +import page.clab.api.external.community.board.application.service.ExternalBoardHashtagRetrieveService; +import page.clab.api.external.hashtag.application.port.ExternalRetrieveHashtagUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import java.util.List; @@ -24,7 +30,8 @@ public class BoardDetailsRetrievalService implements RetrieveBoardDetailsUseCase private final RetrieveBoardPort retrieveBoardPort; private final RetrieveBoardEmojiPort retrieveBoardEmojiPort; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; - private final BoardDtoMapper mapper; + private final ExternalBoardHashtagRetrieveService externalBoardHashtagRetrieveService; + private final BoardDtoMapper boardDtoMapper; @Transactional @Override @@ -34,7 +41,8 @@ public BoardDetailsResponseDto retrieveBoardDetails(Long boardId) { MemberDetailedInfoDto memberInfo = externalRetrieveMemberUseCase.getMemberDetailedInfoById(board.getMemberId()); boolean isOwner = board.isOwner(currentMemberInfo.getMemberId()); List emojiInfos = getBoardEmojiCountResponseDtoList(boardId, currentMemberInfo.getMemberId()); - return mapper.toDto(board, memberInfo, isOwner, emojiInfos); + List boardHashtagInfos = externalBoardHashtagRetrieveService.getAllByBoardId(boardId); + return boardDtoMapper.toDto(board, memberInfo, isOwner, emojiInfos, boardHashtagInfos); } @Transactional(readOnly = true) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRetrievalService.java index f3e7e227e..376314a6a 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRetrievalService.java @@ -1,17 +1,20 @@ package page.clab.api.domain.community.board.application.service; import com.drew.lang.annotations.NotNull; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; +import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; import page.clab.api.domain.community.board.application.port.in.RetrieveBoardUseCase; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; import page.clab.api.domain.community.board.domain.Board; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; +import page.clab.api.external.community.board.application.port.ExternalRetrieveBoardHashtagUseCase; import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.global.common.dto.PagedResponseDto; @@ -23,6 +26,7 @@ public class BoardRetrievalService implements RetrieveBoardUseCase { private final RetrieveBoardPort retrieveBoardPort; private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; + private final ExternalRetrieveBoardHashtagUseCase externalRetrieveBoardHashtagUseCase; private final BoardDtoMapper mapper; @Transactional @@ -45,6 +49,7 @@ private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) { @NotNull private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetailedInfoDto memberInfo) { Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); - return mapper.toListDto(board, memberInfo, commentCount); + List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getAllByBoardId(board.getId()); + return mapper.toListDto(board, memberInfo, commentCount, boardHashtagInfos); } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java index 6898c7a91..22237e502 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java @@ -1,5 +1,6 @@ package page.clab.api.domain.community.board.application.service; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -7,11 +8,13 @@ import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; import page.clab.api.domain.community.board.application.dto.response.BoardCategoryResponseDto; +import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; import page.clab.api.domain.community.board.application.port.in.RetrieveBoardsByCategoryUseCase; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; import page.clab.api.domain.community.board.domain.Board; import page.clab.api.domain.community.board.domain.BoardCategory; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; +import page.clab.api.external.community.board.application.port.ExternalRetrieveBoardHashtagUseCase; import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.global.common.dto.PagedResponseDto; @@ -23,6 +26,7 @@ public class BoardsByCategoryRetrievalService implements RetrieveBoardsByCategor private final RetrieveBoardPort retrieveBoardPort; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; + private final ExternalRetrieveBoardHashtagUseCase externalRetrieveBoardHashtagUseCase; private final BoardDtoMapper mapper; @Transactional @@ -31,7 +35,8 @@ public PagedResponseDto retrieveBoardsByCategory(Board Page boards = retrieveBoardPort.findAllByCategory(category, pageable); return new PagedResponseDto<>(boards.map(board -> { long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); - return mapper.toCategoryDto(board, getMemberDetailedInfoByBoard(board), commentCount); + List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getAllByBoardId(board.getId()); + return mapper.toCategoryDto(board, getMemberDetailedInfoByBoard(board), commentCount, boardHashtagInfos); })); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/DeletedBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/DeletedBoardsRetrievalService.java index 0a0671a19..b968534ec 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/DeletedBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/DeletedBoardsRetrievalService.java @@ -1,17 +1,20 @@ package page.clab.api.domain.community.board.application.service; import com.drew.lang.annotations.NotNull; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; +import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardListResponseDto; import page.clab.api.domain.community.board.application.port.in.RetrieveDeletedBoardsUseCase; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; import page.clab.api.domain.community.board.domain.Board; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; +import page.clab.api.external.community.board.application.service.ExternalBoardHashtagRetrieveService; import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.global.common.dto.PagedResponseDto; @@ -23,6 +26,7 @@ public class DeletedBoardsRetrievalService implements RetrieveDeletedBoardsUseCa private final RetrieveBoardPort retrieveBoardPort; private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; + private final ExternalBoardHashtagRetrieveService externalBoardHashtagRetrieveService; private final BoardDtoMapper mapper; @Transactional(readOnly = true) @@ -40,6 +44,7 @@ private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) { @NotNull private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetailedInfoDto memberInfo) { Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); - return mapper.toListDto(board, memberInfo, commentCount); + List boardHashtagInfos = externalBoardHashtagRetrieveService.getAllByBoardId(board.getId()); + return mapper.toListDto(board, memberInfo, commentCount, boardHashtagInfos); } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/MyBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/MyBoardsRetrievalService.java index f5d63b4e6..3282802f3 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/MyBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/MyBoardsRetrievalService.java @@ -1,16 +1,20 @@ package page.clab.api.domain.community.board.application.service; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; +import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardMyResponseDto; import page.clab.api.domain.community.board.application.port.in.RetrieveMyBoardsUseCase; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; import page.clab.api.domain.community.board.domain.Board; +import page.clab.api.domain.community.board.domain.BoardHashtag; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberBasicInfoDto; +import page.clab.api.external.community.board.application.port.ExternalRetrieveBoardHashtagUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.global.common.dto.PagedResponseDto; @@ -20,6 +24,7 @@ public class MyBoardsRetrievalService implements RetrieveMyBoardsUseCase { private final RetrieveBoardPort retrieveBoardPort; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; + private final ExternalRetrieveBoardHashtagUseCase externalRetrieveBoardHashtagUseCase; private final BoardDtoMapper mapper; @Transactional @@ -27,6 +32,9 @@ public class MyBoardsRetrievalService implements RetrieveMyBoardsUseCase { public PagedResponseDto retrieveMyBoards(Pageable pageable) { MemberBasicInfoDto currentMemberInfo = externalRetrieveMemberUseCase.getCurrentMemberBasicInfo(); Page boards = retrieveBoardPort.findAllByMemberId(currentMemberInfo.getMemberId(), pageable); - return new PagedResponseDto<>(boards.map(board -> mapper.toDto(board, currentMemberInfo))); + return new PagedResponseDto<>(boards.map(board -> { + List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getAllByBoardId(board.getId()); + return mapper.toDto(board, currentMemberInfo, boardHashtagInfos); + })); } } diff --git a/src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java b/src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java new file mode 100644 index 000000000..41ff145e7 --- /dev/null +++ b/src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java @@ -0,0 +1,8 @@ +package page.clab.api.external.community.board.application.port; + +import java.util.List; +import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; + +public interface ExternalRetrieveBoardHashtagUseCase { + List getAllByBoardId(Long boardId); +} diff --git a/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java b/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java new file mode 100644 index 000000000..4dffbd7bb --- /dev/null +++ b/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java @@ -0,0 +1,31 @@ +package page.clab.api.external.community.board.application.service; + +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import page.clab.api.domain.community.board.application.dto.mapper.BoardHashtagDtoMapper; +import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; +import page.clab.api.domain.community.board.application.port.out.RetrieveBoardHashtagPort; +import page.clab.api.domain.community.board.domain.BoardHashtag; +import page.clab.api.external.community.board.application.port.ExternalRetrieveBoardHashtagUseCase; +import page.clab.api.external.hashtag.application.port.ExternalRetrieveHashtagUseCase; + +@Service +@RequiredArgsConstructor +public class ExternalBoardHashtagRetrieveService implements ExternalRetrieveBoardHashtagUseCase { + + private final RetrieveBoardHashtagPort retrieveBoardHashtagPort; + private final ExternalRetrieveHashtagUseCase externalRetrieveHashtagUseCase; + private final BoardHashtagDtoMapper boardHashtagDtoMapper; + + public List getAllByBoardId(Long boardId) { + List boardHashtagList = retrieveBoardHashtagPort.getAllByBoardId(boardId); + return boardHashtagList.stream() + .map(entity -> { + String name = externalRetrieveHashtagUseCase.getById(entity.getHashtagId()).getName(); + return boardHashtagDtoMapper.toDto(entity, name); + }) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java b/src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java index 52e12250c..8cc4b8871 100644 --- a/src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java +++ b/src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java @@ -1,7 +1,9 @@ package page.clab.api.external.hashtag.application.service; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import page.clab.api.domain.community.board.domain.BoardHashtag; import page.clab.api.domain.community.hashtag.application.port.out.RetrieveHashtagPort; import page.clab.api.domain.community.hashtag.domain.Hashtag; import page.clab.api.external.hashtag.application.port.ExternalRetrieveHashtagUseCase; From 1ca75907197cd58c10387d2704ef1643b0f5c0b5 Mon Sep 17 00:00:00 2001 From: mingmingmon Date: Sat, 14 Dec 2024 20:55:59 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat(BoardUpdate):=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EC=88=98=EC=A0=95=20=EC=8B=9C=20=ED=95=B4=EC=8B=9C?= =?UTF-8?q?=ED=83=9C=EA=B7=B8=EC=9D=98=20=EB=B3=80=EA=B2=BD=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 --- .../BoardHashtagPersistenceAdapter.java | 7 ++ .../persistence/BoardHashtagRepository.java | 3 + .../port/out/RetrieveBoardHashtagPort.java | 2 + .../service/BoardDetailsRetrievalService.java | 2 +- .../service/BoardRetrievalService.java | 2 +- .../service/BoardUpdateService.java | 66 ++++++++++++++++++- .../BoardsByCategoryRetrievalService.java | 2 +- .../DeletedBoardsRetrievalService.java | 2 +- .../service/MyBoardsRetrievalService.java | 2 +- .../domain/community/board/domain/Board.java | 1 + .../community/board/domain/BoardHashtag.java | 5 ++ .../ExternalRetrieveBoardHashtagUseCase.java | 10 ++- .../ExternalBoardHashtagRetrieveService.java | 22 ++++++- 13 files changed, 117 insertions(+), 9 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java index 05e986894..22bec8e7a 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java @@ -28,4 +28,11 @@ public List getAllByBoardId(Long boardId) { .map(mapper::toDomain) .collect(Collectors.toList()); } + + @Override + public List getAllIncludingDeletedByBoardId(Long boardId) { + return boardHashtagRepository.findAllIncludingDeletedByBoardId(boardId).stream() + .map(mapper::toDomain) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java index 12b120d56..b359a67d2 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java @@ -2,6 +2,7 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @Repository @@ -9,4 +10,6 @@ public interface BoardHashtagRepository extends JpaRepository findAllByBoardId(Long boardId); + @Query(value = "SELECT b.* FROM board_hashtag b WHERE b.board_id = :boardId", nativeQuery = true) + List findAllIncludingDeletedByBoardId(Long boardId); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java index 908ac7a51..ffff31f8f 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java @@ -5,4 +5,6 @@ public interface RetrieveBoardHashtagPort { List getAllByBoardId(Long boardId); + + List getAllIncludingDeletedByBoardId(Long boardId); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardDetailsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardDetailsRetrievalService.java index 38749b3fe..4221b9e36 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardDetailsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardDetailsRetrievalService.java @@ -41,7 +41,7 @@ public BoardDetailsResponseDto retrieveBoardDetails(Long boardId) { MemberDetailedInfoDto memberInfo = externalRetrieveMemberUseCase.getMemberDetailedInfoById(board.getMemberId()); boolean isOwner = board.isOwner(currentMemberInfo.getMemberId()); List emojiInfos = getBoardEmojiCountResponseDtoList(boardId, currentMemberInfo.getMemberId()); - List boardHashtagInfos = externalBoardHashtagRetrieveService.getAllByBoardId(boardId); + List boardHashtagInfos = externalBoardHashtagRetrieveService.getBoardHashtagInfoByBoardId(boardId); return boardDtoMapper.toDto(board, memberInfo, isOwner, emojiInfos, boardHashtagInfos); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRetrievalService.java index 376314a6a..a373e73b6 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRetrievalService.java @@ -49,7 +49,7 @@ private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) { @NotNull private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetailedInfoDto memberInfo) { Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); - List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getAllByBoardId(board.getId()); + List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getBoardHashtagInfoByBoardId(board.getId()); return mapper.toListDto(board, memberInfo, commentCount, boardHashtagInfos); } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardUpdateService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardUpdateService.java index de477a919..4070d8b21 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardUpdateService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardUpdateService.java @@ -1,16 +1,25 @@ package page.clab.api.domain.community.board.application.service; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import page.clab.api.domain.community.board.application.dto.mapper.BoardHashtagDtoMapper; import page.clab.api.domain.community.board.application.dto.request.BoardUpdateRequestDto; import page.clab.api.domain.community.board.application.event.BoardUpdatedEvent; import page.clab.api.domain.community.board.application.port.in.UpdateBoardUseCase; +import page.clab.api.domain.community.board.application.port.out.RegisterBoardHashtagPort; import page.clab.api.domain.community.board.application.port.out.RegisterBoardPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; import page.clab.api.domain.community.board.domain.Board; +import page.clab.api.domain.community.board.domain.BoardHashtag; import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; +import page.clab.api.external.community.board.application.port.ExternalRegisterBoardHashtagUseCase; +import page.clab.api.external.community.board.application.port.ExternalRetrieveBoardHashtagUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.global.exception.PermissionDeniedException; @@ -20,8 +29,12 @@ public class BoardUpdateService implements UpdateBoardUseCase { private final RetrieveBoardPort retrieveBoardPort; private final RegisterBoardPort registerBoardPort; + private final RegisterBoardHashtagPort registerBoardHashtagPort; private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; + private final ExternalRetrieveBoardHashtagUseCase externalRetrieveBoardHashtagUseCase; + private final ExternalRegisterBoardHashtagUseCase externalRegisterBoardHashtagUseCase; private final ApplicationEventPublisher eventPublisher; + private final BoardHashtagDtoMapper boardHashtagDtoMapper; @Transactional @Override @@ -30,8 +43,59 @@ public String updateBoard(Long boardId, BoardUpdateRequestDto requestDto) throws Board board = retrieveBoardPort.getById(boardId); board.validateAccessPermission(currentMemberInfo); board.update(requestDto); - registerBoardPort.save(board); + updateBoardHashtag(boardId, requestDto.getHashtagIdList(), externalRetrieveBoardHashtagUseCase.getAllIncludingDeletedByBoardId(boardId)); eventPublisher.publishEvent(new BoardUpdatedEvent(this, board.getId())); + registerBoardPort.save(board); return board.getCategory().getKey(); } + + @Transactional + public void updateBoardHashtag(Long boardId, List newHashtagIds, List currentBoardHashtags) { + List currentHashtagIds = externalRetrieveBoardHashtagUseCase.extractAllHashtagId(currentBoardHashtags); + List hashtagsToRemove = currentHashtagIds.stream() + .filter(id -> !newHashtagIds.contains(id)) + .toList(); + + List hashtagsToAdd = newHashtagIds.stream() + .filter(id -> { + // 조건 1: currentHashtagIds에 없는 경우 + if (!currentHashtagIds.contains(id)) { + return true; + } + // 조건 2: currentBoardHashtags에서 isDeleted=true이고 newHashtagIds에 포함된 경우 + return currentBoardHashtags.stream() + .anyMatch(boardHashtag -> + boardHashtag.getHashtagId().equals(id) && + boardHashtag.getIsDeleted() + ); + }) + .toList(); + hashtagsToRemove.forEach(idToRemove -> { + currentBoardHashtags.stream() + .filter(boardHashtag -> boardHashtag.getHashtagId().equals(idToRemove)) + .forEach(boardHashtag -> { + boardHashtag.toggleIsDeletedStatus(); + registerBoardHashtagPort.save(boardHashtag); + }); + }); + + hashtagsToAdd.forEach(idToAdd -> addHashtag(boardId, idToAdd, currentBoardHashtags)); + } + + private void addHashtag(Long boardId, Long hashtagId, List currentBoardHashtags) { + currentBoardHashtags.stream() + .filter(boardHashtag -> boardHashtag.getHashtagId().equals(hashtagId) && boardHashtag.getIsDeleted()) + .findFirst() + .ifPresentOrElse( + boardHashtag -> { + boardHashtag.toggleIsDeletedStatus(); + registerBoardHashtagPort.save(boardHashtag); + }, + () -> { + externalRegisterBoardHashtagUseCase.registerBoardHashtag( + boardHashtagDtoMapper.toDto(boardId, new ArrayList<>(Arrays.asList(hashtagId))) + ); + } + ); + } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java index 22237e502..bed0f1115 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java @@ -35,7 +35,7 @@ public PagedResponseDto retrieveBoardsByCategory(Board Page boards = retrieveBoardPort.findAllByCategory(category, pageable); return new PagedResponseDto<>(boards.map(board -> { long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); - List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getAllByBoardId(board.getId()); + List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getBoardHashtagInfoByBoardId(board.getId()); return mapper.toCategoryDto(board, getMemberDetailedInfoByBoard(board), commentCount, boardHashtagInfos); })); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/DeletedBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/DeletedBoardsRetrievalService.java index b968534ec..58d1c556e 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/DeletedBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/DeletedBoardsRetrievalService.java @@ -44,7 +44,7 @@ private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) { @NotNull private BoardListResponseDto mapToBoardListResponseDto(Board board, MemberDetailedInfoDto memberInfo) { Long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); - List boardHashtagInfos = externalBoardHashtagRetrieveService.getAllByBoardId(board.getId()); + List boardHashtagInfos = externalBoardHashtagRetrieveService.getBoardHashtagInfoByBoardId(board.getId()); return mapper.toListDto(board, memberInfo, commentCount, boardHashtagInfos); } } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/MyBoardsRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/MyBoardsRetrievalService.java index 3282802f3..61e97a291 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/MyBoardsRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/MyBoardsRetrievalService.java @@ -33,7 +33,7 @@ public PagedResponseDto retrieveMyBoards(Pageable pageable) MemberBasicInfoDto currentMemberInfo = externalRetrieveMemberUseCase.getCurrentMemberBasicInfo(); Page boards = retrieveBoardPort.findAllByMemberId(currentMemberInfo.getMemberId(), pageable); return new PagedResponseDto<>(boards.map(board -> { - List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getAllByBoardId(board.getId()); + List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getBoardHashtagInfoByBoardId(board.getId()); return mapper.toDto(board, currentMemberInfo, boardHashtagInfos); })); } diff --git a/src/main/java/page/clab/api/domain/community/board/domain/Board.java b/src/main/java/page/clab/api/domain/community/board/domain/Board.java index 458ea1c2b..ef72c2921 100644 --- a/src/main/java/page/clab/api/domain/community/board/domain/Board.java +++ b/src/main/java/page/clab/api/domain/community/board/domain/Board.java @@ -1,5 +1,6 @@ package page.clab.api.domain.community.board.domain; +import java.util.stream.Collectors; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/page/clab/api/domain/community/board/domain/BoardHashtag.java b/src/main/java/page/clab/api/domain/community/board/domain/BoardHashtag.java index 7e9331a6b..aca9d9f33 100644 --- a/src/main/java/page/clab/api/domain/community/board/domain/BoardHashtag.java +++ b/src/main/java/page/clab/api/domain/community/board/domain/BoardHashtag.java @@ -1,5 +1,6 @@ package page.clab.api.domain.community.board.domain; +import java.time.LocalDateTime; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -18,4 +19,8 @@ public class BoardHashtag { Long boardId; Long hashtagId; Boolean isDeleted; + + public void toggleIsDeletedStatus() { + this.isDeleted = !this.isDeleted; + } } diff --git a/src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java b/src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java index 41ff145e7..70bc823ab 100644 --- a/src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java +++ b/src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java @@ -2,7 +2,15 @@ import java.util.List; import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; +import page.clab.api.domain.community.board.domain.BoardHashtag; public interface ExternalRetrieveBoardHashtagUseCase { - List getAllByBoardId(Long boardId); + List getBoardHashtagInfoByBoardId(Long boardId); + + List getAllByBoardId(Long boardId); + + List extractAllHashtagId(List boardHashtagList); + + List getAllIncludingDeletedByBoardId(Long boardId); + } diff --git a/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java b/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java index 4dffbd7bb..b8bdace90 100644 --- a/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java +++ b/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java @@ -19,8 +19,9 @@ public class ExternalBoardHashtagRetrieveService implements ExternalRetrieveBoar private final ExternalRetrieveHashtagUseCase externalRetrieveHashtagUseCase; private final BoardHashtagDtoMapper boardHashtagDtoMapper; - public List getAllByBoardId(Long boardId) { - List boardHashtagList = retrieveBoardHashtagPort.getAllByBoardId(boardId); + @Override + public List getBoardHashtagInfoByBoardId(Long boardId) { + List boardHashtagList = getAllByBoardId(boardId); return boardHashtagList.stream() .map(entity -> { String name = externalRetrieveHashtagUseCase.getById(entity.getHashtagId()).getName(); @@ -28,4 +29,21 @@ public List getAllByBoardId(Long boardId) { }) .collect(Collectors.toList()); } + + @Override + public List getAllByBoardId(Long boardId) { + return retrieveBoardHashtagPort.getAllByBoardId(boardId); + } + + @Override + public List extractAllHashtagId(List boardHashtagList) { + return boardHashtagList.stream() + .map(BoardHashtag::getHashtagId) + .collect(Collectors.toList()); + } + + @Override + public List getAllIncludingDeletedByBoardId(Long boardId) { + return retrieveBoardHashtagPort.getAllIncludingDeletedByBoardId(boardId); + } } From 6b2a81e291b06f18a1ce28e12c0148dabfa1bb82 Mon Sep 17 00:00:00 2001 From: mingmingmon Date: Sun, 15 Dec 2024 00:03:43 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat(BoardsByHashtagRetrievalController):?= =?UTF-8?q?=20=ED=95=B4=EC=8B=9C=ED=83=9C=EA=B7=B8=EB=A1=9C=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8A=94=20API?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BoardsByCategoryRetrievalController.java | 8 +-- .../BoardsByHashtagRetrievalController.java | 46 +++++++++++++++ .../BoardHashtagPersistenceAdapter.java | 6 ++ .../persistence/BoardHashtagRepository.java | 3 +- .../BoardHashtagRepositoryCustom.java | 9 +++ .../BoardHashtagRepositoryImpl.java | 29 +++++++++ .../dto/mapper/BoardDtoMapper.java | 6 +- ...Dto.java => BoardOverviewResponseDto.java} | 3 +- .../in/RetrieveBoardsByCategoryUseCase.java | 4 +- .../in/RetrieveBoardsByHashtagUseCase.java | 10 ++++ .../port/out/RetrieveBoardHashtagPort.java | 4 ++ .../BoardsByCategoryRetrievalService.java | 4 +- .../BoardsByHashtagRetrievalService.java | 59 +++++++++++++++++++ .../ExternalRetrieveBoardHashtagUseCase.java | 3 + .../ExternalBoardHashtagRetrieveService.java | 6 ++ .../port/ExternalRetrieveHashtagUseCase.java | 2 + .../ExternalHashtagRetrievalService.java | 8 ++- 17 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardsByHashtagRetrievalController.java create mode 100644 src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepositoryCustom.java create mode 100644 src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepositoryImpl.java rename src/main/java/page/clab/api/domain/community/board/application/dto/response/{BoardCategoryResponseDto.java => BoardOverviewResponseDto.java} (84%) create mode 100644 src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveBoardsByHashtagUseCase.java create mode 100644 src/main/java/page/clab/api/domain/community/board/application/service/BoardsByHashtagRetrievalService.java diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardsByCategoryRetrievalController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardsByCategoryRetrievalController.java index 980443e66..ff9f50427 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardsByCategoryRetrievalController.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardsByCategoryRetrievalController.java @@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import page.clab.api.domain.community.board.application.dto.response.BoardCategoryResponseDto; +import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto; import page.clab.api.domain.community.board.application.port.in.RetrieveBoardsByCategoryUseCase; import page.clab.api.domain.community.board.domain.BoardCategory; import page.clab.api.global.common.dto.ApiResponse; @@ -33,15 +33,15 @@ public class BoardsByCategoryRetrievalController { "DTO의 필드명을 기준으로 정렬 가능하며, 정렬 방향은 오름차순(asc)과 내림차순(desc)이 가능함") @PreAuthorize("hasRole('GUEST')") @GetMapping("/category") - public ApiResponse> retrieveBoardsByCategory( + public ApiResponse> retrieveBoardsByCategory( @RequestParam(name = "category") BoardCategory category, @RequestParam(name = "page", defaultValue = "0") int page, @RequestParam(name = "size", defaultValue = "20") int size, @RequestParam(name = "sortBy", defaultValue = "createdAt") List sortBy, @RequestParam(name = "sortDirection", defaultValue = "desc") List sortDirection ) throws SortingArgumentException, InvalidColumnException { - Pageable pageable = pageableUtils.createPageable(page, size, sortBy, sortDirection, BoardCategoryResponseDto.class); - PagedResponseDto boards = retrieveBoardsByCategoryUseCase.retrieveBoardsByCategory(category, pageable); + Pageable pageable = pageableUtils.createPageable(page, size, sortBy, sortDirection, BoardOverviewResponseDto.class); + PagedResponseDto boards = retrieveBoardsByCategoryUseCase.retrieveBoardsByCategory(category, pageable); return ApiResponse.success(boards); } } diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardsByHashtagRetrievalController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardsByHashtagRetrievalController.java new file mode 100644 index 000000000..699a55a2e --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardsByHashtagRetrievalController.java @@ -0,0 +1,46 @@ +package page.clab.api.domain.community.board.adapter.in.web; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto; +import page.clab.api.domain.community.board.application.port.in.RetrieveBoardsByHashtagUseCase; +import page.clab.api.global.common.dto.ApiResponse; +import page.clab.api.global.common.dto.PagedResponseDto; +import page.clab.api.global.exception.InvalidColumnException; +import page.clab.api.global.exception.SortingArgumentException; +import page.clab.api.global.util.PageableUtils; + +@RestController +@RequestMapping("/api/v1/boards") +@RequiredArgsConstructor +@Tag(name = "Community - Board", description = "커뮤니티 게시판") +public class BoardsByHashtagRetrievalController { + + private final RetrieveBoardsByHashtagUseCase retrieveBoardsByHashtagUseCase; + private final PageableUtils pageableUtils; + + @Operation(summary = "[G] 커뮤니티 게시글 해시태그로 조회", description = "ROLE_GUEST 이상의 권한이 필요함
" + + "DTO의 필드명을 기준으로 정렬 가능하며, 정렬 방향은 오름차순(asc)과 내림차순(desc)이 가능함
" + + "현재는 카테고리가 개발질문인 게시글만 해시태그가 적용되어 있어서 해당 API의 응답으로 개발질문 게시판만 반환됨") + @PreAuthorize("hasRole('GUEST')") + @GetMapping("/hashtag") + public ApiResponse> retrieveBoardsByCategory( + @RequestParam(name = "hashtags") List hashtags, + @RequestParam(name = "page", defaultValue = "0") int page, + @RequestParam(name = "size", defaultValue = "20") int size, + @RequestParam(name = "sortBy", defaultValue = "createdAt") List sortBy, + @RequestParam(name = "sortDirection", defaultValue = "desc") List sortDirection + ) throws SortingArgumentException, InvalidColumnException { + Pageable pageable = pageableUtils.createPageable(page, size, sortBy, sortDirection, BoardOverviewResponseDto.class); + PagedResponseDto boards = retrieveBoardsByHashtagUseCase.retrieveBoardsByHashtag(hashtags, pageable); + return ApiResponse.success(boards); + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java index 22bec8e7a..6db57b8ca 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagPersistenceAdapter.java @@ -3,6 +3,8 @@ import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Component; import page.clab.api.domain.community.board.application.port.out.RegisterBoardHashtagPort; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardHashtagPort; @@ -35,4 +37,8 @@ public List getAllIncludingDeletedByBoardId(Long boardId) { .map(mapper::toDomain) .collect(Collectors.toList()); } + + public List getBoardIdsByHashTagId(List hashtagIds, Pageable pageable) { + return boardHashtagRepository.getBoardIdsByHashTagId(hashtagIds, pageable); + } } diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java index b359a67d2..41b9d9889 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepository.java @@ -3,10 +3,11 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.stereotype.Repository; @Repository -public interface BoardHashtagRepository extends JpaRepository { +public interface BoardHashtagRepository extends JpaRepository, BoardHashtagRepositoryCustom, QuerydslPredicateExecutor { List findAllByBoardId(Long boardId); diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepositoryCustom.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepositoryCustom.java new file mode 100644 index 000000000..97644c1c6 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepositoryCustom.java @@ -0,0 +1,9 @@ +package page.clab.api.domain.community.board.adapter.out.persistence; + +import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface BoardHashtagRepositoryCustom { + List getBoardIdsByHashTagId(List hashtagIds, Pageable pageable); +} diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepositoryImpl.java b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepositoryImpl.java new file mode 100644 index 000000000..e2a23c601 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/adapter/out/persistence/BoardHashtagRepositoryImpl.java @@ -0,0 +1,29 @@ +package page.clab.api.domain.community.board.adapter.out.persistence; + +import com.querydsl.jpa.JPQLQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class BoardHashtagRepositoryImpl implements BoardHashtagRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + @Override + public List getBoardIdsByHashTagId(List hashtagIds, Pageable pageable) { + QBoardHashtagJpaEntity boardHashtag = QBoardHashtagJpaEntity.boardHashtagJpaEntity; + + JPQLQuery query = queryFactory.selectDistinct(boardHashtag.boardId) + .from(boardHashtag) + .where(boardHashtag.hashtagId.in(hashtagIds) + .and(boardHashtag.isDeleted.eq(false))) + .groupBy(boardHashtag.boardId) + .having(boardHashtag.hashtagId.count().eq((long) hashtagIds.size())); + + return query.fetch(); + } +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java index 79466421c..214a032c3 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/mapper/BoardDtoMapper.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import page.clab.api.domain.community.board.application.dto.request.BoardRequestDto; -import page.clab.api.domain.community.board.application.dto.response.BoardCategoryResponseDto; +import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardDetailsResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardEmojiCountResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; @@ -81,9 +81,9 @@ public BoardCommentInfoDto toDto(Board board) { .build(); } - public BoardCategoryResponseDto toCategoryDto(Board board, MemberDetailedInfoDto memberInfo, Long commentCount, List boardHashtagInfos) { + public BoardOverviewResponseDto toCategoryDto(Board board, MemberDetailedInfoDto memberInfo, Long commentCount, List boardHashtagInfos) { WriterInfo writerInfo = create(board, memberInfo); - return BoardCategoryResponseDto.builder() + return BoardOverviewResponseDto.builder() .id(board.getId()) .category(board.getCategory().getKey()) .writerId(writerInfo.getId()) diff --git a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardCategoryResponseDto.java b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardOverviewResponseDto.java similarity index 84% rename from src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardCategoryResponseDto.java rename to src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardOverviewResponseDto.java index d05a22eb6..bbaba7afe 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardCategoryResponseDto.java +++ b/src/main/java/page/clab/api/domain/community/board/application/dto/response/BoardOverviewResponseDto.java @@ -1,6 +1,5 @@ package page.clab.api.domain.community.board.application.dto.response; -import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; import lombok.Builder; import lombok.Getter; @@ -9,7 +8,7 @@ @Getter @Builder -public class BoardCategoryResponseDto { +public class BoardOverviewResponseDto { private Long id; private String category; diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveBoardsByCategoryUseCase.java b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveBoardsByCategoryUseCase.java index a704d28f2..1693bb27e 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveBoardsByCategoryUseCase.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveBoardsByCategoryUseCase.java @@ -1,10 +1,10 @@ package page.clab.api.domain.community.board.application.port.in; import org.springframework.data.domain.Pageable; -import page.clab.api.domain.community.board.application.dto.response.BoardCategoryResponseDto; +import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto; import page.clab.api.domain.community.board.domain.BoardCategory; import page.clab.api.global.common.dto.PagedResponseDto; public interface RetrieveBoardsByCategoryUseCase { - PagedResponseDto retrieveBoardsByCategory(BoardCategory category, Pageable pageable); + PagedResponseDto retrieveBoardsByCategory(BoardCategory category, Pageable pageable); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveBoardsByHashtagUseCase.java b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveBoardsByHashtagUseCase.java new file mode 100644 index 000000000..5038be794 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/port/in/RetrieveBoardsByHashtagUseCase.java @@ -0,0 +1,10 @@ +package page.clab.api.domain.community.board.application.port.in; + +import java.util.List; +import org.springframework.data.domain.Pageable; +import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto; +import page.clab.api.global.common.dto.PagedResponseDto; + +public interface RetrieveBoardsByHashtagUseCase { + PagedResponseDto retrieveBoardsByHashtag(List hashtags, Pageable pageable); +} diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java index ffff31f8f..b34250bb8 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/out/RetrieveBoardHashtagPort.java @@ -1,10 +1,14 @@ package page.clab.api.domain.community.board.application.port.out; import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import page.clab.api.domain.community.board.domain.BoardHashtag; public interface RetrieveBoardHashtagPort { List getAllByBoardId(Long boardId); List getAllIncludingDeletedByBoardId(Long boardId); + + List getBoardIdsByHashTagId(List hashtagIds, Pageable pageable); } diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java index bed0f1115..2a79e8821 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByCategoryRetrievalService.java @@ -7,7 +7,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; -import page.clab.api.domain.community.board.application.dto.response.BoardCategoryResponseDto; +import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto; import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; import page.clab.api.domain.community.board.application.port.in.RetrieveBoardsByCategoryUseCase; import page.clab.api.domain.community.board.application.port.out.RetrieveBoardPort; @@ -31,7 +31,7 @@ public class BoardsByCategoryRetrievalService implements RetrieveBoardsByCategor @Transactional @Override - public PagedResponseDto retrieveBoardsByCategory(BoardCategory category, Pageable pageable) { + public PagedResponseDto retrieveBoardsByCategory(BoardCategory category, Pageable pageable) { Page boards = retrieveBoardPort.findAllByCategory(category, pageable); return new PagedResponseDto<>(boards.map(board -> { long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByHashtagRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByHashtagRetrievalService.java new file mode 100644 index 000000000..a36acc61d --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByHashtagRetrievalService.java @@ -0,0 +1,59 @@ +package page.clab.api.domain.community.board.application.service; + +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; +import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; +import page.clab.api.domain.community.board.application.dto.response.BoardOverviewResponseDto; +import page.clab.api.domain.community.board.application.port.in.RetrieveBoardsByHashtagUseCase; +import page.clab.api.domain.community.board.domain.Board; +import page.clab.api.domain.memberManagement.member.application.dto.shared.MemberDetailedInfoDto; +import page.clab.api.external.community.board.application.port.ExternalRetrieveBoardHashtagUseCase; +import page.clab.api.external.community.board.application.port.ExternalRetrieveBoardUseCase; +import page.clab.api.external.community.comment.application.port.ExternalRetrieveCommentUseCase; +import page.clab.api.external.hashtag.application.port.ExternalRetrieveHashtagUseCase; +import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; +import page.clab.api.global.common.dto.PagedResponseDto; + +@Service +@RequiredArgsConstructor +public class BoardsByHashtagRetrievalService implements RetrieveBoardsByHashtagUseCase { + + private final ExternalRetrieveHashtagUseCase externalRetrieveHashtagUseCase; + private final ExternalRetrieveCommentUseCase externalRetrieveCommentUseCase; + private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase; + private final ExternalRetrieveBoardHashtagUseCase externalRetrieveBoardHashtagUseCase; + private final ExternalRetrieveBoardUseCase externalRetrieveBoardUseCase; + private final BoardDtoMapper mapper; + + @Override + public PagedResponseDto retrieveBoardsByHashtag(List hashtags, Pageable pageable) { + List hashtagIds = new ArrayList<>(); + for (String hashtag : hashtags) { + hashtagIds.add(externalRetrieveHashtagUseCase.getByName(hashtag).getId()); + } + + List boardIds = externalRetrieveBoardHashtagUseCase.getBoardIdsByHashTagId(hashtagIds, pageable); + + List boards = boardIds.stream() + .map(externalRetrieveBoardUseCase::getById) + .toList(); + + Page boardPage = new PageImpl<>(boards, pageable, boardIds.size()); + + return new PagedResponseDto<>(boardPage.map(board -> { + long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); + List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getBoardHashtagInfoByBoardId(board.getId()); + return mapper.toCategoryDto(board, getMemberDetailedInfoByBoard(board), commentCount, boardHashtagInfos); + })); + } + + private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) { + return externalRetrieveMemberUseCase.getMemberDetailedInfoById(board.getMemberId()); + } +} diff --git a/src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java b/src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java index 70bc823ab..3fbe05d3c 100644 --- a/src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java +++ b/src/main/java/page/clab/api/external/community/board/application/port/ExternalRetrieveBoardHashtagUseCase.java @@ -1,6 +1,8 @@ package page.clab.api.external.community.board.application.port; import java.util.List; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; import page.clab.api.domain.community.board.domain.BoardHashtag; @@ -13,4 +15,5 @@ public interface ExternalRetrieveBoardHashtagUseCase { List getAllIncludingDeletedByBoardId(Long boardId); + List getBoardIdsByHashTagId(List hashtagIds, Pageable pageable); } diff --git a/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java b/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java index b8bdace90..e5346fb84 100644 --- a/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java +++ b/src/main/java/page/clab/api/external/community/board/application/service/ExternalBoardHashtagRetrieveService.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import page.clab.api.domain.community.board.application.dto.mapper.BoardHashtagDtoMapper; import page.clab.api.domain.community.board.application.dto.response.BoardHashtagResponseDto; @@ -46,4 +47,9 @@ public List extractAllHashtagId(List boardHashtagList) { public List getAllIncludingDeletedByBoardId(Long boardId) { return retrieveBoardHashtagPort.getAllIncludingDeletedByBoardId(boardId); } + + @Override + public List getBoardIdsByHashTagId(List hashtagIds, Pageable pageable) { + return retrieveBoardHashtagPort.getBoardIdsByHashTagId(hashtagIds, pageable); + } } diff --git a/src/main/java/page/clab/api/external/hashtag/application/port/ExternalRetrieveHashtagUseCase.java b/src/main/java/page/clab/api/external/hashtag/application/port/ExternalRetrieveHashtagUseCase.java index fa25c7c8a..3fdb078db 100644 --- a/src/main/java/page/clab/api/external/hashtag/application/port/ExternalRetrieveHashtagUseCase.java +++ b/src/main/java/page/clab/api/external/hashtag/application/port/ExternalRetrieveHashtagUseCase.java @@ -6,4 +6,6 @@ public interface ExternalRetrieveHashtagUseCase { Hashtag getById(Long id); Boolean existById(Long id); + + Hashtag getByName(String name); } diff --git a/src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java b/src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java index 8cc4b8871..232af9ed1 100644 --- a/src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java +++ b/src/main/java/page/clab/api/external/hashtag/application/service/ExternalHashtagRetrievalService.java @@ -3,7 +3,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import page.clab.api.domain.community.board.domain.BoardHashtag; +import page.clab.api.domain.community.hashtag.application.port.in.RetrieveHashtagUseCase; import page.clab.api.domain.community.hashtag.application.port.out.RetrieveHashtagPort; import page.clab.api.domain.community.hashtag.domain.Hashtag; import page.clab.api.external.hashtag.application.port.ExternalRetrieveHashtagUseCase; @@ -12,6 +12,7 @@ @RequiredArgsConstructor public class ExternalHashtagRetrievalService implements ExternalRetrieveHashtagUseCase { + private final RetrieveHashtagUseCase retrieveHashtagUseCase; private final RetrieveHashtagPort retrieveHashtagPort; @Override @@ -23,4 +24,9 @@ public Hashtag getById(Long id) { public Boolean existById(Long id) { return retrieveHashtagPort.existsById(id); } + + @Override + public Hashtag getByName(String name) { + return retrieveHashtagUseCase.getByName(name); + } } From 4c4a70dca7521a3409758f19ef3541dd6d4017ea Mon Sep 17 00:00:00 2001 From: mingmingmon Date: Sun, 15 Dec 2024 01:53:06 +0900 Subject: [PATCH 7/9] =?UTF-8?q?fix(BoardsByHashtagRetrievalService):=20?= =?UTF-8?q?=EC=9E=98=EB=AA=BB=EB=90=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/BoardsByHashtagRetrievalService.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByHashtagRetrievalService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByHashtagRetrievalService.java index a36acc61d..2053635c5 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByHashtagRetrievalService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardsByHashtagRetrievalService.java @@ -19,6 +19,7 @@ import page.clab.api.external.hashtag.application.port.ExternalRetrieveHashtagUseCase; import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase; import page.clab.api.global.common.dto.PagedResponseDto; +import page.clab.api.global.util.PaginationUtils; @Service @RequiredArgsConstructor @@ -44,13 +45,16 @@ public PagedResponseDto retrieveBoardsByHashtag(List boardPage = new PageImpl<>(boards, pageable, boardIds.size()); + List boardOverviewResponseDtos = + boards.stream().map(board -> { + long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); + List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getBoardHashtagInfoByBoardId(board.getId()); + return mapper.toCategoryDto(board, getMemberDetailedInfoByBoard(board), commentCount, boardHashtagInfos); + }).toList(); - return new PagedResponseDto<>(boardPage.map(board -> { - long commentCount = externalRetrieveCommentUseCase.countByBoardId(board.getId()); - List boardHashtagInfos = externalRetrieveBoardHashtagUseCase.getBoardHashtagInfoByBoardId(board.getId()); - return mapper.toCategoryDto(board, getMemberDetailedInfoByBoard(board), commentCount, boardHashtagInfos); - })); + List paginatedBoardOverviewDtos = PaginationUtils.applySortingAndSlicing(boardOverviewResponseDtos, pageable); + + return new PagedResponseDto<>(paginatedBoardOverviewDtos, boardIds.size(), pageable); } private MemberDetailedInfoDto getMemberDetailedInfoByBoard(Board board) { From 8496bd4efd4e3c3f4701f53d67d57380318b76ba Mon Sep 17 00:00:00 2001 From: mingmingmon Date: Sun, 15 Dec 2024 02:02:11 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor(BoardRegisterController):=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=EC=A7=88=EB=AC=B8=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=ED=8C=90=EC=9D=B4=20=EC=95=84=EB=8B=8C=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=ED=8C=90=EC=97=90=20=ED=95=B4=EC=8B=9C=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=ED=95=A0=20=EA=B2=BD=EC=9A=B0=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=EB=B0=9C=EC=83=9D=20=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../board/adapter/in/web/BoardRegisterController.java | 1 + .../exception/InvalidBoardCategoryHashtagException.java | 9 +++++++++ .../board/application/port/in/RegisterBoardUseCase.java | 1 + .../board/application/service/BoardRegisterService.java | 5 +++++ .../clab/api/global/handler/GlobalExceptionHandler.java | 4 +++- 5 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/main/java/page/clab/api/domain/community/board/application/exception/InvalidBoardCategoryHashtagException.java diff --git a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardRegisterController.java b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardRegisterController.java index 4a3050f5a..114b06266 100644 --- a/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardRegisterController.java +++ b/src/main/java/page/clab/api/domain/community/board/adapter/in/web/BoardRegisterController.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.apache.coyote.BadRequestException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; diff --git a/src/main/java/page/clab/api/domain/community/board/application/exception/InvalidBoardCategoryHashtagException.java b/src/main/java/page/clab/api/domain/community/board/application/exception/InvalidBoardCategoryHashtagException.java new file mode 100644 index 000000000..4e1e0ee46 --- /dev/null +++ b/src/main/java/page/clab/api/domain/community/board/application/exception/InvalidBoardCategoryHashtagException.java @@ -0,0 +1,9 @@ +package page.clab.api.domain.community.board.application.exception; + +public class InvalidBoardCategoryHashtagException extends RuntimeException { + + public InvalidBoardCategoryHashtagException(String message) { + super(message); + } +} + diff --git a/src/main/java/page/clab/api/domain/community/board/application/port/in/RegisterBoardUseCase.java b/src/main/java/page/clab/api/domain/community/board/application/port/in/RegisterBoardUseCase.java index af5fdf3c6..4f49dd60b 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/port/in/RegisterBoardUseCase.java +++ b/src/main/java/page/clab/api/domain/community/board/application/port/in/RegisterBoardUseCase.java @@ -1,5 +1,6 @@ package page.clab.api.domain.community.board.application.port.in; +import org.apache.coyote.BadRequestException; import page.clab.api.domain.community.board.application.dto.request.BoardRequestDto; import page.clab.api.global.exception.PermissionDeniedException; diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java index c09536742..dfba2467c 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java @@ -2,12 +2,14 @@ import java.util.List; import lombok.RequiredArgsConstructor; +import org.apache.coyote.BadRequestException; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import page.clab.api.domain.community.board.application.dto.mapper.BoardDtoMapper; import page.clab.api.domain.community.board.application.dto.mapper.BoardHashtagDtoMapper; import page.clab.api.domain.community.board.application.dto.request.BoardRequestDto; +import page.clab.api.domain.community.board.application.exception.InvalidBoardCategoryHashtagException; import page.clab.api.domain.community.board.application.port.in.RegisterBoardUseCase; import page.clab.api.domain.community.board.application.port.out.RegisterBoardPort; import page.clab.api.domain.community.board.domain.Board; @@ -65,6 +67,9 @@ public String registerBoard(BoardRequestDto requestDto) throws PermissionDeniedE Board savedBoard = registerBoardPort.save(board); + if (!savedBoard.isDevelopmentQna() && requestDto.getHashtagIdList() != null) { + throw new InvalidBoardCategoryHashtagException("개발질문 게시판에만 해시태그를 등록할 수 있습니다."); + } if (savedBoard.isDevelopmentQna() && requestDto.getHashtagIdList() != null) { externalRegisterBoardHashtagUseCase.registerBoardHashtag(boardHashtagDtoMapper.toDto(savedBoard.getId(), requestDto.getHashtagIdList())); } diff --git a/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java b/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java index ac269d054..3bc92ca24 100644 --- a/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java +++ b/src/main/java/page/clab/api/global/handler/GlobalExceptionHandler.java @@ -51,6 +51,7 @@ import page.clab.api.domain.auth.login.application.exception.LoginFailedException; import page.clab.api.domain.auth.login.application.exception.MemberLockedException; import page.clab.api.domain.community.accuse.application.exception.AccuseTargetTypeIncorrectException; +import page.clab.api.domain.community.board.application.exception.InvalidBoardCategoryHashtagException; import page.clab.api.domain.hiring.application.application.exception.NotApprovedApplicationException; import page.clab.api.domain.hiring.application.application.exception.RecruitmentEndDateExceededException; import page.clab.api.domain.hiring.application.application.exception.RecruitmentNotActiveException; @@ -125,7 +126,8 @@ public class GlobalExceptionHandler { SortingArgumentException.class, UnknownPathException.class, AssignmentBoardHasNoDueDateTimeException.class, - FeedbackBoardHasNoContentException.class + FeedbackBoardHasNoContentException.class, + InvalidBoardCategoryHashtagException.class }) public ErrorResponse badRequestException(HttpServletResponse response, Exception e) { response.setStatus(HttpServletResponse.SC_OK); From c85f96bc97c22bdfc9a5c585971cdf2794517628 Mon Sep 17 00:00:00 2001 From: mingmingmon Date: Sun, 15 Dec 2024 15:06:52 +0900 Subject: [PATCH 9/9] =?UTF-8?q?fix(BoardRegisterService,=20BoardUpdateServ?= =?UTF-8?q?ice):=20=EA=B0=9C=EB=B0=9C=EC=A7=88=EB=AC=B8=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC=EA=B0=80=20=EC=95=84=EB=8B=8C=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20=ED=95=B4=EC=8B=9C=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=8B=9C=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/service/BoardRegisterService.java | 2 +- .../board/application/service/BoardUpdateService.java | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java index dfba2467c..896a1d115 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardRegisterService.java @@ -67,7 +67,7 @@ public String registerBoard(BoardRequestDto requestDto) throws PermissionDeniedE Board savedBoard = registerBoardPort.save(board); - if (!savedBoard.isDevelopmentQna() && requestDto.getHashtagIdList() != null) { + if (!savedBoard.isDevelopmentQna() && (requestDto.getHashtagIdList() != null && !requestDto.getHashtagIdList().isEmpty())) { throw new InvalidBoardCategoryHashtagException("개발질문 게시판에만 해시태그를 등록할 수 있습니다."); } if (savedBoard.isDevelopmentQna() && requestDto.getHashtagIdList() != null) { diff --git a/src/main/java/page/clab/api/domain/community/board/application/service/BoardUpdateService.java b/src/main/java/page/clab/api/domain/community/board/application/service/BoardUpdateService.java index 4070d8b21..2255fcbb1 100644 --- a/src/main/java/page/clab/api/domain/community/board/application/service/BoardUpdateService.java +++ b/src/main/java/page/clab/api/domain/community/board/application/service/BoardUpdateService.java @@ -11,6 +11,7 @@ import page.clab.api.domain.community.board.application.dto.mapper.BoardHashtagDtoMapper; import page.clab.api.domain.community.board.application.dto.request.BoardUpdateRequestDto; import page.clab.api.domain.community.board.application.event.BoardUpdatedEvent; +import page.clab.api.domain.community.board.application.exception.InvalidBoardCategoryHashtagException; import page.clab.api.domain.community.board.application.port.in.UpdateBoardUseCase; import page.clab.api.domain.community.board.application.port.out.RegisterBoardHashtagPort; import page.clab.api.domain.community.board.application.port.out.RegisterBoardPort; @@ -43,14 +44,18 @@ public String updateBoard(Long boardId, BoardUpdateRequestDto requestDto) throws Board board = retrieveBoardPort.getById(boardId); board.validateAccessPermission(currentMemberInfo); board.update(requestDto); - updateBoardHashtag(boardId, requestDto.getHashtagIdList(), externalRetrieveBoardHashtagUseCase.getAllIncludingDeletedByBoardId(boardId)); + updateBoardHashtag(board, requestDto.getHashtagIdList(), externalRetrieveBoardHashtagUseCase.getAllIncludingDeletedByBoardId(boardId)); eventPublisher.publishEvent(new BoardUpdatedEvent(this, board.getId())); registerBoardPort.save(board); return board.getCategory().getKey(); } @Transactional - public void updateBoardHashtag(Long boardId, List newHashtagIds, List currentBoardHashtags) { + public void updateBoardHashtag(Board board, List newHashtagIds, List currentBoardHashtags) { + if (!board.isDevelopmentQna()) { + throw new InvalidBoardCategoryHashtagException("개발질문 게시판에만 해시태그를 적용할 수 있습니다."); + } + List currentHashtagIds = externalRetrieveBoardHashtagUseCase.extractAllHashtagId(currentBoardHashtags); List hashtagsToRemove = currentHashtagIds.stream() .filter(id -> !newHashtagIds.contains(id)) @@ -79,7 +84,7 @@ public void updateBoardHashtag(Long boardId, List newHashtagIds, List addHashtag(boardId, idToAdd, currentBoardHashtags)); + hashtagsToAdd.forEach(idToAdd -> addHashtag(board.getId(), idToAdd, currentBoardHashtags)); } private void addHashtag(Long boardId, Long hashtagId, List currentBoardHashtags) {