diff --git a/src/main/java/com/koliving/api/base/domain/BaseEntity.java b/src/main/java/com/koliving/api/base/domain/BaseEntity.java index 4a7f292d..afe0f416 100644 --- a/src/main/java/com/koliving/api/base/domain/BaseEntity.java +++ b/src/main/java/com/koliving/api/base/domain/BaseEntity.java @@ -19,11 +19,11 @@ public abstract class BaseEntity { private boolean deleted = Boolean.FALSE; @CreatedDate - @Column(nullable = false) + @Column(nullable = false, name = "CREATED_AT") private LocalDateTime createdAt; @LastModifiedDate - @Column(nullable = false) + @Column(nullable = false, name = "UPDATED_AT") private LocalDateTime updatedAt; public void delete() { diff --git a/src/main/java/com/koliving/api/location/application/dto/LocationResponse.java b/src/main/java/com/koliving/api/location/application/dto/LocationResponse.java index 3d6a1884..89f65901 100644 --- a/src/main/java/com/koliving/api/location/application/dto/LocationResponse.java +++ b/src/main/java/com/koliving/api/location/application/dto/LocationResponse.java @@ -2,16 +2,11 @@ import com.koliving.api.location.domain.Location; import com.koliving.api.location.domain.LocationType; -import com.querydsl.core.annotations.QueryProjection; public record LocationResponse(Long id, Long upperLocationId, String name, String displayName, LocationType locationType) { - @QueryProjection - public LocationResponse { - } - public static LocationResponse valueOf(Location entity) { return new LocationResponse( entity.getId(), diff --git a/src/main/java/com/koliving/api/my/ui/MyController.java b/src/main/java/com/koliving/api/my/ui/MyController.java index 1d1ce149..ca2826d7 100644 --- a/src/main/java/com/koliving/api/my/ui/MyController.java +++ b/src/main/java/com/koliving/api/my/ui/MyController.java @@ -2,6 +2,8 @@ import com.koliving.api.base.ErrorResponse; import com.koliving.api.my.application.dto.UserProfileUpdateRequest; +import com.koliving.api.room.application.RoomService; +import com.koliving.api.room.application.dto.RoomResponse; import com.koliving.api.user.User; import com.koliving.api.user.application.UserService; import com.koliving.api.user.application.dto.UserResponse; @@ -11,6 +13,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; @@ -25,7 +29,9 @@ @RequestMapping("api/v1/my") @RequiredArgsConstructor public class MyController { + private final UserService userService; + private final RoomService roomService; @Operation( summary = "프로필 수정", @@ -42,7 +48,8 @@ public class MyController { ), }) @PutMapping("/profile") - public ResponseEntity updateProfile(@RequestBody UserProfileUpdateRequest request, @AuthenticationPrincipal User user) { + public ResponseEntity updateProfile(@RequestBody UserProfileUpdateRequest request, + @AuthenticationPrincipal User user) { userService.updateProfile(request, user.getId()); return ResponseEntity.noContent().build(); } @@ -70,4 +77,26 @@ public ResponseEntity myProfile(@AuthenticationPrincipal User user return ResponseEntity.ok() .body(response); } + + @Operation( + summary = "좋아요 게시글 조회", + description = "좋아요 한 게시글 리스트를 조회합니다", + responses = { + @ApiResponse( + responseCode = "200", + description = "좋아요 게시글 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "좋아요 게시글 조회 실패", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + }) + @GetMapping("/rooms/like") + public ResponseEntity> getLikedRooms(Pageable pageable, @AuthenticationPrincipal User user) { + final Page responses = roomService.findLikeRoomByUser(pageable, user); + + return ResponseEntity.ok() + .body(responses); + } } diff --git a/src/main/java/com/koliving/api/room/application/RoomService.java b/src/main/java/com/koliving/api/room/application/RoomService.java index bde949b0..d70a6403 100644 --- a/src/main/java/com/koliving/api/room/application/RoomService.java +++ b/src/main/java/com/koliving/api/room/application/RoomService.java @@ -1,5 +1,8 @@ package com.koliving.api.room.application; +import static com.koliving.api.base.ServiceError.FORBIDDEN; +import static com.koliving.api.base.ServiceError.RECORD_NOT_EXIST; + import com.google.common.collect.Sets; import com.koliving.api.base.ServiceError; import com.koliving.api.base.exception.KolivingServiceException; @@ -11,10 +14,16 @@ import com.koliving.api.room.application.dto.RoomSaveRequest; import com.koliving.api.room.application.dto.RoomSearchCondition; import com.koliving.api.room.domain.Furnishing; +import com.koliving.api.room.domain.Like; import com.koliving.api.room.domain.Room; import com.koliving.api.room.infra.FurnishingRepository; +import com.koliving.api.room.infra.LikeRepository; import com.koliving.api.room.infra.RoomRepository; import com.koliving.api.user.User; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -22,14 +31,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import static com.koliving.api.base.ServiceError.FORBIDDEN; -import static com.koliving.api.base.ServiceError.RECORD_NOT_EXIST; - /** * author : haedoang date : 2023/08/26 description : */ @@ -42,6 +43,7 @@ public class RoomService { private final LocationRepository locationRepository; private final RoomRepository roomRepository; private final ImageFileRepository imageFileRepository; + private final LikeRepository likeRepository; public List list() { return roomRepository.findAllWithUser() @@ -100,9 +102,11 @@ public Page search(Pageable pageable, RoomSearchCondition conditio return roomRepository.search(pageable, condition); } - public Room findOne(Long id) { - return roomRepository.findByIdWithUser(id) + public RoomResponse findOne(Long id) { + final Room room = roomRepository.findByIdWithUser(id) .orElseThrow(() -> new KolivingServiceException(RECORD_NOT_EXIST)); + + return RoomResponse.valueOf(room); } @Transactional @@ -122,4 +126,15 @@ public void deleteRoomById(Long id, User user) { room.delete(); } + + @Transactional + public void likeRoom(Long id, User user) { + Room room = roomRepository.findById(id).orElseThrow(() -> new KolivingServiceException(RECORD_NOT_EXIST)); + final Like like = Like.of(room, user); + likeRepository.save(like); + } + + public Page findLikeRoomByUser(Pageable pageable, User user) { + return roomRepository.likedRooms(pageable, user.getId()); + } } diff --git a/src/main/java/com/koliving/api/room/application/dto/WriterResponse.java b/src/main/java/com/koliving/api/room/application/dto/WriterResponse.java index 40d9b89e..ed06811e 100644 --- a/src/main/java/com/koliving/api/room/application/dto/WriterResponse.java +++ b/src/main/java/com/koliving/api/room/application/dto/WriterResponse.java @@ -11,6 +11,7 @@ public record WriterResponse( @Schema(description = "작성자 이름") String firstName, + @Schema(description = "작성자 성") String lastName, diff --git a/src/main/java/com/koliving/api/room/domain/Like.java b/src/main/java/com/koliving/api/room/domain/Like.java new file mode 100644 index 00000000..188a18c2 --- /dev/null +++ b/src/main/java/com/koliving/api/room/domain/Like.java @@ -0,0 +1,51 @@ +package com.koliving.api.room.domain; + +import static lombok.AccessLevel.PROTECTED; + +import com.koliving.api.base.domain.BaseEntity; +import com.koliving.api.user.User; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +/** + * author : haedoang date : 2023/10/19 description : + */ +@Getter +@Entity(name = "TB_ROOM_LIKE") +@SQLDelete(sql = "UPDATE TB_ROOM_LIKE SET deleted = true WHERE id = ?") +@Where(clause = "deleted = false") +@EqualsAndHashCode(of = "id", callSuper = false) +@NoArgsConstructor(access = PROTECTED) +public class Like extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "room_id", nullable = false) + private Room room; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + private Like(Room room, User user) { + this.room = room; + this.user = user; + } + + public static Like of(Room room, User user) { + return new Like(room, user); + } +} diff --git a/src/main/java/com/koliving/api/room/infra/LikeRepository.java b/src/main/java/com/koliving/api/room/infra/LikeRepository.java new file mode 100644 index 00000000..29f50ec6 --- /dev/null +++ b/src/main/java/com/koliving/api/room/infra/LikeRepository.java @@ -0,0 +1,10 @@ +package com.koliving.api.room.infra; + +import com.koliving.api.room.domain.Like; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * author : haedoang date : 2023/10/19 description : + */ +public interface LikeRepository extends JpaRepository { +} diff --git a/src/main/java/com/koliving/api/room/infra/RoomRepository.java b/src/main/java/com/koliving/api/room/infra/RoomRepository.java index 23173000..8dfe880a 100644 --- a/src/main/java/com/koliving/api/room/infra/RoomRepository.java +++ b/src/main/java/com/koliving/api/room/infra/RoomRepository.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Optional; +import org.springframework.data.repository.query.Param; public interface RoomRepository extends JpaRepository, RoomRepositoryQueryDsl { @@ -13,6 +14,6 @@ public interface RoomRepository extends JpaRepository, RoomRepositor List findAllWithUser(); @Query("select r from TB_ROOM r join fetch r.user u where r.id=:id") - Optional findByIdWithUser(Long id); + Optional findByIdWithUser(@Param("id") Long id); } diff --git a/src/main/java/com/koliving/api/room/infra/RoomRepositoryImpl.java b/src/main/java/com/koliving/api/room/infra/RoomRepositoryImpl.java index 11fbe5c8..e981eeae 100644 --- a/src/main/java/com/koliving/api/room/infra/RoomRepositoryImpl.java +++ b/src/main/java/com/koliving/api/room/infra/RoomRepositoryImpl.java @@ -1,5 +1,6 @@ package com.koliving.api.room.infra; +import static com.koliving.api.room.domain.QLike.like; import static com.koliving.api.room.domain.QRoom.room; import com.koliving.api.room.application.dto.RoomResponse; @@ -50,6 +51,10 @@ public Page search(Pageable pageable, RoomSearchCondition conditio .fetch() .size(); + return getRoomResponses(pageable, rooms, count); + } + + private Page getRoomResponses(Pageable pageable, List rooms, long count) { return PageableExecutionUtils.getPage(rooms.stream() .map(RoomResponse::valueOf) .collect(Collectors.toList()), pageable, () -> count); @@ -117,4 +122,22 @@ private BooleanExpression filterByMonthlyRent(Integer minMonthlyRent, Integer ma return null; } + + @Override + public Page likedRooms(Pageable pageable, Long userId) { + List rooms = queryFactory.select(like.room) + .from(like) + .where(like.user.id.eq(userId)) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + long count = queryFactory.select(like.room) + .from(like) + .where(like.user.id.eq(userId)) + .fetch() + .size(); + + return getRoomResponses(pageable, rooms, count); + } } diff --git a/src/main/java/com/koliving/api/room/infra/RoomRepositoryQueryDsl.java b/src/main/java/com/koliving/api/room/infra/RoomRepositoryQueryDsl.java index be277bb2..c0c65eaf 100644 --- a/src/main/java/com/koliving/api/room/infra/RoomRepositoryQueryDsl.java +++ b/src/main/java/com/koliving/api/room/infra/RoomRepositoryQueryDsl.java @@ -2,10 +2,12 @@ import com.koliving.api.room.application.dto.RoomResponse; import com.koliving.api.room.application.dto.RoomSearchCondition; -import com.koliving.api.room.domain.Room; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; public interface RoomRepositoryQueryDsl { + Page search(Pageable pageable, RoomSearchCondition condition); + + Page likedRooms(Pageable pageable, Long userId); } diff --git a/src/main/java/com/koliving/api/room/ui/RoomController.java b/src/main/java/com/koliving/api/room/ui/RoomController.java index 20605130..d8295fb2 100644 --- a/src/main/java/com/koliving/api/room/ui/RoomController.java +++ b/src/main/java/com/koliving/api/room/ui/RoomController.java @@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +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; @@ -97,7 +98,7 @@ public ResponseEntity> search(@ParameterObject @PageableDefau ), }) @GetMapping("/{id}") - public ResponseEntity findById(@PathVariable Long id) { + public ResponseEntity findById(@PathVariable Long id) { return ResponseEntity.ok() .body(roomService.findOne(id)); } @@ -121,4 +122,24 @@ public ResponseEntity deleteById(Long id, @AuthenticationPrincipal User us roomService.deleteRoomById(id, user); return ResponseEntity.noContent().build(); } + + @Operation( + summary = "방 좋아요", + description = "방을 좋아요 합니다.", + responses = { + @ApiResponse( + responseCode = "204", + description = "방 좋아요 성공" + ), + @ApiResponse( + responseCode = "400", + description = "방 좋아요 실패", + content = @Content(schema = @Schema(implementation = ErrorResponse.class)) + ), + }) + @PutMapping("/{id}/liked") + public ResponseEntity likeRoom(@PathVariable Long id, @AuthenticationPrincipal User user) { + roomService.likeRoom(id, user); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/koliving/api/user/User.java b/src/main/java/com/koliving/api/user/User.java index f1d83820..723beecb 100644 --- a/src/main/java/com/koliving/api/user/User.java +++ b/src/main/java/com/koliving/api/user/User.java @@ -1,5 +1,7 @@ package com.koliving.api.user; +import static com.koliving.api.base.ServiceError.UNAUTHORIZED; + import com.koliving.api.base.exception.KolivingServiceException; import com.koliving.api.file.domain.ImageFile; import jakarta.persistence.Column; @@ -14,6 +16,10 @@ import jakarta.persistence.OneToOne; import jakarta.persistence.Temporal; import jakarta.persistence.TemporalType; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; import lombok.AccessLevel; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -29,13 +35,6 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.password.PasswordEncoder; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Collection; -import java.util.Collections; - -import static com.koliving.api.base.ServiceError.UNAUTHORIZED; - @Entity(name = "TB_USER") @DynamicInsert @DynamicUpdate @@ -100,7 +99,8 @@ public User(String email) { this.userRole = UserRole.USER; } - private User(String email, String password, String firstName, String lastName, Gender gender, LocalDate birthDate, String description, ImageFile imageFile, UserRole userRole) { + private User(String email, String password, String firstName, String lastName, Gender gender, LocalDate birthDate, + String description, ImageFile imageFile, UserRole userRole) { this.email = email; this.password = password; this.firstName = firstName; @@ -114,12 +114,14 @@ private User(String email, String password, String firstName, String lastName, G @Deprecated public static User valueOf(String email, String encodedPassword, UserRole role) { - return new User(email, encodedPassword, null, null, null, null, null,null, role); + return new User(email, encodedPassword, null, null, null, null, null, null, role); } - public static User of(ImageFile imageFile, Gender gender, String firstName, String lastName, LocalDate birthDate, String description) { - return new User(null, null, firstName, lastName, gender, birthDate,description, imageFile, null); + public static User of(ImageFile imageFile, Gender gender, String firstName, String lastName, LocalDate birthDate, + String description) { + return new User(null, null, firstName, lastName, gender, birthDate, description, imageFile, null); } + public void setPassword(String password) { this.password = password; this.signUpStatus = SignUpStatus.PROFILE_INFORMATION_PENDING;