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

[Feature] - 프로덕션 API v1.2.0 배포 #588

Merged
merged 14 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import kr.touroot.global.auth.dto.MemberAuth;
import kr.touroot.global.exception.dto.ExceptionResponse;
import kr.touroot.member.dto.request.ProfileUpdateRequest;
import kr.touroot.member.dto.response.MyLikeTravelogueResponse;
import kr.touroot.member.dto.response.MyTravelogueResponse;
import kr.touroot.member.dto.response.ProfileResponse;
import kr.touroot.member.service.MyPageFacadeService;
Expand All @@ -24,7 +25,7 @@
import org.springframework.data.web.PageableDefault;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand Down Expand Up @@ -103,6 +104,30 @@ public ResponseEntity<Page<PlanResponse>> readTravelPlans(
return ResponseEntity.ok(data);
}

@Operation(summary = "내가 좋아요 한 여행기 조회")
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "내가 좋아요 한 여행기 조회에 성공했을 때"
),
@ApiResponse(
responseCode = "401",
description = "로그인하지 않은 사용자가 조회를 시도할 때",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))
)
})
@PageableAsQueryParam
@GetMapping("/likes")
public ResponseEntity<Page<MyLikeTravelogueResponse>> readLikes(
@NotNull MemberAuth memberAuth,
@Parameter(hidden = true)
@PageableDefault(size = 5, sort = "id", direction = Sort.Direction.DESC)
Pageable pageable
) {
Page<MyLikeTravelogueResponse> data = myPageFacadeService.readLikes(memberAuth, pageable);
return ResponseEntity.ok(data);
}

@Operation(summary = "나의 프로필 정보 수정")
@ApiResponses(value = {
@ApiResponse(
Expand All @@ -120,7 +145,7 @@ public ResponseEntity<Page<PlanResponse>> readTravelPlans(
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))
)
})
@PatchMapping("/profile")
@PutMapping("/profile")
public ResponseEntity<ProfileResponse> updateProfile(
@Valid @RequestBody ProfileUpdateRequest request,
@NotNull MemberAuth memberAuth
Expand Down
10 changes: 9 additions & 1 deletion backend/src/main/java/kr/touroot/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,16 @@ private void validateProfileImageUrlForm(String profileImageUrl) {
}
}

public void changeNickname(String nickname) {
private void validateProfileImageUrlNotNull(String profileImageUrl) {
if (profileImageUrl == null) {
throw new BadRequestException("프로필 이미지는 비어 있을 수 없습니다");
}
}

public void update(String nickname, String profileImageUrl) {
validateNickname(nickname);
validateProfileImageUrlNotNull(profileImageUrl);
this.nickname = nickname;
this.profileImageUrl = profileImageUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

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

public record ProfileUpdateRequest(
@Schema(description = "사용자 닉네임", example = "아기뚜리")
@NotBlank(message = "닉네임은 비어있을 수 없습니다.")
String nickname
String nickname,
@Schema(description = "사용자 프로필 사진 URL", example = "https://dev.touroot.kr/profile-image-ex.png")
@NotNull(message = "프로필 사진 URL은 null 값일 수 없습니다.")
String profileImageUrl
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package kr.touroot.member.dto.response;

import java.time.format.DateTimeFormatter;
import kr.touroot.travelogue.domain.Travelogue;
import lombok.Builder;

@Builder
public record MyLikeTravelogueResponse(
long id,
String title,
String thumbnailUrl,
String createdAt,
String authorName,
String authorProfileImageUrl
) {

public static MyLikeTravelogueResponse from(Travelogue travelogue) {
String createdAt = travelogue.getCreatedAt()
.toLocalDate()
.format(DateTimeFormatter.ofPattern("yyyy.MM.dd"));

return MyLikeTravelogueResponse.builder()
.id(travelogue.getId())
.title(travelogue.getTitle())
.thumbnailUrl(travelogue.getThumbnail())
.createdAt(createdAt)
.authorName(travelogue.getAuthorNickname())
.authorProfileImageUrl(travelogue.getAuthorProfileImageUrl())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public static ProfileResponse from(Member member) {
return ProfileResponse.builder()
.profileImageUrl(member.getProfileImageUrl())
.nickname(member.getNickname())
.profileImageUrl(member.getProfileImageUrl())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package kr.touroot.member.service;

import java.util.Objects;
import kr.touroot.authentication.infrastructure.PasswordEncryptor;
import kr.touroot.global.auth.dto.MemberAuth;
import kr.touroot.global.exception.BadRequestException;
import kr.touroot.image.infrastructure.AwsS3Provider;
import kr.touroot.member.domain.Member;
import kr.touroot.member.dto.request.MemberRequest;
import kr.touroot.member.dto.request.ProfileUpdateRequest;
Expand All @@ -18,6 +20,7 @@ public class MemberService {

private final MemberRepository memberRepository;
private final PasswordEncryptor passwordEncryptor;
private final AwsS3Provider s3Provider;

@Transactional(readOnly = true)
public Member getMemberById(Long memberId) {
Expand Down Expand Up @@ -54,8 +57,17 @@ private void validateNicknameDuplication(String nickname) {
@Transactional
public ProfileResponse updateProfile(ProfileUpdateRequest request, MemberAuth memberAuth) {
Member member = getMemberById(memberAuth.memberId());
member.changeNickname(request.nickname());
String requestProfileImageUrl = request.profileImageUrl();
if (isUpdatable(requestProfileImageUrl, member)) {
requestProfileImageUrl = s3Provider.copyImageToPermanentStorage(request.profileImageUrl());
}
member.update(request.nickname(), requestProfileImageUrl);

return ProfileResponse.from(member);
}

private boolean isUpdatable(String requestProfileImageUrl, Member member) {
return !requestProfileImageUrl.isEmpty() && !Objects.equals(requestProfileImageUrl,
member.getProfileImageUrl());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import kr.touroot.global.auth.dto.MemberAuth;
import kr.touroot.member.domain.Member;
import kr.touroot.member.dto.request.ProfileUpdateRequest;
import kr.touroot.member.dto.response.MyLikeTravelogueResponse;
import kr.touroot.member.dto.response.MyTravelogueResponse;
import kr.touroot.member.dto.response.ProfileResponse;
import kr.touroot.travelogue.domain.Travelogue;
import kr.touroot.travelogue.domain.TravelogueLike;
import kr.touroot.travelogue.service.TravelogueLikeService;
import kr.touroot.travelogue.service.TravelogueService;
import kr.touroot.travelplan.domain.TravelPlan;
import kr.touroot.travelplan.dto.response.PlanResponse;
Expand All @@ -23,6 +26,7 @@ public class MyPageFacadeService {
private final MemberService memberService;
private final TravelogueService travelogueService;
private final TravelPlanService travelPlanService;
private final TravelogueLikeService travelogueLikeService;

@Transactional(readOnly = true)
public ProfileResponse readProfile(MemberAuth memberAuth) {
Expand All @@ -46,6 +50,15 @@ public Page<PlanResponse> readTravelPlans(MemberAuth memberAuth, Pageable pageab
return travelPlans.map(PlanResponse::from);
}

@Transactional(readOnly = true)
public Page<MyLikeTravelogueResponse> readLikes(MemberAuth memberAuth, Pageable pageable) {
Member member = memberService.getMemberById(memberAuth.memberId());

return travelogueLikeService.findByLiker(member, pageable)
.map(TravelogueLike::getTravelogue)
.map(MyLikeTravelogueResponse::from);
}

@Transactional
public ProfileResponse updateProfile(ProfileUpdateRequest request, MemberAuth memberAuth) {
return memberService.updateProfile(request, memberAuth);
Expand Down
27 changes: 0 additions & 27 deletions backend/src/main/java/kr/touroot/tag/controller/TagController.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
package kr.touroot.tag.controller;

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 jakarta.validation.Valid;
import java.net.URI;
import java.util.List;
import kr.touroot.global.exception.dto.ExceptionResponse;
import kr.touroot.tag.dto.TagCreateRequest;
import kr.touroot.tag.dto.TagResponse;
import kr.touroot.tag.service.TagService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -29,25 +21,6 @@ public class TagController {

private final TagService tagService;

@Operation(summary = "태그 생성")
@ApiResponses(value = {
@ApiResponse(
responseCode = "201",
description = "태그가 생성이 정상적으로 성공했을 때"
),
@ApiResponse(
responseCode = "400",
description = "Body에 유효하지 않은 값이 존재하거나 중복된 태그가 존재할 때",
content = @Content(schema = @Schema(implementation = ExceptionResponse.class))
)
})
@PostMapping
public ResponseEntity<TagResponse> createTag(@Valid @RequestBody TagCreateRequest request) {
TagResponse data = tagService.createTag(request);
return ResponseEntity.created(URI.create("/api/v1/tags/" + data.id()))
.body(data);
}

@Operation(summary = "모든 태그 조회")
@ApiResponses(value = {
@ApiResponse(
Expand Down
15 changes: 0 additions & 15 deletions backend/src/main/java/kr/touroot/tag/dto/TagCreateRequest.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@
import org.springframework.data.jpa.repository.JpaRepository;

public interface TagRepository extends JpaRepository<Tag, Long> {

boolean existsByTag(String tag);
}
17 changes: 0 additions & 17 deletions backend/src/main/java/kr/touroot/tag/service/TagService.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package kr.touroot.tag.service;

import java.util.List;
import kr.touroot.global.exception.BadRequestException;
import kr.touroot.tag.domain.Tag;
import kr.touroot.tag.dto.TagCreateRequest;
import kr.touroot.tag.dto.TagResponse;
import kr.touroot.tag.repository.TagRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -19,20 +16,6 @@ public class TagService {

private final TagRepository tagRepository;

@Transactional
public TagResponse createTag(TagCreateRequest tagCreateRequest) {
validateDuplicated(tagCreateRequest);
Tag savedTag = tagRepository.save(tagCreateRequest.toTag());

return TagResponse.from(savedTag);
}

private void validateDuplicated(TagCreateRequest tagCreateRequest) {
if (tagRepository.existsByTag(tagCreateRequest.tag())) {
throw new BadRequestException("이미 존재하는 태그입니다.");
}
}

@Cacheable(cacheNames = "tag")
@Transactional(readOnly = true)
public List<TagResponse> readTags() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,20 @@ public ResponseEntity<Page<TravelogueSimpleResponse>> findMainPageTravelogues(
@Parameter(hidden = true)
@PageableDefault(size = 5, sort = "id", direction = Direction.DESC)
Pageable pageable,
TravelogueFilterRequest filter
TravelogueFilterRequest filterRequest,
@Valid
TravelogueSearchRequest searchRequest

) {
return ResponseEntity.ok(travelogueFacadeService.findSimpleTravelogues(filter, pageable));
Page<TravelogueSimpleResponse> data = travelogueFacadeService.findSimpleTravelogues(
filterRequest,
searchRequest,
pageable
);
return ResponseEntity.ok(data);
}

// TODO: 프론트엔드 엔드포인트 이전 작업 완료 후 제거
@Operation(summary = "여행기 검색")
@ApiResponses(value = {
@ApiResponse(
Expand Down
Loading
Loading