Skip to content
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

[BE] 피드 스레드 등록 기능 구현 #234

Merged
merged 8 commits into from
Jul 28, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package team.teamby.teambyteam.feed.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import team.teamby.teambyteam.feed.application.dto.FeedThreadWritingRequest;
import team.teamby.teambyteam.feed.domain.Feed;
import team.teamby.teambyteam.feed.domain.FeedRepository;
import team.teamby.teambyteam.feed.domain.FeedThread;
import team.teamby.teambyteam.feed.domain.vo.Content;
import team.teamby.teambyteam.member.configuration.dto.MemberEmailDto;
import team.teamby.teambyteam.member.domain.IdOnly;
import team.teamby.teambyteam.member.domain.MemberRepository;
import team.teamby.teambyteam.member.domain.vo.Email;
import team.teamby.teambyteam.member.exception.MemberException;

@Service
@Transactional
@RequiredArgsConstructor
public class FeedThreadService {

private final FeedRepository feedRepository;
private final MemberRepository memberRepository;

public Long write(
final FeedThreadWritingRequest feedThreadWritingRequest,
final MemberEmailDto memberEmailDto,
final Long teamPlaceId
) {

final Content content = new Content(feedThreadWritingRequest.content());
final IdOnly memberId = memberRepository.findIdByEmail(new Email(memberEmailDto.email()))
.orElseThrow(MemberException.MemberNotFoundException::new);

final Feed feed = new FeedThread(teamPlaceId, content, memberId.id());

final Feed savedFeed = feedRepository.save(feed);
return savedFeed.getId();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package team.teamby.teambyteam.feed.application.dto;

import jakarta.validation.constraints.NotBlank;

public record FeedThreadWritingRequest(
@NotBlank(message = "스레드 내용이 있어야 합니다.")
String content
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class Content {
@Lob
private String value;

public Content(String value) {
public Content(final String value) {
validate(value);
this.value = value;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package team.teamby.teambyteam.feed.presentation;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
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.RestController;
import team.teamby.teambyteam.feed.application.FeedThreadService;
import team.teamby.teambyteam.feed.application.dto.FeedThreadWritingRequest;
import team.teamby.teambyteam.member.configuration.AuthPrincipal;
import team.teamby.teambyteam.member.configuration.dto.MemberEmailDto;

import java.net.URI;

@RestController
@RequestMapping("/api/team-place")
@RequiredArgsConstructor
public class FeedThreadController {

private final FeedThreadService feedThreadService;

@PostMapping("/{teamPlaceId}/feed/threads")
public ResponseEntity<Void> write(
@RequestBody @Valid final FeedThreadWritingRequest request,
@AuthPrincipal final MemberEmailDto memberEmailDto,
@PathVariable final Long teamPlaceId
) {
final Long threadId = feedThreadService.write(request, memberEmailDto, teamPlaceId);
final URI location = URI.create("/api/team-place/" + teamPlaceId + "/feed/threads/" + threadId);

return ResponseEntity.created(location).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package team.teamby.teambyteam.member.domain;

public record IdOnly(Long id) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
boolean existsByEmail(Email email);

Optional<Member> findByEmail(Email email);

Optional<IdOnly> findIdByEmail(Email email);
}
4 changes: 4 additions & 0 deletions backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ spring:
dialect: org.hibernate.dialect.MySQL57Dialect
format_sql: true # for test
show-sql: true # for test

jwt:
secret: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
expiration: 10800000
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.transaction.annotation.Transactional;
import team.teamby.teambyteam.common.builder.TestFixtureBuilder;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@Transactional
@Sql({"/h2-truncate.sql"})
public abstract class ServiceTest {

@Autowired
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import team.teamby.teambyteam.feed.domain.FeedRepository;
import team.teamby.teambyteam.member.domain.MemberRepository;
import team.teamby.teambyteam.member.domain.MemberTeamPlaceRepository;
import team.teamby.teambyteam.schedule.domain.ScheduleRepository;
Expand All @@ -22,6 +23,9 @@ public class BuilderSupporter {
@Autowired
private TeamPlaceRepository teamPlaceRepository;

@Autowired
private FeedRepository feedRepository;

public ScheduleRepository scheduleRepository() {
return scheduleRepository;
}
Expand All @@ -37,4 +41,8 @@ public MemberTeamPlaceRepository memberTeamPlaceRepository() {
public TeamPlaceRepository teamPlaceRepository() {
return teamPlaceRepository;
}

public FeedRepository feedRepository() {
return feedRepository;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import team.teamby.teambyteam.feed.domain.Feed;
import team.teamby.teambyteam.member.domain.Member;
import team.teamby.teambyteam.member.domain.MemberTeamPlace;
import team.teamby.teambyteam.schedule.domain.Schedule;
Expand Down Expand Up @@ -39,11 +40,25 @@ public List<Member> buildMembers(final List<Member> members) {
return bs.memberRepository().saveAll(members);
}

public MemberTeamPlace buildMemberTeamPlace(final Member member, final TeamPlace teamPlace) {
final MemberTeamPlace memberTeamPlace = new MemberTeamPlace();
memberTeamPlace.setMemberAndTeamPlace(member, teamPlace);
return bs.memberTeamPlaceRepository().save(memberTeamPlace);
}

public List<MemberTeamPlace> buildMemberTeamPlaces(final List<MemberTeamPlace> memberTeamPlaces) {
return bs.memberTeamPlaceRepository().saveAll(memberTeamPlaces);
}

public List<TeamPlace> buildTeamPlaces(final List<TeamPlace> teamPlaces) {
return bs.teamPlaceRepository().saveAll(teamPlaces);
}

public Feed buildFeed(final Feed feed) {
return bs.feedRepository().save(feed);
}

public List<Feed> buildFeeds(final List<Feed> feeds) {
return bs.feedRepository().saveAll(feeds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package team.teamby.teambyteam.common.fixtures;

import team.teamby.teambyteam.feed.application.dto.FeedThreadWritingRequest;
import team.teamby.teambyteam.feed.domain.FeedThread;
import team.teamby.teambyteam.feed.domain.vo.Content;

public class FeedThreadFixtures {


/**
* CONTENT
*/
public static final String HELLO_CONTENT = "Hello";
public static final String HI_CONTENT = "Hi";

/**
* REQUEST
*/
public static final FeedThreadWritingRequest HELLO_WRITING_REQUEST = new FeedThreadWritingRequest(HELLO_CONTENT);
public static final FeedThreadWritingRequest HI_WRITING_REQUEST = new FeedThreadWritingRequest(HI_CONTENT);

/**
* ENTITY
*/
public static FeedThread HELLO_THREAD(final Long teamPlaceId, final Long authorId) {
return new FeedThread(teamPlaceId, new Content(HELLO_CONTENT), authorId);
}

public static FeedThread HI_THREAD(final Long teamPlaceId, final Long authorId) {
return new FeedThread(teamPlaceId, new Content(HI_CONTENT), authorId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package team.teamby.teambyteam.common.fixtures.acceptance;

import io.restassured.RestAssured;
import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import team.teamby.teambyteam.feed.application.dto.FeedThreadWritingRequest;
import team.teamby.teambyteam.teamplace.domain.TeamPlace;

public class FeedThreadAcceptanceFixtures {

private static final String JWT_PREFIX = "Bearer ";

public static ExtractableResponse<Response> POST_FEED_THREAD_REQUEST(
final String authToken,
final TeamPlace teamPlace,
final FeedThreadWritingRequest request
) {
return RestAssured.given().log().all()
.header(HttpHeaders.AUTHORIZATION, JWT_PREFIX + authToken)
.contentType(MediaType.APPLICATION_JSON_VALUE)
.body(request)
.post("/api/team-place/{teamPlaceId}/feed/threads", teamPlace.getId())
.then().log().all()
.extract();

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package team.teamby.teambyteam.feed.acceptance;

import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import team.teamby.teambyteam.common.AcceptanceTest;
import team.teamby.teambyteam.common.fixtures.FeedThreadFixtures;
import team.teamby.teambyteam.feed.application.dto.FeedThreadWritingRequest;
import team.teamby.teambyteam.member.domain.Member;
import team.teamby.teambyteam.member.domain.MemberTeamPlace;
import team.teamby.teambyteam.teamplace.domain.TeamPlace;

import static team.teamby.teambyteam.common.fixtures.MemberFixtures.PHILIP;
import static team.teamby.teambyteam.common.fixtures.MemberFixtures.ROY;
import static team.teamby.teambyteam.common.fixtures.TeamPlaceFixtures.ENGLISH_TEAM_PLACE;
import static team.teamby.teambyteam.common.fixtures.TeamPlaceFixtures.JAPANESE_TEAM_PLACE;
import static team.teamby.teambyteam.common.fixtures.acceptance.FeedThreadAcceptanceFixtures.POST_FEED_THREAD_REQUEST;

public class FeedThreadAcceptanceTest extends AcceptanceTest {

@Nested
@DisplayName("피드에 스레드 등록 시")
class PostThread {

private Member authedMember;
private TeamPlace participatedTeamPlace;
private MemberTeamPlace participatedMemberTeamPlace;
private String authToken;

@BeforeEach
void setup() {
authedMember = testFixtureBuilder.buildMember(PHILIP());
participatedTeamPlace = testFixtureBuilder.buildTeamPlace(ENGLISH_TEAM_PLACE());
participatedMemberTeamPlace = testFixtureBuilder.buildMemberTeamPlace(authedMember, participatedTeamPlace);
authToken = jwtTokenProvider.generateToken(authedMember.getEmail().getValue());

}

@Test
@DisplayName("스레드 등록에 성공한다.")
void success() {
// given
final FeedThreadWritingRequest request = FeedThreadFixtures.HELLO_WRITING_REQUEST;

// when
final ExtractableResponse<Response> response = POST_FEED_THREAD_REQUEST(authToken, participatedTeamPlace, request);

//then
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value());
softly.assertThat(response.header(HttpHeaders.LOCATION)).contains("/api/team-place/" + participatedMemberTeamPlace.getTeamPlace().getId() + "/feed/threads");
});
}

@ParameterizedTest
@ValueSource(strings = {"", " ", " "})
@DisplayName("스레드 내용으로 빈 내용의 요청이 오면 등록이 실패한다.")
void failWithBlankContent(final String content) {
// given
final FeedThreadWritingRequest request = new FeedThreadWritingRequest(content);

// when
final ExtractableResponse<Response> response = POST_FEED_THREAD_REQUEST(authToken, participatedTeamPlace, request);

//then
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value());
softly.assertThat(response.body().asString()).contains("스레드 내용이 있어야 합니다.");
});
}

@Test
@DisplayName("사용자가 소속되지 않은 팀플레이스 아이디로 요청시 등록이 실패한다.")
void failWithForbiddenTeamPlace() {
// given
final TeamPlace UN_PARTICIPATED_TEAM_PLACE = testFixtureBuilder.buildTeamPlace(JAPANESE_TEAM_PLACE());

final FeedThreadWritingRequest request = FeedThreadFixtures.HELLO_WRITING_REQUEST;

// when
final ExtractableResponse<Response> response = POST_FEED_THREAD_REQUEST(authToken, UN_PARTICIPATED_TEAM_PLACE, request);

//then
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.FORBIDDEN.value());
softly.assertThat(response.body().asString()).contains("접근할 수 없는 팀플레이스입니다.");
});
}

@Test
@DisplayName("인증되지 않은 사용자로 요청시 등록이 실패한다.")
void failWithUnauthorizedMember() {
// given
final FeedThreadWritingRequest request = FeedThreadFixtures.HELLO_WRITING_REQUEST;
final String unauthorizedToken = jwtTokenProvider.generateToken(ROY().getEmail().getValue());

// when
final ExtractableResponse<Response> response = POST_FEED_THREAD_REQUEST(unauthorizedToken, participatedTeamPlace, request);

//then
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.NOT_FOUND.value());
softly.assertThat(response.body().asString()).contains("조회한 멤버가 존재하지 않습니다.");
});
}

}

}
Loading