Skip to content
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
33 changes: 33 additions & 0 deletions src/main/java/com/school/mohitto/controller/HairController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.school.mohitto.controller;

import com.school.mohitto.domain.authentication.AuthUserInfo;
import com.school.mohitto.dto.responseDTO.HairLikeToggleResponse;
import com.school.mohitto.security.annotation.AuthUser;
import com.school.mohitto.service.HairService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/hairs")
@Tag(name = "Hair", description = "헤어 추천 및 좋아요 API")
public class HairController {

private final HairService hairService;

@Operation(summary = "헤어 좋아요 토글", description = "해당 헤어 ID의 좋아요 상태를 반전시키고, 좋아요된 헤어 ID를 반환합니다.")
@PatchMapping("/{hairId}/like/toggle")
public HairLikeToggleResponse toggleLike(
@AuthUser final AuthUserInfo authUserInfo,
@Parameter(description = "좋아요 토글할 헤어 ID", required = true)
@PathVariable Long hairId) {
return hairService.toggleLikeStatus(hairId);
}

}
29 changes: 5 additions & 24 deletions src/main/java/com/school/mohitto/controller/MypageController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.school.mohitto.controller;

import com.school.mohitto.domain.authentication.AuthUserInfo;
import com.school.mohitto.dto.responseDTO.HairResponseList;
import com.school.mohitto.dto.responseDTO.HairListResponse;
import com.school.mohitto.dto.responseDTO.MypageUserInfoResponse;
import com.school.mohitto.exception.annotation.PageConstraint;
import com.school.mohitto.security.annotation.AuthUser;
import com.school.mohitto.service.HairService;
import com.school.mohitto.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -18,7 +17,6 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
Expand All @@ -32,35 +30,16 @@ public class MypageController {

@Operation(summary = "저장한 헤어스타일 목록 조회")
@GetMapping("hair/saved")
public HairResponseList getSavedHairs(
public HairListResponse getSavedHairs(
@AuthUser AuthUserInfo authUserInfo,
@ParameterObject
@PageableDefault(size = 6, sort = "createdDate", direction = Sort.Direction.DESC)
@Valid @PageConstraint Pageable pageable
) {
HairResponseList result = hairService.getSavedHairs(authUserInfo.userId(), pageable);
HairListResponse result = hairService.getSavedHairs(authUserInfo.userId(), pageable);
return result;
}

@Operation(
summary = "저장한 헤어스타일 삭제",
description = "저장한 헤어스타일의 하트를 다시 눌러 삭제합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "삭제 성공"),
@ApiResponse(responseCode = "404", description = "해당 헤어스타일을 찾을 수 없음"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@DeleteMapping("hair/delete/{hairId}")
public ResponseEntity<String> deleteSavedHair(
@AuthUser AuthUserInfo authUserInfo,
@Parameter(description = "삭제할 헤어스타일 ID", required = true)
@PathVariable Long hairId
) {
hairService.deleteSavedHair(authUserInfo.userId(), hairId);
return ResponseEntity.ok("삭제되었습니다.");
}

@Operation(
summary = "마이페이지 유저 정보 조회",
description = "마이페이지 상단에 보여줄 유저의 닉네임, 프로필이미지 등을 조회합니다."
Expand All @@ -76,5 +55,7 @@ public MypageUserInfoResponse getMypageUserInfo(
){
return userService.getMypageUserInfo(authUserInfo.userId());
}


}

6 changes: 5 additions & 1 deletion src/main/java/com/school/mohitto/domain/Hair.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
@Table(name = "hairs")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Hair {
public class Hair extends BaseTimeEntity{

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down Expand Up @@ -44,5 +44,9 @@ public class Hair {

@OneToOne(mappedBy = "hair", fetch = FetchType.LAZY)
private CreatedImage createdImage;

public void updateLike(boolean isLiked) {
this.isLiked = isLiked;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.school.mohitto.dto.responseDTO;

import io.swagger.v3.oas.annotations.media.Schema;

public record HairLikeToggleResponse(
@Schema(description = "좋아요 상태가 true로 바뀐 헤어 ID (false면 null)")
Long likedHairId
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@

import java.util.List;

public record HairResponseList(
public record HairListResponse(
List<HairResponse> hairs,
int currentPage,
int totalPages,
long totalElements
) {
public static HairResponseList from(Page<Hair> page) {
return new HairResponseList(
public static HairListResponse from(Page<Hair> page) {
return new HairListResponse(
page.stream().map(HairResponse::from).toList(),
page.getNumber() + 1,
page.getTotalPages(),
page.getTotalElements()
);
}
}

Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package com.school.mohitto.dto.responseDTO;


import com.school.mohitto.domain.Hair;

public record HairResponse(
Long id,
String name,
String imageUrl
String imageUrl,
Boolean isLiked
) {
public static HairResponse from(Hair hair) {
return new HairResponse(
hair.getId(),
hair.getName(),
hair.getModelImage().getUploadImageUrl()
hair.getModelImage().getUploadImageUrl(),
hair.getIsLiked()
);
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/school/mohitto/repository/HairRepository.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
package com.school.mohitto.repository;

import com.school.mohitto.domain.Hair;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface HairRepository extends JpaRepository<Hair, Long> {

@Query("""
SELECT h FROM Hair h
JOIN h.diagnosis d
WHERE d.user.id = :userId AND h.isLiked = true
""")
Page<Hair> findLikedHairsByUserId(@Param("userId") Long userId, Pageable pageable);

}
38 changes: 21 additions & 17 deletions src/main/java/com/school/mohitto/service/HairService.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.school.mohitto.service;

import com.school.mohitto.domain.Hair;
import com.school.mohitto.domain.User;
import com.school.mohitto.domain.mapping.UserHair;
import com.school.mohitto.dto.responseDTO.HairResponseList;
import com.school.mohitto.dto.responseDTO.HairLikeToggleResponse;
import com.school.mohitto.dto.responseDTO.HairListResponse;
import com.school.mohitto.exception.CustomException;
import com.school.mohitto.exception.code.ErrorCode;
import com.school.mohitto.repository.HairRepository;
import com.school.mohitto.repository.UserHairRepository;
import com.school.mohitto.repository.UserRepository;
Expand All @@ -14,8 +14,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static com.school.mohitto.exception.code.ErrorCode.*;

@Service
@RequiredArgsConstructor
public class HairService {
Expand All @@ -24,24 +22,30 @@ public class HairService {
private final UserRepository userRepository;
private final HairRepository hairRepository;

public HairResponseList getSavedHairs(Long userId, Pageable pageable) {
Page<Hair> hairs = userHairRepository.findHairsByUserId(userId, pageable);
return HairResponseList.from(hairs);
}

@Transactional
public void deleteSavedHair(Long userId, Long hairId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));

public HairLikeToggleResponse toggleLikeStatus(Long hairId) {
Hair hair = hairRepository.findById(hairId)
.orElseThrow(() -> new CustomException(HAIR_NOT_FOUND));
.orElseThrow(() -> new CustomException(ErrorCode.HAIR_NOT_FOUND));

UserHair userHair = userHairRepository.findByUserAndHair(user, hair)
.orElseThrow(() -> new CustomException(SAVED_HAIR_NOT_FOUND));
boolean newLikeState = !hair.getIsLiked();
hair.updateLike(newLikeState);

userHairRepository.delete(userHair);
return new HairLikeToggleResponse(newLikeState ? hair.getId() : null);
}

@Transactional(readOnly = true)
public HairListResponse getSavedHairs(Long userId, Pageable pageable) {
Page<Hair> page = hairRepository.findLikedHairsByUserId(userId, pageable);
return HairListResponse.from(page);
}









}