-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEAT] 멤버 관련 API 구현 #34
Changes from all commits
6ded067
35a7902
394305c
76c03f4
132761f
c82cf3b
d5aef09
efefdbc
16a32cb
490b4b6
d6de359
beb68e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.debatetimer.controller.member; | ||
|
||
import com.debatetimer.dto.member.MemberCreateRequest; | ||
import com.debatetimer.dto.member.MemberCreateResponse; | ||
import com.debatetimer.dto.member.TableResponses; | ||
import com.debatetimer.service.member.MemberService; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
public class MemberController implements MemberControllerSwagger{ | ||
|
||
private final MemberService memberService; | ||
|
||
@Override | ||
@GetMapping("/api/table") | ||
public TableResponses getTables(@RequestParam Long memberId) { | ||
return memberService.getTables(memberId); | ||
} | ||
|
||
@Override | ||
@PostMapping("/api/member") | ||
@ResponseStatus(HttpStatus.CREATED) | ||
public MemberCreateResponse createMember(@RequestBody MemberCreateRequest request) { | ||
return memberService.createMember(request); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package com.debatetimer.controller.member; | ||
|
||
import com.debatetimer.dto.member.MemberCreateRequest; | ||
import com.debatetimer.dto.member.MemberCreateResponse; | ||
import com.debatetimer.dto.member.TableResponses; | ||
import com.debatetimer.swagger.annotation.ErrorCode400; | ||
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.parameters.RequestBody; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
|
||
@Tag(name = "Member API") | ||
public interface MemberControllerSwagger { | ||
|
||
@Operation( | ||
summary = "멤버의 토론 시간표 조회", | ||
responses = { | ||
@ApiResponse( | ||
responseCode = "200", | ||
description = "멤버의 토론 시간표 조회 성공", | ||
content = @Content(schema = @Schema(implementation = TableResponses.class)) | ||
) | ||
} | ||
) | ||
@ErrorCode400 | ||
TableResponses getTables(Long memberId); | ||
|
||
@Operation( | ||
summary = "멤버 생성", | ||
requestBody = @RequestBody( | ||
content = @Content(schema = @Schema(implementation = MemberCreateRequest.class)) | ||
), | ||
responses = { | ||
@ApiResponse( | ||
responseCode = "201", | ||
description = "멤버 생성 성공", | ||
content = @Content(schema = @Schema(implementation = MemberCreateResponse.class)) | ||
) | ||
} | ||
) | ||
@ErrorCode400 | ||
MemberCreateResponse createMember(MemberCreateRequest request); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.debatetimer.dto.member; | ||
|
||
import com.debatetimer.domain.member.Member; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
public record MemberCreateRequest( | ||
@Schema(description = "멤버 닉네임", example = "콜리") | ||
String nickname | ||
) { | ||
|
||
public Member toMember() { | ||
return new Member(nickname); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.debatetimer.dto.member; | ||
|
||
import com.debatetimer.domain.member.Member; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
public record MemberCreateResponse( | ||
@Schema(description = "멤버 아이디", example = "1") | ||
long id, | ||
|
||
@Schema(description = "멤버 닉네임", example = "콜리") | ||
String nickname | ||
) { | ||
|
||
public MemberCreateResponse(Member member) { | ||
this(member.getId(), member.getNickname()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.debatetimer.dto.member; | ||
|
||
import com.debatetimer.domain.parliamentary.ParliamentaryTable; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
public record TableResponse( | ||
@Schema(description = "테이블 이름", example = "테이블1") | ||
String name, | ||
|
||
@Schema(description = "토론 타입", example = "PARLIAMENTARY") | ||
TableType type, | ||
|
||
@Schema(description = "소요 시간 (초 단위)", example = "1800") | ||
int duration | ||
) { | ||
|
||
public TableResponse(ParliamentaryTable parliamentaryTable) { | ||
this(parliamentaryTable.getName(), TableType.PARLIAMENTARY, parliamentaryTable.getDuration()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.debatetimer.dto.member; | ||
|
||
import com.debatetimer.domain.parliamentary.ParliamentaryTable; | ||
import io.swagger.v3.oas.annotations.media.ArraySchema; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import java.util.List; | ||
|
||
public record TableResponses( | ||
@ArraySchema(schema = @Schema(description = "테이블들", implementation = TableResponse.class)) | ||
List<TableResponse> tables | ||
) { | ||
|
||
public static TableResponses from(List<ParliamentaryTable> parliamentaryTables) { | ||
return new TableResponses(toTableResponses(parliamentaryTables)); | ||
} | ||
|
||
private static List<TableResponse> toTableResponses(List<ParliamentaryTable> parliamentaryTables) { | ||
return parliamentaryTables.stream() | ||
.map(TableResponse::new) | ||
.toList(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.debatetimer.dto.member; | ||
|
||
public enum TableType { | ||
|
||
PARLIAMENTARY, | ||
; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.debatetimer.repository.member; | ||
|
||
import com.debatetimer.domain.member.Member; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface MemberRepository extends JpaRepository<Member, Long> { | ||
|
||
default Member getById(Long id) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [순수한 질문] Optional 가 아니라 default 메서드를 통해 예외 처리를 해준 이유가 궁금해요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 보통 |
||
return findById(id).orElseThrow(() -> new IllegalArgumentException("해당 회원이 존재하지 않습니다")); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.debatetimer.repository.parliamentary; | ||
|
||
import com.debatetimer.domain.member.Member; | ||
import com.debatetimer.domain.parliamentary.ParliamentaryTable; | ||
import java.util.List; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface ParliamentaryTableRepository extends JpaRepository<ParliamentaryTable, Long> { | ||
|
||
List<ParliamentaryTable> findAllByMember(Member member); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.debatetimer.service.member; | ||
|
||
import com.debatetimer.dto.member.MemberCreateRequest; | ||
import com.debatetimer.dto.member.MemberCreateResponse; | ||
import com.debatetimer.dto.member.TableResponses; | ||
import com.debatetimer.domain.member.Member; | ||
import com.debatetimer.domain.parliamentary.ParliamentaryTable; | ||
import com.debatetimer.repository.member.MemberRepository; | ||
import com.debatetimer.repository.parliamentary.ParliamentaryTableRepository; | ||
import java.util.List; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class MemberService { | ||
|
||
private final MemberRepository memberRepository; | ||
private final ParliamentaryTableRepository parliamentaryTableRepository; | ||
|
||
@Transactional(readOnly = true) | ||
public TableResponses getTables(Long memberId) { | ||
Member member = memberRepository.getById(memberId); | ||
List<ParliamentaryTable> parliamentaryTable = parliamentaryTableRepository.findAllByMember(member); | ||
return TableResponses.from(parliamentaryTable); | ||
} | ||
|
||
@Transactional | ||
public MemberCreateResponse createMember(MemberCreateRequest request) { | ||
Member member = memberRepository.save(request.toMember()); | ||
return new MemberCreateResponse(member); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,18 @@ | ||
package com.debatetimer; | ||
|
||
import com.debatetimer.repository.member.MemberRepository; | ||
import com.debatetimer.repository.parliamentary.ParliamentaryTableRepository; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
|
||
@ExtendWith(DataBaseCleaner.class) | ||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) | ||
public abstract class BaseServiceTest { | ||
|
||
@Autowired | ||
protected MemberRepository memberRepository; | ||
|
||
@Autowired | ||
protected ParliamentaryTableRepository parliamentaryTableRepository; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.debatetimer.controller.member; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import com.debatetimer.BaseControllerTest; | ||
import com.debatetimer.dto.member.MemberCreateRequest; | ||
import com.debatetimer.dto.member.MemberCreateResponse; | ||
import com.debatetimer.dto.member.TableResponses; | ||
import com.debatetimer.domain.member.Member; | ||
import com.debatetimer.domain.parliamentary.ParliamentaryTable; | ||
import io.restassured.RestAssured; | ||
import io.restassured.http.ContentType; | ||
import org.junit.jupiter.api.Nested; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class MemberControllerTest extends BaseControllerTest { | ||
|
||
@Nested | ||
class CreateMember { | ||
|
||
@Test | ||
void 회원을_생성한다() { | ||
MemberCreateRequest request = new MemberCreateRequest("커찬"); | ||
|
||
MemberCreateResponse response = RestAssured.given().log().all() | ||
.contentType(ContentType.JSON) | ||
.body(request) | ||
.when().post("/api/member") | ||
.then().log().all() | ||
.statusCode(201) | ||
.extract().as(MemberCreateResponse.class); | ||
|
||
assertThat(response.nickname()).isEqualTo(request.nickname()); | ||
} | ||
} | ||
|
||
@Nested | ||
class getTables { | ||
|
||
@Test | ||
void 회원의_전체_토론_시간표를_조회한다() { | ||
Member member = memberRepository.save(new Member("커찬")); | ||
parliamentaryTableRepository.save(new ParliamentaryTable(member, "토론 시간표 A", "주제", 1800)); | ||
parliamentaryTableRepository.save(new ParliamentaryTable(member, "토론 시간표 B", "주제", 1900)); | ||
|
||
TableResponses response = RestAssured.given().log().all() | ||
.contentType(ContentType.JSON) | ||
.queryParam("memberId", member.getId()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오.. 저는 url에 직접 명시해주었는데 queryparameter를 지정해주는 이런 방법이 있었군요 👍 |
||
.when().get("/api/table") | ||
.then().log().all() | ||
.statusCode(200) | ||
.extract().as(TableResponses.class); | ||
|
||
assertThat(response.tables()).hasSize(2); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
비토와 커찬 모두에게 어떤 방향을 더 선호하는지 묻습니다.
저는 responsebody 이외에 응답에 대한 header와 같은 설정을 해줄 수 있다는 점에서 확장성을 고려해 ResponseEntity를 사용해오긴 했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
저는 header 설정이 필요할 땜만 ResponseEntity 를 사용하는 편입니다. 주로 헤더는 인증 용도로 사용되서 잘 사용할 일도 적을 것 같구요