Skip to content

Commit

Permalink
Runner profile 수정 API 구현 (#325)
Browse files Browse the repository at this point in the history
* 오류나는 Dockerfile 수정 (#295)

* refactor: 필요없는 러너 서포터 필드 삭제

* refactor: 필요없는 ChattingCount 필드 삭제

* feat: RunnerTechnicalTag 관련 클래스 추가

* refactor: SupporterTechnicalTag 빌더 생성자 접근제한자 private 으로 변경

* feat: SupporterRunnerPost 엔티티 생성

* refactor: dev CI 스크립트 변경

* refactor: dev CD 스크립트 변경

* refactor: deploy CI 스크립트 변경

* refactor: deploy CD 스크립트 변경

* Auth ArgumentResolver 익명 null 반환 변경 (#271)

* refactor: Auth ArgumentResolver 익명 null 반환 변경

* refactor: AuthSupporterPrincipal 내부 속성 변경

* refactor: docker container timezone 변경

* feat: Dockerfile 타임존 설정

* feat: jar 타임존 설정

* fix: Dockerfile 오류 해결

---------

Co-authored-by: HyunSeo Park (Hyena) <[email protected]>

* feat: Runner 의 프로필 수정 기능 구현

* fix: 충돌 해결

* refactor: ClientErrorCode 수정

* refactor: RunnerProfileRequest 위치 변경 및 이름 수정

* refactor: setter 삭제

* refactor: RunnerProfileController 수정 및 redirectUri 추가

* refactor: RunnerProfileService 의 코드 RunnerService 로 이동

* fix: TechnicalTag, RunnerTechnicalTag 중복 저장 해결

* test: RunnerProfileServiceTest 수정

* docs: RunnerProfileUpdateApi RestDocs 작성

* refactor: ClientErrorCode static import 추가

* test: RunnerProfileAssuredUpdateTest 작성

* refactor: RunnerUpdateRequest 컨벤션에 맞도록 수정

* refactor: ServiceTestConfig 컨벤션에 맞도록 수정

* refactor: request 전체를 보내지 않도록 수정

* feat: runner의 introduction 기본값 설정

* style: 사용하지 않는 주석 제거

* test: RunnerServiceTest로 이동 및 테스트 코드 작성

* refactor: Runner 프로필 수정 기능 컨벤션에 맞도록 수정 및 리팩터링

* fix: DDL 오류 수정

* refactor: runner, support에서 Introduction null 체크

* test: service 테스트에서 Response 를 의존하지 않도록 작성

* test: service 테스트에서 Response 를 의존하지 않도록 작성

* refactor: 컨벤션에 맞도록 수정

---------

Co-authored-by: Jeonghoon Park <[email protected]>
Co-authored-by: HyunSeo Park (Hyena) <[email protected]>
  • Loading branch information
3 people authored Aug 13, 2023
1 parent d02d408 commit 837403c
Show file tree
Hide file tree
Showing 19 changed files with 494 additions and 12 deletions.
30 changes: 30 additions & 0 deletions backend/baton/src/docs/asciidoc/RunnerProfileUpdateApi.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
ifndef::snippets[]
:snippets: ../../../build/generated-snippets
endif::[]
:doctype: investment
:icons: font
:source-highlighter: highlight.js
:toc: left
:toclevels: 2
:sectlinks:
:operation-http-request-title: Example Request
:operation-http-response-title: Example Response

== *러너 프로필 수정*

=== *러너 프로필 수정 API*

==== *Http Request*
include::{snippets}/../../build/generated-snippets/runner-profile-update-api-test/update-runner-profile/http-request.adoc[]

==== *Http Request Headers*
include::{snippets}/../../build/generated-snippets/runner-profile-update-api-test/update-runner-profile/request-headers.adoc[]

==== *Http Request Body*
include::{snippets}/../../build/generated-snippets/runner-profile-update-api-test/update-runner-profile/request-body.adoc[]

==== *Http Response*
include::{snippets}/../../build/generated-snippets/runner-profile-update-api-test/update-runner-profile/http-response.adoc[]

==== *Http Response Headers*
include::{snippets}/../../build/generated-snippets/runner-profile-update-api-test/update-runner-profile/response-headers.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
import org.springframework.boot.context.properties.bind.DefaultValue;

import java.util.Objects;

import static lombok.AccessLevel.PROTECTED;

Expand All @@ -14,10 +18,17 @@
@Embeddable
public class Introduction {

private static final String DEFAULT_VALUE = "'안녕하세요.'";

@ColumnDefault(DEFAULT_VALUE)
@Column(name = "introduction", nullable = true)
private String value;

public Introduction(final String value) {
this.value = value;
}

public static String getDefaultValue() {
return DEFAULT_VALUE;
}
}
21 changes: 21 additions & 0 deletions backend/baton/src/main/java/touch/baton/domain/runner/Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import touch.baton.domain.common.BaseEntity;
import touch.baton.domain.common.vo.Introduction;
import touch.baton.domain.member.Member;
import touch.baton.domain.member.vo.Company;
import touch.baton.domain.member.vo.MemberName;
import touch.baton.domain.runner.exception.RunnerDomainException;
import touch.baton.domain.technicaltag.RunnerTechnicalTag;
import touch.baton.domain.technicaltag.RunnerTechnicalTags;
Expand Down Expand Up @@ -73,6 +75,25 @@ public void addAllRunnerTechnicalTags(final List<RunnerTechnicalTag> runnerTechn
this.runnerTechnicalTags.addAll(runnerTechnicalTags);
}

public void updateIntroduction(final Introduction introduction) {
this.introduction = defaultIntroductionIfNull(introduction);
}

private Introduction defaultIntroductionIfNull(final Introduction introduction) {
if (Objects.isNull(introduction.getValue())) {
return new Introduction(introduction.getDefaultValue());
}
return introduction;
}

public void updateMemberName(final MemberName memberName) {
this.member.updateMemberName(memberName);
}

public void updateCompany(final Company company) {
this.member.updateCompany(company);
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
package touch.baton.domain.runner.controller;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import touch.baton.domain.oauth.controller.resolver.AuthRunnerPrincipal;
import touch.baton.domain.runner.Runner;
import touch.baton.domain.runner.controller.response.RunnerMyProfileResponse;
import touch.baton.domain.runner.controller.response.RunnerProfileResponse;
import touch.baton.domain.runner.controller.response.RunnerResponse;
import touch.baton.domain.runner.service.RunnerService;
import touch.baton.domain.runner.service.dto.RunnerUpdateRequest;
import touch.baton.domain.runnerpost.controller.response.RunnerPostResponse;
import touch.baton.domain.runnerpost.service.RunnerPostService;

import java.net.URI;
import java.util.List;

@RequiredArgsConstructor
Expand All @@ -41,9 +47,17 @@ public ResponseEntity<RunnerResponse.MyProfile> readMyProfileByToken(@AuthRunner
return ResponseEntity.ok(response);
}

@PatchMapping("/me")
public ResponseEntity<Void> updateMyProfile(@AuthRunnerPrincipal final Runner runner,
@RequestBody @Valid final RunnerUpdateRequest runnerUpdateRequest) {
runnerService.updateRunner(runner, runnerUpdateRequest);
final URI redirectUri = UriComponentsBuilder.fromPath("/api/v1/profile/runner/me").build().toUri();
return ResponseEntity.noContent().location(redirectUri).build();
}

@GetMapping("/{runnerId}")
public ResponseEntity<RunnerProfileResponse.Detail> readRunnerProfile(@PathVariable Long runnerId) {
final Runner runner = runnerService.readRunnerById(runnerId);
final Runner runner = runnerService.readByRunnerId(runnerId);
return ResponseEntity.ok(RunnerProfileResponse.Detail.from(runner));
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package touch.baton.domain.runner.controller.response;

import touch.baton.domain.common.vo.Introduction;
import touch.baton.domain.member.Member;
import touch.baton.domain.runner.Runner;

import java.util.List;
import java.util.Objects;

public record RunnerProfileResponse() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,73 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import touch.baton.domain.common.vo.Introduction;
import touch.baton.domain.common.vo.TagName;
import touch.baton.domain.member.vo.Company;
import touch.baton.domain.member.vo.MemberName;
import touch.baton.domain.runner.Runner;
import touch.baton.domain.runner.exception.RunnerBusinessException;
import touch.baton.domain.runner.repository.RunnerRepository;
import touch.baton.domain.runner.service.dto.RunnerUpdateRequest;
import touch.baton.domain.technicaltag.RunnerTechnicalTag;
import touch.baton.domain.technicaltag.TechnicalTag;
import touch.baton.domain.technicaltag.repository.RunnerTechnicalTagRepository;
import touch.baton.domain.technicaltag.repository.TechnicalTagRepository;

import java.util.List;

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

private final RunnerRepository runnerRepository;
private final RunnerTechnicalTagRepository runnerTechnicalTagRepository;
private final TechnicalTagRepository technicalTagRepository;

public Runner readRunnerById(final Long runnerId) {
public Runner readByRunnerId(final Long runnerId) {
return runnerRepository.joinMemberByRunnerId(runnerId)
.orElseThrow(() -> new RunnerBusinessException("Runner가 존재하지 않습니다."));
.orElseThrow(() -> new RunnerBusinessException("Runner 가 존재하지 않습니다."));
}

@Transactional
public void updateRunner(Runner runner, RunnerUpdateRequest runnerUpdateRequest) {
runner.updateMemberName(new MemberName(runnerUpdateRequest.name()));
runner.updateCompany(new Company(runnerUpdateRequest.company()));
runner.updateIntroduction(new Introduction(runnerUpdateRequest.introduction()));
updateTechnicalTags(runner, runnerUpdateRequest.technicalTags());
}

private void updateTechnicalTags(final Runner runner, final List<String> technicalTags) {
runnerTechnicalTagRepository.deleteByRunner(runner);
createRunnerTechnicalTags(runner, technicalTags);
}

private List<RunnerTechnicalTag> createRunnerTechnicalTags(final Runner runner, final List<String> technicalTags) {
return technicalTags.stream()
.map(tagName -> createRunnerTechnicalTag(runner, new TagName(tagName)))
.toList();
}

private RunnerTechnicalTag createRunnerTechnicalTag(final Runner runner, final TagName tagName) {
final TechnicalTag technicalTag = findTechnicalTagIfExistElseCreate(tagName);
return createRunnerTechnicalTagAndSave(runner, technicalTag);
}

private TechnicalTag findTechnicalTagIfExistElseCreate(final TagName tagName) {
return technicalTagRepository.findByTagName(tagName)
.orElseGet(() -> technicalTagRepository.save(
TechnicalTag.builder()
.tagName(tagName)
.build())
);
}

private RunnerTechnicalTag createRunnerTechnicalTagAndSave(final Runner runner, final TechnicalTag technicalTag) {
RunnerTechnicalTag runnerTechnicalTag = RunnerTechnicalTag.builder()
.runner(runner)
.technicalTag(technicalTag)
.build();
return runnerTechnicalTagRepository.save(runnerTechnicalTag);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package touch.baton.domain.runner.service.dto;

import touch.baton.domain.common.exception.validator.ValidNotNull;

import java.util.List;

import static touch.baton.domain.common.exception.ClientErrorCode.COMPANY_IS_NULL;
import static touch.baton.domain.common.exception.ClientErrorCode.NAME_IS_NULL;
import static touch.baton.domain.common.exception.ClientErrorCode.RUNNER_TECHNICAL_TAGS_ARE_NULL;

public record RunnerUpdateRequest(@ValidNotNull(clientErrorCode = NAME_IS_NULL)
String name,
@ValidNotNull(clientErrorCode = COMPANY_IS_NULL)
String company,
String introduction,
@ValidNotNull(clientErrorCode = RUNNER_TECHNICAL_TAGS_ARE_NULL)
List<String> technicalTags
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,14 @@ public void updateCompany(final Company company) {
}

public void updateIntroduction(final Introduction introduction) {
this.introduction = introduction;
this.introduction = defaultIntroductionIfNull(introduction);
}

private Introduction defaultIntroductionIfNull(final Introduction introduction) {
if (Objects.isNull(introduction.getValue())) {
return new Introduction(introduction.getDefaultValue());
}
return introduction;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

import java.util.List;

import static touch.baton.domain.common.exception.ClientErrorCode.*;
import static touch.baton.domain.common.exception.ClientErrorCode.COMPANY_IS_NULL;
import static touch.baton.domain.common.exception.ClientErrorCode.NAME_IS_NULL;
import static touch.baton.domain.common.exception.ClientErrorCode.SUPPORTER_TECHNICAL_TAGS_ARE_NULL;

public record SupporterUpdateRequest(@ValidNotNull(clientErrorCode = NAME_IS_NULL)
String name,
@ValidNotNull(clientErrorCode = COMPANY_IS_NULL)
String company,
String introduction,
@ValidNotNull(clientErrorCode = SUPPORTER_TECHNICAL_TAGS_ARE_NULL)
List<String> technicalTags) {
List<String> technicalTags
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package touch.baton.domain.technicaltag.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import touch.baton.domain.runner.Runner;
import touch.baton.domain.technicaltag.RunnerTechnicalTag;

public interface RunnerTechnicalTagRepository extends JpaRepository<RunnerTechnicalTag, Long> {

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("DELETE FROM RunnerTechnicalTag rt WHERE rt.runner = :runner")
int deleteByRunner(@Param("runner") final Runner runner);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import io.restassured.response.ExtractableResponse;
import io.restassured.response.Response;
import org.springframework.http.HttpStatus;
import touch.baton.assure.common.AssuredSupport;
import touch.baton.domain.common.exception.ClientErrorCode;
import touch.baton.domain.common.response.ErrorResponse;
import touch.baton.domain.runner.Runner;
import touch.baton.domain.runner.controller.response.RunnerProfileResponse;
import touch.baton.domain.runner.controller.response.RunnerResponse;
import touch.baton.domain.runner.service.dto.RunnerUpdateRequest;

import static org.assertj.core.api.SoftAssertions.assertSoftly;

Expand Down Expand Up @@ -46,6 +50,11 @@ public static class RunnerProfileClientRequestBuilder {
public RunnerProfileServerResponseBuilder 서버_응답() {
return new RunnerProfileServerResponseBuilder(response);
}

public RunnerProfileClientRequestBuilder 러너_본인_프로필을_수정한다(final RunnerUpdateRequest 러너_업데이트_요청) {
response = AssuredSupport.patch("/api/v1/profile/runner/me", accessToken, 러너_업데이트_요청);
return this;
}
}

public static class RunnerProfileServerResponseBuilder {
Expand Down Expand Up @@ -73,7 +82,7 @@ public RunnerProfileServerResponseBuilder(final ExtractableResponse<Response> re
});
}

public void 러너_프로필_상세_조회를_검증한다(RunnerProfileResponse.Detail 러너_프로필_상세_응답) {
public void 러너_프로필_상세_조회를_검증한다(final RunnerProfileResponse.Detail 러너_프로필_상세_응답) {
final RunnerProfileResponse.Detail actual = this.response.as(RunnerProfileResponse.Detail.class);
assertSoftly(softly -> {
softly.assertThat(actual.runnerId()).isNotNull();
Expand All @@ -86,5 +95,21 @@ public RunnerProfileServerResponseBuilder(final ExtractableResponse<Response> re
}
);
}

public void 러너_본인_프로필_수정_성공을_검증한다(final HttpStatus HTTP_STATUS, final Long 러너_아이디) {
assertSoftly(softly -> {
softly.assertThat(response.statusCode()).isEqualTo(HTTP_STATUS.value());
softly.assertThat(response.header("Location")).isNotNull();
});
}

public void 러너_본인_프로필_수정_실패를_검증한다(final ClientErrorCode 클라이언트_에러_코드) {
final ErrorResponse actual = this.response.as(ErrorResponse.class);

assertSoftly(softly -> {
softly.assertThat(response.statusCode()).isEqualTo(클라이언트_에러_코드.getHttpStatus().value());
softly.assertThat(actual.errorCode()).isEqualTo(클라이언트_에러_코드.getErrorCode());
});
}
}
}
Loading

0 comments on commit 837403c

Please sign in to comment.