Skip to content

Commit

Permalink
feat/LS-18: 회고 목록 조회 API 구현 (#27)
Browse files Browse the repository at this point in the history
* docs: 컨벤션 수정

* docs: 머지 전략 리드미 수정

* chore: 사용하지 않는 어노테이션 삭제

* docs: add 컨벤션 추가

* add: 예외 클래스 및 예외 타입 추가

* feat: 회고 응답 관련 로직을 위해 일급컬렉션 구현

* del: 사용하지 않는 코드 삭제

* docs: del 관련 컨벤션 추가

* chore: Space 연관관계 추가

* feat:
1. 팀 관련 로직을 위한 일급컬렉션 생성
2. 스페이스 내 회원인지 검증 로직 추가
3. 팀 멤버 수 계산 로직 추가

* feat: 회고 목록 조회 컨트롤러 구현 및 스웨거 문서화

* add: 회고 목록 조회를 위한 Dto 추가

* style: dto 위치 변경

* chore: 연관 관계 수정으로 인한 코드 변경

* feat: 회고 목록 조회 서비스 로직 구현

* docs: 협업관련 컨벤션 추가

* docs: 쿼리 관련 컨벤션 추가

* del: 팀원 수 데이터 삭제

* chore: 검증 로직 변경
  • Loading branch information
mikekks authored Jul 13, 2024
1 parent edd871a commit 50dbae3
Show file tree
Hide file tree
Showing 27 changed files with 322 additions and 34 deletions.
3 changes: 2 additions & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
## ⚙️ 테스트 결과
<!-- local에서 postman으로 요청한 결과를 첨부합니다, postman을 사용하지 않으면 관련 화면 캡쳐 -->

### 발생한 쿼리 첨부

## 👉 반영 브랜치
<!-- feat/#issue -> dev와 같이 반영 브랜치를 표시합니다 -->
<!-- closed #issue로 merge되면 issue가 자동으로 close되게 해줍니다 -->
- feat/#
- feat/
- closed #
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
3. 브랜치를 생성한 후에 작업을 진행한다.
4. 진행한 후에 커밋을 한다.
5. 작업이 완료되면 PR을 생성한다.
6. PR을 생성한 후에 팀원들에게 리뷰를 요청한다. 리뷰는 나머지 2명 모두에게 요청하되, 1명의 승인을 받으면 머지 가능한 상태가 된다.
6. PR을 생성한 후에 팀원들에게 리뷰를 요청한다. 리뷰는 PR 올린 시간 기준으로 24시간 내로는 2명의 승인, 그 이후로는 1명의 승인이 필요하다.
7. 리뷰를 받은 후에 PR을 default branch에 merge한다.
8. merge된 후, 배포를 진행한다.

Expand All @@ -57,12 +57,15 @@
- issue는 노션에 생성한다.
- 노션에 생성된 issue 번호를 기반으로 branch 생성
- ex) feat/#{노션이슈번호}
- dev 브랜치에 머지할 때는 브랜치 간소화 및 revert 용이성을 위해 스쿼시 머지를 진행한다.

## 🙏 Commit Convention
- <a href="https://udacity.github.io/git-styleguide/">유다시티 컨벤션

```
feat: 새로운 기능 구현
add: 기능구현까지는 아니지만 새로운 파일이 추가된 경우
del: 기존 코드를 삭제한 경우
fix: 버그, 오류 해결
docs: README나 WIKI 등의 문서 작업
style: 코드가 아닌 스타일 변경을 하는 경우
Expand All @@ -89,6 +92,7 @@ chore: 코드 수정, 내부 파일 수정
- Controller에서 요청/응답하는 DTO와 Service에서 사용하는 DTO를 분리합니다.
- Layered Architecture를 엄격하게 준수합니다.
- 확장/번경에 용이하게 합니다.
- 매개변수가 5개 미만일 경우 controller-service간 Dto를 사용하지 않습니다.
- 네이밍은 아래와 같이 정의합니다.
- Controller DTO: `${Entity명}${복수형일 경우 List 추가}${행위 또는 상태}${Request/Response}`
- Service DTO: `${Entity명}${복수형일 경우 List 추가}${행위 또는 상태}Service${Request/Response}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ public ResponseEntity<ReissueTokenResponse> reissueToken(ReissueTokenRequest rei

@DisableSwaggerSecurity
//== google OAuth2 test용 API 액세스 토큰 발급 ==//
@GetMapping("oauth2/google")
@GetMapping("/oauth2/google")
public String googleTest(@RequestParam("code") String code) {
return googleService.getToken(code);
}

@DisableSwaggerSecurity
//== kakao OAuth2 test용 API 액세스 토큰 발급 ==//
@GetMapping("oauth2/kakao")
@GetMapping("/oauth2/kakao")
public Object kakaoLogin(@RequestParam(value = "code", required = false) String code) {
return kakaoService.getToken(code);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.layer.domain.auth.controller.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.layer.domain.member.entity.SocialType;

public record SignInRequest(@JsonProperty("social_type") SocialType socialType) {
public record SignInRequest(SocialType socialType) {
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package org.layer.domain.auth.controller.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import org.layer.domain.member.entity.SocialType;

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record SignUpRequest(SocialType socialType, String name) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.layer.domain.retrospect.controller;

import org.layer.common.annotation.MemberId;
import org.layer.domain.retrospect.controller.dto.request.RetrospectCreateRequest;
import org.layer.domain.retrospect.controller.dto.response.RetrospectListGetResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -12,7 +14,10 @@
@Tag(name = "회고", description = "회고 관련 API")
public interface RetrospectApi {

@Operation(summary = "회고 생성", description = "")
ResponseEntity<Void> createRetrospect(@PathVariable("spaceId") Long spaceId,
@RequestBody @Valid RetrospectCreateRequest request);
@Operation(summary = "회고 생성", description = "")
ResponseEntity<Void> createRetrospect(@PathVariable("spaceId") Long spaceId,
@RequestBody @Valid RetrospectCreateRequest request, @MemberId Long memberId);

@Operation(summary = "회고 목록 조회", description = "특정 팀 스페이스에서 작성했던 회고 목록을 보는 기능입니다.")
ResponseEntity<RetrospectListGetResponse> getRetrospects(@PathVariable("spaceId") Long spaceId, @MemberId Long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package org.layer.domain.retrospect.controller;

import java.util.List;

import org.layer.common.annotation.MemberId;
import org.layer.domain.retrospect.controller.dto.response.RetrospectGetResponse;
import org.layer.domain.retrospect.controller.dto.response.RetrospectListGetResponse;
import org.layer.domain.retrospect.service.RetrospectService;
import org.layer.domain.retrospect.controller.dto.request.RetrospectCreateRequest;
import org.layer.domain.retrospect.service.dto.response.RetrospectListGetServiceResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
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;
Expand All @@ -17,14 +25,35 @@
@RequestMapping("/space/{spaceId}/retrospect")
public class RetrospectController implements RetrospectApi {

private final RetrospectService retrospectService;
private final RetrospectService retrospectService;

@Override
@PostMapping
@PreAuthorize("isAuthenticated()")
public ResponseEntity<Void> createRetrospect(
@PathVariable("spaceId") Long spaceId,
@RequestBody @Valid RetrospectCreateRequest request,
@MemberId Long memberId) {

retrospectService.create(spaceId, request.formId(), request.title(), request.introduction());
return ResponseEntity.ok(null);
}

@Override
@GetMapping
@PreAuthorize("isAuthenticated()")
public ResponseEntity<RetrospectListGetResponse> getRetrospects(@PathVariable("spaceId") Long spaceId,
@MemberId Long memberId) {

RetrospectListGetServiceResponse serviceResponse = retrospectService.getRetrospects(spaceId, memberId);

List<RetrospectGetResponse> retrospectGetResponses = serviceResponse.retrospects().stream()
.map(r -> RetrospectGetResponse.of(r.title(), r.introduction(), r.isWrite(), r.retrospectStatus(),
r.writeCount()))
.toList();

@Override
@PostMapping
public ResponseEntity<Void> createRetrospect(@PathVariable("spaceId") Long spaceId,
@RequestBody @Valid RetrospectCreateRequest request) {
return ResponseEntity.ok()
.body(RetrospectListGetResponse.of(serviceResponse.layerCount(), retrospectGetResponses));
}

retrospectService.create(spaceId, request.formId(), request.title(), request.introduction());
return ResponseEntity.ok(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.layer.domain.retrospect.controller.dto.response;

import org.layer.domain.retrospect.entity.RetrospectStatus;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(name = "RetrospectGetResponse", description = "특정 회고 조회 Dto")
public record RetrospectGetResponse(
@Schema(description = "회고 이름", example = "중간 발표 이후")
String title,
@Schema(description = "회고 설명", example = "중간 발표 관련해서 KPT 회고를 해봅시다.")
String introduction,
@Schema(description = "회고 작성 여부", example = "false")
boolean isWrite,
@Schema(description = "회고 상태 : PROCEEDING 나 DONE 중에 하나입니다.", example = "PROCEEDING")
RetrospectStatus retrospectStatus,
@Schema(description = "해당 회고 응답 수", example = "4")
int writeCount
) {
public static RetrospectGetResponse of(String title, String introduction, boolean isWrite, RetrospectStatus retrospectStatus,
int writeCount){

return new RetrospectGetResponse(title, introduction, isWrite, retrospectStatus, writeCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.layer.domain.retrospect.controller.dto.response;

import java.util.List;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(name = "RetrospectListGetResponse", description = "회고 목록 조회 Dto")
public record RetrospectListGetResponse(
@Schema(description = "쌓인 레이어 수", example = "3")
int layerCount,
@Schema(description = "회고 객체 목록", example = "")
List<RetrospectGetResponse> retrospects

) {
public static RetrospectListGetResponse of(int layerCount, List<RetrospectGetResponse> retrospects){
return new RetrospectListGetResponse(layerCount, retrospects);
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
package org.layer.domain.retrospect.service;

import static org.layer.common.exception.MemberSpaceRelationExceptionType.*;

import java.util.List;
import java.util.Optional;

import org.layer.domain.answer.entity.Answers;
import org.layer.domain.answer.repository.AnswerRepository;
import org.layer.domain.retrospect.entity.Retrospect;
import org.layer.domain.retrospect.entity.RetrospectStatus;
import org.layer.domain.retrospect.repository.RetrospectRepository;
import org.layer.domain.retrospect.service.dto.response.RetrospectGetServiceResponse;
import org.layer.domain.retrospect.service.dto.response.RetrospectListGetServiceResponse;
import org.layer.domain.space.entity.MemberSpaceRelation;
import org.layer.domain.space.exception.MemberSpaceRelationException;
import org.layer.domain.space.repository.MemberSpaceRelationRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class RetrospectService {

private final RetrospectRepository retrospectRepository;
private final MemberSpaceRelationRepository memberSpaceRelationRepository;
private final AnswerRepository answerRepository;

public void create(Long spaceId, Long formId, String title, String introduction){
@Transactional
public void create(Long spaceId, Long formId, String title, String introduction) {
Retrospect retrospect = Retrospect.builder()
.title(title)
.formId(formId)
Expand All @@ -24,4 +41,23 @@ public void create(Long spaceId, Long formId, String title, String introduction)

retrospectRepository.save(retrospect);
}

public RetrospectListGetServiceResponse getRetrospects(Long spaceId, Long memberId) {
Optional<MemberSpaceRelation> team = memberSpaceRelationRepository.findBySpaceIdAndMemberId(
spaceId, memberId);
if (team.isEmpty()) { // 해당 멤버가 요청한 스페이스 소속 여부 확인
throw new MemberSpaceRelationException(NOT_FOUND_MEMBER_SPACE_RELATION);
}

List<Retrospect> retrospects = retrospectRepository.findAllBySpaceId(spaceId);
List<Long> retrospectIds = retrospects.stream().map(Retrospect::getId).toList();
Answers answers = new Answers(answerRepository.findAllByRetrospectIdIn(retrospectIds));

List<RetrospectGetServiceResponse> retrospectDtos = retrospects.stream()
.map(r -> RetrospectGetServiceResponse.of(r.getTitle(), r.getIntroduction(),
answers.hasRetrospectAnswer(memberId), r.getRetrospectStatus(), answers.getWriteCount()))
.toList();

return RetrospectListGetServiceResponse.of(retrospects.size(), retrospectDtos);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.layer.domain.retrospect.service.dto;
package org.layer.domain.retrospect.service.dto.request;

import java.util.List;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.layer.domain.retrospect.service.dto.response;

import org.layer.domain.retrospect.entity.RetrospectStatus;

public record RetrospectGetServiceResponse(
String title,
String introduction,
boolean isWrite,
RetrospectStatus retrospectStatus,
int writeCount
) {
public static RetrospectGetServiceResponse of(String title, String introduction, boolean isWrite,
RetrospectStatus retrospectStatus, int writeCount){
return new RetrospectGetServiceResponse(title, introduction, isWrite, retrospectStatus, writeCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.layer.domain.retrospect.service.dto.response;

import java.util.List;


public record RetrospectListGetServiceResponse(
int layerCount,
List<RetrospectGetServiceResponse> retrospects
) {
public static RetrospectListGetServiceResponse of(int layerCount, List<RetrospectGetServiceResponse> retrospects){
return new RetrospectListGetServiceResponse(layerCount, retrospects);
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package org.layer.domain.space.service;


import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.layer.common.dto.Meta;
import org.layer.common.exception.BaseCustomException;
import org.layer.domain.space.dto.SpaceRequest;
import org.layer.domain.space.dto.SpaceResponse;
import org.layer.domain.space.dto.SpaceWithMemberCount;
import org.layer.domain.space.entity.MemberSpaceRelation;
import org.layer.domain.space.entity.Space;
import org.layer.domain.space.repository.MemberSpaceRelationRepository;
import org.layer.domain.space.repository.SpaceRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.stream.Collectors;

Expand Down Expand Up @@ -62,6 +63,4 @@ public SpaceResponse.SpaceWithUserCountInfo getSpaceById(Long memberId, Long spa
return SpaceResponse.SpaceWithUserCountInfo.toResponse(foundSpace);
}


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.layer.common.exception;

import org.springframework.http.HttpStatus;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public enum MemberSpaceRelationExceptionType implements ExceptionType {
/**
* 400
*/

NOT_FOUND_MEMBER_SPACE_RELATION(HttpStatus.NOT_FOUND, "해당 스페이스에 소속되지 않은 멤버입니다.");

private final HttpStatus status;
private final String message;

@Override
public HttpStatus httpStatus() {
return status;
}

@Override
public String message() {
return message;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.layer.common.exception;

import org.springframework.http.HttpStatus;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public enum SpaceExceptionType implements ExceptionType{
NOT_FOUND_SPACE(HttpStatus.NOT_FOUND, "유효하지 않은 스페이스 id 입니다.");

private final HttpStatus status;
private final String message;
@Override
public HttpStatus httpStatus() {
return status;
}

@Override
public String message() {
return message;
}
}
Loading

0 comments on commit 50dbae3

Please sign in to comment.