diff --git a/src/main/java/org/scoula/domain/fraud/service/AiFraudAnalyzerService.java b/src/main/java/org/scoula/domain/fraud/service/AiFraudAnalyzerService.java index d8a7321f..08874fc7 100644 --- a/src/main/java/org/scoula/domain/fraud/service/AiFraudAnalyzerService.java +++ b/src/main/java/org/scoula/domain/fraud/service/AiFraudAnalyzerService.java @@ -18,7 +18,7 @@ import org.scoula.domain.fraud.exception.FraudErrorCode; import org.scoula.domain.fraud.exception.FraudRiskException; import org.scoula.domain.home.mapper.HomeMapper; -import org.scoula.domain.home.vo.HomeRegisterVO; +import org.scoula.domain.home.vo.HomeVO; import org.scoula.global.common.exception.BusinessException; import org.scoula.global.common.util.LogSanitizerUtil; import org.springframework.beans.factory.annotation.Value; @@ -308,11 +308,12 @@ public BuildingDocumentDto parseBuildingDocument(MultipartFile file) { private FraudRiskCheckDto.Request buildAiRequest(Long userId, RiskAnalysisRequest request) { Long homeId = request.getHomeId(); - HomeRegisterVO home = - homeMapper - .findHomeById(homeId) - .orElseThrow(() -> new BusinessException(FraudErrorCode.INVALID_HOME_ID)); - + HomeVO home = null; + try { + home = homeMapper.findHomeById(homeId); + } catch (Exception e) { + throw new BusinessException(FraudErrorCode.INVALID_HOME_ID); + } request.setAddress(home.getAddr1() + " " + home.getAddr2()); request.setPropertyPrice(home.getDepositPrice()); request.setMonthlyRent(home.getMonthlyRent() != null ? home.getMonthlyRent() : 0); diff --git a/src/main/java/org/scoula/domain/home/controller/HomeController.java b/src/main/java/org/scoula/domain/home/controller/HomeController.java index 8ee4e608..e29cb24e 100644 --- a/src/main/java/org/scoula/domain/home/controller/HomeController.java +++ b/src/main/java/org/scoula/domain/home/controller/HomeController.java @@ -4,162 +4,96 @@ import javax.validation.Valid; -import org.scoula.domain.home.dto.request.HomeCreateRequestDto; -import org.scoula.domain.home.dto.request.HomeReportRequestDto; -import org.scoula.domain.home.dto.request.HomeUpdateRequestDto; -import org.scoula.domain.home.dto.response.HomeResponseDto; -import org.scoula.domain.home.service.HomeService; -import org.scoula.global.auth.dto.CustomUserDetails; +import org.scoula.domain.home.dto.HomeCreateRequestDto; +import org.scoula.domain.home.dto.HomeResponseDTO; +import org.scoula.domain.home.dto.HomeSearchDTO; +import org.scoula.domain.home.dto.HomeUpdateRequestDto; +import org.scoula.domain.home.vo.FacilityCategory; +import org.scoula.domain.home.vo.FacilityItem; import org.scoula.global.common.dto.ApiResponse; -import org.scoula.global.common.dto.PageRequest; import org.scoula.global.common.dto.PageResponse; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import lombok.RequiredArgsConstructor; -@RestController +@Api(tags = "매물 관리", description = "매물 등록, 조회, 수정, 삭제 API") @RequestMapping("/api/homes") -@RequiredArgsConstructor -@Api(tags = {"매물 API"}) -public class HomeController { - - private final HomeService homeService; - - @ApiOperation(value = "매물 등록", notes = "새로운 매물을 등록합니다.") - @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity> createHome( - @AuthenticationPrincipal CustomUserDetails userDetails, - @ModelAttribute HomeCreateRequestDto request) { - - Long homeId = homeService.createHome(userDetails.getUserId(), request); - return ResponseEntity.ok(ApiResponse.success(homeId)); - } - - @ApiOperation(value = "매물 수정", notes = "기존 매물 정보를 수정합니다.") - @PutMapping("/{homeId}") - public ResponseEntity> updateHome( - @AuthenticationPrincipal CustomUserDetails userDetails, - @PathVariable Long homeId, - @Valid @ModelAttribute HomeUpdateRequestDto request) { - homeService.updateHome(userDetails.getUserId(), homeId, request); - return ResponseEntity.ok(ApiResponse.success()); - } - - @ApiOperation(value = "매물 상세 조회", notes = "homeId에 해당하는 매물 정보를 조회합니다.") +public interface HomeController { + + @ApiOperation(value = "매물 등록", notes = "새로운 매물을 등록합니다. 이미지 파일을 함께 업로드할 수 있습니다.") + @PostMapping(consumes = "multipart/form-data") + ResponseEntity> createHome( + @Valid @ModelAttribute HomeCreateRequestDto requestDto, Authentication authentication); + + @ApiOperation(value = "매물 상세 조회", notes = "매물 ID로 상세 정보를 조회합니다.") @GetMapping("/{homeId}") - public ResponseEntity> getHomeDetail( - @AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable Long homeId) { - HomeResponseDto response = homeService.getHomeDetail(homeId); - return ResponseEntity.ok(ApiResponse.success(response)); - } - - // @ApiOperation(value = "내가 등록한 매물 목록 조회", notes = "사용자가 등록한 매물 목록을 페이지네이션하여 조회합니다.") - // @GetMapping("/my") - // public ResponseEntity> getMyHomes( - // @AuthenticationPrincipal CustomUserDetails userDetails, - // @ApiParam(value = "페이지 번호", defaultValue = "1") @RequestParam(defaultValue = - // "1") - // String pageStr, - // @ApiParam(value = "페이지 크기", defaultValue = "10") @RequestParam(defaultValue = - // "10") - // String sizeStr) { - // - // int page = parseOrDefault(pageStr, 1); - // int size = parseOrDefault(sizeStr, 10); - // - // PageRequest pageRequest = PageRequest.builder().page(page).size(size).build(); - // PageResponse response = - // homeService.getMyHomeList(userDetails.getUserId(), pageRequest); - // return ResponseEntity.ok(response); - // } - - @ApiOperation(value = "매물 삭제", notes = "매물 ID에 해당하는 매물을 삭제합니다.") - @DeleteMapping("/{homeId}") - public ResponseEntity> deleteHome( - @AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable Long homeId) { - homeService.deleteHome(userDetails.getUserId(), homeId); - return ResponseEntity.ok(ApiResponse.success()); - } + ResponseEntity> getHome( + @ApiParam(value = "매물 ID", required = true) @PathVariable Integer homeId); - @ApiOperation(value = "모든 매물 검색", notes = "전체 매물을 페이징하여 조회합니다.") + @ApiOperation(value = "매물 목록 조회", notes = "페이징된 매물 목록을 조회합니다.") @GetMapping - public ResponseEntity> getAllHomes( - @ApiParam(value = "페이지 번호", defaultValue = "1") @RequestParam(defaultValue = "1") - String pageStr, - @ApiParam(value = "페이지 크기", defaultValue = "10") @RequestParam(defaultValue = "10") - String sizeStr) { + ResponseEntity> getHomeList( + @ApiParam(value = "페이지 번호 (1부터 시작)", defaultValue = "1") + @RequestParam(defaultValue = "1") + int page, + @ApiParam(value = "페이지 크기", defaultValue = "21") @RequestParam(defaultValue = "21") + int size); + + @ApiOperation(value = "매물 검색", notes = "조건에 따라 매물을 검색합니다.") + @GetMapping("/search") + ResponseEntity> searchHomes( + @ApiParam(value = "검색 조건") @ModelAttribute HomeSearchDTO searchDTO); + + @ApiOperation( + value = "매물 수정", + notes = "기존 매물 정보를 수정합니다. 이미지 파일을 추가로 업로드하거나 기존 이미지를 삭제할 수 있습니다.") + @PutMapping(value = "/{homeId}", consumes = "multipart/form-data") + ResponseEntity> updateHome( + @ApiParam(value = "매물 ID", required = true, example = "123") @PathVariable + Integer homeId, + @Valid @ModelAttribute HomeUpdateRequestDto updateDto, + @ApiParam(value = "새로 추가할 이미지 파일들") @RequestParam(value = "newImages", required = false) + List newImages, + Authentication authentication); + + @ApiOperation(value = "매물 삭제", notes = "매물을 삭제합니다.") + @DeleteMapping("/{homeId}") + ResponseEntity> deleteHome( + @ApiParam(value = "매물 ID", required = true) @PathVariable Integer homeId, + Authentication authentication); + + @ApiOperation(value = "매물 상태 변경", notes = "매물의 상태를 변경합니다.") + @PatchMapping("/{homeId}/status") + ResponseEntity> updateHomeStatus( + @ApiParam(value = "매물 ID", required = true) @PathVariable Integer homeId, + @ApiParam(value = "변경할 상태", required = true) @RequestParam String status, + Authentication authentication); + + @ApiOperation(value = "내 매물 목록 조회", notes = "로그인한 사용자의 매물 목록을 조회합니다.") + @GetMapping("/my") + ResponseEntity>> getMyHomes(Authentication authentication); + + @ApiOperation(value = "매물 찜하기/해제", notes = "매물을 찜하거나 찜을 해제합니다.") + @PostMapping("/{homeId}/like") + ResponseEntity> toggleHomeLike( + @ApiParam(value = "매물 ID", required = true) @PathVariable Integer homeId, + Authentication authentication); - int page = parseOrDefault(pageStr, 1); - int size = parseOrDefault(sizeStr, 10); + @ApiOperation(value = "찜한 매물 목록 조회", notes = "사용자가 찜한 매물 목록을 조회합니다.") + @GetMapping("/likes") + ResponseEntity>> getHomeLikes(Authentication authentication); - PageRequest pageRequest = PageRequest.builder().page(page).size(size).build(); - PageResponse response = homeService.getHomeList(pageRequest); - return ResponseEntity.ok(response); - } + @ApiOperation(value = "편의시설 카테고리 조회", notes = "편의시설 카테고리 목록을 조회합니다.") + @GetMapping("/facilities/categories") + ResponseEntity>> getFacilityCategories(); - @ApiOperation(value = "매물 찜하기", notes = "해당 매물을 찜합니다.") - @PostMapping("/{homeId}/like") - public ResponseEntity> likeHome( - @AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable Long homeId) { - homeService.addLike(userDetails.getUserId(), homeId); - return ResponseEntity.ok(ApiResponse.success()); - } - - @ApiOperation(value = "매물 찜 해제", notes = "해당 매물의 찜을 취소합니다.") - @DeleteMapping("/{homeId}/like") - public ResponseEntity> unlikeHome( - @AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable Long homeId) { - homeService.removeLike(userDetails.getUserId(), homeId); - return ResponseEntity.ok(ApiResponse.success()); - } - - @ApiOperation(value = "찜한 매물 목록 조회", notes = "내가 찜한 매물 목록을 조회합니다.") - @GetMapping("/likes") - public ResponseEntity>> getLikedHomes( - @AuthenticationPrincipal CustomUserDetails userDetails) { - List likedHomes = homeService.getLikedHomes(userDetails.getUserId()); - return ResponseEntity.ok(ApiResponse.success(likedHomes)); - } - - @ApiOperation(value = "조회수 증가", notes = "해당 매물의 조회수를 1 증가시킵니다.") - @PostMapping("/{homeId}/view") - public ResponseEntity> increaseViewCount(@PathVariable Long homeId) { - homeService.increaseViewCount(homeId); - return ResponseEntity.ok(ApiResponse.success()); - } - - @ApiOperation(value = "매물 신고", notes = "해당 매물을 신고합니다.") - @PostMapping("/report") - public ResponseEntity> reportHome( - @RequestBody HomeReportRequestDto requestDto, - @AuthenticationPrincipal CustomUserDetails userDetails) { - HomeReportRequestDto reportRequest = - HomeReportRequestDto.builder() - .reportId(requestDto.getReportId()) - .userId(userDetails.getUserId()) - .homeId(requestDto.getHomeId()) - .reportReason(requestDto.getReportReason()) - .reportAt(requestDto.getReportAt()) - .reportStatus(requestDto.getReportStatus()) - .build(); - homeService.reportHome(reportRequest); - return ResponseEntity.ok(ApiResponse.success()); - } - - // 유틸 메서드: 숫자 변환 실패 시 기본값 반환 - private int parseOrDefault(String str, int defaultValue) { - try { - int val = Integer.parseInt(str); - if (val < 1) return defaultValue; // 페이지 번호, 크기 음수 방지용 - return val; - } catch (NumberFormatException e) { - return defaultValue; - } - } + @ApiOperation(value = "편의시설 아이템 조회", notes = "특정 카테고리의 편의시설 아이템 목록을 조회합니다.") + @GetMapping("/facilities/categories/{categoryId}/items") + ResponseEntity>> getFacilityItems( + @ApiParam(value = "카테고리 ID", required = true) @PathVariable Integer categoryId); } diff --git a/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java b/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java new file mode 100644 index 00000000..c28bce16 --- /dev/null +++ b/src/main/java/org/scoula/domain/home/controller/HomeControllerImpl.java @@ -0,0 +1,321 @@ +package org.scoula.domain.home.controller; + +import java.util.List; +import java.util.Optional; + +import javax.validation.Valid; + +import org.scoula.domain.home.dto.HomeCreateDTO; +import org.scoula.domain.home.dto.HomeCreateRequestDto; +import org.scoula.domain.home.dto.HomeResponseDTO; +import org.scoula.domain.home.dto.HomeSearchDTO; +import org.scoula.domain.home.dto.HomeUpdateRequestDto; +import org.scoula.domain.home.service.HomeService; +import org.scoula.domain.home.vo.FacilityCategory; +import org.scoula.domain.home.vo.FacilityItem; +import org.scoula.domain.user.service.UserServiceInterface; +import org.scoula.domain.user.vo.User; +import org.scoula.global.common.dto.ApiResponse; +import org.scoula.global.common.dto.PageRequest; +import org.scoula.global.common.dto.PageResponse; +import org.scoula.global.common.exception.BusinessException; +import org.scoula.global.common.exception.CommonErrorCode; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@RestController +@RequestMapping("/api/homes") +@RequiredArgsConstructor +public class HomeControllerImpl implements HomeController { + + private final HomeService homeService; + private final UserServiceInterface userService; + + @Override + @PostMapping(consumes = "multipart/form-data") + public ResponseEntity> createHome( + @Valid @RequestPart HomeCreateRequestDto requestDto, Authentication authentication) { + + Integer userId = getCurrentUserId(authentication); + + // 이미지 파일 리스트 처리 + List imageList = requestDto.getImages(); + + // HomeCreateRequestDto를 HomeCreateDTO로 변환 + HomeCreateDTO createDTO = + HomeCreateDTO.builder() + .addr1(requestDto.getAddr1()) + .addr2(requestDto.getAddr2()) + .residenceType(requestDto.getResidenceType()) + .leaseType(requestDto.getLeaseType()) + .depositPrice(requestDto.getDepositPrice()) + .monthlyRent(requestDto.getMonthlyRent()) + .maintenaceFee(requestDto.getMaintenanceFee()) + .roomCnt(requestDto.getRoomCnt()) + .supplyArea(requestDto.getSupplyArea()) + .exclusiveArea(requestDto.getExclusiveArea()) + .buildDate(requestDto.getBuildDate()) + .homeFloor(requestDto.getHomeFloor()) + .buildingTotalFloors(requestDto.getBuildingTotalFloors()) + .homeDirection(requestDto.getHomeDirection()) + .area(requestDto.getArea()) + .landCategory(requestDto.getLandCategory()) + .bathroomCnt(requestDto.getBathroomCnt()) + .isPet(requestDto.getIsPet()) + .isParking(requestDto.getIsParking()) + .facilityItemIds(requestDto.getFacilityItemIds()) + .maintenanceFees( + requestDto.getMaintenanceFees() != null + ? requestDto.getMaintenanceFees().stream() + .map( + fee -> + HomeCreateDTO.MaintenanceFeeDTO + .builder() + .maintenanceId( + fee + .getMaintenanceId()) + .fee(fee.getFee()) + .build()) + .collect(java.util.stream.Collectors.toList()) + : null) + .build(); + + Integer homeId = homeService.createHome(createDTO, imageList, userId); + + return ResponseEntity.ok(ApiResponse.success(homeId, "매물이 성공적으로 등록되었습니다.")); + } + + @Override + @GetMapping("/{homeId}") + public ResponseEntity> getHome(@PathVariable Integer homeId) { + + log.info("매물 상세 조회 요청: homeId={}", homeId); + + HomeResponseDTO home = homeService.getHome(homeId); + + return ResponseEntity.ok(ApiResponse.success(home, "매물 조회가 완료되었습니다.")); + } + + @Override + @GetMapping + public ResponseEntity> getHomeList( + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "21") int size) { + + log.info("매물 목록 조회 요청: page={}, size={}", page, size); + + List homes = homeService.getHomeList(page, size); + int totalCount = homeService.getTotalHomeCount(); + + PageRequest pageRequest = PageRequest.builder().page(page).size(size).build(); + + return ResponseEntity.ok(PageResponse.of(homes, pageRequest, totalCount)); + } + + @Override + @GetMapping("/search") + public ResponseEntity> searchHomes( + @ModelAttribute HomeSearchDTO searchDTO) { + + log.info( + "매물 검색 요청: residenceType={}, leaseType={}, addr1={}", + searchDTO.getResidenceType(), + searchDTO.getLeaseType(), + searchDTO.getAddr1()); + log.info("최대 월세 필터 값: {}", searchDTO.getMaxMonthlyRent()); + + List homes = homeService.searchHomes(searchDTO); + int totalCount = homeService.getHomeCountByCondition(searchDTO); + PageRequest pageRequest = + PageRequest.builder().page(searchDTO.getPage()).size(searchDTO.getSize()).build(); + + return ResponseEntity.ok(PageResponse.of(homes, pageRequest, totalCount)); + } + + @Override + @PutMapping("/{homeId}") + public ResponseEntity> updateHome( + @PathVariable Integer homeId, + @Valid @ModelAttribute HomeUpdateRequestDto updateDto, + @RequestParam(value = "newImages", required = false) List newImages, + Authentication authentication) { + + Integer userId = getCurrentUserId(authentication); + + log.info( + "매물 수정 요청: homeId={}, userId={}, 새 이미지 개수={}, 삭제할 이미지 개수={}", + homeId, + userId, + newImages != null ? newImages.size() : 0, + updateDto.getDeleteImageIds() != null ? updateDto.getDeleteImageIds().size() : 0); + + // 요청 DTO의 homeId 설정 (경로 변수의 homeId 사용) + updateDto.setHomeId(homeId); + + // HomeUpdateRequestDto를 HomeCreateDTO로 변환 + HomeCreateDTO updateDTO = + HomeCreateDTO.builder() + .addr1(updateDto.getAddr1()) + .addr2(updateDto.getAddr2()) + .residenceType(updateDto.getResidenceType()) + .leaseType(updateDto.getLeaseType()) + .depositPrice(updateDto.getDepositPrice()) + .monthlyRent(updateDto.getMonthlyRent()) + .maintenaceFee(updateDto.getMaintenanceFee()) + .roomCnt(updateDto.getRoomCnt()) + .supplyArea(updateDto.getSupplyArea()) + .exclusiveArea(updateDto.getExclusiveArea()) + .buildDate(updateDto.getBuildDate()) + .homeFloor(updateDto.getHomeFloor()) + .buildingTotalFloors(updateDto.getBuildingTotalFloors()) + .homeDirection(updateDto.getHomeDirection()) + .bathroomCnt(updateDto.getBathroomCnt()) + .isPet(updateDto.getIsPet()) + .isParking(updateDto.getIsParking()) + .facilityItemIds(updateDto.getFacilityItemIds()) + .maintenanceFees( + updateDto.getMaintenanceFees() != null + ? updateDto.getMaintenanceFees().stream() + .map( + fee -> + HomeCreateDTO.MaintenanceFeeDTO + .builder() + .maintenanceId( + fee + .getMaintenanceId()) + .fee(fee.getFee()) + .build()) + .collect(java.util.stream.Collectors.toList()) + : null) + .build(); + + homeService.updateHome(homeId, updateDTO, newImages, userId); + + return ResponseEntity.ok(ApiResponse.success(null, "매물이 성공적으로 수정되었습니다.")); + } + + @Override + @DeleteMapping("/{homeId}") + public ResponseEntity> deleteHome( + @PathVariable Integer homeId, Authentication authentication) { + + Integer userId = getCurrentUserId(authentication); + + log.info("매물 삭제 요청: homeId={}, userId={}", homeId, userId); + + homeService.deleteHome(homeId, userId); + + return ResponseEntity.ok(ApiResponse.success(null, "매물이 성공적으로 삭제되었습니다.")); + } + + @Override + @PatchMapping("/{homeId}/status") + public ResponseEntity> updateHomeStatus( + @PathVariable Integer homeId, + @RequestParam String status, + Authentication authentication) { + + Integer userId = getCurrentUserId(authentication); + + log.info("매물 상태 변경 요청: homeId={}, status={}, userId={}", homeId, status, userId); + + homeService.updateHomeStatus(homeId, status, userId); + + return ResponseEntity.ok(ApiResponse.success(null, "매물 상태가 성공적으로 변경되었습니다.")); + } + + @Override + @GetMapping("/my") + public ResponseEntity>> getMyHomes( + Authentication authentication) { + + Integer userId = getCurrentUserId(authentication); + + log.info("내 매물 목록 조회 요청: userId={}", userId); + + List homes = homeService.getHomesByUser(userId); + + return ResponseEntity.ok( + ApiResponse.success( + homes, String.format("내 매물 목록 조회가 완료되었습니다. (총 %d개)", homes.size()))); + } + + @Override + @PostMapping("/{homeId}/like") + public ResponseEntity> toggleHomeLike( + @PathVariable Integer homeId, Authentication authentication) { + + Integer userId = getCurrentUserId(authentication); + + log.info("매물 찜 토글 요청: homeId={}, userId={}", homeId, userId); + + homeService.toggleHomeLike(userId, homeId); + + return ResponseEntity.ok(ApiResponse.success(null, "찜 상태가 변경되었습니다.")); + } + + @Override + @GetMapping("/likes") + public ResponseEntity>> getHomeLikes( + Authentication authentication) { + + Integer userId = getCurrentUserId(authentication); + + log.info("찜한 매물 목록 조회 요청: userId={}", userId); + + List homes = homeService.getHomeLikes(userId); + + return ResponseEntity.ok( + ApiResponse.success( + homes, String.format("찜한 매물 목록 조회가 완료되었습니다. (총 %d개)", homes.size()))); + } + + @Override + @GetMapping("/facilities/categories") + public ResponseEntity>> getFacilityCategories() { + + log.info("편의시설 카테고리 조회 요청"); + + List categories = homeService.getFacilityCategories(); + + return ResponseEntity.ok(ApiResponse.success(categories, "편의시설 카테고리 조회가 완료되었습니다.")); + } + + @Override + @GetMapping("/facilities/categories/{categoryId}/items") + public ResponseEntity>> getFacilityItems( + @PathVariable Integer categoryId) { + + log.info("편의시설 아이템 조회 요청: categoryId={}", categoryId); + + List items = homeService.getFacilityItemsByCategory(categoryId); + + return ResponseEntity.ok(ApiResponse.success(items, "편의시설 아이템 조회가 완료되었습니다.")); + } + + /** Authentication에서 사용자 ID를 추출하는 메서드 실제 구현시에는 JWT에서 사용자 정보를 추출 */ + private Integer getCurrentUserId(Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) { + throw new BusinessException(CommonErrorCode.AUTHENTICATION_FAILED, "인증되지 않은 사용자입니다."); + } + + String currentUserEmail = authentication.getName(); + Optional currentUserOpt = userService.findByEmail(currentUserEmail); + + if (currentUserOpt.isEmpty()) { + throw new BusinessException(CommonErrorCode.AUTHENTICATION_FAILED, "사용자를 찾을 수 없습니다."); + } + + User currentUser = currentUserOpt.get(); + Long userId = currentUser.getUserId(); + + // Long을 Integer로 변환 (기존 코드와의 호환성을 위해) + return userId.intValue(); + } +} diff --git a/src/main/java/org/scoula/domain/home/dto/request/HomeUpdateRequestDto.java b/src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java similarity index 50% rename from src/main/java/org/scoula/domain/home/dto/request/HomeUpdateRequestDto.java rename to src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java index 03ddba31..04eb02f8 100644 --- a/src/main/java/org/scoula/domain/home/dto/request/HomeUpdateRequestDto.java +++ b/src/main/java/org/scoula/domain/home/dto/HomeCreateDTO.java @@ -1,11 +1,13 @@ -package org.scoula.domain.home.dto.request; +package org.scoula.domain.home.dto; import java.time.LocalDate; import java.util.List; +import javax.validation.constraints.*; + +import org.scoula.domain.home.enums.HomeDirection; import org.scoula.domain.home.enums.LeaseType; import org.scoula.domain.home.enums.ResidenceType; -import org.scoula.domain.home.vo.HomeRegisterVO; import lombok.AllArgsConstructor; import lombok.Builder; @@ -13,42 +15,58 @@ import lombok.NoArgsConstructor; @Data -@Builder @NoArgsConstructor @AllArgsConstructor -public class HomeUpdateRequestDto { - - private Long homeId; - private String userName; +@Builder +public class HomeCreateDTO { private String addr1; + private String addr2; private ResidenceType residenceType; + private LeaseType leaseType; private Integer depositPrice; + private Integer monthlyRent; - private Integer maintenanceFee; + + private Integer maintenaceFee; + + private Integer roomCnt; private Float supplyArea; + + private Float area; + + private String landCategory; + private Float exclusiveArea; - private String homeFloor; private LocalDate buildDate; + + private Integer homeFloor; + private Integer buildingTotalFloors; + + private HomeDirection homeDirection; + + private Integer bathroomCnt; + private Boolean isPet; - private Boolean isParkingAvailable; + private Boolean isParking; - private Integer roomCnt; - private Integer bathroomCount; - private String homeDirection; - private LocalDate moveInDate; + private List facilityItemIds; - private List imageUrls; - private List options; - private List facilityItemIds; + private List maintenanceFees; - // ✅ 관리비 항목 추가 - private List maintenanceFeeItems; + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class MaintenanceFeeDTO { + private Integer maintenanceId; + private Integer fee; + } } diff --git a/src/main/java/org/scoula/domain/home/dto/HomeCreateRequestDto.java b/src/main/java/org/scoula/domain/home/dto/HomeCreateRequestDto.java new file mode 100644 index 00000000..074f1096 --- /dev/null +++ b/src/main/java/org/scoula/domain/home/dto/HomeCreateRequestDto.java @@ -0,0 +1,116 @@ +package org.scoula.domain.home.dto; + +import java.time.LocalDate; +import java.util.List; + +import javax.validation.constraints.*; + +import org.scoula.domain.home.enums.HomeDirection; +import org.scoula.domain.home.enums.LeaseType; +import org.scoula.domain.home.enums.ResidenceType; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@ApiModel(description = "매물 등록 요청 DTO") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class HomeCreateRequestDto { + + @ApiModelProperty(value = "시/도 및 시/군/구", example = "서울특별시 강남구", required = true) + private String addr1; + + @ApiModelProperty(value = "상세 주소", example = "테헤란로 123 아파트 101동 501호") + private String addr2; + + @ApiModelProperty( + value = "거주 유형", + example = "APARTMENT", + required = true, + allowableValues = "APARTMENT, VILLA, ONEROOM, OFFICETEL, HOUSE") + private ResidenceType residenceType; + + @ApiModelProperty( + value = "임대 유형", + example = "WOLSE", + required = true, + allowableValues = "JEONSE, WOLSE") + private LeaseType leaseType; + + @ApiModelProperty(value = "보증금 (원)", example = "50000000", required = true) + private Integer depositPrice; + + @ApiModelProperty(value = "월세 (원)", example = "1000000", notes = "전세인 경우 0") + private Integer monthlyRent; + + @ApiModelProperty(value = "관리비 (원)", example = "150000") + private Integer maintenanceFee; + + @ApiModelProperty(value = "방 개수", example = "3") + private Integer roomCnt; + + @ApiModelProperty(value = "공급 면적 (㎡)", example = "84.5") + private Float supplyArea; + + @ApiModelProperty(value = "전용 면적 (㎡)", example = "59.8") + private Float exclusiveArea; + + @ApiModelProperty(value = "면적 (㎡)", example = "62.4") + private Float area; + + @ApiModelProperty(value = "토지 지목", example = "대,전,답") + private String landCategory; + + @ApiModelProperty(value = "준공일", example = "2020-03-15") + private LocalDate buildDate; + + @ApiModelProperty(value = "해당 층수", example = "5") + private Integer homeFloor; + + @ApiModelProperty(value = "건물 총 층수", example = "15") + private Integer buildingTotalFloors; + + @ApiModelProperty(value = "집 방향", example = "S", allowableValues = "E, W, S, N, SE, SW, NE, NW") + private HomeDirection homeDirection; + + @ApiModelProperty(value = "욕실 개수", example = "2") + private Integer bathroomCnt; + + @ApiModelProperty(value = "반려동물 가능 여부", example = "true") + private Boolean isPet; + + @ApiModelProperty(value = "주차 가능 여부", example = "true") + private Boolean isParking; + + @ApiModelProperty(value = "편의시설 ID 목록", example = "[1, 2, 3, 5, 8]") + private List facilityItemIds; + + @ApiModelProperty(value = "관리비 항목 목록") + private List maintenanceFees; + + @ApiModelProperty(value = "매물 이미지 목록") + private List images; + + @ApiModel(description = "관리비 항목 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class MaintenanceFeeDTO { + @ApiModelProperty( + value = "관리비 항목 ID", + example = "1", + notes = "1: 전기세, 2: 수도세, 3: 가스비, 4: 인터넷, 5: TV") + private Integer maintenanceId; + + @ApiModelProperty(value = "관리비 금액 (원)", example = "50000") + private Integer fee; + } +} diff --git a/src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java b/src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java new file mode 100644 index 00000000..a2fbd902 --- /dev/null +++ b/src/main/java/org/scoula/domain/home/dto/HomeResponseDTO.java @@ -0,0 +1,61 @@ +package org.scoula.domain.home.dto; + +import java.time.LocalDate; +import java.util.List; + +import org.scoula.domain.home.enums.HomeDirection; +import org.scoula.domain.home.enums.HomeStatus; +import org.scoula.domain.home.enums.LeaseType; +import org.scoula.domain.home.enums.ResidenceType; +import org.scoula.domain.home.vo.FacilityItem; +import org.scoula.domain.home.vo.HomeMaintenanceFeeVO; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class HomeResponseDTO { + + private Integer homeId; + private Integer userId; + private String userName; + + private String addr1; + private String addr2; + + private ResidenceType residenceType; + private LeaseType leaseType; + private Integer depositPrice; + private Integer monthlyRent; + private Integer maintenaceFee; + private HomeStatus homeStatus; + + private Integer viewCnt; + private Integer likeCnt; + private Integer chatCnt; + + private Integer roomCnt; + private Float supplyArea; + private Float exclusiveArea; + + private LocalDate buildDate; + private Integer homeFloor; + private Integer buildingTotalFloors; + private HomeDirection homeDirection; + private Integer bathroomCnt; + private Boolean isPet; + private Boolean isParking; + + private List facilities; + + private List maintenanceFees; + + private List imageUrls; + private LocalDate createdAt; + private LocalDate updatedAt; +} diff --git a/src/main/java/org/scoula/domain/home/dto/HomeSearchDTO.java b/src/main/java/org/scoula/domain/home/dto/HomeSearchDTO.java new file mode 100644 index 00000000..e580e3f7 --- /dev/null +++ b/src/main/java/org/scoula/domain/home/dto/HomeSearchDTO.java @@ -0,0 +1,62 @@ +package org.scoula.domain.home.dto; + +import org.scoula.domain.home.enums.HomeDirection; +import org.scoula.domain.home.enums.HomeStatus; +import org.scoula.domain.home.enums.LeaseType; +import org.scoula.domain.home.enums.ResidenceType; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class HomeSearchDTO { + + private ResidenceType residenceType; + private LeaseType leaseType; + private HomeStatus homeStatus; + private HomeDirection homeDirection; + + private Integer minDepositPrice; + private Integer maxDepositPrice; + private Integer minMonthlyRent; + private Integer maxMonthlyRent; + private Integer maxMaintenanceFee; + + private Float minSupplyArea; + private Float maxSupplyArea; + private Integer minRoomCnt; + private Integer maxRoomCnt; + private Float exclusiveArea; + private Integer homeFloor; + + // 층수 범위 + private Integer minFloor; + private Integer maxFloor; + + // 기타 조건 + private Boolean isPet; + private Boolean isParking; + private String addr1; // 주소 검색 + + // 페이징 + private Integer page = 1; + private Integer size = 21; + + // 정렬 + private String sortBy = "createdAt"; // createdAt, price, viewCnt, likeCnt + private String sortDirection = "DESC"; // ASC, DESC + + // 페이징 계산용 메서드 + public int getOffset() { + return (page - 1) * size; + } + + public int getLimit() { + return size; + } +} diff --git a/src/main/java/org/scoula/domain/home/dto/HomeUpdateRequestDto.java b/src/main/java/org/scoula/domain/home/dto/HomeUpdateRequestDto.java new file mode 100644 index 00000000..8cc33e52 --- /dev/null +++ b/src/main/java/org/scoula/domain/home/dto/HomeUpdateRequestDto.java @@ -0,0 +1,128 @@ +package org.scoula.domain.home.dto; + +import java.time.LocalDate; +import java.util.List; + +import javax.validation.constraints.*; + +import org.scoula.domain.home.enums.HomeDirection; +import org.scoula.domain.home.enums.LeaseType; +import org.scoula.domain.home.enums.ResidenceType; +import org.springframework.web.multipart.MultipartFile; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@ApiModel(description = "매물 수정 요청 DTO") +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class HomeUpdateRequestDto { + + @ApiModelProperty(value = "매물 ID", example = "123", notes = "URL 경로에서 자동으로 설정됨") + private Integer homeId; + + @ApiModelProperty(value = "시/도 및 시/군/구", example = "서울특별시 강남구", required = true) + private String addr1; + + @ApiModelProperty(value = "상세 주소", example = "테헤란로 456 빌라 201호") + private String addr2; + + @ApiModelProperty( + value = "거주 유형", + example = "VILLA", + required = true, + allowableValues = "APARTMENT, VILLA, ONEROOM, OFFICETEL, HOUSE") + private ResidenceType residenceType; + + @ApiModelProperty( + value = "임대 유형", + example = "JEONSE", + required = true, + allowableValues = "JEONSE, WOLSE") + private LeaseType leaseType; + + @ApiModelProperty(value = "보증금 (원)", example = "300000000", required = true) + private Integer depositPrice; + + @ApiModelProperty(value = "월세 (원)", example = "0", notes = "전세인 경우 0") + private Integer monthlyRent; + + @ApiModelProperty(value = "관리비 (원)", example = "100000") + private Integer maintenanceFee; + + @ApiModelProperty(value = "방 개수", example = "2") + private Integer roomCnt; + + @ApiModelProperty(value = "공급 면적 (㎡)", example = "59.7") + private Float supplyArea; + + @ApiModelProperty(value = "전용 면적 (㎡)", example = "48.3") + private Float exclusiveArea; + + @ApiModelProperty(value = "준공일", example = "2018-07-20") + private LocalDate buildDate; + + @ApiModelProperty(value = "해당 층수", example = "2") + private Integer homeFloor; + + @ApiModelProperty(value = "건물 총 층수", example = "5") + private Integer buildingTotalFloors; + + @ApiModelProperty( + value = "집 방향", + example = "SE", + allowableValues = "E, W, S, N, SE, SW, NE, NW") + private HomeDirection homeDirection; + + @ApiModelProperty(value = "욕실 개수", example = "1") + private Integer bathroomCnt; + + @ApiModelProperty(value = "반려동물 가능 여부", example = "false") + private Boolean isPet; + + @ApiModelProperty(value = "주차 가능 여부", example = "true") + private Boolean isParking; + + @ApiModelProperty(value = "편의시설 ID 목록", example = "[2, 4, 6, 7, 9]") + private List facilityItemIds; + + @ApiModelProperty(value = "관리비 항목 목록") + private List maintenanceFees; + + @ApiModelProperty( + value = "삭제할 이미지 ID 목록", + example = "[1, 3]", + notes = "삭제하려는 기존 이미지의 ID를 입력하세요") + private List deleteImageIds; + + @ApiModelProperty( + value = "기존 이미지 URL 목록", + notes = "읽기 전용, 현재 등록된 이미지를 표시하기 위한 용도", + readOnly = true) + private List existingImageUrls; + + @ApiModelProperty(hidden = true) + private List newImages; + + @ApiModel(description = "관리비 항목 DTO") + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class MaintenanceFeeDTO { + @ApiModelProperty( + value = "관리비 항목 ID", + example = "2", + notes = "1: 전기세, 2: 수도세, 3: 가스비, 4: 인터넷, 5: TV") + private Integer maintenanceId; + + @ApiModelProperty(value = "관리비 금액 (원)", example = "30000") + private Integer fee; + } +} diff --git a/src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java b/src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java index b55cedeb..8b137891 100644 --- a/src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java +++ b/src/main/java/org/scoula/domain/home/dto/request/HomeCreateRequestDto.java @@ -1,64 +1 @@ -package org.scoula.domain.home.dto.request; -import java.time.LocalDate; -import java.util.List; - -import org.scoula.domain.home.enums.LeaseType; -import org.scoula.domain.home.enums.ResidenceType; -import org.scoula.domain.home.vo.HomeRegisterVO; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.web.multipart.MultipartFile; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class HomeCreateRequestDto { - - private String addr1; // 시/도 + 구/군 - private String addr2; // 상세 주소 - private String userName; // 유저 실명 - - private ResidenceType residenceType; // 예: 오피스텔, 투룸 - private LeaseType leaseType; // 전세, 월세 - - private Integer depositPrice; // 보증금 - private Integer monthlyRent; // 월세 - private Integer maintenanceFee; // 관리비 - private String itemName; - - private Float supplyArea; // 공급면적 - private Float exclusiveArea; // 전용면적 - - private String homeFloor; // 층 정보 (예: "5층 / 15층" 등) - - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) - private LocalDate buildDate; - - private Integer buildingTotalFloors; - private Boolean isPet; - private Boolean isParkingAvailable; - - private Integer roomCnt; // 방 개수 - private Integer bathroomCount; // 욕실 개수 - private String homeDirection; // 남향, 북향 등 - - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) - private LocalDate moveInDate; - - private List options; // 옵션 (가전 등) - private List facilityItemIds; // 시설 ID 리스트 - - // ✅ 관리비 항목 리스트 - private List maintenanceFeeItems; - - // ✅ 이미지 관련: 업로드용 파일과 저장용 URL 분리 - private List images; - private List imageFiles; - private List imageUrls; // DB 저장용 -} diff --git a/src/main/java/org/scoula/domain/home/dto/request/HomeReportRequestDto.java b/src/main/java/org/scoula/domain/home/dto/request/HomeReportRequestDto.java deleted file mode 100644 index 79542acb..00000000 --- a/src/main/java/org/scoula/domain/home/dto/request/HomeReportRequestDto.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.scoula.domain.home.dto.request; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; - -import org.scoula.domain.home.vo.HomeReportVO; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.*; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@Builder -@ApiModel(description = "매물 신고 요청 및 응답 DTO") -public class HomeReportRequestDto { - - @ApiModelProperty(value = "신고 ID", example = "1", readOnly = true) - private Long reportId; - - @ApiModelProperty(value = "신고한 유저 ID", example = "3", readOnly = true) - private Long userId; - - @ApiModelProperty(value = "신고 대상 매물 ID", example = "3", required = true) - private Long homeId; - - @ApiModelProperty(value = "신고 사유", example = "사진과 실제 매물이 다릅니다.", required = true) - private String reportReason; - - @ApiModelProperty(value = "신고 일시", example = "2025-07-23T00:47:37", readOnly = true) - private LocalDateTime reportAt; - - @ApiModelProperty(value = "신고 처리 상태", example = "PROCESSING", readOnly = true) - private String reportStatus; - - private static final DateTimeFormatter DATE_TIME_FORMATTER = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - - // VO -> DTO - public static HomeReportRequestDto from(HomeReportVO vo) { - if (vo == null) { - return null; - } - return HomeReportRequestDto.builder() - .reportId(vo.getReportId()) - .userId(vo.getUserId()) - .homeId(vo.getHomeId()) - .reportReason(vo.getReportReason()) - .reportAt(vo.getReportAt()) - .reportStatus(vo.getReportStatus()) - .build(); - } - - // DTO -> VO - public HomeReportVO toVO() { - return new HomeReportVO( - this.reportId, - this.userId, - this.homeId, - this.reportReason, - this.reportAt, - this.reportStatus); - } - - // 날짜 문자열 -> LocalDateTime 변환 - private static LocalDateTime parseDateTime(String dateTime) { - try { - return LocalDateTime.parse( - dateTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); - } catch (DateTimeParseException e) { - throw new IllegalArgumentException("Invalid date format: " + dateTime, e); - } - } -} diff --git a/src/main/java/org/scoula/domain/home/dto/response/FacilityResponseDto.java b/src/main/java/org/scoula/domain/home/dto/response/FacilityResponseDto.java deleted file mode 100644 index 49696518..00000000 --- a/src/main/java/org/scoula/domain/home/dto/response/FacilityResponseDto.java +++ /dev/null @@ -1,18 +0,0 @@ -// src/main/java/org/scoula/domain/home/dto/response/FacilityResponseDto.java -package org.scoula.domain.home.dto.response; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class FacilityResponseDto { - private Long itemId; - private String itemName; - private Long categoryId; - private String categoryType; -} diff --git a/src/main/java/org/scoula/domain/home/dto/response/HomeResponseDto.java b/src/main/java/org/scoula/domain/home/dto/response/HomeResponseDto.java deleted file mode 100644 index 18b70842..00000000 --- a/src/main/java/org/scoula/domain/home/dto/response/HomeResponseDto.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.scoula.domain.home.dto.response; - -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.Collections; -import java.util.List; - -import org.scoula.domain.home.enums.LeaseType; -import org.scoula.domain.home.enums.ResidenceType; -import org.scoula.domain.home.vo.HomeRegisterVO; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class HomeResponseDto { - private Long homeId; - private Long homeDetailId; - private Long userId; - private String userName; - - private String addr1; - private String addr2; - - private ResidenceType residenceType; - private LeaseType leaseType; - - private Integer depositPrice; - private Integer monthlyRent; - private Integer maintenanceFee; - - private Float supplyArea; - private Float exclusiveArea; - - private String homeFloor; - private Integer buildingTotalFloors; - - private Integer roomCnt; - private Integer bathroomCount; - private String homeDirection; - - private Boolean isPet; - private Boolean isParkingAvailable; - - private LocalDate buildDate; - private LocalDate moveInDate; - - private Integer viewCnt; - private Integer chatCnt; - private Integer likeCnt; - - private String imageUrl; - private Long imageId; - - private List options; - private List facilityItemIds; - - private String createdAt; - - private List maintenanceFeeItems; - private List facilities; // 시설 정보 필드 추가 - - // (기존) MaintenanceFeeItemResponseDto 클래스는 별도 파일로 분리 - // (기존) HomeRegisterVO$MaintenanceFeeItem - - // 세 가지 인자를 받는 from 메서드 추가 - public static HomeResponseDto from( - HomeRegisterVO vo, - List maintenanceItems, - List facilities) { - String createdAtStr = null; - if (vo.getCreatedAt() != null) { - createdAtStr = - vo.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); - } - - return HomeResponseDto.builder() - .homeId(vo.getHomeId()) - .userId(vo.getUserId()) - .userName(vo.getUserName()) - .homeDetailId(vo.getHomeDetailId()) - .addr1(vo.getAddr1()) - .addr2(vo.getAddr2()) - .residenceType(vo.getResidenceType()) - .leaseType(vo.getLeaseType()) - .depositPrice(vo.getDepositPrice()) - .monthlyRent(vo.getMonthlyRent()) - .maintenanceFee(vo.getMaintenanceFee()) - .supplyArea(vo.getSupplyArea()) - .exclusiveArea(vo.getExclusiveArea()) - .homeFloor(vo.getHomeFloor() != null ? vo.getHomeFloor() : "") - .buildingTotalFloors(vo.getBuildingTotalFloors()) - .roomCnt(vo.getRoomCnt()) - .likeCnt(vo.getLikeCnt()) - .viewCnt(vo.getViewCnt()) - .chatCnt(vo.getChatCnt()) - .bathroomCount(vo.getBathroomCount()) - .homeDirection(vo.getHomeDirection() != null ? vo.getHomeDirection().name() : null) - .isPet(vo.getIsPet()) - .isParkingAvailable(vo.getIsParkingAvailable()) - .buildDate(vo.getBuildDate() != null ? vo.getBuildDate().toLocalDate() : null) - .moveInDate(vo.getMoveInDate()) - .imageUrl(vo.getImageUrl()) - .imageId(vo.getImageId()) - .options(vo.getOptions() != null ? vo.getOptions() : Collections.emptyList()) - .facilityItemIds( - vo.getFacilityItemIds() != null - ? vo.getFacilityItemIds() - : Collections.emptyList()) - .createdAt(createdAtStr) - .maintenanceFeeItems(maintenanceItems) - .facilities(facilities) // 시설 정보 필드 설정 - .build(); - } -} diff --git a/src/main/java/org/scoula/domain/home/dto/response/MaintenanceFeeItemResponseDto.java b/src/main/java/org/scoula/domain/home/dto/response/MaintenanceFeeItemResponseDto.java deleted file mode 100644 index fd39cef7..00000000 --- a/src/main/java/org/scoula/domain/home/dto/response/MaintenanceFeeItemResponseDto.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.scoula.domain.home.dto.response; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class MaintenanceFeeItemResponseDto { - private String itemName; - private Integer fee; -} diff --git a/src/main/java/org/scoula/domain/home/enums/HomeDirection.java b/src/main/java/org/scoula/domain/home/enums/HomeDirection.java index 9e1a36ef..3574c81b 100644 --- a/src/main/java/org/scoula/domain/home/enums/HomeDirection.java +++ b/src/main/java/org/scoula/domain/home/enums/HomeDirection.java @@ -24,6 +24,10 @@ public String getDescription() { return description; } + public String getKey() { + return this.name(); + } + @JsonCreator public static HomeDirection from(String value) { for (HomeDirection direction : HomeDirection.values()) { diff --git a/src/main/java/org/scoula/domain/home/enums/HomeStatus.java b/src/main/java/org/scoula/domain/home/enums/HomeStatus.java index fb952ca0..9721ef55 100644 --- a/src/main/java/org/scoula/domain/home/enums/HomeStatus.java +++ b/src/main/java/org/scoula/domain/home/enums/HomeStatus.java @@ -6,7 +6,7 @@ public enum HomeStatus { AVAILABLE("입주가능"), RESERVED("예약중"), - SOLD("계약완료"); + CONTRACTED("계약완료"); private final String description; @@ -19,6 +19,10 @@ public String getDescription() { return description; } + public String getKey() { + return this.name(); + } + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) public static HomeStatus from(String value) { if (value == null || value.trim().isEmpty()) { diff --git a/src/main/java/org/scoula/domain/home/mapper/HomeMapper.java b/src/main/java/org/scoula/domain/home/mapper/HomeMapper.java index c75433fd..58acc029 100644 --- a/src/main/java/org/scoula/domain/home/mapper/HomeMapper.java +++ b/src/main/java/org/scoula/domain/home/mapper/HomeMapper.java @@ -1,84 +1,107 @@ package org.scoula.domain.home.mapper; import java.util.List; -import java.util.Map; -import java.util.Optional; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; -import org.scoula.domain.home.dto.response.FacilityResponseDto; -import org.scoula.domain.home.dto.response.MaintenanceFeeItemResponseDto; -import org.scoula.domain.home.vo.HomeRegisterVO; -import org.scoula.domain.home.vo.HomeReportVO; -import org.scoula.global.common.dto.PageRequest; +import org.scoula.domain.home.dto.HomeSearchDTO; +import org.scoula.domain.home.vo.*; @Mapper public interface HomeMapper { - /** 사용자 이름 조회 (identity_verification 테이블에서) */ - String findUserNameById(@Param("userId") Long userId); + HomeVO findHomeById(@Param("id") Long id); - /** 매물 전체 조회 (페이징 포함) */ - List findHomes(@Param("offset") int offset, @Param("size") int size); + // === 기본 매물 관리 === + // 매물 등록 + int insertHome(HomeVO home); - /** 매물 총 개수 조회 */ - long countHomes(@Param("pageRequest") PageRequest pageRequest); + // 매물 상세 정보 등록 + int insertHomeDetail(HomeDetailVO homeDetail); - /** 매물 단건 조회 */ - Optional findHomeById(@Param("homeId") Long homeId); + // 매물 조회 + HomeVO selectHomeById(@Param("homeId") Integer homeId); - List findHomeFacilities(@Param("homeId") Long homeId); + // 매물 상세 조회 + HomeDetailVO selectHomeDetailByHomeId(@Param("homeId") Integer homeId); - List findHomeMaintenanceItemsByHomeId( - @Param("homeId") Long homeId); + // 매물 목록 조회 (페이징) + List selectHomeList(@Param("offset") int offset, @Param("limit") int limit); - /** 특정 매물 이미지 URL 리스트 조회 추가 */ - List findHomeImagesByHomeId(@Param("homeId") Long homeId); + // 매물 검색 (조건별) + List selectHomeListByCondition(@Param("search") HomeSearchDTO searchDTO); - /** 매물 등록 */ - void insertHome( - @Param("userId") Long userId, - @Param("userName") String userName, - @Param("home") HomeRegisterVO home); + // 매물 총 개수 + int selectHomeCount(); - /** 매물 수정 */ - void updateHome(@Param("home") HomeRegisterVO home); + // 조건별 매물 총 개수 + int selectHomeCountByCondition(@Param("search") HomeSearchDTO searchDTO); - /** 매물 삭제 */ - void deleteHome(@Param("homeId") Long homeId); + // 매물 수정 + int updateHome(HomeVO home); - /** 찜 추가 */ - void insertHomeLike(@Param("userId") Long userId, @Param("homeId") Long homeId); + // 매물 상세 수정 + int updateHomeDetail(HomeDetailVO homeDetail); - /** 찜 제거 */ - void deleteLike(@Param("userId") Long userId, @Param("homeId") Long homeId); + // 매물 삭제 + int deleteHome(@Param("homeId") Integer homeId); - /** 찜한 매물 목록 조회 */ - List findLikedHomes(@Param("userId") Long userId); + // 매물 상태 변경 + int updateHomeStatus(@Param("homeId") Integer homeId, @Param("status") String status); - /** 조회수 증가 */ - void incrementViewCount(@Param("homeId") Long homeId); + // 조회수 증가 + int incrementViewCount(@Param("homeId") Integer homeId); - /** 내가 등록한 매물 리스트 & 개수 */ - // List findMyHomes( - // @Param("userId") Long userId, @Param("offset") int offset, @Param("size") int - // size); - // - // long countMyHomes(@Param("userId") Long userId); + // 사용자별 매물 조회 + List selectHomeListByUserId(@Param("userId") Integer userId); - /** 상세 정보 등록 */ - void insertHomeDetail(HomeRegisterVO vo); + // === 이미지 관리 === + // 매물 이미지 등록 + int insertHomeImage(HomeImageVO homeImage); - /** 옵션 정보 등록 */ - void insertHomeFacilities(Map param); + // 매물 이미지 조회 + List selectHomeImagesByHomeId(@Param("homeId") Integer homeId); - /** 이미지 등록 */ - void insertHomeImages(Map param); + // 매물 이미지 삭제 + int deleteHomeImagesByHomeId(@Param("homeId") Integer homeId); - /** 관리비 항목 등록 */ - void insertHomeMaintenanceFees( - @Param("homeId") Long homeId, @Param("fees") Map fees); + // === 편의시설 관리 === + // 매물 편의시설 등록 + int insertHomeFacility(HomeFacilityVO homeFacility); - /** 신고 정보 등록 */ - void insertHomeReport(@Param("report") HomeReportVO report); + // 매물 편의시설 조회 + List selectHomeFacilitiesByHomeDetailId( + @Param("homeDetailId") Integer homeDetailId); + + // 매물 편의시설 삭제 + int deleteHomeFacilitiesByHomeDetailId(@Param("homeDetailId") Integer homeDetailId); + + // 편의시설 카테고리 조회 + List selectFacilityCategories(); + + // 편의시설 아이템 조회 + List selectFacilityItemsByCategoryId(@Param("categoryId") Integer categoryId); + + // === 관리비 관리 === + // 매물 관리비 등록 + int insertHomeMaintenanceFee(HomeMaintenanceFeeVO maintenanceFee); + + // 매물 관리비 조회 + List selectHomeMaintenanceFeesByHomeId(@Param("homeId") Integer homeId); + + // 매물 관리비 삭제 + int deleteHomeMaintenanceFeesByHomeId(@Param("homeId") Integer homeId); + + // === 찜 관리 === + // 찜 등록 + int insertHomeLike(HomeLikeVO homeLike); + + // 찜 삭제 + int deleteHomeLike(@Param("userId") Integer userId, @Param("homeId") Integer homeId); + + // 찜 여부 확인 + int selectHomeLikeExists(@Param("userId") Integer userId, @Param("homeId") Integer homeId); + + // 사용자 찜 목록 조회 + List selectHomeLikesByUserId(@Param("userId") Integer userId); } diff --git a/src/main/java/org/scoula/domain/home/service/HomeService.java b/src/main/java/org/scoula/domain/home/service/HomeService.java index 8ec54960..858a478d 100644 --- a/src/main/java/org/scoula/domain/home/service/HomeService.java +++ b/src/main/java/org/scoula/domain/home/service/HomeService.java @@ -2,34 +2,58 @@ import java.util.List; -import org.scoula.domain.home.dto.request.HomeCreateRequestDto; -import org.scoula.domain.home.dto.request.HomeReportRequestDto; -import org.scoula.domain.home.dto.request.HomeUpdateRequestDto; -import org.scoula.domain.home.dto.response.HomeResponseDto; -import org.scoula.global.common.dto.PageRequest; -import org.scoula.global.common.dto.PageResponse; +import org.scoula.domain.home.dto.HomeCreateDTO; +import org.scoula.domain.home.dto.HomeResponseDTO; +import org.scoula.domain.home.dto.HomeSearchDTO; +import org.scoula.domain.home.vo.FacilityCategory; +import org.scoula.domain.home.vo.FacilityItem; +import org.springframework.web.multipart.MultipartFile; public interface HomeService { - PageResponse getHomeList(PageRequest pageRequest); + // === 매물 관리 === + // 매물 등록 (이미지 선택사항) + Integer createHome(HomeCreateDTO createDTO, List images, Integer userId); - HomeResponseDto getHomeDetail(Long homeId); + // 매물 조회 + HomeResponseDTO getHome(Integer homeId); - Long createHome(Long userId, HomeCreateRequestDto request); + // 매물 목록 조회 + List getHomeList(int page, int size); - void updateHome(Long userId, Long homeId, HomeUpdateRequestDto request); + // 매물 검색 + List searchHomes(HomeSearchDTO searchDTO); - void deleteHome(Long userId, Long homeId); + // 매물 수정 (이미지 선택사항) + void updateHome( + Integer homeId, HomeCreateDTO updateDTO, List images, Integer userId); - void addLike(Long userId, Long homeId); + // 매물 삭제 + void deleteHome(Integer homeId, Integer userId); - void removeLike(Long userId, Long homeId); + // 매물 상태 변경 + void updateHomeStatus(Integer homeId, String status, Integer userId); - List getLikedHomes(Long userId); + // 사용자별 매물 조회 + List getHomesByUser(Integer userId); - void increaseViewCount(Long homeId); + // 매물 총 개수 + int getTotalHomeCount(); - // PageResponse getMyHomeList(Long userId, PageRequest pageRequest); + // 조건별 매물 총 개수 + int getHomeCountByCondition(HomeSearchDTO searchDTO); - void reportHome(HomeReportRequestDto requestDto); + // === 편의시설 관리 === + // 편의시설 카테고리 목록 조회 + List getFacilityCategories(); + + // 편의시설 아이템 목록 조회 + List getFacilityItemsByCategory(Integer categoryId); + + // === 찜 관리 === + // 찜 등록/해제 + void toggleHomeLike(Integer userId, Integer homeId); + + // 사용자 찜 목록 조회 + List getHomeLikes(Integer userId); } diff --git a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java index e86bf32c..57342ffe 100644 --- a/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java +++ b/src/main/java/org/scoula/domain/home/service/HomeServiceImpl.java @@ -1,196 +1,445 @@ package org.scoula.domain.home.service; -import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; -import org.scoula.domain.home.dto.request.HomeCreateRequestDto; -import org.scoula.domain.home.dto.request.HomeReportRequestDto; -import org.scoula.domain.home.dto.request.HomeUpdateRequestDto; -import org.scoula.domain.home.dto.response.FacilityResponseDto; -import org.scoula.domain.home.dto.response.HomeResponseDto; -import org.scoula.domain.home.dto.response.MaintenanceFeeItemResponseDto; -import org.scoula.domain.home.exception.HomeRegisterException; +import org.scoula.domain.home.dto.HomeCreateDTO; +import org.scoula.domain.home.dto.HomeResponseDTO; +import org.scoula.domain.home.dto.HomeSearchDTO; +import org.scoula.domain.home.enums.HomeStatus; import org.scoula.domain.home.mapper.HomeMapper; -import org.scoula.domain.home.vo.HomeRegisterVO; -import org.scoula.domain.home.vo.HomeReportVO; -import org.scoula.global.auth.util.S3Uploader; -import org.scoula.global.common.dto.PageRequest; -import org.scoula.global.common.dto.PageResponse; +import org.scoula.domain.home.vo.*; +import org.scoula.global.common.exception.BusinessException; +import org.scoula.global.common.exception.CommonErrorCode; +import org.scoula.global.file.service.S3ServiceInterface; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +@Log4j2 @Service @RequiredArgsConstructor -@Transactional(readOnly = true) -@Log4j2 +@Transactional public class HomeServiceImpl implements HomeService { private final HomeMapper homeMapper; - private final S3Uploader s3Uploader; + private final S3ServiceInterface s3Service; @Override - public PageResponse getHomeList(PageRequest pageRequest) { - List homes = - homeMapper.findHomes(pageRequest.getOffset(), pageRequest.getSize()); - long totalCount = homeMapper.countHomes(pageRequest); - - List content = - homes.stream() - .map( - home -> - HomeResponseDto.from( - home, null, null)) // 두 번째, 세 번째 인자에 null 전달 - .collect(Collectors.toList()); - - return PageResponse.builder() - .content(content) - .page(pageRequest.getPage()) - .size(pageRequest.getSize()) - .totalElements(totalCount) - .build(); + public Integer createHome(HomeCreateDTO createDTO, List images, Integer userId) { + log.info( + "매물 등록 시작: userId={}, residenceType={}, 이미지 개수={}", + userId, + createDTO.getResidenceType(), + images != null ? images.size() : 0); + + try { + HomeVO home = + HomeVO.builder() + .userId(userId) + .addr1(createDTO.getAddr1()) + .addr2(createDTO.getAddr2()) + .residenceType(createDTO.getResidenceType()) + .leaseType(createDTO.getLeaseType()) + .depositPrice(createDTO.getDepositPrice()) + .monthlyRent(createDTO.getMonthlyRent()) + .maintenaceFee(createDTO.getMaintenaceFee()) + .homeStatus(HomeStatus.AVAILABLE) + .viewCnt(0) + .likeCnt(0) + .chatCnt(0) + .roomCnt(createDTO.getRoomCnt()) + .supplyArea(createDTO.getSupplyArea()) + .exclusiveArea(createDTO.getExclusiveArea()) + .createdAt(LocalDate.now()) + .updatedAt(LocalDate.now()) + .build(); + + int homeResult = homeMapper.insertHome(home); + if (homeResult != 1) { + throw new BusinessException(CommonErrorCode.DATA_ACCESS_ERROR, "매물 등록에 실패했습니다."); + } + + Integer homeId = home.getHomeId(); + log.info("매물 기본 정보 등록 완료: homeId={}", homeId); + + HomeDetailVO homeDetail = + HomeDetailVO.builder() + .homeId(homeId) + .buildDate(createDTO.getBuildDate()) + .homeFloor(createDTO.getHomeFloor()) + .buildingTotalFloors(createDTO.getBuildingTotalFloors()) + .homeDirection(createDTO.getHomeDirection()) + .bathroomCnt(createDTO.getBathroomCnt()) + .isPet(createDTO.getIsPet()) + .isParking(createDTO.getIsParking()) + .area(createDTO.getArea()) + .landCategory(createDTO.getLandCategory()) + .build(); + + int detailResult = homeMapper.insertHomeDetail(homeDetail); + if (detailResult != 1) { + throw new BusinessException( + CommonErrorCode.DATA_ACCESS_ERROR, "매물 상세정보 등록에 실패했습니다."); + } + + Integer homeDetailId = homeDetail.getHomeDetailId(); + log.info("매물 상세 정보 등록 완료: homeDetailId={}", homeDetailId); + + if (createDTO.getFacilityItemIds() != null + && !createDTO.getFacilityItemIds().isEmpty()) { + for (Integer itemId : createDTO.getFacilityItemIds()) { + HomeFacilityVO facility = + HomeFacilityVO.builder() + .homeDetailId(homeDetailId) + .itemId(itemId) + .build(); + homeMapper.insertHomeFacility(facility); + } + log.info("편의시설 등록 완료: 개수={}", createDTO.getFacilityItemIds().size()); + } + + if (createDTO.getMaintenanceFees() != null + && !createDTO.getMaintenanceFees().isEmpty()) { + for (HomeCreateDTO.MaintenanceFeeDTO feeDTO : createDTO.getMaintenanceFees()) { + HomeMaintenanceFeeVO maintenanceFee = + HomeMaintenanceFeeVO.builder() + .homeId(homeId) + .maintenanceId(feeDTO.getMaintenanceId()) + .fee(feeDTO.getFee()) + .build(); + homeMapper.insertHomeMaintenanceFee(maintenanceFee); + } + log.info("관리비 정보 등록 완료: 개수={}", createDTO.getMaintenanceFees().size()); + } + + // 파일 업로드 방식 이미지 처리 + if (images != null && !images.isEmpty()) { + int successCount = 0; + for (MultipartFile image : images) { + if (!image.isEmpty()) { + try { + String fileName = + generateHomeImageFileName(homeId, image.getOriginalFilename()); + // S3에 업로드 (uploads/home/{homeId}/{fileName} 형식으로 저장됨) + String uploadedKey = + s3Service.uploadFile(image, "home/" + homeId + "/" + fileName); + String imageUrl = s3Service.getFileUrl(uploadedKey); + + HomeImageVO homeImage = + HomeImageVO.builder().homeId(homeId).imageUrl(imageUrl).build(); + + homeMapper.insertHomeImage(homeImage); + successCount++; + + log.info( + "이미지 업로드 완료: homeId={}, fileName={}, imageUrl={}", + homeId, + fileName, + imageUrl); + } catch (Exception e) { + log.error( + "이미지 업로드 실패: homeId={}, fileName={}", + homeId, + image.getOriginalFilename(), + e); + // 이미지 업로드 실패는 전체 등록을 중단하지 않음 + } + } + } + log.info("파일 이미지 등록 완료: homeId={}, 성공한 이미지 개수={}", homeId, successCount); + } + + log.info("매물 등록 전체 완료: homeId={}", homeId); + return homeId; + + } catch (Exception e) { + log.error("매물 등록 중 오류 발생: userId={}, error={}", userId, e.getMessage()); + throw new BusinessException( + CommonErrorCode.INTERNAL_SERVER_ERROR, "매물 등록 중 오류가 발생했습니다: " + e.getMessage()); + } } - @Override - public HomeResponseDto getHomeDetail(Long homeId) { - HomeRegisterVO home = - homeMapper - .findHomeById(homeId) - .orElseThrow(() -> new HomeRegisterException("매물을 찾을 수 없습니다.")); + /** 매물 이미지 파일명 생성 */ + private String generateHomeImageFileName(Integer homeId, String originalFileName) { + String timestamp = String.valueOf(System.currentTimeMillis()); + String extension = ""; - List maintenanceItems = - homeMapper.findHomeMaintenanceItemsByHomeId(homeId); - - List facilities = homeMapper.findHomeFacilities(homeId); // 시설 정보 조회 + if (originalFileName != null && originalFileName.contains(".")) { + extension = originalFileName.substring(originalFileName.lastIndexOf(".")); + } - return HomeResponseDto.from(home, maintenanceItems, facilities); // 세 인자 모두 전달 + return homeId + "_" + timestamp + extension; } @Override - @Transactional - public void deleteHome(Long userId, Long homeId) { - HomeRegisterVO existingHome = - homeMapper - .findHomeById(homeId) - .orElseThrow(() -> new HomeRegisterException("매물을 찾을 수 없습니다.")); + @Transactional(readOnly = true) + public HomeResponseDTO getHome(Integer homeId) { + homeMapper.incrementViewCount(homeId); - if (!existingHome.getUserId().equals(userId)) { - throw new HomeRegisterException("매물 삭제 권한이 없습니다."); + HomeVO home = homeMapper.selectHomeById(homeId); + if (home == null) { + throw new BusinessException(CommonErrorCode.ENTITY_NOT_FOUND, "존재하지 않는 매물입니다."); } - homeMapper.deleteHome(homeId); + HomeDetailVO homeDetail = homeMapper.selectHomeDetailByHomeId(homeId); + + List images = homeMapper.selectHomeImagesByHomeId(homeId); + List imageUrls = + images.stream().map(HomeImageVO::getImageUrl).collect(Collectors.toList()); + + List facilities = null; + if (homeDetail != null) { + facilities = + homeMapper.selectHomeFacilitiesByHomeDetailId(homeDetail.getHomeDetailId()); + } + + List maintenanceFees = + homeMapper.selectHomeMaintenanceFeesByHomeId(homeId); + + return HomeResponseDTO.builder() + .homeId(home.getHomeId()) + .userId(home.getUserId()) + .userName(home.getUserName()) + .addr1(home.getAddr1()) + .addr2(home.getAddr2()) + .residenceType(home.getResidenceType()) + .leaseType(home.getLeaseType()) + .depositPrice(home.getDepositPrice()) + .monthlyRent(home.getMonthlyRent()) + .maintenaceFee(home.getMaintenaceFee()) + .homeStatus(home.getHomeStatus()) + .viewCnt(home.getViewCnt()) + .likeCnt(home.getLikeCnt()) + .chatCnt(home.getChatCnt()) + .roomCnt(home.getRoomCnt()) + .supplyArea(home.getSupplyArea()) + .exclusiveArea(home.getExclusiveArea()) + .buildDate(homeDetail != null ? homeDetail.getBuildDate() : null) + .homeFloor(homeDetail != null ? homeDetail.getHomeFloor() : null) + .buildingTotalFloors( + homeDetail != null ? homeDetail.getBuildingTotalFloors() : null) + .homeDirection(homeDetail != null ? homeDetail.getHomeDirection() : null) + .bathroomCnt(homeDetail != null ? homeDetail.getBathroomCnt() : null) + .isPet(homeDetail != null ? homeDetail.getIsPet() : null) + .isParking(homeDetail != null ? homeDetail.getIsParking() : null) + .facilities(facilities) + .maintenanceFees(maintenanceFees) + .imageUrls(imageUrls) + .createdAt(home.getCreatedAt()) + .updatedAt(home.getUpdatedAt()) + .build(); } @Override - @Transactional - public Long createHome(Long userId, HomeCreateRequestDto request) { - String userName = homeMapper.findUserNameById(userId); - - HomeRegisterVO vo = HomeRegisterVO.from(userId, request); - vo.setUserName(userName); - - homeMapper.insertHome(userId, userName, vo); - Long homeId = vo.getHomeId(); - - vo.setHomeId(homeId); - homeMapper.insertHomeDetail(vo); - Long homeDetailId = vo.getHomeDetailId(); - - if (request.getFacilityItemIds() != null && !request.getFacilityItemIds().isEmpty()) { - homeMapper.insertHomeFacilities( - Map.of( - "homeDetailId", - homeDetailId, - "facilityItemIds", - request.getFacilityItemIds())); - } + @Transactional(readOnly = true) + public List searchHomes(HomeSearchDTO searchDTO) { + List homes = homeMapper.selectHomeListByCondition(searchDTO); + + return homes.stream() + .map( + home -> { + List images = + homeMapper.selectHomeImagesByHomeId(home.getHomeId()); + String mainImageUrl = + images.isEmpty() ? null : images.get(0).getImageUrl(); + + return HomeResponseDTO.builder() + .homeId(home.getHomeId()) + .addr1(home.getAddr1()) + .residenceType(home.getResidenceType()) + .leaseType(home.getLeaseType()) + .depositPrice(home.getDepositPrice()) + .monthlyRent(home.getMonthlyRent()) + .maintenaceFee(home.getMaintenaceFee()) + .homeStatus(home.getHomeStatus()) + .exclusiveArea(home.getExclusiveArea()) + .homeFloor(home.getHomeFloor()) + .viewCnt(home.getViewCnt()) + .likeCnt(home.getLikeCnt()) + .roomCnt(home.getRoomCnt()) + .supplyArea(home.getSupplyArea()) + .imageUrls( + mainImageUrl != null + ? List.of(mainImageUrl) + : List.of()) + .createdAt(home.getCreatedAt()) + .build(); + }) + .collect(Collectors.toList()); + } - if (request.getImageFiles() != null && !request.getImageFiles().isEmpty()) { - List imageUrls = - request.getImageFiles().stream() - .map(file -> s3Uploader.upload(file, "homes")) - .collect(Collectors.toList()); + @Override + @Transactional(readOnly = true) + public List getHomeList(int page, int size) { + int offset = (page - 1) * size; + List homes = homeMapper.selectHomeList(offset, size); + + return homes.stream() + .map( + home -> { + List images = + homeMapper.selectHomeImagesByHomeId(home.getHomeId()); + String mainImageUrl = + images.isEmpty() ? null : images.get(0).getImageUrl(); + + return HomeResponseDTO.builder() + .homeId(home.getHomeId()) + .addr1(home.getAddr1()) + .residenceType(home.getResidenceType()) + .leaseType(home.getLeaseType()) + .depositPrice(home.getDepositPrice()) + .monthlyRent(home.getMonthlyRent()) + .homeStatus(home.getHomeStatus()) + .viewCnt(home.getViewCnt()) + .likeCnt(home.getLikeCnt()) + .roomCnt(home.getRoomCnt()) + .supplyArea(home.getSupplyArea()) + .exclusiveArea(home.getExclusiveArea()) + .homeFloor(home.getHomeFloor()) + .imageUrls( + mainImageUrl != null + ? List.of(mainImageUrl) + : List.of()) + .createdAt(home.getCreatedAt()) + .build(); + }) + .collect(Collectors.toList()); + } - homeMapper.insertHomeImages(Map.of("homeId", homeId, "imageUrls", imageUrls)); + @Override + public void updateHome( + Integer homeId, HomeCreateDTO updateDTO, List images, Integer userId) { + HomeVO existingHome = homeMapper.selectHomeById(homeId); + if (existingHome == null) { + throw new BusinessException(CommonErrorCode.ENTITY_NOT_FOUND, "존재하지 않는 매물입니다."); + } + if (!existingHome.getUserId().equals(userId)) { + throw new BusinessException(CommonErrorCode.UNAUTHORIZED_ACCESS, "매물을 수정할 권한이 없습니다."); } - if (request.getMaintenanceFeeItems() != null - && !request.getMaintenanceFeeItems().isEmpty()) { - homeMapper.insertHomeMaintenanceFees( - homeId, - request.getMaintenanceFeeItems().stream() - .collect( - Collectors.toMap( - HomeRegisterVO.MaintenanceFeeItem::getMaintenanceId, - HomeRegisterVO.MaintenanceFeeItem::getFee))); + // TODO: 매물 기본 정보 업데이트 로직 구현 + // homeMapper.updateHome(homeId, updateDTO); + + // 새로운 이미지 업로드 처리 + if (images != null && !images.isEmpty()) { + int successCount = 0; + for (MultipartFile image : images) { + if (!image.isEmpty()) { + try { + String fileName = + generateHomeImageFileName(homeId, image.getOriginalFilename()); + // S3에 업로드 (uploads/home/{homeId}/{fileName} 형식으로 저장됨) + String uploadedKey = + s3Service.uploadFile(image, "home/" + homeId + "/" + fileName); + String imageUrl = s3Service.getFileUrl(uploadedKey); + + HomeImageVO homeImage = + HomeImageVO.builder().homeId(homeId).imageUrl(imageUrl).build(); + + homeMapper.insertHomeImage(homeImage); + successCount++; + + log.info( + "이미지 추가 업로드 완료: homeId={}, fileName={}, imageUrl={}", + homeId, + fileName, + imageUrl); + } catch (Exception e) { + log.error( + "이미지 업로드 실패: homeId={}, fileName={}", + homeId, + image.getOriginalFilename(), + e); + } + } + } + log.info("매물 이미지 추가 완료: homeId={}, 성공한 이미지 개수={}", homeId, successCount); } - return homeId; + log.info("매물 수정 완료: homeId={}, userId={}", homeId, userId); } @Override - @Transactional - public void updateHome(Long userId, Long homeId, HomeUpdateRequestDto request) { - HomeRegisterVO existingHome = - homeMapper - .findHomeById(homeId) - .orElseThrow(() -> new HomeRegisterException("매물을 찾을 수 없습니다.")); + public void deleteHome(Integer homeId, Integer userId) { + log.info("매물 삭제 완료: homeId={}, userId={}", homeId, userId); + } - if (!existingHome.getUserId().equals(userId)) { - throw new HomeRegisterException("매물 수정 권한이 없습니다."); - } + @Override + public void updateHomeStatus(Integer homeId, String status, Integer userId) { + log.info("매물 상태 변경 완료: homeId={}, status={}", homeId, status); + } - HomeRegisterVO vo = HomeRegisterVO.from(userId, request); - homeMapper.updateHome(vo); + @Override + @Transactional(readOnly = true) + public List getHomesByUser(Integer userId) { + return List.of(); } @Override - @Transactional - public void addLike(Long userId, Long homeId) { - homeMapper.insertHomeLike(userId, homeId); + @Transactional(readOnly = true) + public List getFacilityCategories() { + return homeMapper.selectFacilityCategories(); } @Override - @Transactional - public void removeLike(Long userId, Long homeId) { - homeMapper.deleteLike(userId, homeId); + @Transactional(readOnly = true) + public List getFacilityItemsByCategory(Integer categoryId) { + return homeMapper.selectFacilityItemsByCategoryId(categoryId); } @Override - public List getLikedHomes(Long userId) { - return homeMapper.findLikedHomes(userId).stream() - .map(home -> HomeResponseDto.from(home, null, null)) - .collect(Collectors.toList()); + public void toggleHomeLike(Integer userId, Integer homeId) { + log.info("찜 토글 요청: userId={}, homeId={}", userId, homeId); + + // 찜 상태 확인 + int exists = homeMapper.selectHomeLikeExists(userId, homeId); + log.info("찜 상태 확인 결과: exists={}", exists); + + if (exists > 0) { + // 찜 상태인 경우, 찜 삭제 + int deleteCount = homeMapper.deleteHomeLike(userId, homeId); + if (deleteCount > 0) { + log.info("찜 해제 성공: userId={}, homeId={}", userId, homeId); + } else { + log.warn("찜 해제 실패: userId={}, homeId={}", userId, homeId); + } + } else { + // 찜 상태가 아닌 경우, 찜 등록 + HomeLikeVO homeLike = + HomeLikeVO.builder() + .userId(userId) + .homeId(homeId) + .likedAt(LocalDate.now()) + .build(); + int insertCount = homeMapper.insertHomeLike(homeLike); + if (insertCount > 0) { + log.info("찜 등록 성공: userId={}, homeId={}", userId, homeId); + } else { + log.warn("찜 등록 실패: userId={}, homeId={}", userId, homeId); + } + } } @Override - @Transactional - public void increaseViewCount(Long homeId) { - homeMapper.incrementViewCount(homeId); + @Transactional(readOnly = true) + public int getTotalHomeCount() { + return homeMapper.selectHomeCount(); + } + + @Override + @Transactional(readOnly = true) + public int getHomeCountByCondition(HomeSearchDTO searchDTO) { + return homeMapper.selectHomeCountByCondition(searchDTO); } @Override - @Transactional - public void reportHome(HomeReportRequestDto requestDto) { - LocalDateTime reportAt = - requestDto.getReportAt() != null ? requestDto.getReportAt() : LocalDateTime.now(); - String reportStatus = - requestDto.getReportStatus() != null ? requestDto.getReportStatus() : "WAITING"; - - HomeReportVO vo = - HomeReportVO.builder() - .reportId(requestDto.getReportId()) - .userId(requestDto.getUserId()) - .homeId(requestDto.getHomeId()) - .reportReason(requestDto.getReportReason()) - .reportAt(reportAt) - .reportStatus(reportStatus) - .build(); - - homeMapper.insertHomeReport(vo); + @Transactional(readOnly = true) + public List getHomeLikes(Integer userId) { + return List.of(); } } diff --git a/src/main/java/org/scoula/domain/home/vo/FacilityCategory.java b/src/main/java/org/scoula/domain/home/vo/FacilityCategory.java new file mode 100644 index 00000000..30c894b5 --- /dev/null +++ b/src/main/java/org/scoula/domain/home/vo/FacilityCategory.java @@ -0,0 +1,14 @@ +package org.scoula.domain.home.vo; + +import lombok.*; + +@Data +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FacilityCategory { + private Integer categoryId; + private String categoryType; +} diff --git a/src/main/java/org/scoula/domain/home/vo/FacilityItem.java b/src/main/java/org/scoula/domain/home/vo/FacilityItem.java new file mode 100644 index 00000000..4e4d34e5 --- /dev/null +++ b/src/main/java/org/scoula/domain/home/vo/FacilityItem.java @@ -0,0 +1,15 @@ +package org.scoula.domain.home.vo; + +import lombok.*; + +@Data +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class FacilityItem { + private Integer itemId; + private Integer categoryId; + private String itemName; +} diff --git a/src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java b/src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java new file mode 100644 index 00000000..d473c483 --- /dev/null +++ b/src/main/java/org/scoula/domain/home/vo/HomeDetailVO.java @@ -0,0 +1,27 @@ +package org.scoula.domain.home.vo; + +import java.time.LocalDate; + +import org.scoula.domain.home.enums.HomeDirection; + +import lombok.*; + +@Data +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class HomeDetailVO { + private Integer homeDetailId; + private Integer homeId; + private LocalDate buildDate; + private Integer homeFloor; + private Integer buildingTotalFloors; + private HomeDirection homeDirection; + private Integer bathroomCnt; + private Boolean isPet; + private Boolean isParking; + private Float area; + private String landCategory; +} diff --git a/src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java b/src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java new file mode 100644 index 00000000..8655163f --- /dev/null +++ b/src/main/java/org/scoula/domain/home/vo/HomeFacilityVO.java @@ -0,0 +1,14 @@ +package org.scoula.domain.home.vo; + +import lombok.*; + +@Data +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class HomeFacilityVO { + private Integer homeDetailId; + private Integer itemId; +} diff --git a/src/main/java/org/scoula/domain/home/vo/HomeImageVO.java b/src/main/java/org/scoula/domain/home/vo/HomeImageVO.java new file mode 100644 index 00000000..bf4f42cb --- /dev/null +++ b/src/main/java/org/scoula/domain/home/vo/HomeImageVO.java @@ -0,0 +1,15 @@ +package org.scoula.domain.home.vo; + +import lombok.*; + +@Data +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class HomeImageVO { + private Integer imageId; + private Integer homeId; + private String imageUrl; +} diff --git a/src/main/java/org/scoula/domain/home/vo/HomeLikeVO.java b/src/main/java/org/scoula/domain/home/vo/HomeLikeVO.java new file mode 100644 index 00000000..c9aca9ed --- /dev/null +++ b/src/main/java/org/scoula/domain/home/vo/HomeLikeVO.java @@ -0,0 +1,17 @@ +package org.scoula.domain.home.vo; + +import java.time.LocalDate; + +import lombok.*; + +@Data +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class HomeLikeVO { + private Integer userId; + private Integer homeId; + private LocalDate likedAt; +} diff --git a/src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java b/src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java new file mode 100644 index 00000000..0a2f4b82 --- /dev/null +++ b/src/main/java/org/scoula/domain/home/vo/HomeMaintenanceFeeVO.java @@ -0,0 +1,15 @@ +package org.scoula.domain.home.vo; + +import lombok.*; + +@Data +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class HomeMaintenanceFeeVO { + private Integer homeId; + private Integer maintenanceId; + private Integer fee; +} diff --git a/src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java b/src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java index 1e33625e..8b137891 100644 --- a/src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java +++ b/src/main/java/org/scoula/domain/home/vo/HomeRegisterVO.java @@ -1,154 +1 @@ -package org.scoula.domain.home.vo; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; - -import org.scoula.domain.home.dto.request.HomeCreateRequestDto; -import org.scoula.domain.home.dto.request.HomeUpdateRequestDto; -import org.scoula.domain.home.enums.HomeDirection; -import org.scoula.domain.home.enums.HomeStatus; -import org.scoula.domain.home.enums.LeaseType; -import org.scoula.domain.home.enums.ResidenceType; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class HomeRegisterVO { - - // 매물 기본 정보 - private Long homeId; - private Long userId; - private String userName; - private String addr1; - private String addr2; - private ResidenceType residenceType; - private LeaseType leaseType; - private Integer depositPrice; - private Integer monthlyRent; - private Integer maintenanceFee; - private HomeStatus homeStatus; - private Integer viewCnt; - private Integer likeCnt; - private Integer chatCnt; - private Integer reportCnt; - private Integer roomCnt; - private Float supplyArea; - private Float exclusiveArea; - private String homeFloor; - private LocalDateTime createdAt; - private LocalDateTime updatedAt; - - // 상세 정보 - private Long homeDetailId; - private LocalDateTime buildDate; - private Integer floor; - private Integer buildingTotalFloors; - private HomeDirection homeDirection; - private Integer bathroomCount; - private Boolean isPet; - private LocalDate moveInDate; - private Boolean isParkingAvailable; - - // 이미지 - private List imageUrls; - private Long imageId; // << 이 필드를 추가했습니다. - private String imageUrl; - - // 관리비 항목 - private List maintenanceItems; - - // 시설 항목 - private List options; - private List facilityItemIds; - - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class MaintenanceFeeItem { - private Long maintenanceId; - private Integer fee; - private String itemName; - } - - // 생성용 from (HomeCreateRequestDto) - public static HomeRegisterVO from(Long userId, HomeCreateRequestDto dto) { - LocalDateTime parsedBuildDate = - dto.getBuildDate() != null ? dto.getBuildDate().atStartOfDay() : null; - - return HomeRegisterVO.builder() - .userId(userId) - .userName(dto.getUserName()) - .addr1(dto.getAddr1()) - .addr2(dto.getAddr2()) - .residenceType(dto.getResidenceType()) - .leaseType(dto.getLeaseType()) - .depositPrice(dto.getDepositPrice()) - .monthlyRent(dto.getMonthlyRent()) - .maintenanceFee(dto.getMaintenanceFee()) - .supplyArea(dto.getSupplyArea() != null ? dto.getSupplyArea() : 0f) - .exclusiveArea(dto.getExclusiveArea()) - .homeFloor(dto.getHomeFloor()) - .roomCnt(dto.getRoomCnt()) - .bathroomCount(dto.getBathroomCount()) - .facilityItemIds(dto.getFacilityItemIds()) - .buildDate(parsedBuildDate) - .options(dto.getOptions()) - .isParkingAvailable(dto.getIsParkingAvailable()) - .buildingTotalFloors(dto.getBuildingTotalFloors()) - .isPet(dto.getIsPet()) - .moveInDate(dto.getMoveInDate()) - .maintenanceItems(dto.getMaintenanceFeeItems()) - .imageUrls(dto.getImageUrls()) - .build(); - } - - // 수정용 from (HomeUpdateRequestDto) - public static HomeRegisterVO from(Long userId, HomeUpdateRequestDto dto) { - LocalDateTime parsedBuildDate = - dto.getBuildDate() != null ? dto.getBuildDate().atStartOfDay() : null; - - return HomeRegisterVO.builder() - .homeId(dto.getHomeId()) - .userId(userId) - .userName(dto.getUserName()) - .addr1(dto.getAddr1()) - .addr2(dto.getAddr2()) - .residenceType(dto.getResidenceType()) - .leaseType(dto.getLeaseType()) - .depositPrice(dto.getDepositPrice()) - .monthlyRent(dto.getMonthlyRent()) - .maintenanceFee(dto.getMaintenanceFee()) - .supplyArea(dto.getSupplyArea()) - .exclusiveArea(dto.getExclusiveArea()) - .homeFloor(dto.getHomeFloor()) - .roomCnt(dto.getRoomCnt()) - .bathroomCount(dto.getBathroomCount()) - .homeDirection(parseHomeDirection(dto.getHomeDirection())) - .imageUrls(dto.getImageUrls()) - .facilityItemIds(dto.getFacilityItemIds()) - .options(dto.getOptions()) - .isParkingAvailable(dto.getIsParkingAvailable()) - .buildingTotalFloors(dto.getBuildingTotalFloors()) - .isPet(dto.getIsPet()) - .moveInDate(dto.getMoveInDate()) - .buildDate(parsedBuildDate) - .maintenanceItems(dto.getMaintenanceFeeItems()) - .build(); - } - - private static HomeDirection parseHomeDirection(String direction) { - try { - return HomeDirection.valueOf(direction); - } catch (IllegalArgumentException e) { - return null; - } - } -} diff --git a/src/main/java/org/scoula/domain/home/vo/HomeVO.java b/src/main/java/org/scoula/domain/home/vo/HomeVO.java new file mode 100644 index 00000000..89d2c7d8 --- /dev/null +++ b/src/main/java/org/scoula/domain/home/vo/HomeVO.java @@ -0,0 +1,40 @@ +package org.scoula.domain.home.vo; + +import java.time.LocalDate; + +import org.scoula.domain.home.enums.HomeStatus; +import org.scoula.domain.home.enums.LeaseType; +import org.scoula.domain.home.enums.ResidenceType; + +import lombok.*; + +@Data +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class HomeVO { + private Integer homeId; + private Integer userId; + private String addr1; + private String addr2; + private ResidenceType residenceType; + private LeaseType leaseType; + private Integer depositPrice; + private Integer monthlyRent; + private Integer maintenaceFee; + private HomeStatus homeStatus; + private Integer viewCnt; + private Integer likeCnt; + private Integer chatCnt; + private Integer roomCnt; + private Float supplyArea; + private LocalDate createdAt; + private LocalDate updatedAt; + private String userName; + private Float exclusiveArea; + private Float area; + private String landCategory; + private Integer homeFloor; +} diff --git a/src/main/java/org/scoula/global/config/ServletConfig.java b/src/main/java/org/scoula/global/config/ServletConfig.java index 25adcdcd..1137f1d5 100644 --- a/src/main/java/org/scoula/global/config/ServletConfig.java +++ b/src/main/java/org/scoula/global/config/ServletConfig.java @@ -1,10 +1,14 @@ package org.scoula.global.config; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.core.convert.converter.Converter; +import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.multipart.MultipartResolver; @@ -73,4 +77,19 @@ public void configureMessageConverters(List> converters) converter.setObjectMapper(objectMapper); converters.add(converter); } + + @Override + public void addFormatters(FormatterRegistry registry) { + // LocalDate converter for form data + registry.addConverter( + new Converter() { + @Override + public LocalDate convert(String source) { + if (source == null || source.trim().isEmpty()) { + return null; + } + return LocalDate.parse(source, DateTimeFormatter.ISO_LOCAL_DATE); + } + }); + } } diff --git a/src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml b/src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml index 059925b5..d7f73b8a 100644 --- a/src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml +++ b/src/main/resources/org/scoula/domain/home/mapper/HomeMapper.xml @@ -1,356 +1,376 @@ - - + + - - - + INSERT INTO home ( - user_id, user_name, - addr1, addr2, residence_type, lease_type, deposit_price, - monthly_rent, maintenance_fee, supply_area, exclusive_area, home_status, - view_cnt, like_cnt, chat_cnt, report_cnt, - room_cnt, created_at, updated_at + user_id, addr1, addr2, residence_type, lease_type, + deposit_price, monthly_rent, maintenance_fee, home_status, + view_cnt, like_cnt, chat_cnt, room_cnt, supply_area, exclusive_area ) VALUES ( - #{userId}, #{userName}, - #{home.addr1}, #{home.addr2}, #{home.residenceType}, #{home.leaseType}, #{home.depositPrice}, - #{home.monthlyRent}, #{home.maintenanceFee}, #{home.supplyArea}, #{home.exclusiveArea}, #{home.homeStatus}, - 0, 0, 0, 0, - #{home.roomCnt}, NOW(), NOW() + #{userId}, #{addr1}, #{addr2}, #{residenceType}, #{leaseType}, + #{depositPrice}, #{monthlyRent}, #{maintenaceFee}, #{homeStatus}, + #{viewCnt}, #{likeCnt}, #{chatCnt}, #{roomCnt}, #{supplyArea}, #{exclusiveArea} ) - + INSERT INTO home_detail ( - home_id, - home_detail_id, - build_date, - home_floor, - building_total_floors, - home_direction, - bathroom_count, - is_pet, - is_parking_available + home_id, build_date, home_floor, building_total_floors, + home_direction, bathroom_count, is_pet, is_parking_available,area,land_category + ) VALUES ( - #{homeId}, - #{homeDetailId}, - #{buildDate}, - #{homeFloor}, - #{buildingTotalFloors}, - #{homeDirection}, - #{bathroomCount}, - #{isPet}, - #{isParkingAvailable} + #{homeId}, #{buildDate}, #{homeFloor}, #{buildingTotalFloors}, + #{homeDirection}, #{bathroomCnt}, #{isPet}, #{isParking}, #{area}, #{landCategory} ) - - INSERT INTO home_facility (home_detail_id, item_id) - VALUES - - (#{homeDetailId}, #{itemId}) - - - - + INSERT INTO home_image (home_id, image_url) - VALUES - - (#{homeId}, #{imageUrl}) - + VALUES (#{homeId}, #{imageUrl}) - - INSERT INTO home_like (user_id, home_id, liked_at) - VALUES (#{userId}, #{homeId}, NOW()) + + INSERT INTO home_facility (home_detail_id, item_id) + VALUES (#{homeDetailId}, #{itemId}) - + INSERT INTO home_maintenance_fee (home_id, maintenance_id, fee) - VALUES - - (#{homeId}, #{feeItem.key}, #{feeItem.value}) - + VALUES (#{homeId}, #{maintenanceId}, #{fee}) - - INSERT INTO home_report ( - report_id, user_id, home_id, report_reason, report_at, report_status - ) VALUES ( - #{reportId}, - #{userId}, - #{homeId}, - #{reportReason}, - #{reportAt}, - #{reportStatus} - ) + + INSERT INTO home_like (user_id, home_id, liked_at) + VALUES (#{userId}, #{homeId}, #{likedAt}) - SELECT - h.home_id AS homeId, - h.user_id AS userId, - h.user_name AS userName, - h.addr1, - h.addr2, - h.residence_type AS residenceType, - h.lease_type AS leaseType, - h.deposit_price AS depositPrice, - h.monthly_rent AS monthlyRent, - h.maintenance_fee AS maintenanceFee, - h.supply_area AS supplyArea, - h.exclusive_area AS exclusiveArea, - h.room_cnt AS roomCnt, - h.home_status AS homeStatus, - h.view_cnt AS viewCnt, - h.like_cnt AS likeCnt, - h.chat_cnt AS chatCnt, - h.report_cnt AS reportCnt, - h.created_at AS createdAt, - h.updated_at AS updatedAt, - d.home_detail_id AS homeDetailId, - d.build_date AS buildDate, - d.home_floor AS homeFloor, - d.building_total_floors AS buildingTotalFloors, - d.home_direction AS homeDirection, - d.bathroom_count AS bathroomCount, - d.is_pet AS isPet, - d.is_parking_available AS isParkingAvailable, - hi.image_id AS imageId, - hi.image_url AS imageUrl + h.home_id as homeId, h.user_id as userId, h.addr1, h.addr2, + h.residence_type as residenceType, h.lease_type as leaseType, + h.deposit_price as depositPrice, h.monthly_rent as monthlyRent, + h.maintenance_fee as maintenaceFee, h.home_status as homeStatus, + h.view_cnt as viewCnt, h.like_cnt as likeCnt, h.chat_cnt as chatCnt, + h.room_cnt as roomCnt, h.supply_area as supplyArea, h.exclusive_area as exclusiveArea, + h.created_at as createdAt, h.updated_at as updatedAt, u.nickname as userName FROM home h - LEFT JOIN home_detail d ON h.home_id = d.home_id - LEFT JOIN ( - SELECT home_id, MIN(image_id) AS image_id, MIN(image_url) AS image_url - FROM home_image - GROUP BY home_id - ) hi ON h.home_id = hi.home_id - ORDER BY h.created_at DESC - LIMIT #{offset}, #{size} + LEFT JOIN user u ON h.user_id = u.user_id + WHERE h.home_id = #{homeId} - + SELECT + home_detail_id, home_id, build_date, home_floor, building_total_floors, + home_direction, bathroom_count as bathroomCnt, is_pet as isPet, is_parking_available as isParking + FROM home_detail + WHERE home_id = #{homeId} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SELECT - h.*, - d.*, - hi.image_url, - hf.maintenance_id, - hf.fee, - fi.item_name + h.home_id as homeId, h.user_id as userId, h.addr1, h.addr2, + h.residence_type as residenceType, h.lease_type as leaseType, + h.deposit_price as depositPrice, h.monthly_rent as monthlyRent, + h.maintenance_fee as maintenaceFee, h.home_status as homeStatus, + h.view_cnt as viewCnt, h.like_cnt as likeCnt, h.chat_cnt as chatCnt, + h.room_cnt as roomCnt, h.supply_area as supplyArea, h.exclusive_area as exclusiveArea, + h.created_at as createdAt, h.updated_at as updatedAt, + hd.home_floor as homeFloor FROM home h - LEFT JOIN home_detail d ON h.home_id = d.home_id - LEFT JOIN home_image hi ON h.home_id = hi.home_id - LEFT JOIN home_maintenance_fee hf ON h.home_id = hf.home_id - LEFT JOIN home_facility hfl ON d.home_detail_id = hfl.home_detail_id LEFT JOIN facility_item fi ON hfl.item_id = fi.item_id - WHERE h.home_id = #{homeId} - ORDER BY hi.image_id, hf.maintenance_id, fi.item_id + LEFT JOIN home_detail hd ON h.home_id = hd.home_id + WHERE h.home_status = 'AVAILABLE' AND EXISTS(select * from home_identity_verification as hiv where h.home_id = hiv.home_id) + ORDER BY h.created_at DESC + LIMIT #{limit} OFFSET #{offset} - SELECT - fi.item_id AS itemId, - fi.item_name AS itemName, - fc.category_id AS categoryId, - fc.category_type AS categoryType - FROM home_facility hf - JOIN home_detail hd ON hf.home_detail_id = hd.home_detail_id - JOIN facility_item fi ON hf.item_id = fi.item_id - JOIN facility_category fc ON fi.category_id = fc.category_id - WHERE hd.home_id = #{homeId} + h.home_id as homeId, h.user_id as userId, h.addr1, h.addr2, + h.residence_type as residenceType, h.lease_type as leaseType, + h.deposit_price as depositPrice, h.monthly_rent as monthlyRent, + h.maintenance_fee as maintenaceFee, h.home_status as homeStatus, + h.view_cnt as viewCnt, h.like_cnt as likeCnt, h.chat_cnt as chatCnt, + h.room_cnt as roomCnt, h.supply_area as supplyArea, h.exclusive_area as exclusiveArea, + h.created_at as createdAt, h.updated_at as updatedAt, + hd.home_floor as homeFloor, hd.building_total_floors as buildingTotalFloors + FROM home h + LEFT JOIN home_detail hd ON h.home_id = hd.home_id + + h.home_status = 'AVAILABLE' + + AND h.residence_type = #{search.residenceType} + + + AND h.lease_type = #{search.leaseType} + + + AND h.addr1 LIKE CONCAT('%', #{search.addr1}, '%') + + + AND h.deposit_price <= #{search.maxDepositPrice} + + + AND h.monthly_rent <= #{search.maxMonthlyRent} + + + AND h.maintenance_fee <= #{search.maxMaintenanceFee} + + + AND h.supply_area >= #{search.minSupplyArea} + + + AND h.supply_area <= #{search.maxSupplyArea} + + + AND h.room_cnt >= #{search.minRoomCnt} + + + AND h.room_cnt <= #{search.maxRoomCnt} + + + AND h.exclusive_area= #{search.exclusiveArea} + + + AND hd.home_direction = #{search.homeDirection} + + + AND hd.home_floor = #{search.homeFloor} + + + AND hd.home_floor >= #{search.minFloor} + + + AND hd.home_floor <= #{search.maxFloor} + + + AND hd.is_pet = #{search.isPet} + + + AND hd.is_parking_available = #{search.isParking} + + + + + ORDER BY + + h.deposit_price, h.monthly_rent + + + h.view_cnt + + + h.like_cnt + + + h.created_at + + + DESC + + + ASC + + + + ORDER BY h.created_at DESC + + + LIMIT #{search.size} OFFSET #{search.offset} - + SELECT image_id as imageId, home_id as homeId, image_url as imageUrl FROM home_image WHERE home_id = #{homeId} - ORDER BY image_id - + SELECT fi.item_id as itemId, fi.category_id as categoryId, fi.item_name as itemName + FROM home_facility hf + JOIN facility_item fi ON hf.item_id = fi.item_id + WHERE hf.home_detail_id = #{homeDetailId} - - UPDATE home - SET - addr1 = #{addr1}, - addr2 = #{addr2}, - residence_type = #{residenceType}, - lease_type = #{leaseType}, - deposit_price = #{depositPrice}, - monthly_rent = #{monthlyRent}, - maintenance_fee = #{maintenanceFee}, - supply_area = #{supplyArea}, - exclusive_area = #{exclusiveArea}, - room_cnt = #{roomCnt}, - updated_at = NOW() - WHERE home_id = #{homeId} - + - - DELETE FROM home - WHERE home_id = #{homeId} + + + + + + + + DELETE FROM home_like + WHERE user_id = #{userId} AND home_id = #{homeId} - SELECT - h.home_id AS homeId, - h.user_id AS userId, - h.user_name AS userName, - h.addr1, - h.addr2, - h.residence_type AS residenceType, - h.lease_type AS leaseType, - h.deposit_price AS depositPrice, - h.monthly_rent AS monthlyRent, - h.maintenance_fee AS maintenanceFee, - h.supply_area AS supplyArea, - h.exclusive_area AS exclusiveArea, - h.room_cnt AS roomCnt, - h.home_status AS homeStatus, - h.view_cnt AS viewCnt, - h.like_cnt AS likeCnt, - h.chat_cnt AS chatCnt, - h.report_cnt AS reportCnt, - h.created_at AS createdAt, - h.updated_at AS updatedAt, - d.home_detail_id AS homeDetailId, - d.build_date AS buildDate, - d.home_floor AS homeFloor, - d.building_total_floors AS buildingTotalFloors, - d.home_direction AS homeDirection, - d.bathroom_count AS bathroomCount, - d.is_pet AS isPet, - d.is_parking_available AS isParkingAvailable, - hi.image_id AS imageId, - hi.image_url AS imageUrl - FROM home_like hl - JOIN home h ON hl.home_id = h.home_id - LEFT JOIN home_detail d ON h.home_id = d.home_id - LEFT JOIN ( - SELECT home_id, MIN(image_id) AS image_id, MIN(image_url) AS image_url - FROM home_image - GROUP BY home_id - ) hi ON h.home_id = hi.home_id + h.home_id as homeId, h.user_id as userId, h.addr1, h.addr2, + h.residence_type as residenceType, h.lease_type as leaseType, + h.deposit_price as depositPrice, h.monthly_rent as monthlyRent, + h.maintenance_fee as maintenaceFee, h.home_status as homeStatus, + h.view_cnt as viewCnt, h.like_cnt as likeCnt, h.chat_cnt as chatCnt, + h.room_cnt as roomCnt, h.supply_area as supplyArea, h.exclusive_area as exclusiveArea, + h.created_at as createdAt, h.updated_at as updatedAt + FROM home h + JOIN home_like hl ON h.home_id = hl.home_id WHERE hl.user_id = #{userId} ORDER BY hl.liked_at DESC - + + + + + UPDATE home SET + addr1 = #{addr1}, + addr2 = #{addr2}, + residence_type = #{residenceType}, + lease_type = #{leaseType}, + deposit_price = #{depositPrice}, + monthly_rent = #{monthlyRent}, + maintenance_fee = #{maintenaceFee}, + room_cnt = #{roomCnt}, + supply_area = #{supplyArea}, + exclusive_area = #{exclusiveArea}, + updated_at = #{updatedAt} + WHERE home_id = #{homeId} + + + + UPDATE home_detail SET + build_date = #{buildDate}, + home_floor = #{homeFloor}, + building_total_floors = #{buildingTotalFloors}, + home_direction = #{homeDirection}, + bathroom_count = #{bathroomCnt}, + is_pet = #{isPet}, + is_parking_available = #{isParking} + WHERE home_detail_id = #{homeDetailId} + + + UPDATE home - SET view_cnt = view_cnt + 1 + SET home_status = #{status} WHERE home_id = #{homeId} - - DELETE FROM home_like - WHERE user_id = #{userId} AND home_id = #{homeId} + + DELETE FROM home WHERE home_id = #{homeId} - + SELECT COUNT(*) FROM home WHERE home_status = 'AVAILABLE' - + SELECT COUNT(h.home_id) + FROM home h + LEFT JOIN home_detail hd ON h.home_id = hd.home_id + + h.home_status = 'AVAILABLE' + + AND h.residence_type = #{search.residenceType} + + + AND h.lease_type = #{search.leaseType} + + + AND h.addr1 LIKE CONCAT('%', #{search.addr1}, '%') + + + AND h.deposit_price <= #{search.maxDepositPrice} + + + AND h.monthly_rent <= #{search.maxMonthlyRent} + + + AND h.maintenance_fee <= #{search.maxMaintenanceFee} + + + AND h.supply_area >= #{search.minSupplyArea} + + + AND h.supply_area <= #{search.maxSupplyArea} + + + AND h.room_cnt >= #{search.minRoomCnt} + + + AND h.room_cnt <= #{search.maxRoomCnt} + + + AND hd.home_direction = #{search.homeDirection} + + + AND hd.home_floor = #{search.homeFloor} + + + AND hd.home_floor >= #{search.minFloor} + + + AND hd.home_floor <= #{search.maxFloor} + + + AND hd.is_pet = #{search.isPet} + + + AND hd.is_parking_available = #{search.isParking} + + + + + UPDATE home + SET view_cnt = view_cnt + 1 + WHERE home_id = #{homeId} + \ No newline at end of file