From 6aed5379ebff7f9b07b631ca2822cb5ea04ab15f Mon Sep 17 00:00:00 2001 From: devdynam0507 Date: Wed, 27 Jul 2022 01:46:55 +0900 Subject: [PATCH 1/4] =?UTF-8?q?letters=20=EC=9E=84=EC=8B=9C=20=20=ED=97=88?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/net/collabstack/app/letter/LetterController.java | 2 ++ .../main/java/net/collabstack/app/letter/LetterService.java | 2 ++ .../main/java/net/collabstack/app/letter/domain/Letter.java | 2 ++ .../net/collabstack/app/letter/domain/LetterRepository.java | 2 ++ .../java/net/collabstack/app/letter/dto/LetterResponse.java | 2 ++ .../net/collabstack/app/letter/dto/LetterSaveRequest.java | 2 ++ .../main/java/net/collabstack/app/letter/util/DateUtil.java | 2 ++ .../java/net/collabstack/app/letter/util/DateUtilTest.java | 4 ++++ .../main/java/net/collabstack/security/SecurityConfig.java | 6 +++++- 9 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java create mode 100644 collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java create mode 100644 collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/Letter.java create mode 100644 collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java create mode 100644 collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterResponse.java create mode 100644 collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterSaveRequest.java create mode 100644 collabstack-be/app/src/main/java/net/collabstack/app/letter/util/DateUtil.java create mode 100644 collabstack-be/app/src/test/java/net/collabstack/app/letter/util/DateUtilTest.java diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java new file mode 100644 index 0000000..68b5f43 --- /dev/null +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java @@ -0,0 +1,2 @@ +package net.collabstack.app.letter;public class LetterController { +} diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java new file mode 100644 index 0000000..b45ddff --- /dev/null +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java @@ -0,0 +1,2 @@ +package net.collabstack.app.letter;public class LetterService { +} diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/Letter.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/Letter.java new file mode 100644 index 0000000..fa4c858 --- /dev/null +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/Letter.java @@ -0,0 +1,2 @@ +package net.collabstack.app.letter.domain;public class Letter { +} diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java new file mode 100644 index 0000000..11d2ee1 --- /dev/null +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java @@ -0,0 +1,2 @@ +package net.collabstack.app.letter.domain;public interface LetterRepository { +} diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterResponse.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterResponse.java new file mode 100644 index 0000000..c42056c --- /dev/null +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterResponse.java @@ -0,0 +1,2 @@ +package net.collabstack.app.letter.dto;public class LetterResponse { +} diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterSaveRequest.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterSaveRequest.java new file mode 100644 index 0000000..74edfc4 --- /dev/null +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterSaveRequest.java @@ -0,0 +1,2 @@ +package net.collabstack.app.letter.dto;public class LetterSaveRequest { +} diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/util/DateUtil.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/util/DateUtil.java new file mode 100644 index 0000000..4965f30 --- /dev/null +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/util/DateUtil.java @@ -0,0 +1,2 @@ +package net.collabstack.app.letter.util;public class DateUtil { +} diff --git a/collabstack-be/app/src/test/java/net/collabstack/app/letter/util/DateUtilTest.java b/collabstack-be/app/src/test/java/net/collabstack/app/letter/util/DateUtilTest.java new file mode 100644 index 0000000..f2a830d --- /dev/null +++ b/collabstack-be/app/src/test/java/net/collabstack/app/letter/util/DateUtilTest.java @@ -0,0 +1,4 @@ +import static org.junit.jupiter.api.Assertions.*; +class DateUtilTest { + +} \ No newline at end of file diff --git a/collabstack-be/security/src/main/java/net/collabstack/security/SecurityConfig.java b/collabstack-be/security/src/main/java/net/collabstack/security/SecurityConfig.java index 3f34e9a..8afbedf 100644 --- a/collabstack-be/security/src/main/java/net/collabstack/security/SecurityConfig.java +++ b/collabstack-be/security/src/main/java/net/collabstack/security/SecurityConfig.java @@ -46,19 +46,22 @@ public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws E http.formLogin().disable(); http.logout().disable(); http.httpBasic().disable(); + http.headers().frameOptions().sameOrigin(); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http .authorizeRequests() .antMatchers(HttpMethod.POST, "/v1/login").permitAll() // TODO: 추후에 인증 필터 거치도록 변경해야합니다. .antMatchers(HttpMethod.GET, "/v1/member/*").permitAll() + .antMatchers(HttpMethod.GET, "/v1/letters/*").permitAll() + .antMatchers(HttpMethod.POST, "/v1/letters/*").permitAll() .and().authorizeRequests() .antMatchers( "/favicon.ico", "/h2-console/**", "/hello", "/error" - ).hasAnyRole() + ).permitAll() .and() .authorizeRequests().anyRequest().hasRole("Member") .and() @@ -84,6 +87,7 @@ CorsConfigurationSource corsConfigurationSource() { corsConfiguration.addAllowedOrigin("https://collabstack.net"); corsConfiguration.addAllowedOrigin("https://www.collabstack.net"); corsConfiguration.addAllowedOrigin("http://localhost:8700"); + corsConfiguration.addAllowedOrigin("http://localhost:8080"); corsConfiguration.addAllowedMethod("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.setAllowCredentials(true); From 89fa9b72cabb654abd3b86a77c01dee447044c04 Mon Sep 17 00:00:00 2001 From: devdynam0507 Date: Wed, 27 Jul 2022 01:47:15 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=EC=B6=94=EC=B2=9C=EC=84=9C=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/collabstack/app/CollabStackApp.java | 1 - .../app/letter/LetterController.java | 53 ++++++++++- .../collabstack/app/letter/LetterService.java | 45 ++++++++- .../collabstack/app/letter/domain/Letter.java | 54 ++++++++++- .../app/letter/domain/LetterRepository.java | 10 +- .../app/letter/dto/LetterResponse.java | 36 ++++++- .../app/letter/dto/LetterSaveRequest.java | 25 ++++- .../collabstack/app/letter/util/DateUtil.java | 36 ++++++- .../app/letter/util/DateUtilTest.java | 94 ++++++++++++++++++- 9 files changed, 344 insertions(+), 10 deletions(-) diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/CollabStackApp.java b/collabstack-be/app/src/main/java/net/collabstack/app/CollabStackApp.java index f2cd63e..7316999 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/CollabStackApp.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/CollabStackApp.java @@ -11,5 +11,4 @@ public class CollabStackApp { public static void main(final String[] args) { SpringApplication.run(CollabStackApp.class, args); } - } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java index 68b5f43..b1774bd 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java @@ -1,2 +1,53 @@ -package net.collabstack.app.letter;public class LetterController { +package net.collabstack.app.letter; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.validation.Valid; +import javax.validation.constraints.Email; + +import org.springframework.http.HttpStatus; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import net.collabstack.app.letter.domain.Letter; +import net.collabstack.app.letter.dto.LetterResponse; +import net.collabstack.app.letter.dto.LetterSaveRequest; +import net.collabstack.common.CommonResponse; +import net.collabstack.common.ResultCode; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/v1/letters") +@RequiredArgsConstructor +@Validated +public class LetterController { + + private final LetterService letterService; + + @GetMapping("/{ownerEmail}") + public CommonResponse> getLetters( + @PathVariable("ownerEmail") @Email final String ownerEmail) { + final List letters = letterService.getLetters(ownerEmail); + final List letterResponses = letters.stream() + .map(LetterResponse::from) + .collect(Collectors.toList()); + return CommonResponse.success(ResultCode.OK, "letters", letterResponses); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public CommonResponse saveLetter( + @RequestBody @Valid final LetterSaveRequest letterSaveRequest) { + final Letter savedLetter = letterService.saveLetter(letterSaveRequest); + final LetterResponse letterResponse = LetterResponse.from(savedLetter); + return CommonResponse.success(ResultCode.CREATED, "saved", letterResponse); + } } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java index b45ddff..ba5c693 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java @@ -1,2 +1,45 @@ -package net.collabstack.app.letter;public class LetterService { +package net.collabstack.app.letter; + +import java.time.LocalDateTime; +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import net.collabstack.app.letter.domain.Letter; +import net.collabstack.app.letter.domain.LetterRepository; +import net.collabstack.app.letter.dto.LetterSaveRequest; +import net.collabstack.app.member.MemberService; +import net.collabstack.app.member.domain.Member; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class LetterService { + + private final LetterRepository letterRepository; + private final MemberService memberService; + + @Transactional(readOnly = true) + public List getLetters(final String ownerId) { + return letterRepository.findByOwner_email(ownerId); + } + + @Transactional + public Letter saveLetter(final LetterSaveRequest letterSaveRequest) { + final Member recommender = memberService.getMember(letterSaveRequest.getRecommenderEmail()); + final Member owner = memberService.getMember(letterSaveRequest.getOwnerEmail()); + final Letter letter = Letter.toLetter(make -> { + make.setLetterTitle(letterSaveRequest.getLetterTitle()); + make.setContent(letterSaveRequest.getLetterContent()); + make.setOwner(owner); + make.setRecommender(recommender); + make.setPinned(false); + make.setStar(0); + make.setCreateDate(LocalDateTime.now()); + return make; + }); + return letterRepository.save(letter); + } } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/Letter.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/Letter.java index fa4c858..83efb6b 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/Letter.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/Letter.java @@ -1,2 +1,54 @@ -package net.collabstack.app.letter.domain;public class Letter { +package net.collabstack.app.letter.domain; + +import java.time.LocalDateTime; +import java.util.function.Function; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; + +import org.springframework.data.annotation.CreatedDate; + +import net.collabstack.app.member.domain.Member; + +import lombok.Data; + +@Entity +@Data +public class Letter { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(length = 30, nullable = false) + private String letterTitle; + + @Column(length = 300, nullable = false) + private String content; + + @Column + private Integer star; + + private boolean isPinned; + + @CreatedDate + private LocalDateTime createDate; + + @ManyToOne + @JoinColumn(name = "recommenderEmail") + private Member recommender; + + @ManyToOne + @JoinColumn(name = "ownerEmail") + private Member owner; + + public static Letter toLetter(final Function function) { + final Letter letter = new Letter(); + return function.apply(letter); + } } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java index 11d2ee1..5c75d97 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java @@ -1,2 +1,10 @@ -package net.collabstack.app.letter.domain;public interface LetterRepository { +package net.collabstack.app.letter.domain; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LetterRepository extends JpaRepository { + + List findByOwner_email(final String ownerEmail); } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterResponse.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterResponse.java index c42056c..bdf47c5 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterResponse.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterResponse.java @@ -1,2 +1,36 @@ -package net.collabstack.app.letter.dto;public class LetterResponse { +package net.collabstack.app.letter.dto; + +import java.util.Date; + +import net.collabstack.app.letter.domain.Letter; +import net.collabstack.app.letter.util.DateUtil; + +import lombok.Data; + +@Data +public class LetterResponse { + + private Long id; + private String letterTitle; + private String letterContent; + private Integer star; + private boolean isPinned; + /** + * 생성일을 좀더 정확하게 표현합니다. + * e.g) 1 hours ago, 1 years ago + * */ + private String creationDate; + private String recommenderName; + + public static LetterResponse from(final Letter letter) { + final LetterResponse letterResponse = new LetterResponse(); + letterResponse.setId(letter.getId()); + letterResponse.setLetterTitle(letter.getLetterTitle()); + letterResponse.setLetterContent(letter.getContent()); + letterResponse.setStar(letter.getStar()); + letterResponse.setPinned(letter.isPinned()); + letterResponse.setCreationDate(DateUtil.toStandingTypeDate(new Date(), letter.getCreateDate())); + letterResponse.setRecommenderName(letter.getRecommender().getName()); + return letterResponse; + } } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterSaveRequest.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterSaveRequest.java index 74edfc4..87d578c 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterSaveRequest.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterSaveRequest.java @@ -1,2 +1,25 @@ -package net.collabstack.app.letter.dto;public class LetterSaveRequest { +package net.collabstack.app.letter.dto; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import lombok.Data; + +@Data +public class LetterSaveRequest { + + @Size(min = 5, max = 30) + private String letterTitle; + + @Size(min = 30, max = 300) + private String letterContent; + + @Email + @NotNull + private String recommenderEmail; + + @Email + @NotNull + private String ownerEmail; } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/util/DateUtil.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/util/DateUtil.java index 4965f30..61ac3c0 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/util/DateUtil.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/util/DateUtil.java @@ -1,2 +1,36 @@ -package net.collabstack.app.letter.util;public class DateUtil { +package net.collabstack.app.letter.util; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Date; + +public class DateUtil { + + public static String toStandingTypeDate(final Date now, final LocalDateTime creationDate) { + final long nowTimeMills = now.getTime(); + final long creationDateTimeMills = Timestamp.valueOf(creationDate).getTime(); + long gap = (nowTimeMills - creationDateTimeMills) / 1000; + + if (gap < 60) { + return gap + " seconds ago"; + } + gap /= 60; + if (gap < 60) { + return gap + " minutes ago"; + } + gap /= 60; + if (gap < 24) { + return gap + " hours ago"; + } + gap /= 24; + if (gap < 31) { + return gap + " days ago"; + } + gap /= 30; + if (gap < 12) { + return gap + " months ago"; + } + gap /= 12; + return gap + " years ago"; + } } diff --git a/collabstack-be/app/src/test/java/net/collabstack/app/letter/util/DateUtilTest.java b/collabstack-be/app/src/test/java/net/collabstack/app/letter/util/DateUtilTest.java index f2a830d..e1ea1b2 100644 --- a/collabstack-be/app/src/test/java/net/collabstack/app/letter/util/DateUtilTest.java +++ b/collabstack-be/app/src/test/java/net/collabstack/app/letter/util/DateUtilTest.java @@ -1,4 +1,94 @@ -import static org.junit.jupiter.api.Assertions.*; +package net.collabstack.app.letter.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.util.Date; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + class DateUtilTest { - + + @Test + void toStandingTypeDateSecondTest() { + LocalDateTime now = LocalDateTime.now(); + now = now.minusSeconds(20); + + final String standingTypeString = DateUtil.toStandingTypeDate(new Date(), now); + + assertThat(standingTypeString).isEqualTo("20 seconds ago"); + } + + @Test + void toStandingTypeDateMinuteTest() { + LocalDateTime now = LocalDateTime.now(); + now = now.minusMinutes(12); + + final String standingTypeString = DateUtil.toStandingTypeDate(new Date(), now); + + assertThat(standingTypeString).isEqualTo("12 minutes ago"); + } + + @Test + void toStandingTypeDateHoursTest() { + LocalDateTime now = LocalDateTime.now(); + now = now.minusHours(23); + + final String standingTypeString = DateUtil.toStandingTypeDate(new Date(), now); + + assertThat(standingTypeString).isEqualTo("23 hours ago"); + } + + @Test + void toStandingTypeDateDaysTest() { + LocalDateTime now = LocalDateTime.now(); + now = now.minusDays(29); + + final String standingTypeString = DateUtil.toStandingTypeDate(new Date(), now); + + assertThat(standingTypeString).isEqualTo("29 days ago"); + } + + @Test + void toStandingTypeDateMonthsTest() { + LocalDateTime now = LocalDateTime.now(); + now = now.minusMonths(11); + + final String standingTypeString = DateUtil.toStandingTypeDate(new Date(), now); + + assertThat(standingTypeString).isEqualTo("11 months ago"); + } + + @Test + void toStandingTypeDateYearsTest() { + LocalDateTime now = LocalDateTime.now(); + now = now.minusYears(3); + + final String standingTypeString = DateUtil.toStandingTypeDate(new Date(), now); + + assertThat(standingTypeString).isEqualTo("3 years ago"); + } + + @Test + @DisplayName("62초 뒤일경우") + void toStandingTypeDateTest_after62sec() { + LocalDateTime now = LocalDateTime.now(); + now = now.minusSeconds(62); + + final String standingTypeString = DateUtil.toStandingTypeDate(new Date(), now); + + assertThat(standingTypeString).isEqualTo("1 minutes ago"); + } + + @Test + @DisplayName("32일 뒤일경우") + void toStandingTypeDateTest_after32days() { + LocalDateTime now = LocalDateTime.now(); + now = now.minusDays(32); + + final String standingTypeString = DateUtil.toStandingTypeDate(new Date(), now); + + assertThat(standingTypeString).isEqualTo("1 months ago"); + } } \ No newline at end of file From bd4f5d63335468ea3c042bfc5c2e4c12c14fe6d2 Mon Sep 17 00:00:00 2001 From: devdynam0507 Date: Wed, 27 Jul 2022 22:00:56 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=EC=8B=9C=ED=81=90=EB=A6=AC=ED=8B=B0=20@Aut?= =?UTF-8?q?henticationPrincipal=20Wrapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/letter/dto/LetterPinningRequest.java | 2 ++ .../letter/exception/LetterNotFoundException.java | 2 ++ .../letter/exception/PinAlreadyFullException.java | 2 ++ .../security/annotation/AuthenticatedUser.java | 14 ++++++++++++++ 4 files changed, 20 insertions(+) create mode 100644 collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterPinningRequest.java create mode 100644 collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterNotFoundException.java create mode 100644 collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/PinAlreadyFullException.java create mode 100644 collabstack-be/security/src/main/java/net/collabstack/security/annotation/AuthenticatedUser.java diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterPinningRequest.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterPinningRequest.java new file mode 100644 index 0000000..e12ee29 --- /dev/null +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterPinningRequest.java @@ -0,0 +1,2 @@ +package net.collabstack.app.letter.dto;public class LetterPinningRequest { +} diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterNotFoundException.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterNotFoundException.java new file mode 100644 index 0000000..2c50fcf --- /dev/null +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterNotFoundException.java @@ -0,0 +1,2 @@ +package net.collabstack.app.letter.exception;public class LetterNotFoundException { +} diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/PinAlreadyFullException.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/PinAlreadyFullException.java new file mode 100644 index 0000000..3d2b467 --- /dev/null +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/PinAlreadyFullException.java @@ -0,0 +1,2 @@ +package net.collabstack.app.letter.exception;public class PinAlreadyFullException { +} diff --git a/collabstack-be/security/src/main/java/net/collabstack/security/annotation/AuthenticatedUser.java b/collabstack-be/security/src/main/java/net/collabstack/security/annotation/AuthenticatedUser.java new file mode 100644 index 0000000..95ae366 --- /dev/null +++ b/collabstack-be/security/src/main/java/net/collabstack/security/annotation/AuthenticatedUser.java @@ -0,0 +1,14 @@ +package net.collabstack.security.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.security.core.annotation.AuthenticationPrincipal; + +@Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@AuthenticationPrincipal +public @interface AuthenticatedUser { +} From a6d06f8289ffb85d0eec90ecc4124af949ebabc5 Mon Sep 17 00:00:00 2001 From: devdynam0507 Date: Wed, 27 Jul 2022 22:53:29 +0900 Subject: [PATCH 4/4] =?UTF-8?q?pinned=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- collabstack-be/app/build.gradle | 1 + .../app/letter/LetterController.java | 25 +++++++++++ .../collabstack/app/letter/LetterService.java | 31 ++++++++++++++ .../app/letter/domain/LetterRepository.java | 4 ++ .../app/letter/dto/LetterPinningRequest.java | 17 +++++++- .../exception/LetterExceptionHandler.java | 30 +++++++++++++ .../exception/LetterNotFoundException.java | 16 ++++++- .../exception/PinAlreadyFullException.java | 14 ++++++- .../app/member/MemberResolverImpl.java | 1 - .../app/letter/LetterControllerTest.java | 42 +++++++++++++++++++ .../filter/PreJwtAuthenticationProvider.java | 2 +- .../security/member/SecurityMember.java | 38 +---------------- 12 files changed, 179 insertions(+), 42 deletions(-) create mode 100644 collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterExceptionHandler.java create mode 100644 collabstack-be/app/src/test/java/net/collabstack/app/letter/LetterControllerTest.java diff --git a/collabstack-be/app/build.gradle b/collabstack-be/app/build.gradle index 0b4aa0c..58caa9c 100644 --- a/collabstack-be/app/build.gradle +++ b/collabstack-be/app/build.gradle @@ -36,6 +36,7 @@ dependencies { implementation('org.apache.httpcomponents:httpcore') implementation('org.apache.httpcomponents:httpclient') implementation("com.auth0:java-jwt:$jwt_version") + testImplementation("org.springframework.security:spring-security-test"); } test { diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java index b1774bd..a640855 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterController.java @@ -5,22 +5,27 @@ import javax.validation.Valid; import javax.validation.constraints.Email; +import javax.validation.constraints.NotNull; import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import net.collabstack.app.letter.domain.Letter; +import net.collabstack.app.letter.dto.LetterPinningRequest; import net.collabstack.app.letter.dto.LetterResponse; import net.collabstack.app.letter.dto.LetterSaveRequest; import net.collabstack.common.CommonResponse; import net.collabstack.common.ResultCode; +import net.collabstack.security.annotation.AuthenticatedUser; +import net.collabstack.security.member.SecurityMember; import lombok.RequiredArgsConstructor; @@ -50,4 +55,24 @@ public CommonResponse saveLetter( final LetterResponse letterResponse = LetterResponse.from(savedLetter); return CommonResponse.success(ResultCode.CREATED, "saved", letterResponse); } + + @PutMapping("/pins") + public CommonResponse pinning( + @AuthenticatedUser @NotNull final SecurityMember authenticatedUser, + @RequestBody @Valid final LetterPinningRequest letterPinningRequest) { + final Letter pinnedLetter = + letterService.setPinned(authenticatedUser.getUsername(), letterPinningRequest); + final LetterResponse letterResponse = LetterResponse.from(pinnedLetter); + return CommonResponse.success(ResultCode.OK, "pinning", letterResponse); + } + + @GetMapping("/{ownerEmail}/pins") + public CommonResponse> getPinnedLetters( + @PathVariable("ownerEmail") @Email final String ownerEmail) { + final List pinnedLetters = letterService.getPinnedLetters(ownerEmail); + final List letterResponses = pinnedLetters.stream() + .map(LetterResponse::from) + .collect(Collectors.toList()); + return CommonResponse.success(ResultCode.OK, "pinned letters", letterResponses); + } } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java index ba5c693..27d613c 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/LetterService.java @@ -2,13 +2,17 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import net.collabstack.app.letter.domain.Letter; import net.collabstack.app.letter.domain.LetterRepository; +import net.collabstack.app.letter.dto.LetterPinningRequest; import net.collabstack.app.letter.dto.LetterSaveRequest; +import net.collabstack.app.letter.exception.LetterNotFoundException; +import net.collabstack.app.letter.exception.PinAlreadyFullException; import net.collabstack.app.member.MemberService; import net.collabstack.app.member.domain.Member; @@ -18,6 +22,8 @@ @RequiredArgsConstructor public class LetterService { + private final static int MAX_PINNING_COUNT = 4; + private final LetterRepository letterRepository; private final MemberService memberService; @@ -42,4 +48,29 @@ public Letter saveLetter(final LetterSaveRequest letterSaveRequest) { }); return letterRepository.save(letter); } + + @Transactional(readOnly = true) + final List getPinnedLetters(final String ownerEmail) { + return letterRepository.findByOwner_emailAndIsPinned(ownerEmail, true); + } + + @Transactional + public Letter setPinned(final String ownerEmail, final LetterPinningRequest pinningRequest) { + final List letters = letterRepository.findByOwner_email(ownerEmail); + final long pinningCount = letters.stream() + .filter(Letter::isPinned) + .count(); + if (pinningCount >= MAX_PINNING_COUNT) { + throw new PinAlreadyFullException(ownerEmail, "이미 지정된 최대 pinned 개수에 도달하였습니다 (핀 지정갯수 4)"); + } + final Optional letterOptional = letters.stream() + .filter( + letter -> letter.getOwner().getEmail().equals(ownerEmail) + && letter.getId().compareTo(pinningRequest.getLetterId()) == 0) + .findFirst(); + letterOptional.ifPresent(letter -> letter.setPinned(pinningRequest.getIsPinning())); + return letterOptional.orElseThrow(() -> new LetterNotFoundException(pinningRequest.getLetterId(), + ownerEmail, + "추천서를 찾을 수 없습니다.")); + } } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java index 5c75d97..32f329e 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/domain/LetterRepository.java @@ -6,5 +6,9 @@ public interface LetterRepository extends JpaRepository { + int countByOwnerEmailAndIsPinned(final String ownerEmail, final boolean isPinned); + + List findByOwner_emailAndIsPinned(final String ownerEmail, final boolean isPinned); + List findByOwner_email(final String ownerEmail); } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterPinningRequest.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterPinningRequest.java index e12ee29..e9991dd 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterPinningRequest.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/dto/LetterPinningRequest.java @@ -1,2 +1,17 @@ -package net.collabstack.app.letter.dto;public class LetterPinningRequest { +package net.collabstack.app.letter.dto; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +import lombok.Data; + +@Data +public class LetterPinningRequest { + + @NotNull + @Min(1) + private Long letterId; + + @NotNull + private Boolean isPinning; } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterExceptionHandler.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterExceptionHandler.java new file mode 100644 index 0000000..00b6243 --- /dev/null +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterExceptionHandler.java @@ -0,0 +1,30 @@ +package net.collabstack.app.letter.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import net.collabstack.common.CommonResponse; +import net.collabstack.common.ResultCode; + +@RestControllerAdvice +public class LetterExceptionHandler { + + @ExceptionHandler(PinAlreadyFullException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public CommonResponse pinAlreadyFullExceptionHandler( + final PinAlreadyFullException exception) { + return CommonResponse.failure( + ResultCode.BAD_REQUEST, exception.getMessage(), exception.getOwnerEmail()); + } + + @ExceptionHandler(LetterNotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public CommonResponse letterNotFoundException( + final LetterNotFoundException exception) { + return CommonResponse.failure( + ResultCode.NOT_FOUND, exception.getLetterId() + " " + exception.getMessage(), + exception.getOwnerEmail()); + } +} diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterNotFoundException.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterNotFoundException.java index 2c50fcf..1d62099 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterNotFoundException.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/LetterNotFoundException.java @@ -1,2 +1,16 @@ -package net.collabstack.app.letter.exception;public class LetterNotFoundException { +package net.collabstack.app.letter.exception; + +import lombok.Getter; + +@Getter +public class LetterNotFoundException extends RuntimeException { + + private final Long letterId; + private final String ownerEmail; + + public LetterNotFoundException(final Long letterId, final String ownerEmail, final String message) { + super(message); + this.letterId = letterId; + this.ownerEmail = ownerEmail; + } } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/PinAlreadyFullException.java b/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/PinAlreadyFullException.java index 3d2b467..a05f23c 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/PinAlreadyFullException.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/letter/exception/PinAlreadyFullException.java @@ -1,2 +1,14 @@ -package net.collabstack.app.letter.exception;public class PinAlreadyFullException { +package net.collabstack.app.letter.exception; + +import lombok.Data; + +@Data +public class PinAlreadyFullException extends RuntimeException { + + private String ownerEmail; + + public PinAlreadyFullException(final String ownerEmail, final String message) { + super(message); + this.ownerEmail = ownerEmail; + } } diff --git a/collabstack-be/app/src/main/java/net/collabstack/app/member/MemberResolverImpl.java b/collabstack-be/app/src/main/java/net/collabstack/app/member/MemberResolverImpl.java index 2cfe0db..ac9b4a0 100644 --- a/collabstack-be/app/src/main/java/net/collabstack/app/member/MemberResolverImpl.java +++ b/collabstack-be/app/src/main/java/net/collabstack/app/member/MemberResolverImpl.java @@ -25,7 +25,6 @@ public SecurityMember resolveMember(final String id) { final Member member = memberService.getMember(id); return new SecurityMember<>(id, member.getName(), - false, List.of(new SimpleGrantedAuthority("Member"))); } } diff --git a/collabstack-be/app/src/test/java/net/collabstack/app/letter/LetterControllerTest.java b/collabstack-be/app/src/test/java/net/collabstack/app/letter/LetterControllerTest.java new file mode 100644 index 0000000..aa9f040 --- /dev/null +++ b/collabstack-be/app/src/test/java/net/collabstack/app/letter/LetterControllerTest.java @@ -0,0 +1,42 @@ +package net.collabstack.app.letter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; + +import net.collabstack.app.letter.dto.LetterPinningRequest; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@SpringBootTest +@AutoConfigureMockMvc +@EnableConfigurationProperties +@ActiveProfiles(profiles = "test") +class LetterControllerTest { + + @Autowired + ObjectMapper objectMapper; + + @Autowired + MockMvc mockMvc; + + @Test + void getPinnedLettersTest() throws Exception { + final String email = "wsnam0507@gmail.com"; + + final ResultActions resultActions = mockMvc.perform(get("/v1/" + email + "/pins")); + + resultActions.andDo(print()) + .andExpect(status().isBadRequest()); + } +} \ No newline at end of file diff --git a/collabstack-be/security/src/main/java/net/collabstack/security/filter/PreJwtAuthenticationProvider.java b/collabstack-be/security/src/main/java/net/collabstack/security/filter/PreJwtAuthenticationProvider.java index adf9c66..c32352a 100644 --- a/collabstack-be/security/src/main/java/net/collabstack/security/filter/PreJwtAuthenticationProvider.java +++ b/collabstack-be/security/src/main/java/net/collabstack/security/filter/PreJwtAuthenticationProvider.java @@ -34,7 +34,7 @@ public Authentication authenticate(final Authentication authentication) throws A if (token == null) { final SecurityMember anonymous = new SecurityMember<>( - null, "anonymous", false, anonymousRoles); + null, "anonymous", anonymousRoles); return new PreAuthenticatedAuthenticationToken(anonymousRoles, "", anonymousRoles); } final String id = jwtProvider.decrypt(token, "id", String.class); diff --git a/collabstack-be/security/src/main/java/net/collabstack/security/member/SecurityMember.java b/collabstack-be/security/src/main/java/net/collabstack/security/member/SecurityMember.java index 8a42b32..4ba619f 100644 --- a/collabstack-be/security/src/main/java/net/collabstack/security/member/SecurityMember.java +++ b/collabstack-be/security/src/main/java/net/collabstack/security/member/SecurityMember.java @@ -3,49 +3,13 @@ import java.util.Collection; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; import lombok.Data; @Data -public class SecurityMember implements UserDetails { +public class SecurityMember { private final T id; private final String username; - private final boolean isAccountNonExpired; private final Collection authorities; - - @Override - public Collection getAuthorities() { - return authorities; - } - - public String getUsername() { - return username; - } - - @Override - public boolean isAccountNonExpired() { - return isAccountNonExpired; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return isAccountNonExpired; - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public String getPassword() { - return null; - } }