From c4f988d85554da07ee7c145fb956cc527660f94f Mon Sep 17 00:00:00 2001 From: kjungw1025 Date: Mon, 25 Dec 2023 19:54:26 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=EA=B8=B0=EC=A1=B4=20StudyTag=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=83=9D=EC=84=B1=20=ED=9B=84,?= =?UTF-8?q?=20=EC=83=88=EB=A1=9C=EC=9A=B4=20StudyTag=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../studytag/model/entity/StudyTag.java | 23 +++++++++++ .../repository/StudyTagRepository.java | 20 +++++++++ .../domain/tag/model/entity/StudyTag.java | 41 ------------------- 3 files changed, 43 insertions(+), 41 deletions(-) create mode 100644 src/main/java/com/dku/council/domain/studytag/model/entity/StudyTag.java create mode 100644 src/main/java/com/dku/council/domain/studytag/repository/StudyTagRepository.java delete mode 100644 src/main/java/com/dku/council/domain/tag/model/entity/StudyTag.java diff --git a/src/main/java/com/dku/council/domain/studytag/model/entity/StudyTag.java b/src/main/java/com/dku/council/domain/studytag/model/entity/StudyTag.java new file mode 100644 index 00000000..d4bce459 --- /dev/null +++ b/src/main/java/com/dku/council/domain/studytag/model/entity/StudyTag.java @@ -0,0 +1,23 @@ +package com.dku.council.domain.studytag.model.entity; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class StudyTag { + @Id + @GeneratedValue + @Column(name = "study_tag_id") + private Long id; + + private String name; + + public StudyTag(String name) { + this.name = name; + } +} diff --git a/src/main/java/com/dku/council/domain/studytag/repository/StudyTagRepository.java b/src/main/java/com/dku/council/domain/studytag/repository/StudyTagRepository.java new file mode 100644 index 00000000..84dd0541 --- /dev/null +++ b/src/main/java/com/dku/council/domain/studytag/repository/StudyTagRepository.java @@ -0,0 +1,20 @@ +package com.dku.council.domain.studytag.repository; + +import com.dku.council.domain.studytag.model.entity.StudyTag; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface StudyTagRepository extends JpaRepository { + @Query("select s from StudyTag s where s.name = :name") + Optional findByName(@Param("name") String name); + + @Query("select s from StudyTag s where s.id = :id") + Optional findById(@Param("id") Long id); + + @Query("select s from StudyTag s") + List findAll(); +} diff --git a/src/main/java/com/dku/council/domain/tag/model/entity/StudyTag.java b/src/main/java/com/dku/council/domain/tag/model/entity/StudyTag.java deleted file mode 100644 index 7cccafe5..00000000 --- a/src/main/java/com/dku/council/domain/tag/model/entity/StudyTag.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.dku.council.domain.tag.model.entity; - -import com.dku.council.domain.with_dankook.model.entity.type.Study; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import javax.persistence.*; - -import static lombok.AccessLevel.*; - -@Entity -@Getter -@NoArgsConstructor(access = PROTECTED) -public class StudyTag { - - @Id - @GeneratedValue - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "study_id") - private Study study; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "tag_id") - private Tag tag; - - public StudyTag(Tag tag) { - this.tag = tag; - } - - public void changeStudy(Study study) { - if (this.study != null) { - this.study.getTags().remove(this); - } - - this.study = study; - this.study.getTags().add(this); - } -} From 8b5f116dc1d671875780dcf89a66d146fecd7c0d Mon Sep 17 00:00:00 2001 From: kjungw1025 Date: Mon, 25 Dec 2023 19:59:30 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20WithDankookUser=20=EC=97=94?= =?UTF-8?q?=ED=8B=B0=ED=8B=B0=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=EC=97=90=20=EB=AA=A8?= =?UTF-8?q?=EC=A7=91=EB=90=9C=20=EC=9D=B8=EC=9B=90=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/entity/WithDankookUser.java | 2 ++ .../repository/WithDankookUserRepository.java | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/main/java/com/dku/council/domain/with_dankook/repository/WithDankookUserRepository.java diff --git a/src/main/java/com/dku/council/domain/with_dankook/model/entity/WithDankookUser.java b/src/main/java/com/dku/council/domain/with_dankook/model/entity/WithDankookUser.java index bc7f8335..25b03e33 100644 --- a/src/main/java/com/dku/council/domain/with_dankook/model/entity/WithDankookUser.java +++ b/src/main/java/com/dku/council/domain/with_dankook/model/entity/WithDankookUser.java @@ -10,6 +10,7 @@ import javax.persistence.*; import static com.dku.council.domain.with_dankook.model.ParticipantStatus.VALID; +import static javax.persistence.EnumType.STRING; import static javax.persistence.FetchType.LAZY; import static lombok.AccessLevel.*; @@ -31,6 +32,7 @@ public class WithDankookUser extends BaseEntity { @JoinColumn(name = "with_dankook_id") private WithDankook withDankook; + @Enumerated(STRING) private ParticipantStatus participantStatus; @Builder diff --git a/src/main/java/com/dku/council/domain/with_dankook/repository/WithDankookUserRepository.java b/src/main/java/com/dku/council/domain/with_dankook/repository/WithDankookUserRepository.java new file mode 100644 index 00000000..e61d47ac --- /dev/null +++ b/src/main/java/com/dku/council/domain/with_dankook/repository/WithDankookUserRepository.java @@ -0,0 +1,17 @@ +package com.dku.council.domain.with_dankook.repository; + +import com.dku.council.domain.with_dankook.model.entity.WithDankookUser; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface WithDankookUserRepository extends JpaRepository, JpaSpecificationExecutor { + @Query("select COUNT(u.withDankook.id) " + + "from WithDankookUser u " + + "where u.withDankook.withDankookStatus = 'ACTIVE' and " + + "u.participantStatus = 'VALID' " + + "group by u.withDankook.id " + + "having u.withDankook.id = :id ") + int findRecruitedById(@Param("id") Long id); +} From c2a05183b549fb4c5237e1e4903befe3c3359af0 Mon Sep 17 00:00:00 2001 From: kjungw1025 Date: Mon, 25 Dec 2023 20:03:32 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=EC=83=88=EB=A1=9C=EC=9A=B4=20Study?= =?UTF-8?q?Tag=20=EC=97=94=ED=8B=B0=ED=8B=B0=EC=97=90=20=EB=8C=80=ED=95=9C?= =?UTF-8?q?=20Study=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=97=B0=EA=B4=80=20?= =?UTF-8?q?=EA=B4=80=EA=B3=84=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../with_dankook/model/entity/type/Study.java | 13 +++++++------ .../with_dankook/repository/StudyRepository.java | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/dku/council/domain/with_dankook/repository/StudyRepository.java diff --git a/src/main/java/com/dku/council/domain/with_dankook/model/entity/type/Study.java b/src/main/java/com/dku/council/domain/with_dankook/model/entity/type/Study.java index 688f0cf7..ddb2eb38 100644 --- a/src/main/java/com/dku/council/domain/with_dankook/model/entity/type/Study.java +++ b/src/main/java/com/dku/council/domain/with_dankook/model/entity/type/Study.java @@ -1,16 +1,15 @@ package com.dku.council.domain.with_dankook.model.entity.type; -import com.dku.council.domain.tag.model.entity.StudyTag; +import com.dku.council.domain.studytag.model.entity.StudyTag; import com.dku.council.domain.user.model.entity.User; import com.dku.council.domain.with_dankook.model.entity.WithDankook; import lombok.*; +import org.springframework.format.annotation.DateTimeFormat; import javax.persistence.*; import javax.validation.constraints.NotNull; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; import static lombok.AccessLevel.*; @@ -36,20 +35,22 @@ public class Study extends WithDankook { @NotNull private LocalDateTime endTime; - @OneToMany - private List tags = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "study_tag_id") + private StudyTag tag; @NotNull @Lob private String content; @Builder - private Study(User user, String chatLink, String title, int minStudentId, LocalDateTime startTime, LocalDateTime endTime, String content) { + private Study(User user, String chatLink, String title, int minStudentId, LocalDateTime startTime, LocalDateTime endTime, StudyTag tag, String content) { super(user, chatLink); this.title = title; this.minStudentId = minStudentId; this.startTime = startTime; this.endTime = endTime; + this.tag = tag; this.content = content; } @Override diff --git a/src/main/java/com/dku/council/domain/with_dankook/repository/StudyRepository.java b/src/main/java/com/dku/council/domain/with_dankook/repository/StudyRepository.java new file mode 100644 index 00000000..b4b57200 --- /dev/null +++ b/src/main/java/com/dku/council/domain/with_dankook/repository/StudyRepository.java @@ -0,0 +1,14 @@ +package com.dku.council.domain.with_dankook.repository; + +import com.dku.council.domain.with_dankook.model.entity.type.Study; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface StudyRepository extends WithDankookRepository{ + @Query("select s from Study s " + + "join fetch s.tag " + + "where s.tag.name = :name ") + List findAllByStudyTagName(@Param("name") String name); +} From c1ca4d92ad6ca1cd228cba965827c70bf92c8d34 Mon Sep 17 00:00:00 2001 From: kjungw1025 Date: Mon, 25 Dec 2023 20:07:23 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20=EB=8B=A8=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=83=9D=EC=84=B1=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=EC=98=A4=EB=A5=98=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../with_dankook/exception/StudyCooltimeException.java | 10 ++++++++++ src/main/resources/errors.properties | 1 + src/main/resources/errors_en_US.properties | 1 + 3 files changed, 12 insertions(+) create mode 100644 src/main/java/com/dku/council/domain/with_dankook/exception/StudyCooltimeException.java diff --git a/src/main/java/com/dku/council/domain/with_dankook/exception/StudyCooltimeException.java b/src/main/java/com/dku/council/domain/with_dankook/exception/StudyCooltimeException.java new file mode 100644 index 00000000..8064c46e --- /dev/null +++ b/src/main/java/com/dku/council/domain/with_dankook/exception/StudyCooltimeException.java @@ -0,0 +1,10 @@ +package com.dku.council.domain.with_dankook.exception; + +import com.dku.council.global.error.exception.LocalizedMessageException; +import org.springframework.http.HttpStatus; + +public class StudyCooltimeException extends LocalizedMessageException { + public StudyCooltimeException(String withDankookType) { + super(HttpStatus.BAD_REQUEST, "cooltime." + withDankookType); + } +} diff --git a/src/main/resources/errors.properties b/src/main/resources/errors.properties index 0c638349..71c8f119 100644 --- a/src/main/resources/errors.properties +++ b/src/main/resources/errors.properties @@ -81,6 +81,7 @@ already.homebus-cancel=\uC774\uBBF8 \uCDE8\uC18C\uB97C \uC694\uCCAD\uD588\uC2B5\ cooltime.petition=\uCCAD\uC6D0\uC744 \uC62C\uB9B0 \uB4A4\uB85C 1\uC77C\uC774 \uC9C0\uB098\uC57C \uB2E4\uC2DC \uAC8C\uC2DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. cooltime.general-forum=\uAC8C\uC2DC\uAE00\uC744 \uC62C\uB9B0 \uB4A4\uB85C 3\uBD84\uC774 \uC9C0\uB098\uC57C \uB2E4\uC2DC \uAC8C\uC2DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. cooltime.trade=\uB2E8\uAD6D \uAC70\uB798 \uAC8C\uC2DC\uAE00\uC744 \uC62C\uB9B0 \uB4A4\uB85C 3\uBD84\uC774 \uC9C0\uB098\uC57C \uB2E4\uC2DC \uAC8C\uC2DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. +cooltime.study=\uB2E8\uD130\uB514 \uAC8C\uC2DC\uAE00\uC744 \uC62C\uB9B0 \uB4A4\uB85C 3\uBD84\uC774 \uC9C0\uB098\uC57C \uB2E4\uC2DC \uAC8C\uC2DC\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. integrity.tag=\uC774 \uD0DC\uADF8\uB97C \uCC38\uC870\uD558\uB294 \uB2E4\uB978 \uAC8C\uC2DC\uAE00\uB4E4\uC774 \uC788\uC2B5\uB2C8\uB2E4. diff --git a/src/main/resources/errors_en_US.properties b/src/main/resources/errors_en_US.properties index 3f20a116..5fedb82e 100644 --- a/src/main/resources/errors_en_US.properties +++ b/src/main/resources/errors_en_US.properties @@ -82,6 +82,7 @@ already.homebus-cancel=You have already requested canceling a homebus ticket. cooltime.petition=You can't post again until one day after posting the petition. cooltime.general-forum=You can't post again until 3 minutes after posting. cooltime.trade=You can't post again until 3 minutes after posting. +cooltime.study=You can't post again until 3 minutes after posting. integrity.tag=There are other posts referencing this tag. From 252a8f27caf3ad3381d1d62c4fe44ff6bb121c7a Mon Sep 17 00:00:00 2001 From: kjungw1025 Date: Mon, 25 Dec 2023 20:12:57 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=EB=8B=A8=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C,=20=EC=83=9D=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=9C?= =?UTF-8?q?=20dto=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20specification=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/dto/list/SummarizedStudyDto.java | 29 ++++++++ .../dto/request/RequestCreateStudyDto.java | 68 +++++++++++++++++++ .../repository/spec/WithDankookSpec.java | 15 ++++ 3 files changed, 112 insertions(+) create mode 100644 src/main/java/com/dku/council/domain/with_dankook/model/dto/list/SummarizedStudyDto.java create mode 100644 src/main/java/com/dku/council/domain/with_dankook/model/dto/request/RequestCreateStudyDto.java diff --git a/src/main/java/com/dku/council/domain/with_dankook/model/dto/list/SummarizedStudyDto.java b/src/main/java/com/dku/council/domain/with_dankook/model/dto/list/SummarizedStudyDto.java new file mode 100644 index 00000000..d7e9a140 --- /dev/null +++ b/src/main/java/com/dku/council/domain/with_dankook/model/dto/list/SummarizedStudyDto.java @@ -0,0 +1,29 @@ +package com.dku.council.domain.with_dankook.model.dto.list; + +import com.dku.council.domain.with_dankook.model.entity.type.Study; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class SummarizedStudyDto extends SummarizedWithDankookDto { + + @Schema(description = "제목", example = "게시글 제목") + private final String title; + + @Schema(description = "내용", example = "게시글 본문") + private final String content; + + @Schema(description = "해시태그") + private final String tag; + + @Schema(description = "모집된 인원", example = "1") + private final int recruited; + + public SummarizedStudyDto(SummarizedWithDankookDto dto, Study study, int recruited) { + super(dto); + this.title = study.getTitle(); + this.content = study.getContent(); + this.tag = study.getTag().getName(); + this.recruited = recruited; + } +} diff --git a/src/main/java/com/dku/council/domain/with_dankook/model/dto/request/RequestCreateStudyDto.java b/src/main/java/com/dku/council/domain/with_dankook/model/dto/request/RequestCreateStudyDto.java new file mode 100644 index 00000000..aa64956b --- /dev/null +++ b/src/main/java/com/dku/council/domain/with_dankook/model/dto/request/RequestCreateStudyDto.java @@ -0,0 +1,68 @@ +package com.dku.council.domain.with_dankook.model.dto.request; + +import com.dku.council.domain.user.model.entity.User; +import com.dku.council.domain.with_dankook.model.entity.type.Study; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +@Getter +public class RequestCreateStudyDto extends RequestCreateWithDankookDto { + @NotBlank + @Schema(description = "제목", example = "제목") + private final String title; + + @NotNull + @Schema(description = "최소 학번", example = "19") + private final int minStudentId; + + @NotNull + @Schema(description = "스터디 시작 시간", example = "2023-12-25 17:30:00") + private final LocalDateTime startTime; + + @NotNull + @Schema(description = "스터디 끝나는 시간", example = "2023-12-25 18:30:00") + private final LocalDateTime endTime; + + @Schema(description = "해시태그", example = "자격증") + private final String tag; + + @NotBlank + @Schema(description = "본문", example = "내용") + private final String content; + + @NotBlank + @Schema(description = "오픈채팅방 링크", example = "https://open.kakao.com/o/abc123") + private final String chatLink; + + public RequestCreateStudyDto (@NotBlank String title, + @NotBlank int minStudentId, + @NotBlank LocalDateTime startTime, + @NotBlank LocalDateTime endTime, + String tag, + @NotBlank String content, + @NotBlank String chatLink) { + this.title = title; + this.minStudentId = minStudentId; + this.startTime = startTime; + this.endTime = endTime; + this.tag = tag; + this.content = content; + this.chatLink = chatLink; + } + + public Study toEntity(User user) { + return Study.builder() + .title(title) + .minStudentId(minStudentId) + .startTime(startTime) + .endTime(endTime) + .content(content) + .user(user) + .chatLink(chatLink) + .build(); + } +} diff --git a/src/main/java/com/dku/council/domain/with_dankook/repository/spec/WithDankookSpec.java b/src/main/java/com/dku/council/domain/with_dankook/repository/spec/WithDankookSpec.java index 077a651b..874da7ee 100644 --- a/src/main/java/com/dku/council/domain/with_dankook/repository/spec/WithDankookSpec.java +++ b/src/main/java/com/dku/council/domain/with_dankook/repository/spec/WithDankookSpec.java @@ -4,6 +4,8 @@ import com.dku.council.domain.with_dankook.model.entity.WithDankook; import org.springframework.data.jpa.domain.Specification; +import javax.persistence.criteria.JoinType; + public class WithDankookSpec { public static Specification withActive() { @@ -23,4 +25,17 @@ public static Specification withTitleOrBody(String ke builder.like(root.get("content"), pattern) ); } + + public static Specification withStudyTagName(String studyTagName) { + if (studyTagName == null || studyTagName.equals("null")) { + return Specification.where(null); + } + + String pattern = "%" + studyTagName + "%"; + return (root, query, builder) -> { + root.fetch("tag", JoinType.LEFT); // 태그가 없는 엔티티를 포함하려면 LEFT JOIN을 사용 + return builder.like(root.get("tag").get("name"), pattern); + }; + } + } From 1d8ae625c357863abaeac8aab38da1a0ff90daf0 Mon Sep 17 00:00:00 2001 From: kjungw1025 Date: Mon, 25 Dec 2023 20:14:32 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=EB=8B=A8=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=93=B1=EB=A1=9D,=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C,=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C,?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../with_dankook/service/StudyService.java | 122 ++++++++++++++++++ .../service/WithDankookUserService.java | 23 ++++ 2 files changed, 145 insertions(+) create mode 100644 src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java create mode 100644 src/main/java/com/dku/council/domain/with_dankook/service/WithDankookUserService.java diff --git a/src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java b/src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java new file mode 100644 index 00000000..a314e55e --- /dev/null +++ b/src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java @@ -0,0 +1,122 @@ +package com.dku.council.domain.with_dankook.service; + +import com.dku.council.domain.studytag.model.entity.StudyTag; +import com.dku.council.domain.studytag.repository.StudyTagRepository; +import com.dku.council.domain.user.model.entity.User; +import com.dku.council.domain.user.repository.UserRepository; +import com.dku.council.domain.with_dankook.exception.StudyCooltimeException; +import com.dku.council.domain.with_dankook.model.dto.list.SummarizedStudyDto; +import com.dku.council.domain.with_dankook.model.dto.request.RequestCreateStudyDto; +import com.dku.council.domain.with_dankook.model.entity.WithDankookUser; +import com.dku.council.domain.with_dankook.model.entity.type.Study; +import com.dku.council.domain.with_dankook.repository.StudyRepository; +import com.dku.council.domain.with_dankook.repository.WithDankookMemoryRepository; +import com.dku.council.domain.with_dankook.repository.WithDankookUserRepository; +import com.dku.council.domain.with_dankook.repository.spec.WithDankookSpec; +import com.dku.council.global.error.exception.UserNotFoundException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional(readOnly = true) +public class StudyService { + + public static final String STUDY_KEY = "study"; + + private final StudyRepository studyRepository; + private final WithDankookMemoryRepository withDankookMemoryRepository; + private final UserRepository userRepository; + private final StudyTagRepository studyTagRepository; + private final WithDankookUserRepository withDankookUserRepository; + + private final WithDankookService withDankookService; + private final WithDankookUserService withDankookUserService; + + private final Clock clock; + + @Value("${app.with-dankook.study.write-cooltime}") + private final Duration writeCooltime; + + @Transactional + public Long create(Long userId, RequestCreateStudyDto dto) { + User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new); + Instant now = Instant.now(clock); + if (withDankookMemoryRepository.isAlreadyContains(STUDY_KEY, userId, now)) { + throw new StudyCooltimeException("study"); + } + + StudyTag studyTag = retrieveStudyTag(dto.getTag()); + + Study study = Study.builder() + .user(user) + .title(dto.getTitle()) + .minStudentId(dto.getMinStudentId()) + .startTime(dto.getStartTime()) + .endTime(dto.getEndTime()) + .tag(studyTag) + .content(dto.getContent()) + .chatLink(dto.getChatLink()) + .build(); + + Long result = studyRepository.save(study).getId(); + + WithDankookUser withDankookUser = WithDankookUser.builder() + .user(user) + .withDankook(study) + .build(); + withDankookUserRepository.save(withDankookUser); + + withDankookMemoryRepository.put(STUDY_KEY, userId, writeCooltime, now); + return result; + } + + private StudyTag retrieveStudyTag (String tagName) { + return studyTagRepository.findByName(tagName) + .orElseGet(() -> { + StudyTag entity = new StudyTag(tagName); + entity = studyTagRepository.save(entity); + return entity; + }); + } + + @Transactional(readOnly = true) + public Page list(String keyword, Pageable pageable, int bodySize) { + Specification spec = WithDankookSpec.withTitleOrBody(keyword); + spec = spec.and(WithDankookSpec.withActive()); + Page result = studyRepository.findAll(spec, pageable); + return result.map((study) -> + new SummarizedStudyDto(withDankookService.makeListDto(bodySize, study), + study, + withDankookUserService.recruitedCount(withDankookService.makeListDto(bodySize, study).getId()) + )); + } + + @Transactional(readOnly = true) + public Page listByStudyTag(String studyTagName, Pageable pageable, int bodySize) { + Specification spec = WithDankookSpec.withStudyTagName(studyTagName); + spec = spec.and(WithDankookSpec.withActive()); + Page result = studyRepository.findAll(spec, pageable); + return result.map((study) -> + new SummarizedStudyDto(withDankookService.makeListDto(bodySize, study), + study, + withDankookUserService.recruitedCount(withDankookService.makeListDto(bodySize, study).getId()) + )); + } + + @Transactional + public void delete(Long studyId, Long userId, boolean isAdmin) { + withDankookService.delete(studyRepository, studyId, userId, isAdmin); + } +} diff --git a/src/main/java/com/dku/council/domain/with_dankook/service/WithDankookUserService.java b/src/main/java/com/dku/council/domain/with_dankook/service/WithDankookUserService.java new file mode 100644 index 00000000..1c98ee28 --- /dev/null +++ b/src/main/java/com/dku/council/domain/with_dankook/service/WithDankookUserService.java @@ -0,0 +1,23 @@ +package com.dku.council.domain.with_dankook.service; + +import com.dku.council.domain.with_dankook.repository.WithDankookUserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class WithDankookUserService { + private final WithDankookUserRepository withDankookUserRepository; + + /** + * With-Dankook 특정 게시판의 모집된 인원 수 조회 + * + * @param withDankookId 조회할 게시글 id + * @return 모집된 인원 수 + */ + public int recruitedCount(Long withDankookId) { + return withDankookUserRepository.findRecruitedById(withDankookId); + } +} From 7ff407b0d5959b555238b0bccb790245f5a8ec20 Mon Sep 17 00:00:00 2001 From: kjungw1025 Date: Mon, 25 Dec 2023 20:15:21 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20=EB=8B=A8=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EB=93=B1=EB=A1=9D,=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C,=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C,?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=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 --- .../controller/StudyController.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java diff --git a/src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java b/src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java new file mode 100644 index 00000000..9a55f5c6 --- /dev/null +++ b/src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java @@ -0,0 +1,82 @@ +package com.dku.council.domain.with_dankook.controller; + +import com.dku.council.domain.post.model.dto.response.ResponsePage; +import com.dku.council.domain.with_dankook.model.dto.list.SummarizedStudyDto; +import com.dku.council.domain.with_dankook.model.dto.request.RequestCreateStudyDto; +import com.dku.council.domain.with_dankook.service.StudyService; +import com.dku.council.global.auth.jwt.AppAuthentication; +import com.dku.council.global.auth.role.UserAuth; +import com.dku.council.global.model.dto.ResponseIdDto; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springdoc.api.annotations.ParameterObject; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +@Tag(name = "단터디 게시판", description = "단터디 게시판 API") +@RestController +@RequestMapping("with-dankook/study") +@RequiredArgsConstructor +public class StudyController { + + private final StudyService studyService; + + /** + * 단터디 게시글 목록 조회 + * + * @param keyword 제목이나 내용에 포함된 검색어. 지정하지 않으면 모든 게시글 조회. + * @param bodySize 게시글 본문 길이. (글자 단위) 지정하지 않으면 50 글자. + * @param pageable 페이징 size, sort, page + * @return 페이징된 단터디 게시판 목록 + */ + @GetMapping + public ResponsePage list(@RequestParam(required = false) String keyword, + @RequestParam(defaultValue = "50") int bodySize, + @ParameterObject Pageable pageable) { + Page list = studyService.list(keyword, pageable, bodySize); + return new ResponsePage<>(list); + } + + /** + * 태그를 통한 단터디 게시글 목록 조회 + * + * @param studyTagName 태그 이름. 지정하지 않으면 모든 게시글 조회. + * @param bodySize 게시글 본문 길이. (글자 단위) 지정하지 않으면 50 글자. + * @param pageable 페이징 size, sort, page + * @return 페이징된 단터디 게시판 목록 + */ + @GetMapping("/tag") + public ResponsePage listByStudyTag(@RequestParam(required = false) String studyTagName, + @RequestParam(defaultValue = "50") int bodySize, + @ParameterObject Pageable pageable) { + Page list = studyService.listByStudyTag(studyTagName, pageable, bodySize); + return new ResponsePage<>(list); + } + + /** + * 단터디 게시글 등록 + */ + @PostMapping + @UserAuth + public ResponseIdDto create(AppAuthentication auth, + @Valid @RequestBody RequestCreateStudyDto dto) { + Long id = studyService.create(auth.getUserId(), dto); + return new ResponseIdDto(id); + } + + /** + * 단터디 게시글 삭제 + * + * @param auth 사용자 인증정보 + * @param id 삭제할 게시글 id + */ + @DeleteMapping("/{id}") + @UserAuth + public void delete(AppAuthentication auth, + @PathVariable Long id) { + studyService.delete(id, auth.getUserId(), auth.isAdmin()); + } +} From 76ff496e1553f106de3652f84346e48f4b997822 Mon Sep 17 00:00:00 2001 From: kjungw1025 Date: Mon, 25 Dec 2023 20:34:39 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20=EB=8B=A8=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EB=82=B4=EA=B0=80=20=EC=9E=91=EC=84=B1=ED=95=9C=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../with_dankook/controller/StudyController.java | 14 ++++++++++++++ .../domain/with_dankook/service/StudyService.java | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java b/src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java index 9a55f5c6..b2cb1bb1 100644 --- a/src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java +++ b/src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java @@ -56,6 +56,20 @@ public ResponsePage listByStudyTag(@RequestParam(required = return new ResponsePage<>(list); } + /** + * 내가 작성한 단터디 게시글 목록 조회 + * + * @param pageable 페이징 size, sort, page + * @return 페이징된 내가 쓴 단터디 게시판 목록 + */ + @GetMapping("/my") + @UserAuth + public ResponsePage listMyPosts(AppAuthentication auth, + @ParameterObject Pageable pageable) { + Page list = studyService.listMyPosts(auth.getUserId(), pageable); + return new ResponsePage<>(list); + } + /** * 단터디 게시글 등록 */ diff --git a/src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java b/src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java index a314e55e..27fde9a7 100644 --- a/src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java +++ b/src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java @@ -115,6 +115,15 @@ public Page listByStudyTag(String studyTagName, Pageable pag )); } + @Transactional(readOnly = true) + public Page listMyPosts(Long userId, Pageable pageable) { + return studyRepository.findAllByUserId(userId, pageable) + .map(study -> new SummarizedStudyDto(withDankookService.makeListDto(50, study), + study, + withDankookUserService.recruitedCount(withDankookService.makeListDto(50, study).getId()) + )); + } + @Transactional public void delete(Long studyId, Long userId, boolean isAdmin) { withDankookService.delete(studyRepository, studyId, userId, isAdmin); From 43996f42a5881b09973f2daec1320a7c57b0e770 Mon Sep 17 00:00:00 2001 From: kjungw1025 Date: Mon, 25 Dec 2023 21:13:18 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=EB=8B=A8=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=83=81=EC=84=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/StudyController.java | 12 ++++++ .../dto/response/ResponseSingleStudyDto.java | 43 +++++++++++++++++++ .../with_dankook/service/StudyService.java | 23 ++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/main/java/com/dku/council/domain/with_dankook/model/dto/response/ResponseSingleStudyDto.java diff --git a/src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java b/src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java index b2cb1bb1..c34f9d74 100644 --- a/src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java +++ b/src/main/java/com/dku/council/domain/with_dankook/controller/StudyController.java @@ -3,6 +3,7 @@ import com.dku.council.domain.post.model.dto.response.ResponsePage; import com.dku.council.domain.with_dankook.model.dto.list.SummarizedStudyDto; import com.dku.council.domain.with_dankook.model.dto.request.RequestCreateStudyDto; +import com.dku.council.domain.with_dankook.model.dto.response.ResponseSingleStudyDto; import com.dku.council.domain.with_dankook.service.StudyService; import com.dku.council.global.auth.jwt.AppAuthentication; import com.dku.council.global.auth.role.UserAuth; @@ -70,6 +71,17 @@ public ResponsePage listMyPosts(AppAuthentication auth, return new ResponsePage<>(list); } + /** + * 단터디 게시글 상세 조회 + * + */ + @GetMapping("/{id}") + @UserAuth + public ResponseSingleStudyDto findOne(AppAuthentication auth, + @PathVariable Long id) { + return studyService.findOne(id, auth.getUserId(), auth.getUserRole()); + } + /** * 단터디 게시글 등록 */ diff --git a/src/main/java/com/dku/council/domain/with_dankook/model/dto/response/ResponseSingleStudyDto.java b/src/main/java/com/dku/council/domain/with_dankook/model/dto/response/ResponseSingleStudyDto.java new file mode 100644 index 00000000..b54f7399 --- /dev/null +++ b/src/main/java/com/dku/council/domain/with_dankook/model/dto/response/ResponseSingleStudyDto.java @@ -0,0 +1,43 @@ +package com.dku.council.domain.with_dankook.model.dto.response; + +import com.dku.council.domain.with_dankook.model.entity.type.Study; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public class ResponseSingleStudyDto extends ResponseSingleWithDankookDto { + + @Schema(description = "제목", example = "게시글 제목") + private final String title; + + @Schema(description = "최소 학번", example = "19") + private final int minStudentId; + + @Schema(description = "스터디 시작 시간", example = "2023-12-25 17:30:00") + private final LocalDateTime startTime; + + @Schema(description = "스터디 끝나는 시간", example = "2023-12-25 18:30:00") + private final LocalDateTime endTime; + + @Schema(description = "해시태그") + private final String tag; + + @Schema(description = "내용", example = "게시글 본문") + private final String content; + + @Schema(description = "모집된 인원", example = "1") + private final int recruited; + + public ResponseSingleStudyDto(ResponseSingleWithDankookDto dto, Study study, int recruited) { + super(dto); + this.title = study.getTitle(); + this.minStudentId = study.getMinStudentId(); + this.startTime = study.getStartTime(); + this.endTime = study.getEndTime(); + this.content = study.getContent(); + this.tag = study.getTag().getName(); + this.recruited = recruited; + } +} diff --git a/src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java b/src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java index 27fde9a7..ba6a925e 100644 --- a/src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java +++ b/src/main/java/com/dku/council/domain/with_dankook/service/StudyService.java @@ -5,14 +5,17 @@ import com.dku.council.domain.user.model.entity.User; import com.dku.council.domain.user.repository.UserRepository; import com.dku.council.domain.with_dankook.exception.StudyCooltimeException; +import com.dku.council.domain.with_dankook.exception.WithDankookNotFoundException; import com.dku.council.domain.with_dankook.model.dto.list.SummarizedStudyDto; import com.dku.council.domain.with_dankook.model.dto.request.RequestCreateStudyDto; +import com.dku.council.domain.with_dankook.model.dto.response.ResponseSingleStudyDto; import com.dku.council.domain.with_dankook.model.entity.WithDankookUser; import com.dku.council.domain.with_dankook.model.entity.type.Study; import com.dku.council.domain.with_dankook.repository.StudyRepository; import com.dku.council.domain.with_dankook.repository.WithDankookMemoryRepository; import com.dku.council.domain.with_dankook.repository.WithDankookUserRepository; import com.dku.council.domain.with_dankook.repository.spec.WithDankookSpec; +import com.dku.council.global.auth.role.UserRole; import com.dku.council.global.error.exception.UserNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -26,6 +29,7 @@ import java.time.Clock; import java.time.Duration; import java.time.Instant; +import java.util.Optional; @Service @RequiredArgsConstructor @@ -124,6 +128,25 @@ public Page listMyPosts(Long userId, Pageable pageable) { )); } + @Transactional(readOnly = true) + public ResponseSingleStudyDto findOne(Long studyId, Long userId, UserRole role) { + Study study = findStudy(studyRepository, studyId, role); + return new ResponseSingleStudyDto(withDankookService.makeSingleDto(userId, study), + study, + withDankookUserService.recruitedCount(withDankookService.makeSingleDto(userId, study).getId()) + ); + } + + private Study findStudy(StudyRepository studyRepository, Long studyId, UserRole role) { + Optional study; + if (role.isAdmin()) { + study = studyRepository.findWithClosedById(studyId); + } else { + study = studyRepository.findById(studyId); + } + return study.orElseThrow(WithDankookNotFoundException::new); + } + @Transactional public void delete(Long studyId, Long userId, boolean isAdmin) { withDankookService.delete(studyRepository, studyId, userId, isAdmin);