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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions collabstack-be/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,4 @@ public class CollabStackApp {
public static void main(final String[] args) {
SpringApplication.run(CollabStackApp.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package net.collabstack.app.letter;

import java.util.List;
import java.util.stream.Collectors;

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;

@RestController
@RequestMapping("/v1/letters")
@RequiredArgsConstructor
@Validated
public class LetterController {

private final LetterService letterService;

@GetMapping("/{ownerEmail}")
public CommonResponse<List<LetterResponse>> getLetters(
@PathVariable("ownerEmail") @Email final String ownerEmail) {
final List<Letter> letters = letterService.getLetters(ownerEmail);
final List<LetterResponse> letterResponses = letters.stream()
.map(LetterResponse::from)
.collect(Collectors.toList());
return CommonResponse.success(ResultCode.OK, "letters", letterResponses);
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public CommonResponse<LetterResponse> 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);
}

@PutMapping("/pins")
public CommonResponse<LetterResponse> pinning(
@AuthenticatedUser @NotNull final SecurityMember<String> 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<List<LetterResponse>> getPinnedLetters(
@PathVariable("ownerEmail") @Email final String ownerEmail) {
final List<Letter> pinnedLetters = letterService.getPinnedLetters(ownerEmail);
final List<LetterResponse> letterResponses = pinnedLetters.stream()
.map(LetterResponse::from)
.collect(Collectors.toList());
return CommonResponse.success(ResultCode.OK, "pinned letters", letterResponses);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package net.collabstack.app.letter;

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;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class LetterService {

private final static int MAX_PINNING_COUNT = 4;

private final LetterRepository letterRepository;
private final MemberService memberService;

@Transactional(readOnly = true)
public List<Letter> 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);
}

@Transactional(readOnly = true)
final List<Letter> getPinnedLetters(final String ownerEmail) {
return letterRepository.findByOwner_emailAndIsPinned(ownerEmail, true);
}

@Transactional
public Letter setPinned(final String ownerEmail, final LetterPinningRequest pinningRequest) {
final List<Letter> 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<Letter> 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,
"추천서를 찾을 수 없습니다."));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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<Letter, Letter> function) {
final Letter letter = new Letter();
return function.apply(letter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package net.collabstack.app.letter.domain;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

public interface LetterRepository extends JpaRepository<Letter, Long> {

int countByOwnerEmailAndIsPinned(final String ownerEmail, final boolean isPinned);

List<Letter> findByOwner_emailAndIsPinned(final String ownerEmail, final boolean isPinned);

List<Letter> findByOwner_email(final String ownerEmail);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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;
}
Original file line number Diff line number Diff line change
@@ -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<String> pinAlreadyFullExceptionHandler(
final PinAlreadyFullException exception) {
return CommonResponse.failure(
ResultCode.BAD_REQUEST, exception.getMessage(), exception.getOwnerEmail());
}

@ExceptionHandler(LetterNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public CommonResponse<String> letterNotFoundException(
final LetterNotFoundException exception) {
return CommonResponse.failure(
ResultCode.NOT_FOUND, exception.getLetterId() + " " + exception.getMessage(),
exception.getOwnerEmail());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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;
}
}
Loading