diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index af34830b..769fcc26 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,11 +1,8 @@ name: CD for main on: - workflow_run: - workflows: ["CI for main"] # CI 워크플로우 이름과 정확히 일치해야 함 + push: branches: [main] - types: - - completed workflow_dispatch: inputs: branch: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7356a222..ad1d3206 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,8 +3,6 @@ name: CI for main on: pull_request: branches: [ main ] - push: - branches: [ main ] env: DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} diff --git a/src/main/java/com/example/smartair/controller/adminController/AdminController.java b/src/main/java/com/example/smartair/controller/adminController/AdminController.java index e0b0cae7..d80b62ae 100644 --- a/src/main/java/com/example/smartair/controller/adminController/AdminController.java +++ b/src/main/java/com/example/smartair/controller/adminController/AdminController.java @@ -92,4 +92,14 @@ public ResponseEntity> getAllSensors( Page sensors = adminDeviceService.getAllSensorsDetailForAdmin(pageable); return ResponseEntity.ok(sensors); } + + // 센서 활성 상태 지정 + @Override + @PatchMapping("/sensors/{serialNumber}/active") + public ResponseEntity setSensorActiveStatus( + @PathVariable String serialNumber, + @RequestParam boolean active) { + adminDeviceService.setSensorActiveStatus(serialNumber, active); + return ResponseEntity.ok("센서 활성 상태가 성공적으로 변경되었습니다."); + } } \ No newline at end of file diff --git a/src/main/java/com/example/smartair/controller/adminController/AdminControllerDocs.java b/src/main/java/com/example/smartair/controller/adminController/AdminControllerDocs.java index d89ac0c1..7b5281d6 100644 --- a/src/main/java/com/example/smartair/controller/adminController/AdminControllerDocs.java +++ b/src/main/java/com/example/smartair/controller/adminController/AdminControllerDocs.java @@ -22,7 +22,7 @@ @Tag(name = "Admin API", description = "관리자 기능 관련 API 명세") public interface AdminControllerDocs { - @Operation(summary = "전체 방 현황 조회 (관리자)", description = "시스템의 모든 방 목록을 페이징하여 조회합니다. 관리자 권한이 필요합니다.") + @Operation(summary = "전체 방 현황 조회 (관리자)", description = "시스템의 모든 방 목록을 페이징하여 조회합니다.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "전체 방 목록 조회 성공", content = @Content(schema = @Schema(implementation = Page.class))), // 실제론 Page 형태 @@ -35,7 +35,7 @@ ResponseEntity> getAllRooms( " \"sort\": \"id,desc\")", schema = @Schema(implementation = Pageable.class)) Pageable pageable); - @Operation(summary = "특정 방 상세 조회 (관리자)", description = "특정 방의 상세 정보를 조회합니다. 관리자 권한이 필요합니다.") + @Operation(summary = "특정 방 상세 조회 (관리자)", description = "특정 방의 상세 정보를 조회합니다.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "방 상세 정보 조회 성공", content = @Content(schema = @Schema(implementation = RoomDetailResponseDto.class))), @@ -47,7 +47,7 @@ ResponseEntity getRoomDetailForAdmin( @Parameter(name = "roomId", description = "조회할 방의 ID", required = true, in = ParameterIn.PATH) @PathVariable Long roomId); - @Operation(summary = "방 정보 수정 (관리자)", description = "특정 방의 정보를 수정합니다. 관리자 권한이 필요합니다.", + @Operation(summary = "방 정보 수정 (관리자)", description = "특정 방의 정보를 수정합니다.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "방 수정 요청 정보", required = true, content = @Content(schema = @Schema(implementation = CreateRoomRequestDto.class)))) @ApiResponses(value = { @@ -63,7 +63,7 @@ ResponseEntity updateRoomForAdmin( @PathVariable Long roomId, @RequestBody CreateRoomRequestDto requestDto); - @Operation(summary = "방 삭제 (관리자)", description = "특정 방을 삭제합니다. 관리자 권한이 필요합니다.") + @Operation(summary = "방 삭제 (관리자)", description = "특정 방을 삭제합니다.") @ApiResponses(value = { @ApiResponse(responseCode = "204", description = "방 삭제 성공 (No Content)"), @ApiResponse(responseCode = "401", description = "인증되지 않은 사용자"), @@ -74,7 +74,7 @@ ResponseEntity deleteRoomForAdmin( @Parameter(name = "roomId", description = "삭제할 방의 ID", required = true, in = ParameterIn.PATH) @PathVariable Long roomId); - @Operation(summary = "전체 사용자 현황 조회 (관리자)", description = "시스템의 모든 사용자 목록을 페이징하여 조회합니다. 관리자 권한이 필요합니다.") + @Operation(summary = "전체 사용자 현황 조회 (관리자)", description = "시스템의 모든 사용자 목록을 페이징하여 조회합니다.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "전체 사용자 목록 조회 성공", content = @Content(schema = @Schema(implementation = Page.class))), // 실제론 Page 형태 @@ -85,7 +85,7 @@ ResponseEntity> getAllUsers( @Parameter(description = "페이지네이션 정보 (예 : \"page\": 0,\n" + " \"size\": 10,\n" + " \"sort\": \"id,desc\")", schema = @Schema(implementation = Pageable.class)) Pageable pageable); - @Operation(summary = "전체 기기 현황 조회 (관리자)", description = "시스템의 모든 기기 목록을 페이징하여 조회합니다. 관리자 권한이 필요합니다.") + @Operation(summary = "전체 기기 현황 조회 (관리자)", description = "시스템의 모든 기기 목록을 페이징하여 조회합니다.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "전체 기기 목록 조회 성공", content = @Content(schema = @Schema(implementation = Page.class))), // 실제론 Page 형태 @@ -97,7 +97,7 @@ ResponseEntity> getAllDevices( " \"size\": 10,\n" + " \"sort\": \"id,desc\")", schema = @Schema(implementation = Pageable.class)) Pageable pageable); - @Operation(summary = "전체 센서 현황 조회 (관리자)", description = "시스템의 모든 센서 목록을 페이징하여 조회합니다. 관리자 권한이 필요합니다.") + @Operation(summary = "전체 센서 현황 조회 (관리자)", description = "시스템의 모든 센서 목록을 페이징하여 조회합니다.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "전체 센서 목록 조회 성공", content = @Content(schema = @Schema(implementation = Page.class))), // 실제론 Page 형태 @@ -108,4 +108,18 @@ ResponseEntity> getAllSensors( @Parameter(description = "페이지네이션 정보 (예 : \"page\": 0,\n" + " \"size\": 10,\n" + " \"sort\": \"id,desc\")", schema = @Schema(implementation = Pageable.class)) Pageable pageable); + + @Operation(summary = "센서 활성 상태 지정 (관리자)", description = "특정 센서의 활성 상태를 지정합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "센서 활성 상태 지정 성공", + content = @Content(schema = @Schema(implementation = SensorDetailDto.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청"), + @ApiResponse(responseCode = "401", description = "인증되지 않은 사용자"), + @ApiResponse(responseCode = "403", description = "권한 없음"), + @ApiResponse(responseCode = "404", description = "센서를 찾을 수 없음") + }) + ResponseEntity setSensorActiveStatus( + @Parameter(name = "serialNumber", description = "활성 상태를 지정할 센서의 일련번호", required = true, in = ParameterIn.PATH) + @PathVariable String serialNumber, + @RequestBody boolean isActive); } \ No newline at end of file diff --git a/src/main/java/com/example/smartair/controller/roomController/RoomControllerDocs.java b/src/main/java/com/example/smartair/controller/roomController/RoomControllerDocs.java index 8c3191ce..c3be5055 100644 --- a/src/main/java/com/example/smartair/controller/roomController/RoomControllerDocs.java +++ b/src/main/java/com/example/smartair/controller/roomController/RoomControllerDocs.java @@ -25,7 +25,7 @@ @Tag(name = "Room API", description = "방 관련 API 명세") public interface RoomControllerDocs { - @Operation(summary = "방 생성", description = "새로운 방을 생성합니다. 시스템 관리자(ADMIN) 또는 매니저(MANAGER) 권한의 사용자만 생성 가능하며, 생성한 사용자가 방의 소유자(MANAGER)가 됩니다.", + @Operation(summary = "방 생성", description = "새로운 방을 생성합니다. 생성한 사용자가 방의 소유자(MANAGER)가 됩니다.", requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "방 생성 요청 정보", required = true, content = @Content(schema = @Schema(implementation = CreateRoomRequestDto.class)))) @ApiResponses(value = { diff --git a/src/main/java/com/example/smartair/controller/sensorContoller/SensorController.java b/src/main/java/com/example/smartair/controller/sensorContoller/SensorController.java index 595837c9..faac289d 100644 --- a/src/main/java/com/example/smartair/controller/sensorContoller/SensorController.java +++ b/src/main/java/com/example/smartair/controller/sensorContoller/SensorController.java @@ -10,6 +10,7 @@ import com.example.smartair.exception.CustomException; import com.example.smartair.exception.ErrorCode; import com.example.smartair.service.sensorService.SensorService; +import io.swagger.v3.oas.annotations.Parameter; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -57,15 +58,15 @@ public ResponseEntity addSensorToRoom(@AuthenticationPrin @Override @DeleteMapping("/sensor") public ResponseEntity deleteSensor(@AuthenticationPrincipal CustomUserDetails userDetails, - @RequestBody SensorRequestDto.deleteSensorDto deviceDto) throws Exception { + @RequestParam String serialNumber) throws Exception { if(userDetails == null){ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } User user = userDetails.getUser(); - sensorService.deleteSensor(user, deviceDto); + sensorService.deleteSensor(user, serialNumber); - return ResponseEntity.noContent().build(); + return ResponseEntity.ok("센서가 성공적으로 삭제되었습니다."); } @Override @@ -79,21 +80,22 @@ public ResponseEntity getSensorStatus(@AuthenticationPrincipal CustomUserDeta Boolean status = sensorService.getSensorStatus(deviceSerialNumber); - return ResponseEntity.ok("device"+ deviceSerialNumber + " running state: " + status); + return ResponseEntity.ok("sensor"+ deviceSerialNumber + " running state: " + status); } @Override @DeleteMapping("/sensor/room") public ResponseEntity unregisterSensorFromRoom( @AuthenticationPrincipal CustomUserDetails userDetails, - @RequestBody SensorRequestDto.unregisterSensorFromRoomDto request) throws Exception { + @RequestParam String serialNumber, + @RequestParam Long roomId) throws Exception { if(userDetails == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } User user = userDetails.getUser(); - sensorService.unregisterSensorFromRoom(user, request); - return ResponseEntity.noContent().build(); + sensorService.unregisterSensorFromRoom(user, serialNumber, roomId); + return ResponseEntity.ok("센서가 방에서 성공적으로 등록 해제되었습니다."); } @Override @@ -131,5 +133,20 @@ public ResponseEntity> getUserSensors( return ResponseEntity.ok(sensors); } + @Override + @GetMapping("/sensor/find/{serialNumber}") + public ResponseEntity getSensorBySerialNumber( + @AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable String serialNumber) { + if (userDetails == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + User user = userDetails.getUser(); + + SensorResponseDto sensorDto = sensorService.getSensorBySerialNumber(serialNumber); + + return ResponseEntity.ok(sensorDto); + } + } diff --git a/src/main/java/com/example/smartair/controller/sensorContoller/SensorControllerDocs.java b/src/main/java/com/example/smartair/controller/sensorContoller/SensorControllerDocs.java index 81082085..b0c98f4a 100644 --- a/src/main/java/com/example/smartair/controller/sensorContoller/SensorControllerDocs.java +++ b/src/main/java/com/example/smartair/controller/sensorContoller/SensorControllerDocs.java @@ -84,16 +84,13 @@ ResponseEntity addSensorToRoom(@AuthenticationPrincipal C ## 센서 삭제 사용자가 등록한 센서를 삭제합니다. + 관련된 SensorAirQualityData, PredictedAirQualityData, Fineparticlesdata, DailyReport, WeeklyReport, HourlySnapshot도 삭제됩니다. --- **요청 정보** - 인증 정보는 `@AuthenticationPrincipal` 을 통해 자동 주입됩니다. - - **요청 본문 (`RequestBody`)** - - `deviceDto` (Object): 삭제할 센서 정보 - - `serialNumber` (Long): 센서의 일련번호 - - `roomId` (Long): 삭제할 센서가 등록된 방의 ID + - 'serialNumber' (String): 삭제할 센서의 일련번호 --- @@ -103,7 +100,7 @@ ResponseEntity addSensorToRoom(@AuthenticationPrincipal C """ ) ResponseEntity deleteSensor(@AuthenticationPrincipal CustomUserDetails userDetails, - @RequestBody SensorRequestDto.deleteSensorDto deviceDto) throws Exception; + @RequestParam String serialNumber) throws Exception; @Operation( summary = "센서 상태 조회", @@ -140,7 +137,8 @@ ResponseEntity getSensorStatus(@AuthenticationPrincipal CustomUserDetails use }) ResponseEntity unregisterSensorFromRoom( @Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails userDetails, - @RequestBody SensorRequestDto.unregisterSensorFromRoomDto request) throws Exception; + @RequestParam String serialNumber, + @RequestParam Long roomId) throws Exception; @GetMapping("/sensor/{sensorId}") @Operation(summary = "센서 ID로 센서 정보 조회", description = "센서 ID를 기반으로 센서 정보를 조회합니다.") @@ -161,4 +159,15 @@ ResponseEntity getSensorById( @ApiResponse(responseCode = "401", description = "인증 실패") }) ResponseEntity> getUserSensors(@AuthenticationPrincipal CustomUserDetails userDetails); + + @GetMapping("/sensor/find/{serialNumber}") + @Operation(summary = "일련번호로 센서 정보 조회", description = "센서의 일련번호를 기반으로 센서 정보를 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "센서 정보 조회 성공"), + @ApiResponse(responseCode = "401", description = "인증 실패"), + @ApiResponse(responseCode = "404", description = "해당 일련번호의 센서를 찾을 수 없음") + }) + ResponseEntity getSensorBySerialNumber( + @AuthenticationPrincipal CustomUserDetails userDetails, + @Parameter(description = "센서 일련번호", required = true) String serialNumber); } \ No newline at end of file diff --git a/src/main/java/com/example/smartair/dto/sensorDto/SensorRequestDto.java b/src/main/java/com/example/smartair/dto/sensorDto/SensorRequestDto.java index a1699a12..aa1d62d0 100644 --- a/src/main/java/com/example/smartair/dto/sensorDto/SensorRequestDto.java +++ b/src/main/java/com/example/smartair/dto/sensorDto/SensorRequestDto.java @@ -14,18 +14,9 @@ public record setSensorDto( String name ){} - public record deleteSensorDto( - String serialNumber, - Long roomId - ){} - public record addSensorToRoomDto( String serialNumber, Long roomId ){} - public record unregisterSensorFromRoomDto( - String serialNumber, - Long roomId - ){} } diff --git a/src/main/java/com/example/smartair/repository/airQualityRepository/airQualityDataRepository/AirQualityDataRepository.java b/src/main/java/com/example/smartair/repository/airQualityRepository/airQualityDataRepository/SensorAirQualityDataRepository.java similarity index 61% rename from src/main/java/com/example/smartair/repository/airQualityRepository/airQualityDataRepository/AirQualityDataRepository.java rename to src/main/java/com/example/smartair/repository/airQualityRepository/airQualityDataRepository/SensorAirQualityDataRepository.java index 9d552af6..6c0a0d2c 100644 --- a/src/main/java/com/example/smartair/repository/airQualityRepository/airQualityDataRepository/AirQualityDataRepository.java +++ b/src/main/java/com/example/smartair/repository/airQualityRepository/airQualityDataRepository/SensorAirQualityDataRepository.java @@ -3,7 +3,6 @@ import com.example.smartair.entity.airData.airQualityData.SensorAirQualityData; import com.example.smartair.entity.sensor.Sensor; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; @@ -11,21 +10,17 @@ import java.util.Optional; @Repository -public interface AirQualityDataRepository extends JpaRepository { - List findBySensorAndCreatedAtBetweenOrderByCreatedAtAsc(Sensor sensor, LocalDateTime snapshotHour, LocalDateTime nextHour); +public interface SensorAirQualityDataRepository extends JpaRepository { + Optional findTopBySensorIdOrderByCreatedAtDesc(Long sensorId); - Optional findBySensor_Id(Long sensor_id); + List findAllBySensor_Id(Long sensorId); - Optional findTopBySensor_SerialNumberOrderByCreatedAtDesc(String serialNumber); + List findBySensorAndCreatedAtBetweenOrderByCreatedAtAsc( + Sensor sensor, LocalDateTime startTime, LocalDateTime endTime); List findByCreatedAtBetween(LocalDateTime startTime, LocalDateTime endTime); - int deleteByCreatedAtBefore(LocalDateTime dateTime); - - Optional findFirstBySensorAndCreatedAtAfterOrderByCreatedAtDesc(Sensor sensor, LocalDateTime createdAt); - - Optional findTopBySensorIdOrderByCreatedAtDesc(Long sensorId); - - + Optional findTopBySensor_SerialNumberOrderByCreatedAtDesc(String serialNumber); + int deleteByCreatedAtBefore(LocalDateTime createdAt); } diff --git a/src/main/java/com/example/smartair/repository/airQualityRepository/airQualityScoreRepository/SensorAirQualityScoreRepository.java b/src/main/java/com/example/smartair/repository/airQualityRepository/airQualityScoreRepository/SensorAirQualityScoreRepository.java index d4a67351..2c79101c 100644 --- a/src/main/java/com/example/smartair/repository/airQualityRepository/airQualityScoreRepository/SensorAirQualityScoreRepository.java +++ b/src/main/java/com/example/smartair/repository/airQualityRepository/airQualityScoreRepository/SensorAirQualityScoreRepository.java @@ -39,4 +39,6 @@ List findScoresBySensorSerialNumberAndTimeRange( @Param("startTime") LocalDateTime startTime, @Param("endTime") LocalDateTime endTime); + List findAllBySensorAirQualityDataId(Long sensorAirQualityDataId); + } \ No newline at end of file diff --git a/src/main/java/com/example/smartair/repository/airQualityRepository/airQualitySnapshotRepository/HourlyDeviceAirQualitySnapshotRepository.java b/src/main/java/com/example/smartair/repository/airQualityRepository/airQualitySnapshotRepository/HourlySensorAirQualitySnapshotRepository.java similarity index 91% rename from src/main/java/com/example/smartair/repository/airQualityRepository/airQualitySnapshotRepository/HourlyDeviceAirQualitySnapshotRepository.java rename to src/main/java/com/example/smartair/repository/airQualityRepository/airQualitySnapshotRepository/HourlySensorAirQualitySnapshotRepository.java index 8f315be7..500956b8 100644 --- a/src/main/java/com/example/smartair/repository/airQualityRepository/airQualitySnapshotRepository/HourlyDeviceAirQualitySnapshotRepository.java +++ b/src/main/java/com/example/smartair/repository/airQualityRepository/airQualitySnapshotRepository/HourlySensorAirQualitySnapshotRepository.java @@ -13,7 +13,7 @@ import java.util.Set; @Repository -public interface HourlyDeviceAirQualitySnapshotRepository extends JpaRepository { +public interface HourlySensorAirQualitySnapshotRepository extends JpaRepository { Optional findBySensorAndSnapshotHour(Sensor sensor, LocalDateTime snapshotHour); @Query("SELECT DISTINCT h.sensor FROM HourlySensorAirQualitySnapshot h " + @@ -30,4 +30,6 @@ Optional findBySensorAndHourRange( @Param("sensor") Sensor sensor, @Param("startHour") LocalDateTime startHour, @Param("endHour") LocalDateTime endHour); + + List findAllBySensor_Id(Long sensorId); } diff --git a/src/main/java/com/example/smartair/repository/airQualityRepository/predictedAirQualityRepository/PredictedAirQualityRepository.java b/src/main/java/com/example/smartair/repository/airQualityRepository/predictedAirQualityRepository/PredictedAirQualityRepository.java index bac2ea0f..eca16b55 100644 --- a/src/main/java/com/example/smartair/repository/airQualityRepository/predictedAirQualityRepository/PredictedAirQualityRepository.java +++ b/src/main/java/com/example/smartair/repository/airQualityRepository/predictedAirQualityRepository/PredictedAirQualityRepository.java @@ -18,6 +18,6 @@ public interface PredictedAirQualityRepository extends JpaRepository findBySensorSerialNumberOrderByTimestamp(String sensorSerialNumber); - List findBySensorSerialNumber(String sensorSerialNumber); + List findAllBySensorSerialNumber(String sensorSerialNumber); Optional findBySensorSerialNumberAndTimestamp(String sensorSerialNumber, LocalDateTime timestamp); } diff --git a/src/main/java/com/example/smartair/repository/roomSensorRepository/RoomSensorRepository.java b/src/main/java/com/example/smartair/repository/roomSensorRepository/RoomSensorRepository.java index 87212a87..0b854753 100644 --- a/src/main/java/com/example/smartair/repository/roomSensorRepository/RoomSensorRepository.java +++ b/src/main/java/com/example/smartair/repository/roomSensorRepository/RoomSensorRepository.java @@ -26,7 +26,7 @@ public interface RoomSensorRepository extends JpaRepository { Optional findBySensor_Id(Long sensorId); - boolean existsBySensorId(Long sensor_id); + List findAllBySensor_Id(Long sensorId); boolean existsBySensor_SerialNumberAndRoom_Id(String serialNumber, Long roomId); } \ No newline at end of file diff --git a/src/main/java/com/example/smartair/service/adminService/AdminDeviceService.java b/src/main/java/com/example/smartair/service/adminService/AdminDeviceService.java index d9c3b2fb..0d8ceb8e 100644 --- a/src/main/java/com/example/smartair/service/adminService/AdminDeviceService.java +++ b/src/main/java/com/example/smartair/service/adminService/AdminDeviceService.java @@ -2,8 +2,11 @@ import com.example.smartair.dto.deviceDto.DeviceDetailDto; import com.example.smartair.dto.sensorDto.SensorDetailDto; +import com.example.smartair.dto.sensorDto.SensorResponseDto; import com.example.smartair.entity.device.Device; import com.example.smartair.entity.sensor.Sensor; +import com.example.smartair.exception.CustomException; +import com.example.smartair.exception.ErrorCode; import com.example.smartair.repository.deviceRepository.DeviceRepository; import com.example.smartair.repository.sensorRepository.SensorRepository; import com.example.smartair.repository.roomRepository.RoomRepository; @@ -16,7 +19,6 @@ @Service @RequiredArgsConstructor -@Transactional(readOnly = true) public class AdminDeviceService { private final DeviceRepository deviceRepository; @@ -57,4 +59,13 @@ public Page getAllSensorsDetailForAdmin(Pageable pageable) { // SensorDetailDto.from() 이 Device 및 Room 정보를 잘 가져온다고 가정 return sensorPage.map(SensorDetailDto::from); } + + public void setSensorActiveStatus(String serialNumber, boolean active) { + Sensor sensor = sensorRepository.findBySerialNumber(serialNumber) + .orElseThrow(()-> new CustomException(ErrorCode.SENSOR_NOT_FOUND)); + sensor.setRunningStatus(active); + + // 상태 변경 후 저장 + sensorRepository.save(sensor); + } } \ No newline at end of file diff --git a/src/main/java/com/example/smartair/service/airQualityService/AirQualityDataService.java b/src/main/java/com/example/smartair/service/airQualityService/AirQualityDataService.java index fb8705f8..c6dd9263 100644 --- a/src/main/java/com/example/smartair/service/airQualityService/AirQualityDataService.java +++ b/src/main/java/com/example/smartair/service/airQualityService/AirQualityDataService.java @@ -10,7 +10,7 @@ import com.example.smartair.exception.CustomException; import com.example.smartair.exception.ErrorCode; import com.example.smartair.infrastructure.RecentAirQualityDataCache; -import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.AirQualityDataRepository; +import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.SensorAirQualityDataRepository; import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.FineParticlesDataPt2Repository; import com.example.smartair.repository.sensorRepository.SensorRepository; import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.FineParticlesDataRepository; @@ -29,18 +29,18 @@ public class AirQualityDataService { private final SensorRepository sensorRepository; private final RoomSensorRepository roomSensorRepository; - private final AirQualityDataRepository airQualityDataRepository; private final FineParticlesDataRepository fineParticlesDataRepository; private final RecentAirQualityDataCache recentAirQualityDataCache; private final FineParticlesDataPt2Repository fineParticlesDataPt2Repository; private final AirQualityScoreService airQualityScoreService; + private final SensorAirQualityDataRepository sensorAirQualityDataRepository; @Transactional - public AirQualityPayloadDto processAirQualityData(Long deviceId, Long roomId, AirQualityPayloadDto dto) { + public AirQualityPayloadDto processAirQualityData(Long sensorId, Long roomId, AirQualityPayloadDto dto) { try { - // 1. Device 추출 - Sensor sensor = sensorRepository.findById(deviceId) - .orElseThrow(() -> new CustomException(ErrorCode.SENSOR_NOT_FOUND, "sensor Id: " + deviceId)); + // 1. Sensor 추출 + Sensor sensor = sensorRepository.findById(sensorId) + .orElseThrow(() -> new CustomException(ErrorCode.SENSOR_NOT_FOUND, "sensor Id: " + sensorId)); // 2. Room 정보는 옵셔널하게 처리 Room room = null; @@ -49,7 +49,7 @@ public AirQualityPayloadDto processAirQualityData(Long deviceId, Long roomId, Ai .map(RoomSensor::getRoom) .orElse(null); if (room != null) { - log.info("센서 ID {}가 방 ID {}에 매핑되어 있습니다.", deviceId, room.getId()); + log.info("센서 ID {}가 방 ID {}에 매핑되어 있습니다.", sensorId, room.getId()); } } @@ -75,7 +75,7 @@ public AirQualityPayloadDto processAirQualityData(Long deviceId, Long roomId, Ai .build(); // 5. AirQualityData 저장 - SensorAirQualityData savedAirQualityData = airQualityDataRepository.save(airQualityData); + SensorAirQualityData savedAirQualityData = sensorAirQualityDataRepository.save(airQualityData); // 즉시 점수 계산 airQualityScoreService.calculateAndSaveDeviceScore(savedAirQualityData); @@ -103,12 +103,12 @@ public SensorAirQualityData getSavedAirQualityData(Long sensorId) { // 1. 캐시에서 먼저 조회 Optional cachedData = recentAirQualityDataCache.get(sensorId); if (cachedData.isPresent()) { - log.debug("Cache hit for device ID: {}", sensorId); + log.debug("Cache hit for sensor ID: {}", sensorId); return cachedData.get(); } // 2. 캐시에 없는 경우 DB에서 최신 데이터 조회 - SensorAirQualityData latestData = airQualityDataRepository.findTopBySensorIdOrderByCreatedAtDesc(sensorId) + SensorAirQualityData latestData = sensorAirQualityDataRepository.findTopBySensorIdOrderByCreatedAtDesc(sensorId) .orElseThrow(() -> { log.warn("Device ID {}의 최근 데이터를 찾을 수 없습니다.", sensorId); return new CustomException(ErrorCode.SENSOR_AIR_DATA_NOT_FOUND, "Sensor ID {}" + sensorId); @@ -116,7 +116,7 @@ public SensorAirQualityData getSavedAirQualityData(Long sensorId) { // 3. 조회된 데이터를 캐시에 저장 updateCache(sensorId, latestData); - log.debug("Latest data cached for device ID: {}", sensorId); + log.debug("Latest data cached for sensor ID: {}", sensorId); return latestData; } catch (CustomException ce) { diff --git a/src/main/java/com/example/smartair/service/airQualityService/AirQualityQueryService.java b/src/main/java/com/example/smartair/service/airQualityService/AirQualityQueryService.java index 9d79b47d..cce63528 100644 --- a/src/main/java/com/example/smartair/service/airQualityService/AirQualityQueryService.java +++ b/src/main/java/com/example/smartair/service/airQualityService/AirQualityQueryService.java @@ -2,24 +2,16 @@ import com.example.smartair.dto.airQualityScoreDto.AverageScoreDto; import com.example.smartair.dto.airQualityScoreDto.SensorAirQualityScoreDto; -import com.example.smartair.dto.airQualityScoreDto.PlaceAirQualityScoreDto; import com.example.smartair.dto.airQualityScoreDto.RoomAirQualityScoreDto; -import com.example.smartair.entity.airData.airQualityData.SensorAirQualityData; import com.example.smartair.entity.airScore.airQualityScore.SensorAirQualityScore; -import com.example.smartair.entity.airScore.airQualityScore.PlaceAirQualityScore; import com.example.smartair.entity.airScore.airQualityScore.RoomAirQualityScore; import com.example.smartair.entity.sensor.Sensor; -import com.example.smartair.entity.place.Place; import com.example.smartair.entity.room.Room; import com.example.smartair.exception.CustomException; import com.example.smartair.exception.ErrorCode; -import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.AirQualityDataRepository; import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.SensorAirQualityScoreRepository; -import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.PlaceAirQualityScoreRepository; import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.RoomAirQualityScoreRepository; -import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.SensorAirQualityScoreRepository; import com.example.smartair.repository.sensorRepository.SensorRepository; -import com.example.smartair.repository.placeRepository.PlaceRepository; import com.example.smartair.repository.roomRepository.RoomRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -37,11 +29,10 @@ @Transactional(readOnly = true) public class AirQualityQueryService { //공기질 점수 조회 - private final SensorAirQualityScoreRepository sensorAirQualityScoreRepository; private final RoomAirQualityScoreRepository roomAirQualityScoreRepository; private final SensorRepository sensorRepository; private final RoomRepository roomRepository; - private final AirQualityDataRepository airQualityDataRepository; + private final SensorAirQualityScoreRepository sensorAirQualityScoreRepository; private final AirQualityScoreService airQualityScoreService; /** diff --git a/src/main/java/com/example/smartair/service/airQualityService/AirQualityScoreService.java b/src/main/java/com/example/smartair/service/airQualityService/AirQualityScoreService.java index 2782b57d..89261000 100644 --- a/src/main/java/com/example/smartair/service/airQualityService/AirQualityScoreService.java +++ b/src/main/java/com/example/smartair/service/airQualityService/AirQualityScoreService.java @@ -9,30 +9,24 @@ import com.example.smartair.entity.roomSensor.RoomSensor; import com.example.smartair.exception.CustomException; import com.example.smartair.exception.ErrorCode; -import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.AirQualityDataRepository; import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.SensorAirQualityScoreRepository; import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.RoomAirQualityScoreRepository; -import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.PlaceAirQualityScoreRepository; import com.example.smartair.repository.roomSensorRepository.RoomSensorRepository; -import com.example.smartair.repository.roomRepository.RoomRepository; -import com.example.smartair.repository.sensorRepository.SensorRepository; import com.example.smartair.service.airQualityService.calculator.AirQualityCalculator; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.Duration; import java.time.LocalDateTime; import java.util.*; @Service @RequiredArgsConstructor @Slf4j -public class AirQualityScoreService { //스케줄러를 통해 자동으로 점수 계산 실행되는 서비스 +public class AirQualityScoreService { private final AirQualityCalculator airQualityCalculator; - private final AirQualityDataRepository airQualityDataRepository; private final SensorAirQualityScoreRepository sensorAirQualityScoreRepository; private final RoomAirQualityScoreRepository roomAirQualityScoreRepository; private final RoomSensorRepository roomSensorRepository; @@ -73,7 +67,6 @@ public void calculateAndSaveDeviceScore(SensorAirQualityData airQualityData) { / } - public void updateRoomAverageScore(Room room) { //방 평균 점수 업데이트 log.info("Updating average score for Room ID: {}", room.getId()); List airQualityScoreList = new ArrayList<>(); @@ -262,27 +255,34 @@ public AverageScoreDto calculateAverageDeviceScore(List a @Transactional public void calculateHourlyRoomScore(Room room){ //시간별 방 점수 계산 - log.info("Calculating hourly average score for Room ID: {}", room.getId()); + log.info("===Scheduling : Calculating hourly average score for Room ID: {}===", room.getId()); LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1); - // 1. 각 센서의 최근 1시간 데이터 처리 List sensors = roomSensorRepository.findAllSensorByRoom(room); + List hourlyScores = new ArrayList<>(); for (Sensor sensor : sensors) { try { - // 센서의 최근 1시간 내 데이터 중 가장 최신 데이터 조회 - Optional latestData = airQualityDataRepository - .findFirstBySensorAndCreatedAtAfterOrderByCreatedAtDesc(sensor, oneHourAgo); - - // 최신 데이터가 있다면 점수 계산 - latestData.ifPresent(this::calculateAndSaveDeviceScore); + List hourlyData = sensorAirQualityScoreRepository + .findScoresBySensorSerialNumberAndTimeRange(sensor.getSerialNumber(), oneHourAgo, LocalDateTime.now()); + hourlyScores.addAll(hourlyData); } catch (Exception e) { - log.error("Failed to process data for Sensor ID {}: {}", sensor.getId(), e.getMessage()); + log.error("Scheduling : Failed to process data for Sensor ID {}: {}", sensor.getId(), e.getMessage()); } } - - // 2. 기존 updateRoomAverageScore 메서드 활용하여 방 평균 계산 및 저장 - updateRoomAverageScore(room); - log.info("Completed hourly room score calculation for Room ID: {}", room.getId()); + if (!hourlyScores.isEmpty()){ + AverageScoreDto averageScoreDto = calculateAverageDeviceScore(hourlyScores); + RoomAirQualityScore roomAirQualityScore = RoomAirQualityScore.builder() + .room(room) + .overallScore(averageScoreDto.getOverallScore()) + .pm10Score(averageScoreDto.getPm10Score()) + .pm25Score(averageScoreDto.getPm25Score()) + .eco2Score(averageScoreDto.getEco2Score()) + .tvocScore(averageScoreDto.getTvocScore()) + .build(); + roomAirQualityScoreRepository.save(roomAirQualityScore); + log.info("Scheduling : Hourly average score saved for Room ID: {}, Score ID: {}", room.getId(), roomAirQualityScore.getId()); + } + log.info("Scheduling : Completed hourly room score calculation for Room ID: {}", room.getId()); } } diff --git a/src/main/java/com/example/smartair/service/airQualityService/report/AnomalyReportService.java b/src/main/java/com/example/smartair/service/airQualityService/report/AnomalyReportService.java index 394abdfa..10a30c66 100644 --- a/src/main/java/com/example/smartair/service/airQualityService/report/AnomalyReportService.java +++ b/src/main/java/com/example/smartair/service/airQualityService/report/AnomalyReportService.java @@ -4,8 +4,6 @@ import com.example.smartair.dto.airQualityDataDto.AnomalyReportDto; import com.example.smartair.dto.airQualityDataDto.AnomalyReportResponseDto; import com.example.smartair.entity.airData.report.AnomalyReport; -import com.example.smartair.entity.airData.report.DailySensorAirQualityReport; -import com.example.smartair.entity.airData.snapshot.HourlySensorAirQualitySnapshot; import com.example.smartair.entity.device.Device; import com.example.smartair.entity.notification.Notification; import com.example.smartair.entity.room.Room; @@ -17,10 +15,9 @@ import com.example.smartair.exception.ErrorCode; import com.example.smartair.repository.airQualityRepository.airQualityReportRepository.AnomalyReportRepository; import com.example.smartair.repository.airQualityRepository.airQualityReportRepository.DailySensorAirQualityReportRepository; -import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlyDeviceAirQualitySnapshotRepository; +import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlySensorAirQualitySnapshotRepository; import com.example.smartair.repository.deviceRepository.DeviceRepository; import com.example.smartair.repository.notificationRepository.NotificationRepository; -import com.example.smartair.repository.roomRepository.RoomRepository; import com.example.smartair.repository.roomSensorRepository.RoomSensorRepository; import com.example.smartair.repository.sensorRepository.SensorRepository; import com.example.smartair.service.deviceService.ThinQService; @@ -29,7 +26,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import javax.swing.text.html.Option; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -44,7 +40,7 @@ public class AnomalyReportService { private final AnomalyReportRepository anomalyReportRepository; private final SensorRepository sensorRepository; - private final HourlyDeviceAirQualitySnapshotRepository hourlyDeviceAirQualitySnapshotRepository; + private final HourlySensorAirQualitySnapshotRepository hourlySensorAirQualitySnapshotRepository; private final DailySensorAirQualityReportRepository dailySensorAirQualityReportRepository; private final ThinQService thinQService; private final RoomSensorRepository roomSensorRepository; diff --git a/src/main/java/com/example/smartair/service/airQualityService/report/DailyReportService.java b/src/main/java/com/example/smartair/service/airQualityService/report/DailyReportService.java index 220b928e..56c9db1c 100644 --- a/src/main/java/com/example/smartair/service/airQualityService/report/DailyReportService.java +++ b/src/main/java/com/example/smartair/service/airQualityService/report/DailyReportService.java @@ -6,7 +6,7 @@ import com.example.smartair.exception.CustomException; import com.example.smartair.exception.ErrorCode; import com.example.smartair.repository.airQualityRepository.airQualityReportRepository.DailySensorAirQualityReportRepository; -import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlyDeviceAirQualitySnapshotRepository; +import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlySensorAirQualitySnapshotRepository; import com.example.smartair.repository.sensorRepository.SensorRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -29,7 +29,7 @@ public class DailyReportService { private final SensorRepository sensorRepository; - private final HourlyDeviceAirQualitySnapshotRepository snapshotRepository; + private final HourlySensorAirQualitySnapshotRepository snapshotRepository; private final DailySensorAirQualityReportRepository dailyReportRepository; /** diff --git a/src/main/java/com/example/smartair/service/airQualityService/scheduler/AirQualityScoreCalculationScheduler.java b/src/main/java/com/example/smartair/service/airQualityService/scheduler/AirQualityScoreCalculationScheduler.java index a0c18833..207ca80b 100644 --- a/src/main/java/com/example/smartair/service/airQualityService/scheduler/AirQualityScoreCalculationScheduler.java +++ b/src/main/java/com/example/smartair/service/airQualityService/scheduler/AirQualityScoreCalculationScheduler.java @@ -1,12 +1,7 @@ package com.example.smartair.service.airQualityService.scheduler; -import com.example.smartair.entity.airData.airQualityData.SensorAirQualityData; import com.example.smartair.entity.room.Room; -import com.example.smartair.exception.CustomException; -import com.example.smartair.exception.ErrorCode; -import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.AirQualityDataRepository; import com.example.smartair.repository.roomRepository.RoomRepository; -import com.example.smartair.repository.sensorRepository.SensorRepository; import com.example.smartair.service.airQualityService.AirQualityScoreService; import lombok.RequiredArgsConstructor; import lombok.Setter; @@ -14,8 +9,6 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.time.Duration; -import java.time.LocalDateTime; import java.util.List; @Component @@ -29,7 +22,7 @@ public class AirQualityScoreCalculationScheduler { // 시간별 방의 평균 점수만 스케줄러로 계산 // 배치 처리 : 시간차를 두고 순차적 실행 -// @Scheduled(cron = "0 0 * * * *") // 매시 정각 + @Scheduled(cron = "0 0 * * * *") // 매시 정각 public void calculateHourlyRoomAverages() { List roomList = roomRepository.findAll(); @@ -39,7 +32,7 @@ public void calculateHourlyRoomAverages() { airQualityScoreService.calculateHourlyRoomScore(room); Thread.sleep(100); } catch (Exception e) { - log.error("방 ID {} 시간별 점수 계산 실패: {}", room.getId(), e.getMessage()); + log.error("Scheduling : 방 ID {} 시간별 점수 계산 실패: {}", room.getId(), e.getMessage()); } } } diff --git a/src/main/java/com/example/smartair/service/airQualityService/scheduler/ReportGenerationScheduler.java b/src/main/java/com/example/smartair/service/airQualityService/scheduler/ReportGenerationScheduler.java index 4239c931..ab1e254b 100644 --- a/src/main/java/com/example/smartair/service/airQualityService/scheduler/ReportGenerationScheduler.java +++ b/src/main/java/com/example/smartair/service/airQualityService/scheduler/ReportGenerationScheduler.java @@ -1,9 +1,7 @@ package com.example.smartair.service.airQualityService.scheduler; -import com.example.smartair.entity.airData.report.WeeklySensorAirQualityReport; import com.example.smartair.entity.sensor.Sensor; -import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlyDeviceAirQualitySnapshotRepository; -import com.example.smartair.repository.sensorRepository.SensorRepository; +import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlySensorAirQualitySnapshotRepository; import com.example.smartair.service.airQualityService.snapshot.SnapshotService; import com.example.smartair.service.airQualityService.report.DailyReportService; import com.example.smartair.service.airQualityService.report.WeeklyReportService; @@ -17,8 +15,6 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.time.temporal.WeekFields; -import java.util.List; -import java.util.Locale; import java.util.Set; @Component @@ -26,11 +22,10 @@ @Slf4j public class ReportGenerationScheduler { - private final SensorRepository sensorRepository; private final SnapshotService snapshotService; private final DailyReportService dailyReportService; private final WeeklyReportService weeklyReportService; - private final HourlyDeviceAirQualitySnapshotRepository hourlyDeviceAirQualitySnapshotRepository; + private final HourlySensorAirQualitySnapshotRepository hourlySensorAirQualitySnapshotRepository; /** * 매시간 정각에 실행되어, 각 활성 센서에 대해 이전 시간의 시간별 공기질 스냅샷을 생성합니다. @@ -60,7 +55,7 @@ public void generateDailyReports() { log.info("일별 공기질 리포트 생성을 시작합니다. 대상 날짜: {}", yesterday); // 어제 하루동안 데이터가 있는 센서들 조회 - Set sensorsWithData = hourlyDeviceAirQualitySnapshotRepository + Set sensorsWithData = hourlySensorAirQualitySnapshotRepository .findDistinctSensorsBySnapshotHourBetween( yesterday.atStartOfDay(), yesterday.atTime(23, 59, 59) @@ -106,7 +101,7 @@ public void generateWeeklyReports() { .withMinute(59) .withSecond(59); - Set sensorsWithData = hourlyDeviceAirQualitySnapshotRepository + Set sensorsWithData = hourlySensorAirQualitySnapshotRepository .findDistinctSensorsBySnapshotHourBetween(weekStart, weekEnd); if (sensorsWithData.isEmpty()) { diff --git a/src/main/java/com/example/smartair/service/airQualityService/snapshot/SnapshotService.java b/src/main/java/com/example/smartair/service/airQualityService/snapshot/SnapshotService.java index aa00440e..de75123d 100644 --- a/src/main/java/com/example/smartair/service/airQualityService/snapshot/SnapshotService.java +++ b/src/main/java/com/example/smartair/service/airQualityService/snapshot/SnapshotService.java @@ -7,8 +7,8 @@ import com.example.smartair.entity.sensor.Sensor; import com.example.smartair.exception.CustomException; import com.example.smartair.exception.ErrorCode; -import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.AirQualityDataRepository; -import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlyDeviceAirQualitySnapshotRepository; +import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.SensorAirQualityDataRepository; +import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlySensorAirQualitySnapshotRepository; import com.example.smartair.repository.sensorRepository.SensorRepository; import com.example.smartair.service.airQualityService.calculator.AirQualityCalculator; import lombok.RequiredArgsConstructor; @@ -28,9 +28,9 @@ public class SnapshotService { private final SensorRepository sensorRepository; - private final AirQualityDataRepository airQualityDataRepository; - private final HourlyDeviceAirQualitySnapshotRepository snapshotRepository; + private final HourlySensorAirQualitySnapshotRepository snapshotRepository; private final AirQualityCalculator airQualityCalculator; + private final SensorAirQualityDataRepository sensorAirQualityDataRepository; /** * 특정 센서의 특정 시간대에 대한 스냅샷을 생성합니다. @@ -46,7 +46,7 @@ public void createHourlySnapshotForSensor(String serialNumber, LocalDateTime sna "Sensor serialNumber: " + serialNumber)); // 해당 센서의 시간별 데이터를 DB에서 조회 - List airQualityDataList = airQualityDataRepository.findBySensorAndCreatedAtBetweenOrderByCreatedAtAsc( + List airQualityDataList = sensorAirQualityDataRepository.findBySensorAndCreatedAtBetweenOrderByCreatedAtAsc( sensor, snapshotHourBase, snapshotHourBase.plusHours(1)); @@ -71,7 +71,7 @@ public void createHourlySnapshot(LocalDateTime snapshotHourBase) { snapshotHourBase = snapshotHourBase.truncatedTo(ChronoUnit.HOURS); //시간별 데이터를 DB에서 조회 - List airQualityDataList = airQualityDataRepository.findByCreatedAtBetween( + List airQualityDataList = sensorAirQualityDataRepository.findByCreatedAtBetween( snapshotHourBase, snapshotHourBase.plusHours(1)); @@ -87,7 +87,7 @@ public void createHourlySnapshot(LocalDateTime snapshotHourBase) { public void createSensorSnapshot(Long sensorId, List hourlyRawDataList) { try { if (hourlyRawDataList.isEmpty()) { - log.warn("Device ID: {} 의 {} 시간 스냅샷에 대한 데이터가 없어 스냅샷 생성을 건너뜁니다.", + log.warn("Sensor ID: {} 의 {} 시간 스냅샷에 대한 데이터가 없어 스냅샷 생성을 건너뜁니다.", sensorId, hourlyRawDataList.get(0).getCreatedAt()); return; } @@ -171,7 +171,7 @@ public List getHourlySnapshots(String serialNumb @Transactional public HourlySensorAirQualitySnapshot updateHourlySnapshot(HourlySensorAirQualitySnapshot snapshot) { // 기존 스냅샷의 시간 범위에 해당하는 DeviceAirQualityData 리스트 조회 - List dataList = airQualityDataRepository + List dataList = sensorAirQualityDataRepository .findBySensorAndCreatedAtBetweenOrderByCreatedAtAsc(snapshot.getSensor(), snapshot.getSnapshotHour(), snapshot.getSnapshotHour().plusHours(1)); @@ -225,7 +225,7 @@ public HourlySensorAirQualitySnapshot updateHourlySnapshot(HourlySensorAirQualit public SensorAirQualityData getLatestAirQualityData(String serialNumber) { Sensor sensor = sensorRepository.findBySerialNumber(serialNumber).orElseThrow(()-> new CustomException(ErrorCode.SENSOR_NOT_FOUND, "Sensor serialNumber: " + serialNumber)); - return airQualityDataRepository.findTopBySensor_SerialNumberOrderByCreatedAtDesc(serialNumber) + return sensorAirQualityDataRepository.findTopBySensor_SerialNumberOrderByCreatedAtDesc(serialNumber) .orElseThrow(() -> new CustomException(ErrorCode.SENSOR_AIR_DATA_NOT_FOUND, "Sensor serialNumber {}" + serialNumber)); } diff --git a/src/main/java/com/example/smartair/service/mqttService/MqttReceiveService.java b/src/main/java/com/example/smartair/service/mqttService/MqttReceiveService.java index 8a06da13..a7afc24c 100644 --- a/src/main/java/com/example/smartair/service/mqttService/MqttReceiveService.java +++ b/src/main/java/com/example/smartair/service/mqttService/MqttReceiveService.java @@ -2,33 +2,21 @@ import com.example.smartair.dto.airQualityDataDto.AirQualityPayloadDto; import com.example.smartair.entity.airData.airQualityData.SensorAirQualityData; -import com.example.smartair.entity.login.CustomUserDetails; -import com.example.smartair.entity.roomSensor.RoomSensor; import com.example.smartair.entity.sensor.Sensor; -import com.example.smartair.entity.user.User; import com.example.smartair.exception.CustomException; import com.example.smartair.exception.ErrorCode; -import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.AirQualityDataRepository; +import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.SensorAirQualityDataRepository; import com.example.smartair.repository.roomSensorRepository.RoomSensorRepository; import com.example.smartair.repository.sensorRepository.SensorRepository; -import com.example.smartair.repository.userRepository.UserRepository; import com.example.smartair.service.airQualityService.AirQualityDataService; import com.example.smartair.service.awsFileService.S3Service; -import com.example.smartair.service.sensorService.SensorService; -import com.fasterxml.jackson.core.JsonProcessingException; -import jakarta.annotation.PostConstruct; import jakarta.persistence.EntityNotFoundException; import jakarta.transaction.Transactional; -import org.springframework.http.HttpStatus; import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.support.TransactionTemplate; import org.springframework.web.server.ResponseStatusException; import java.time.Duration; @@ -36,7 +24,6 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -45,7 +32,7 @@ public class MqttReceiveService { private final LinkedList recentMessage = new LinkedList<>(); private final AirQualityDataService airQualityDataService; - private final AirQualityDataRepository airQualityDataRepository; + private final SensorAirQualityDataRepository sensorAirQualityDataRepository; private final RoomSensorRepository roomSensorRepository; private final S3Service s3Service; private final SensorRepository sensorRepository; @@ -57,7 +44,7 @@ public class MqttReceiveService { private final Map sensorMessageCounters = new ConcurrentHashMap<>(); private final Map sensorLastResetTimes = new ConcurrentHashMap<>(); -// @Scheduled(fixedRate = 600000) // 10분마다 실행 + @Scheduled(fixedRate = 600000) // 10분마다 실행 @Transactional public void checkSensorStatus(){ LocalDateTime threshold = LocalDateTime.now().minus(INACTIVITY_THRESHOLD); @@ -79,7 +66,7 @@ public void checkSensorStatus(){ } } -// @Scheduled(fixedRate = 86400000) // 24시간마다 실행 + @Scheduled(fixedRate = 86400000) // 24시간마다 실행 @Transactional public void cleanupInactiveSensorData(){ //비활성 센서 데이터 정리 LocalDateTime thresholdTime = LocalDateTime.now().minusDays(2); @@ -104,7 +91,7 @@ public void cleanupInactiveSensorData(){ //비활성 센서 데이터 정리 public void cleanupOldData() { //데이터베이스에 저장된 8일 이전의 오래된 데이터 정리 try { LocalDateTime eightDaysAgo = LocalDateTime.now().minusDays(8); - int deletedCount = airQualityDataRepository.deleteByCreatedAtBefore(eightDaysAgo); + int deletedCount = sensorAirQualityDataRepository.deleteByCreatedAtBefore(eightDaysAgo); log.info("데이터 정리 완료: {} 개의 8일 이전 데이터 삭제됨 (기준 일시: {})", deletedCount, eightDaysAgo); } catch (Exception e) { diff --git a/src/main/java/com/example/smartair/service/sensorService/SensorService.java b/src/main/java/com/example/smartair/service/sensorService/SensorService.java index ffee4c13..b99c2d3e 100644 --- a/src/main/java/com/example/smartair/service/sensorService/SensorService.java +++ b/src/main/java/com/example/smartair/service/sensorService/SensorService.java @@ -8,33 +8,31 @@ import com.example.smartair.entity.airData.predictedAirQualityData.PredictedAirQualityData; import com.example.smartair.entity.airData.report.DailySensorAirQualityReport; import com.example.smartair.entity.airData.report.WeeklySensorAirQualityReport; -import com.example.smartair.entity.sensor.Sensor; +import com.example.smartair.entity.airData.snapshot.HourlySensorAirQualitySnapshot; +import com.example.smartair.entity.airScore.airQualityScore.SensorAirQualityScore; import com.example.smartair.entity.room.Room; import com.example.smartair.entity.roomSensor.RoomSensor; +import com.example.smartair.entity.sensor.Sensor; import com.example.smartair.entity.user.User; import com.example.smartair.exception.CustomException; import com.example.smartair.exception.ErrorCode; -import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.AirQualityDataRepository; +import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.SensorAirQualityDataRepository; +import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.SensorAirQualityScoreRepository; import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.FineParticlesDataPt2Repository; import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.FineParticlesDataRepository; import com.example.smartair.repository.airQualityRepository.airQualityReportRepository.DailySensorAirQualityReportRepository; import com.example.smartair.repository.airQualityRepository.airQualityReportRepository.WeeklySensorAirQualityReportRepository; +import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlySensorAirQualitySnapshotRepository; import com.example.smartair.repository.airQualityRepository.predictedAirQualityRepository.PredictedAirQualityRepository; -import com.example.smartair.repository.sensorRepository.SensorRepository; -import com.example.smartair.repository.roomSensorRepository.RoomSensorRepository; import com.example.smartair.repository.roomRepository.RoomRepository; -import com.example.smartair.repository.userRepository.UserRepository; -import jakarta.persistence.EntityExistsException; -import jakarta.persistence.EntityManager; -import jakarta.transaction.Transactional; +import com.example.smartair.repository.roomSensorRepository.RoomSensorRepository; +import com.example.smartair.repository.sensorRepository.SensorRepository; +import com.example.smartair.service.airQualityService.AirQualityScoreService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.stereotype.Service; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -46,13 +44,15 @@ public class SensorService { private final SensorRepository sensorRepository; private final RoomRepository roomRepository; private final RoomSensorRepository roomSensorRepository; - private final AirQualityDataRepository airQualityDataRepository; private final FineParticlesDataRepository fineParticlesDataRepository; private final FineParticlesDataPt2Repository fineParticlesDataPt2Repository; private final DailySensorAirQualityReportRepository dailySensorAirQualityReportRepository; private final WeeklySensorAirQualityReportRepository weeklySensorAirQualityReportRepository; private final PredictedAirQualityRepository predictedAirQualityRepository; - private final UserRepository userRepository; + private final SensorAirQualityDataRepository sensorAirQualityDataRepository; + private final SensorAirQualityScoreRepository sensorAirQualityScoreRepository; + private final AirQualityScoreService airQualityScoreService; + private final HourlySensorAirQualitySnapshotRepository hourlySensorAirQualitySnapshotRepository; public Sensor setSensor(User user, SensorRequestDto.setSensorDto sensorRequestDto) throws Exception { Sensor sensor = Sensor.builder() @@ -70,24 +70,24 @@ public Sensor setSensor(User user, SensorRequestDto.setSensorDto sensorRequestDt public RoomSensor addSensorToRoom(User user, SensorRequestDto.addSensorToRoomDto sensorDto) throws Exception { Room room = roomRepository.findRoomById(sensorDto.roomId()) - .orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND, "roomId에 맞는 방이 없습니다.")); + .orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND)); Sensor sensor = sensorRepository.findBySerialNumber(sensorDto.serialNumber()) - .orElseThrow(() -> new CustomException(ErrorCode.SENSOR_NOT_FOUND, "serialNumber에 맞는 센서가 없습니다.")); + .orElseThrow(() -> new CustomException(ErrorCode.SENSOR_NOT_FOUND)); - // 센서 소유자 검증 - if (!sensor.getUser().getId().equals(user.getId())) { - throw new CustomException(ErrorCode.NO_AUTHORITY_TO_ACCESS_SENSOR, "해당 센서에 대한 권한이 없습니다."); - } +// // 센서 소유자 검증 +// if (!sensor.getUser().getId().equals(user.getId())) { +// throw new CustomException(ErrorCode.NO_AUTHORITY_TO_ACCESS_SENSOR); +// } - // 등록하려는 유저가 방에 등록된 사람인지 확인 - if (!roomRepository.existsByIdAndParticipants_User(room.getId(), user)) { - throw new CustomException(ErrorCode.PARTICIPANT_NOT_FOUND_IN_ROOM, "해당 방에 등록된 사용자가 아닙니다."); - } +// // 등록하려는 유저가 방에 등록된 사람인지 확인 +// if (!roomRepository.existsByIdAndParticipants_User(room.getId(), user)) { +// throw new CustomException(ErrorCode.PARTICIPANT_NOT_FOUND_IN_ROOM); +// } // 이미 방에 등록되어 있는 센서인지 확인 if (roomSensorRepository.existsBySensor_SerialNumberAndRoom_Id(sensorDto.serialNumber(), room.getId())) { - throw new CustomException(ErrorCode.SENSOR_ALREADY_EXIST_IN_ROOM, "이미 방에 등록된 센서입니다."); + throw new CustomException(ErrorCode.SENSOR_ALREADY_EXIST_IN_ROOM); } sensor.setRegistered(true); sensor.setRoomRegisterDate(LocalDateTime.now()); // 방에 등록된 시점으로 설정 @@ -102,48 +102,42 @@ public RoomSensor addSensorToRoom(User user, SensorRequestDto.addSensorToRoomDto return roomSensor; } - public void deleteSensor(User user, SensorRequestDto.deleteSensorDto sensorDto) throws Exception { - Room room = roomRepository.findRoomById(sensorDto.roomId()) - .orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND, "roomId에 맞는 방이 없습니다.")); + public void deleteSensor(User user, String serialNumber) { + Sensor sensor = sensorRepository.findBySerialNumber(serialNumber) + .orElseThrow(() -> new CustomException(ErrorCode.SENSOR_NOT_FOUND)); - // 등록된 참여자인지 확인 - if (!roomRepository.existsByIdAndParticipants_User(room.getId(), user)) { - throw new CustomException(ErrorCode.PARTICIPANT_NOT_FOUND_IN_ROOM, "해당 방에 등록된 사용자가 아닙니다."); - } +// // 센서 소유자 검증 +// if (!sensor.getUser().getId().equals(user.getId())) { +// throw new CustomException(ErrorCode.NO_AUTHORITY_TO_ACCESS_SENSOR, "센서의 소유자가 아닙니다."); +// } + List affectedRooms = deleteRelatedEntities(sensor); - RoomSensor roomSensor = roomSensorRepository.findBySensor_SerialNumberAndRoom_Id(sensorDto.serialNumber(), sensorDto.roomId()) - .orElseThrow(() -> new CustomException(ErrorCode.ROOM_SENSOR_MAPPING_NOT_FOUND, "roomId에 맞는 방에 등록된 센서가 없습니다.")); + sensorRepository.delete(sensor); - Sensor sensor = roomSensor.getSensor(); - - // 센서 소유자 검증 - if (!sensor.getUser().getId().equals(user.getId())) { - throw new CustomException(ErrorCode.NO_AUTHORITY_TO_ACCESS_SENSOR, "해당 센서에 대한 권한이 없습니다."); - } + updateAffectedRoomScores(serialNumber, affectedRooms); + } - //실시간 데이터 삭제 - Optional optionalFine = fineParticlesDataRepository.findBySensor_Id(sensor.getId()); - optionalFine.ifPresent(fineParticlesDataRepository::delete); + public List deleteRelatedEntities(Sensor sensor){ + List roomSensors = roomSensorRepository.findAllBySensor_Id(sensor.getId()); + List affectedRooms = roomSensors.stream() + .map(RoomSensor::getRoom) + .distinct() + .toList(); + roomSensorRepository.deleteAll(roomSensors); - Optional optionalFine2 = fineParticlesDataPt2Repository.findBySensor_Id(sensor.getId()); - optionalFine2.ifPresent(fineParticlesDataPt2Repository::delete); + // 실시간 데이터 삭제 + deleteRealTimeData(sensor); - Optional qualityDataOptional = airQualityDataRepository.findBySensor_Id(sensor.getId()); - qualityDataOptional.ifPresent(airQualityDataRepository::delete); + // 공기질 데이터 및 점수 삭제 + deleteAirQualityData(sensor); - //하루 평균 데이터 삭제 - List dailyReports = dailySensorAirQualityReportRepository.findAllBySensorId(sensor.getId()); - dailySensorAirQualityReportRepository.deleteAll(dailyReports); - //일주일 평균 데이터 삭제 - List weeklyReports = weeklySensorAirQualityReportRepository.findAllBySensorId(sensor.getId()); - weeklySensorAirQualityReportRepository.deleteAll(weeklyReports); + // 리포트 데이터 삭제 + deleteReportData(sensor); - //예측된 공기질 데이터 삭제 - List predictedAirQualityDataList = predictedAirQualityRepository.findBySensorSerialNumber(sensor.getSerialNumber()); - predictedAirQualityRepository.deleteAll(predictedAirQualityDataList); + // 예측 데이터 삭제 + deletePredictionData(sensor); - sensorRepository.delete(sensor); - roomSensorRepository.delete(roomSensor); + return affectedRooms; } public boolean getSensorStatus(String serialNumber) throws Exception { @@ -154,22 +148,22 @@ public boolean getSensorStatus(String serialNumber) throws Exception { return optionalSensor.get().isRunningStatus(); } - public void unregisterSensorFromRoom(User user, SensorRequestDto.unregisterSensorFromRoomDto request) throws Exception { - Room room = roomRepository.findRoomById(request.roomId()) - .orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND, "roomId에 맞는 방이 없습니다.")); + public void unregisterSensorFromRoom(User user, String serialNumber, Long roomId) throws Exception { + Room room = roomRepository.findRoomById(roomId) + .orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND)); - // 등록된 참여자인지 확인 - if (!roomRepository.existsByIdAndParticipants_User(room.getId(), user)) { - throw new CustomException(ErrorCode.PARTICIPANT_NOT_FOUND_IN_ROOM, "해당 방에 등록된 사용자가 아닙니다."); - } +// // 등록된 참여자인지 확인 +// if (!roomRepository.existsByIdAndParticipants_User(room.getId(), user)) { +// throw new CustomException(ErrorCode.PARTICIPANT_NOT_FOUND_IN_ROOM, "해당 방에 등록된 사용자가 아닙니다."); +// } - RoomSensor roomSensor = roomSensorRepository.findBySensor_SerialNumberAndRoom_Id(request.serialNumber(), request.roomId()) - .orElseThrow(() -> new CustomException(ErrorCode.ROOM_SENSOR_MAPPING_NOT_FOUND, "roomId에 맞는 방에 등록된 센서가 없습니다.")); + RoomSensor roomSensor = roomSensorRepository.findBySensor_SerialNumberAndRoom_Id(serialNumber, roomId) + .orElseThrow(() -> new CustomException(ErrorCode.ROOM_SENSOR_MAPPING_NOT_FOUND)); - // 센서 소유자 검증 - if (!roomSensor.getSensor().getUser().getId().equals(user.getId())) { - throw new CustomException(ErrorCode.NO_AUTHORITY_TO_ACCESS_SENSOR, "해당 센서에 대한 권한이 없습니다."); - } +// // 센서 소유자 검증 +// if (!roomSensor.getSensor().getUser().getId().equals(user.getId())) { +// throw new CustomException(ErrorCode.NO_AUTHORITY_TO_ACCESS_SENSOR, "해당 센서에 대한 권한이 없습니다."); +// } // 센서의 등록 상태 변경 Sensor sensor = roomSensor.getSensor(); @@ -177,44 +171,82 @@ public void unregisterSensorFromRoom(User user, SensorRequestDto.unregisterSenso sensor.setRoomRegisterDate(null); // 방에 등록되지 않은 상태로 변경 sensorRepository.save(sensor); - //실시간 데이터 삭제 + // RoomSensor 매핑 삭제 + roomSensorRepository.delete(roomSensor); + } + + public SensorResponseDto getSensorById(Long sensorId) { + Sensor sensor = sensorRepository.findById(sensorId) + .orElseThrow(() -> new CustomException(ErrorCode.SENSOR_NOT_FOUND)); + + return SensorResponseDto.from(sensor); + } + + public List getUserSensors(User user) { + List sensors = sensorRepository.findByUser(user); + + return sensors.stream() + .map(SensorResponseDto::from) + .toList(); + + } + + public SensorResponseDto getSensorBySerialNumber(String serialNumber) { + Sensor sensor = sensorRepository.findBySerialNumber(serialNumber) + .orElseThrow(() -> new CustomException(ErrorCode.SENSOR_NOT_FOUND)); + + return SensorResponseDto.from(sensor); + } + + public void deleteRealTimeData(Sensor sensor){ Optional optionalFine = fineParticlesDataRepository.findBySensor_Id(sensor.getId()); optionalFine.ifPresent(fineParticlesDataRepository::delete); Optional optionalFine2 = fineParticlesDataPt2Repository.findBySensor_Id(sensor.getId()); optionalFine2.ifPresent(fineParticlesDataPt2Repository::delete); + } + + public void deleteAirQualityData(Sensor sensor){ + List qualityDataList = sensorAirQualityDataRepository.findAllBySensor_Id(sensor.getId()); + + for (SensorAirQualityData data : qualityDataList) { + // 각 데이터에 연결된 점수 삭제 + List scores = sensorAirQualityScoreRepository.findAllBySensorAirQualityDataId(data.getId()); + sensorAirQualityScoreRepository.deleteAll(scores); + } - Optional qualityDataOptional = airQualityDataRepository.findBySensor_Id(sensor.getId()); - qualityDataOptional.ifPresent(airQualityDataRepository::delete); + // 모든 공기질 데이터 삭제 + sensorAirQualityDataRepository.deleteAll(qualityDataList); + } + public void deleteReportData(Sensor sensor){ + //hourlysnapshot 데이터 삭제 + List hourlySnapshots = hourlySensorAirQualitySnapshotRepository.findAllBySensor_Id(sensor.getId()); + hourlySensorAirQualitySnapshotRepository.deleteAll(hourlySnapshots); //하루 평균 데이터 삭제 List dailyReports = dailySensorAirQualityReportRepository.findAllBySensorId(sensor.getId()); dailySensorAirQualityReportRepository.deleteAll(dailyReports); //일주일 평균 데이터 삭제 List weeklyReports = weeklySensorAirQualityReportRepository.findAllBySensorId(sensor.getId()); weeklySensorAirQualityReportRepository.deleteAll(weeklyReports); + } - // 예측된 공기질 데이터 삭제 - List predictedAirQualityDataList = predictedAirQualityRepository.findBySensorSerialNumber(sensor.getSerialNumber()); + public void deletePredictionData(Sensor sensor){ + //예측된 공기질 데이터 삭제 + List predictedAirQualityDataList = predictedAirQualityRepository.findAllBySensorSerialNumber(sensor.getSerialNumber()); predictedAirQualityRepository.deleteAll(predictedAirQualityDataList); - - // RoomSensor 매핑 삭제 - roomSensorRepository.delete(roomSensor); } - public SensorResponseDto getSensorById(Long sensorId) { - Sensor sensor = sensorRepository.findById(sensorId) - .orElseThrow(() -> new CustomException(ErrorCode.SENSOR_NOT_FOUND, "해당 ID에 맞는 센서가 없습니다.")); - - return SensorResponseDto.from(sensor); + public void updateAffectedRoomScores(String serialNumber, List affectedRooms) { + for (Room room : affectedRooms) { + try{ + airQualityScoreService.updateRoomAverageScore(room); + log.info("센서 SerialNumber{} 삭제로 인한 방 {}의 평균 점수 업데이트 완료", serialNumber, room.getId()); + } + catch (Exception e){ + log.error("센서 SerialNumber{} 삭제로 인한 방 {}의 평균 점수 업데이트 중 오류 발생: {}", serialNumber, room.getId(), e.getMessage()); + } + } } - public List getUserSensors(User user) { - List sensors = sensorRepository.findByUser(user); - - return sensors.stream() - .map(SensorResponseDto::from) - .toList(); - - } } diff --git a/src/test/java/com/example/smartair/service/airQualityService/AnomalyReportServiceTest.java b/src/test/java/com/example/smartair/service/airQualityService/AnomalyReportServiceTest.java index f817f8f2..dfe973be 100644 --- a/src/test/java/com/example/smartair/service/airQualityService/AnomalyReportServiceTest.java +++ b/src/test/java/com/example/smartair/service/airQualityService/AnomalyReportServiceTest.java @@ -1,5 +1,4 @@ package com.example.smartair.service.airQualityService; -import com.example.smartair.domain.enums.Pollutant; import com.example.smartair.dto.airQualityDataDto.AnomalyReportDto; import com.example.smartair.dto.airQualityDataDto.AnomalyReportResponseDto; import com.example.smartair.entity.airData.report.AnomalyReport; @@ -9,7 +8,7 @@ import com.example.smartair.exception.CustomException; import com.example.smartair.repository.airQualityRepository.airQualityReportRepository.AnomalyReportRepository; import com.example.smartair.repository.airQualityRepository.airQualityReportRepository.DailySensorAirQualityReportRepository; -import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlyDeviceAirQualitySnapshotRepository; +import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlySensorAirQualitySnapshotRepository; import com.example.smartair.repository.deviceRepository.DeviceRepository; import com.example.smartair.repository.notificationRepository.NotificationRepository; import com.example.smartair.repository.roomSensorRepository.RoomSensorRepository; @@ -17,8 +16,6 @@ import com.example.smartair.service.airQualityService.report.AnomalyReportService; import com.example.smartair.service.deviceService.ThinQService; import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.FirebaseMessagingException; -import com.google.firebase.messaging.MessagingErrorCode; import com.google.firebase.messaging.Message; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -44,7 +41,7 @@ class AnomalyReportServiceTest { private SensorRepository sensorRepository; @Mock - private HourlyDeviceAirQualitySnapshotRepository hourlyDeviceAirQualitySnapshotRepository; + private HourlySensorAirQualitySnapshotRepository hourlySensorAirQualitySnapshotRepository; @Mock private DailySensorAirQualityReportRepository dailySensorAirQualityReportRepository; @@ -66,7 +63,7 @@ void setUp() { anomalyReportService = new AnomalyReportService( anomalyReportRepository, sensorRepository, - hourlyDeviceAirQualitySnapshotRepository, + hourlySensorAirQualitySnapshotRepository, dailySensorAirQualityReportRepository, thinQService, roomSensorRepository, @@ -93,7 +90,7 @@ void setAnomalyReport_success() throws Exception { sensor.getUser().setFcmToken("fake_fcm_token"); when(sensorRepository.findBySerialNumber("ABC123")).thenReturn(Optional.of(sensor)); - when(hourlyDeviceAirQualitySnapshotRepository.findBySensorAndSnapshotHour(sensor, time)) + when(hourlySensorAirQualitySnapshotRepository.findBySensorAndSnapshotHour(sensor, time)) .thenReturn(Optional.of(mock(HourlySensorAirQualitySnapshot.class))); when(dailySensorAirQualityReportRepository.findBySensorAndReportDate(sensor, LocalDate.parse("2025-05-21"))) .thenReturn(Optional.of(mock(DailySensorAirQualityReport.class))); diff --git a/src/test/java/com/example/smartair/service/airQualityService/airQualityDataTest/AirQualityDataServiceTest.java b/src/test/java/com/example/smartair/service/airQualityService/airQualityDataTest/AirQualityDataServiceTest.java index 33cf8f7e..f786ac80 100644 --- a/src/test/java/com/example/smartair/service/airQualityService/airQualityDataTest/AirQualityDataServiceTest.java +++ b/src/test/java/com/example/smartair/service/airQualityService/airQualityDataTest/AirQualityDataServiceTest.java @@ -7,7 +7,8 @@ import com.example.smartair.entity.room.Room; import com.example.smartair.entity.roomSensor.RoomSensor; import com.example.smartair.infrastructure.RecentAirQualityDataCache; -import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.AirQualityDataRepository; +import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.SensorAirQualityDataRepository; +import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.SensorAirQualityScoreRepository; import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.FineParticlesDataPt2Repository; import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.FineParticlesDataRepository; import com.example.smartair.repository.sensorRepository.SensorRepository; @@ -45,7 +46,7 @@ class AirQualityDataServiceTest { private RoomSensorRepository roomSensorRepository; @Mock - private AirQualityDataRepository airQualityDataRepository; + private SensorAirQualityDataRepository sensorAirQualityDataRepository; @Mock private RecentAirQualityDataCache recentAirQualityDataCache; @@ -121,7 +122,7 @@ void processAirQualityData(){ when(sensorRepository.findById(TEST_SENSOR_ID)).thenReturn(Optional.of(sensor)); when(roomSensorRepository.findBySensor(sensor)).thenReturn(Optional.of(roomSensor)); - when(airQualityDataRepository.save(any(SensorAirQualityData.class))).thenAnswer(invocation -> { + when(sensorAirQualityDataRepository.save(any(SensorAirQualityData.class))).thenAnswer(invocation -> { SensorAirQualityData savedData = invocation.getArgument(0); savedData.setId(100L); return savedData; @@ -135,7 +136,7 @@ void processAirQualityData(){ assertEquals(29.47, data.getTemperature()); assertEquals(38.22, data.getHumidity()); verify(fineParticlesDataRepository).save(any(FineParticlesData.class)); - verify(airQualityDataRepository).save(any(SensorAirQualityData.class)); + verify(sensorAirQualityDataRepository).save(any(SensorAirQualityData.class)); verify(recentAirQualityDataCache).put(eq(sensor.getId()), any(SensorAirQualityData.class)); } @@ -152,7 +153,7 @@ void processAirQualityData_DeviceNotFound_ShouldThrowException(){ assertEquals(ErrorCode.SENSOR_NOT_FOUND, exception.getErrorCode()); verify(fineParticlesDataRepository, never()).save(any()); - verify(airQualityDataRepository, never()).save(any()); + verify(sensorAirQualityDataRepository, never()).save(any()); verify(recentAirQualityDataCache, never()).put(any(), any()); } @@ -169,7 +170,7 @@ void processAirQualityData_WithNoRoomMapping_ShouldProcessSuccessfully() { when(sensorRepository.findById(TEST_SENSOR_ID)).thenReturn(Optional.of(sensor)); when(roomSensorRepository.findBySensor(sensor)).thenReturn(Optional.empty()); when(fineParticlesDataRepository.save(any(FineParticlesData.class))).thenReturn(mockSavedFineParticles); - when(airQualityDataRepository.save(any(SensorAirQualityData.class))).thenAnswer(invocation -> { + when(sensorAirQualityDataRepository.save(any(SensorAirQualityData.class))).thenAnswer(invocation -> { SensorAirQualityData savedData = invocation.getArgument(0); savedData.setId(100L); return savedData; diff --git a/src/test/java/com/example/smartair/service/airQualityService/airQualityScoreTest/AirQualityScoreServiceTest.java b/src/test/java/com/example/smartair/service/airQualityService/airQualityScoreTest/AirQualityScoreServiceTest.java index bf169785..dcd228b9 100644 --- a/src/test/java/com/example/smartair/service/airQualityService/airQualityScoreTest/AirQualityScoreServiceTest.java +++ b/src/test/java/com/example/smartair/service/airQualityService/airQualityScoreTest/AirQualityScoreServiceTest.java @@ -2,18 +2,11 @@ import com.example.smartair.entity.airData.airQualityData.SensorAirQualityData; import com.example.smartair.entity.airScore.airQualityScore.SensorAirQualityScore; -import com.example.smartair.entity.airScore.airQualityScore.RoomAirQualityScore; -import com.example.smartair.entity.airScore.airQualityScore.PlaceAirQualityScore; import com.example.smartair.entity.sensor.Sensor; -import com.example.smartair.entity.place.Place; import com.example.smartair.entity.room.Room; import com.example.smartair.entity.roomSensor.RoomSensor; -import com.example.smartair.exception.CustomException; -import com.example.smartair.exception.ErrorCode; -import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.AirQualityDataRepository; import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.SensorAirQualityScoreRepository; import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.RoomAirQualityScoreRepository; -import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.PlaceAirQualityScoreRepository; import com.example.smartair.repository.roomSensorRepository.RoomSensorRepository; import com.example.smartair.repository.roomRepository.RoomRepository; import com.example.smartair.service.airQualityService.AirQualityScoreService; @@ -22,16 +15,13 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.util.Collections; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; class AirQualityScoreServiceTest { @@ -39,21 +29,12 @@ class AirQualityScoreServiceTest { @Mock private AirQualityCalculator airQualityCalculator; - @Mock - private AirQualityDataRepository airQualityDataRepository; - @Mock private SensorAirQualityScoreRepository sensorAirQualityScoreRepository; - @Mock - private RoomAirQualityScoreRepository roomAirQualityScoreRepository; - @Mock private RoomSensorRepository roomSensorRepository; - @Mock - private RoomRepository roomRepository; - @InjectMocks private AirQualityScoreService airQualityScoreService; diff --git a/src/test/java/com/example/smartair/service/sensorService/SensorServiceTest.java b/src/test/java/com/example/smartair/service/sensorService/SensorServiceTest.java index 90c2a99a..8a9dd160 100644 --- a/src/test/java/com/example/smartair/service/sensorService/SensorServiceTest.java +++ b/src/test/java/com/example/smartair/service/sensorService/SensorServiceTest.java @@ -5,12 +5,13 @@ import com.example.smartair.entity.roomSensor.RoomSensor; import com.example.smartair.entity.sensor.Sensor; import com.example.smartair.entity.user.User; -import com.example.smartair.exception.CustomException; -import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.AirQualityDataRepository; +import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.SensorAirQualityDataRepository; +import com.example.smartair.repository.airQualityRepository.airQualityScoreRepository.SensorAirQualityScoreRepository; import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.FineParticlesDataPt2Repository; import com.example.smartair.repository.airQualityRepository.airQualityDataRepository.FineParticlesDataRepository; import com.example.smartair.repository.airQualityRepository.airQualityReportRepository.DailySensorAirQualityReportRepository; import com.example.smartair.repository.airQualityRepository.airQualityReportRepository.WeeklySensorAirQualityReportRepository; +import com.example.smartair.repository.airQualityRepository.airQualitySnapshotRepository.HourlySensorAirQualitySnapshotRepository; import com.example.smartair.repository.airQualityRepository.predictedAirQualityRepository.PredictedAirQualityRepository; import com.example.smartair.repository.roomRepository.RoomRepository; import com.example.smartair.repository.roomSensorRepository.RoomSensorRepository; @@ -21,7 +22,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.time.LocalDateTime; +import java.util.Collections; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; @@ -45,7 +46,7 @@ class SensorServiceTest { private FineParticlesDataPt2Repository fineParticlesDataPt2Repository; @Mock - private AirQualityDataRepository airQualityDataRepository; + private SensorAirQualityScoreRepository sensorAirQualityScoreRepository; @Mock private DailySensorAirQualityReportRepository dailySensorAirQualityReportRepository; @@ -56,6 +57,11 @@ class SensorServiceTest { @Mock private PredictedAirQualityRepository predictedAirQualityRepository; + @Mock + private SensorAirQualityDataRepository sensorAirQualityDataRepository; + + @Mock + private HourlySensorAirQualitySnapshotRepository hourlySensorAirQualitySnapshotRepository; @InjectMocks private SensorService sensorService; @@ -122,22 +128,21 @@ void setUp() { @Test void deleteSensor_성공() throws Exception { - SensorRequestDto.deleteSensorDto dto = new SensorRequestDto.deleteSensorDto("12345", 1L); RoomSensor roomSensor = RoomSensor.builder() .sensor(testSensor) .room(testRoom) .build(); - when(roomRepository.findRoomById(dto.roomId())).thenReturn(Optional.of(testRoom)); + when(sensorRepository.findBySerialNumber(testSensor.getSerialNumber())).thenReturn(Optional.of(testSensor)); + when(roomRepository.findRoomById(anyLong())).thenReturn(Optional.of(testRoom)); when(roomRepository.existsByIdAndParticipants_User(testRoom.getId(), testUser)).thenReturn(true); - when(roomSensorRepository.findBySensor_SerialNumberAndRoom_Id(dto.serialNumber(), dto.roomId())) - .thenReturn(Optional.of(roomSensor)); + when(roomSensorRepository.findAllBySensor_Id(testSensor.getId())).thenReturn(Collections.singletonList(roomSensor)); - sensorService.deleteSensor(testUser, dto); + sensorService.deleteSensor(testUser, testSensor.getSerialNumber()); verify(sensorRepository, times(1)).delete(testSensor); - verify(roomSensorRepository, times(1)).delete(roomSensor); + verify(roomSensorRepository, times(1)).deleteAll(Collections.singletonList(roomSensor)); } @Test @@ -149,4 +154,20 @@ void setUp() { assertFalse(status); verify(sensorRepository, times(1)).findBySerialNumber(testSensor.getSerialNumber()); } + + @Test + void unregitsterSensorFromRoom_성공() throws Exception { + RoomSensor roomSensor = RoomSensor.builder() + .sensor(testSensor) + .room(testRoom) + .build(); + + when(roomRepository.findRoomById(anyLong())).thenReturn(Optional.of(testRoom)); + when(roomSensorRepository.findBySensor_SerialNumberAndRoom_Id(testSensor.getSerialNumber(), testRoom.getId())) + .thenReturn(Optional.of(roomSensor)); + + sensorService.unregisterSensorFromRoom(testUser, testSensor.getSerialNumber(), testRoom.getId()); + + verify(roomSensorRepository, times(1)).delete(roomSensor); + } } \ No newline at end of file