Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
fa97a00
✨ feat: code formatting
leeedongjaee Aug 11, 2025
ecb0677
✨ feat: 최종 특약 수정, 삭제 요청 및 수락 구현
leeedongjaee Aug 11, 2025
474fe5e
✨ feat: 최종 특약 삭제 메서드 수정
leeedongjaee Aug 11, 2025
45f4d11
✨ feat: 최종 특약 수정, 삭제 Dto 구현
leeedongjaee Aug 11, 2025
242a53f
✨ feat: 계약 최종 특약 관련 메시지 AI 처리
leeedongjaee Aug 11, 2025
e09d548
Merge branch 'develop' of https://github.com/ITZEEP/backend into refa…
leeedongjaee Aug 11, 2025
8016f89
✨ feat: 매물 등록, 조회 Service 구현
leeedongjaee Aug 11, 2025
129c2d6
✨ feat: 매물 등록, 조회 Controller 구현
leeedongjaee Aug 11, 2025
921f4cf
✨ feat: 매물 등록, 조회 Dto 구현
leeedongjaee Aug 11, 2025
cebbf84
✨ feat: 매물 등록, 조회 VO 구현
leeedongjaee Aug 11, 2025
21bfcc2
✨ feat: 매물 등록, 조회 Mapper 구현
leeedongjaee Aug 11, 2025
4bfab61
✨ feat: 매물 등록/수정 DTO 구조 개선
MeongW Aug 12, 2025
b0986a0
🐛 fix: HomeMapper.xml 중복 쿼리 제거
MeongW Aug 12, 2025
64ce3d0
♻️ refactor: 매물 Controller 인터페이스와 구현부 분리
MeongW Aug 12, 2025
1390870
♻ refactor: 매물 Service 코드 정리 및 로깅 개선
MeongW Aug 12, 2025
9e0c538
♻ refactor: 매물 Service 인터페이스 이미지 업로드 지원 추가
MeongW Aug 12, 2025
f00258b
🐛 fix: ContractChatServiceImpl 코드 포맷팅 정리
MeongW Aug 12, 2025
50b13b1
🐛 fix: HomeResponseDto buildDate 타입 변환 수정
MeongW Aug 12, 2025
18ea69b
♻ refactor: HomeRegisterVO DTO 변환 로직 개선
MeongW Aug 12, 2025
f6cf1b5
✨ feat: ServletConfig에 LocalDate 컨버터 추가
MeongW Aug 12, 2025
f8a0cbd
🌱 chore: merge develop
MeongW Aug 12, 2025
f9d44eb
♻ refactor: home_detail에 area, land_category 추가
seonju21 Aug 12, 2025
2c02e07
♻ refactor: 매물 이미지 등록 api 수정
seonju21 Aug 12, 2025
d406ae9
♻ refactor: 매물 이미지 등록 api 수정
seonju21 Aug 12, 2025
ffbcc78
♻ refactor: 매물 이미지 등록 api 수정
seonju21 Aug 12, 2025
cf04320
♻ refactor: 매물 리스트에 home_floor 추가
seonju21 Aug 13, 2025
7361f17
♻ refactor: 매물 리스트에 home_floor 추가
seonju21 Aug 13, 2025
a5add29
♻ refactor: 매물리스트 페이지네이션
seonju21 Aug 14, 2025
0d6849f
♻ refactor: 매물리스트 페이지네이션
seonju21 Aug 14, 2025
4bc42d9
♻ refactor: 매물리스트 필터링
seonju21 Aug 14, 2025
df28b50
♻ refactor: 매물리스트 필터링
seonju21 Aug 14, 2025
237672e
♻ refactor: 매물리스트 필터링
seonju21 Aug 16, 2025
31cb4c9
🌱 chore: git merge
leeedongjaee Aug 17, 2025
6b2e7c9
✨ feat: 매물 조회 Mapper 메서드 추가
leeedongjaee Aug 17, 2025
1478e07
✨ feat: Home dto 삭제
leeedongjaee Aug 17, 2025
742e2d8
🌱 chore: git merge
leeedongjaee Aug 17, 2025
0979b4f
✨ feat: Home dto 삭제
leeedongjaee Aug 17, 2025
81a97dc
Merge branch 'refactor/homeCreate' of https://github.com/ITZEEP/backe…
seonju21 Aug 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Comment on lines +311 to +316
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

매물 미존재 시 NPE 가능 — 존재 확인 후 비즈니스 예외로 변환하세요

findHomeById가 null을 반환해도 try/catch로 잡히지 않아 바로 NPE가 발생합니다. null 체크 후 INVALID_HOME_ID로 변환하세요. 또한 VO 매핑 안정성과 사용자명 조회를 위해 selectHomeById 사용을 권장합니다.

-        HomeVO home = null;
-        try {
-            home = homeMapper.findHomeById(homeId);
-        } catch (Exception e) {
-            throw new BusinessException(FraudErrorCode.INVALID_HOME_ID);
-        }
+        HomeVO home = homeMapper.selectHomeById(homeId != null ? homeId.intValue() : null);
+        if (home == null) {
+            throw new BusinessException(FraudErrorCode.INVALID_HOME_ID);
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
HomeVO home = null;
try {
home = homeMapper.findHomeById(homeId);
} catch (Exception e) {
throw new BusinessException(FraudErrorCode.INVALID_HOME_ID);
}
HomeVO home = homeMapper.selectHomeById(homeId != null ? homeId.intValue() : null);
if (home == null) {
throw new BusinessException(FraudErrorCode.INVALID_HOME_ID);
}
🤖 Prompt for AI Agents
In src/main/java/org/scoula/domain/fraud/service/AiFraudAnalyzerService.java
around lines 311 to 316, findHomeById may return null causing an NPE that the
current try/catch won’t catch; change the flow to call the mapper method that
returns a safe DTO (use selectHomeById as recommended), then check the returned
object for null and if null throw new
BusinessException(FraudErrorCode.INVALID_HOME_ID); ensure you perform VO mapping
only after null-check and use the selectHomeById result for stable field access
(including owner/username lookup) to avoid NPEs.

request.setAddress(home.getAddr1() + " " + home.getAddr2());
request.setPropertyPrice(home.getDepositPrice());
request.setMonthlyRent(home.getMonthlyRent() != null ? home.getMonthlyRent() : 0);
Expand Down
218 changes: 76 additions & 142 deletions src/main/java/org/scoula/domain/home/controller/HomeController.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<ApiResponse<Long>> 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<ApiResponse<Void>> 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<ApiResponse<Integer>> createHome(
@Valid @ModelAttribute HomeCreateRequestDto requestDto, Authentication authentication);

@ApiOperation(value = "매물 상세 조회", notes = "매물 ID로 상세 정보를 조회합니다.")
@GetMapping("/{homeId}")
public ResponseEntity<ApiResponse<HomeResponseDto>> 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<PageResponse<HomeResponseDto>> 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<HomeResponseDto> response =
// homeService.getMyHomeList(userDetails.getUserId(), pageRequest);
// return ResponseEntity.ok(response);
// }

@ApiOperation(value = "매물 삭제", notes = "매물 ID에 해당하는 매물을 삭제합니다.")
@DeleteMapping("/{homeId}")
public ResponseEntity<ApiResponse<Void>> deleteHome(
@AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable Long homeId) {
homeService.deleteHome(userDetails.getUserId(), homeId);
return ResponseEntity.ok(ApiResponse.success());
}
ResponseEntity<ApiResponse<HomeResponseDTO>> getHome(
@ApiParam(value = "매물 ID", required = true) @PathVariable Integer homeId);

@ApiOperation(value = "모든 매물 검색", notes = "전체 매물을 페이징하여 조회합니다.")
@ApiOperation(value = "매물 목록 조회", notes = "페이징된 매물 목록을 조회합니다.")
@GetMapping
public ResponseEntity<PageResponse<HomeResponseDto>> getAllHomes(
@ApiParam(value = "페이지 번호", defaultValue = "1") @RequestParam(defaultValue = "1")
String pageStr,
@ApiParam(value = "페이지 크기", defaultValue = "10") @RequestParam(defaultValue = "10")
String sizeStr) {
ResponseEntity<PageResponse<HomeResponseDTO>> 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<PageResponse<HomeResponseDTO>> searchHomes(
@ApiParam(value = "검색 조건") @ModelAttribute HomeSearchDTO searchDTO);

@ApiOperation(
value = "매물 수정",
notes = "기존 매물 정보를 수정합니다. 이미지 파일을 추가로 업로드하거나 기존 이미지를 삭제할 수 있습니다.")
@PutMapping(value = "/{homeId}", consumes = "multipart/form-data")
ResponseEntity<ApiResponse<Void>> updateHome(
@ApiParam(value = "매물 ID", required = true, example = "123") @PathVariable
Integer homeId,
@Valid @ModelAttribute HomeUpdateRequestDto updateDto,
@ApiParam(value = "새로 추가할 이미지 파일들") @RequestParam(value = "newImages", required = false)
List<MultipartFile> newImages,
Authentication authentication);

@ApiOperation(value = "매물 삭제", notes = "매물을 삭제합니다.")
@DeleteMapping("/{homeId}")
ResponseEntity<ApiResponse<Void>> deleteHome(
@ApiParam(value = "매물 ID", required = true) @PathVariable Integer homeId,
Authentication authentication);

@ApiOperation(value = "매물 상태 변경", notes = "매물의 상태를 변경합니다.")
@PatchMapping("/{homeId}/status")
ResponseEntity<ApiResponse<Void>> updateHomeStatus(
@ApiParam(value = "매물 ID", required = true) @PathVariable Integer homeId,
@ApiParam(value = "변경할 상태", required = true) @RequestParam String status,
Authentication authentication);

@ApiOperation(value = "내 매물 목록 조회", notes = "로그인한 사용자의 매물 목록을 조회합니다.")
@GetMapping("/my")
ResponseEntity<ApiResponse<List<HomeResponseDTO>>> getMyHomes(Authentication authentication);

@ApiOperation(value = "매물 찜하기/해제", notes = "매물을 찜하거나 찜을 해제합니다.")
@PostMapping("/{homeId}/like")
ResponseEntity<ApiResponse<Void>> 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<ApiResponse<List<HomeResponseDTO>>> getHomeLikes(Authentication authentication);

PageRequest pageRequest = PageRequest.builder().page(page).size(size).build();
PageResponse<HomeResponseDto> response = homeService.getHomeList(pageRequest);
return ResponseEntity.ok(response);
}
@ApiOperation(value = "편의시설 카테고리 조회", notes = "편의시설 카테고리 목록을 조회합니다.")
@GetMapping("/facilities/categories")
ResponseEntity<ApiResponse<List<FacilityCategory>>> getFacilityCategories();

@ApiOperation(value = "매물 찜하기", notes = "해당 매물을 찜합니다.")
@PostMapping("/{homeId}/like")
public ResponseEntity<ApiResponse<Void>> 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<ApiResponse<Void>> unlikeHome(
@AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable Long homeId) {
homeService.removeLike(userDetails.getUserId(), homeId);
return ResponseEntity.ok(ApiResponse.success());
}

@ApiOperation(value = "찜한 매물 목록 조회", notes = "내가 찜한 매물 목록을 조회합니다.")
@GetMapping("/likes")
public ResponseEntity<ApiResponse<List<HomeResponseDto>>> getLikedHomes(
@AuthenticationPrincipal CustomUserDetails userDetails) {
List<HomeResponseDto> likedHomes = homeService.getLikedHomes(userDetails.getUserId());
return ResponseEntity.ok(ApiResponse.success(likedHomes));
}

@ApiOperation(value = "조회수 증가", notes = "해당 매물의 조회수를 1 증가시킵니다.")
@PostMapping("/{homeId}/view")
public ResponseEntity<ApiResponse<Void>> increaseViewCount(@PathVariable Long homeId) {
homeService.increaseViewCount(homeId);
return ResponseEntity.ok(ApiResponse.success());
}

@ApiOperation(value = "매물 신고", notes = "해당 매물을 신고합니다.")
@PostMapping("/report")
public ResponseEntity<ApiResponse<Void>> 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<ApiResponse<List<FacilityItem>>> getFacilityItems(
@ApiParam(value = "카테고리 ID", required = true) @PathVariable Integer categoryId);
}
Loading
Loading