Skip to content

Commit

Permalink
Merge pull request #35 from depromeet/feat/LS-27
Browse files Browse the repository at this point in the history
feat/LS-27: 액션 아이템 생성 API
  • Loading branch information
clean2001 authored Jul 16, 2024
2 parents e40c038 + 3b23caf commit 5647a15
Show file tree
Hide file tree
Showing 13 changed files with 337 additions and 1 deletion.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ out/

### VS Code ###
.vscode/
*.properties
*.properties

layer-domain/src/main/generated
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.layer.domain.actionItem.api;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.layer.common.annotation.MemberId;
import org.layer.domain.actionItem.dto.*;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;

@Tag(name = "액션아이템 API")
public interface ActionItemApi {
@Operation(summary = "액션 아이템 생성", method = "POST", description = """
특정 회고와 매핑되는 액션 아이템을 생성합니다.
""")
@ApiResponses({
@ApiResponse(responseCode = "201",
content = {
@Content(
mediaType = "application/json",
schema = @Schema(implementation = CreateActionItemResponse.class)
)
}
)
}
)
ResponseEntity<CreateActionItemResponse> createActionItem(@MemberId Long memberId,
@Validated @RequestBody CreateActionItemRequest createActionItemRequest);


@Operation(summary = "개인의 액션 아이템 조회", method = "GET", description = """
회원 아이디로 개인이 작성한 모든 액션아이템을 조회합니다.
""")
@ApiResponses({
@ApiResponse(responseCode = "200",
content = {
@Content(
mediaType = "application/json",
schema = @Schema(implementation = MemberActionItemResponse.class)
)
}
)
}
)
ResponseEntity<CreateActionItemResponse> memberActionItem(@MemberId Long memberId);

@Operation(summary = "팀의 액션 아이템 조회", method = "GET", description = """
팀 아이디로 팀의 모든 액션아이템을 조회합니다.
""")
@ApiResponses({
@ApiResponse(responseCode = "200",
content = {
@Content(
mediaType = "application/json",
schema = @Schema(implementation = TeamActionItemResponse.class)
)
}
)
}
)
ResponseEntity<CreateActionItemResponse> teamActionItem(@MemberId Long memberId,
@Validated @RequestBody TeamActionItemRequest teamActionItemRequest);

@Operation(summary = "액션 아이템 삭제", method = "DELETE", description = """
액션 아이템을 삭제합니다.
""")
@ApiResponses({
@ApiResponse(responseCode = "200")
}
)
ResponseEntity<CreateActionItemResponse> deleteActionItem(@MemberId Long memberId,
@Validated @RequestBody DeleteActionItemRequest deleteActionItemRequest);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.layer.domain.actionItem.controller;

import lombok.RequiredArgsConstructor;
import org.layer.common.annotation.MemberId;
import org.layer.domain.actionItem.api.ActionItemApi;
import org.layer.domain.actionItem.dto.CreateActionItemRequest;
import org.layer.domain.actionItem.dto.CreateActionItemResponse;
import org.layer.domain.actionItem.dto.DeleteActionItemRequest;
import org.layer.domain.actionItem.dto.TeamActionItemRequest;
import org.layer.domain.actionItem.service.ActionItemService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RequestMapping("/api/action-item")
@RestController
public class ActionItemController implements ActionItemApi {
private final ActionItemService actionItemService;
@Override
@PostMapping("/create")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<CreateActionItemResponse> createActionItem(@MemberId Long memberId,
@Validated @RequestBody CreateActionItemRequest createActionItemRequest) {
CreateActionItemResponse actionItem = actionItemService.createActionItem(memberId,
createActionItemRequest.retrospectId(),
createActionItemRequest.content());

return new ResponseEntity<>(actionItem, HttpStatus.CREATED);
}

@Override
@PreAuthorize("isAuthenticated()")
public ResponseEntity<CreateActionItemResponse> memberActionItem(@MemberId Long memberId) {

return null;
}

@Override
@PreAuthorize("isAuthenticated()")
public ResponseEntity<CreateActionItemResponse> teamActionItem(@MemberId Long memberId, TeamActionItemRequest teamActionItemRequest) {
return null;
}

@Override
@PreAuthorize("isAuthenticated()")
public ResponseEntity<CreateActionItemResponse> deleteActionItem(@MemberId Long memberId, DeleteActionItemRequest deleteActionItemRequest) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.layer.domain.actionItem.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

@Schema(description = "액션 아이템 생성 요정")
public record CreateActionItemRequest(@NotNull
@Schema(description = "액션 아이템과 매핑되는 회고 ID")
Long retrospectId,
@NotNull
@Schema(description = "액션 아이템 내용")
@NotNull String content) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.layer.domain.actionItem.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@Schema(description = "액션 아이템 생성 응답")
public record CreateActionItemResponse(
@Schema(description = "액션 아이템 생성 회원 ID")
@NotNull
Long memberId,
@Schema(description = "액션 아이템과 매칭되는 회고가 진행중인 스페이스 ID")
@NotNull
Long spaceId) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.layer.domain.actionItem.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;


@Schema(description = "액션 아이템 삭제 요청")
public record DeleteActionItemRequest(@NotNull
@Schema(description = "액션 아이템 ID")
Long actionItemId) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.layer.domain.actionItem.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;

@Builder
@Schema(description = "특정 회원이 작성한 액션 아이템")
public record MemberActionItemResponse (@NotNull
@Schema(description = "액션아이템 ID")
Long actionItemId,
@NotNull
@Schema(description = "액션아이템 내용")
String actionItemContent,
@NotNull
@Schema(description = "액션 아이템이 속해 있는 스페이스 ID")
Long spaceId,
@NotNull
@Schema(description = "액션 아이템이 속해 있는 스페이스 이름")
String spaceName,
@NotNull
@Schema(description = "액션 아이템과 매핑되는 회고 ID")
Long retrospectId,
@NotNull
@Schema(description = "액션 아이템과 매핑되는 회고 이름")
String retrospectName,
@NotNull
@Schema(description = "팀 스페이스의 액션 아이템인지, 개인 스페이스의 액션 아이템인지")
Boolean isTeam){
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.layer.domain.actionItem.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

public record TeamActionItemRequest(
@NotNull
@Schema(description = "액션 아이템 ID")
Long actionItemId,
@NotNull
@Schema(description = "액션 아이템과 내용")
String actionItemContent,
@NotNull
@Schema(description = "액션 아이템이 속한 스페이스 ID")
Long spaceId,
@NotNull
@Schema(description = "액션 아이템이 속한 스페이스 이름")
String spaceName,
@NotNull
@Schema(description = "액션 아이템과 매핑되는 회고 ID")
Long retrospectId,
@NotNull
@Schema(description = "액션 아이템과 매핑되는 회고 이름")
String retrospectName) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.layer.domain.actionItem.dto;

public record TeamActionItemResponse() {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.layer.domain.actionItem.service;

import lombok.RequiredArgsConstructor;
import org.layer.domain.actionItem.dto.CreateActionItemResponse;
import org.layer.domain.actionItem.entity.ActionItem;
import org.layer.domain.actionItem.repository.ActionItemRepository;
import org.layer.domain.retrospect.entity.Retrospect;
import org.layer.domain.retrospect.repository.RetrospectRepository;
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 java.util.Optional;

import static org.layer.common.exception.MemberSpaceRelationExceptionType.NOT_FOUND_MEMBER_SPACE_RELATION;
import static org.layer.domain.actionItem.enums.ActionItemStatus.PROCESSING;

@RequiredArgsConstructor
@Service
public class ActionItemService {
private final ActionItemRepository actionItemRepository;
private final RetrospectRepository retrospectRepository;
private final MemberSpaceRelationRepository memberSpaceRelationRepository;

public CreateActionItemResponse createActionItem(Long memberId, Long retrospectId, String content) {

// 멤버가 해당 회고가 진행 중인 스페이스에 속하는지 확인
Retrospect retrospect = retrospectRepository.findByIdOrThrow(retrospectId);
Optional<MemberSpaceRelation> team = memberSpaceRelationRepository.findBySpaceIdAndMemberId(retrospect.getSpaceId(), retrospectId);

if(team.isEmpty()) {
throw new MemberSpaceRelationException(NOT_FOUND_MEMBER_SPACE_RELATION);
}

// 액션 아이템 생성
actionItemRepository.save(ActionItem.builder()
.retrospectId(retrospectId)
.spaceId(retrospect.getSpaceId())
.memberId(memberId)
.content(content)
.actionItemStatus(PROCESSING)
.build());

return new CreateActionItemResponse(memberId, retrospect.getSpaceId());
}




}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.layer.domain.actionItem.entity;

import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.layer.domain.BaseEntity;
import org.layer.domain.actionItem.enums.ActionItemStatus;

@Getter
@SuperBuilder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class ActionItem extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotNull
private Long retrospectId; // 어떤 회고에 매핑되는지

@NotNull
private Long spaceId; // 어떤 스페이스에 속하는지(반정규화)

@NotNull
private Long memberId; // 작성자

@NotNull
private String content; // 액션 아이템 내용

@NotNull
@Enumerated(EnumType.STRING)
private ActionItemStatus actionItemStatus; // 액션 아이템 상태

public ActionItem(Long retrospectId, Long spaceId, Long memberId, String content, ActionItemStatus actionItemStatus) {
this.retrospectId = retrospectId;
this.spaceId = spaceId;
this.memberId = memberId;
this.content = content;
this.actionItemStatus = actionItemStatus;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.layer.domain.actionItem.enums;

public enum ActionItemStatus {
PROCESSING,
RESOLVED; // MVP 단계에선 생성, 삭제 밖에 없는 것 같지만, 확장성 고려
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.layer.domain.actionItem.repository;

import org.layer.domain.actionItem.entity.ActionItem;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ActionItemRepository extends JpaRepository<ActionItem, Long> {
}

0 comments on commit 5647a15

Please sign in to comment.