diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 14c7123c..1830036c 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -56,6 +56,7 @@ jobs: SPRING_DATASOURCE_USERNAME: testuser SPRING_DATASOURCE_PASSWORD: testpassword CORS_ALLOWED_ORIGINS: ${{ secrets.CORS_ALLOWED_ORIGINS }} + CCTV_STREAM_URL_PREFIX: ${{ secrets.CCTV_STREAM_URL_PREFIX }} run: ./gradlew test dependency-submission: diff --git a/backend/src/main/java/com/example/backend/dashboard/controller/CctvController.java b/backend/src/main/java/com/example/backend/dashboard/controller/CctvController.java new file mode 100644 index 00000000..37846ff1 --- /dev/null +++ b/backend/src/main/java/com/example/backend/dashboard/controller/CctvController.java @@ -0,0 +1,32 @@ +package com.example.backend.dashboard.controller; + +import com.example.backend.dashboard.dto.CctvInfoResponse; +import com.example.backend.dashboard.service.CctvService; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/cctv") +@RequiredArgsConstructor +public class CctvController { + + + private final CctvService CctvService; + + @GetMapping("") + public ResponseEntity getCctvInfo(HttpSession session) { + List cctvs = CctvService.getCctvInfo(session); + return ResponseEntity.ok(cctvs); + } + + @GetMapping("/{id}") + public ResponseEntity getCctvLive(@PathVariable("id") int id, HttpSession session) { + CctvInfoResponse cctv = CctvService.getCctvLive(id, session); + return ResponseEntity.ok(cctv); + } + +} diff --git a/backend/src/main/java/com/example/backend/dashboard/dto/CaseDetectRequest.java b/backend/src/main/java/com/example/backend/dashboard/dto/CaseDetectRequest.java index 2a59d809..9d5b5806 100644 --- a/backend/src/main/java/com/example/backend/dashboard/dto/CaseDetectRequest.java +++ b/backend/src/main/java/com/example/backend/dashboard/dto/CaseDetectRequest.java @@ -5,7 +5,7 @@ public class CaseDetectRequest { private Integer officeId; // 사무소 ID private Integer cctvId; // CCTV ID - private Integer level; // 위험 레벨 + private int level; // 위험 레벨 private String category; // "fire", "assault" 등 private String video; // 스토리지에 저장된 영상 URL private String memo; // 기타 메모 diff --git a/backend/src/main/java/com/example/backend/dashboard/dto/CctvInfoResponse.java b/backend/src/main/java/com/example/backend/dashboard/dto/CctvInfoResponse.java new file mode 100644 index 00000000..bb31e7e5 --- /dev/null +++ b/backend/src/main/java/com/example/backend/dashboard/dto/CctvInfoResponse.java @@ -0,0 +1,31 @@ +package com.example.backend.dashboard.dto; + +import com.example.backend.common.domain.CctvEntity; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CctvInfoResponse { + private Integer id; // CCTV ID + private double latitude; + private double longitude; + private String address; // CCTV 주소 + private String liveUrl; // 생략 가능: getCctvInfo에는 필요 없음 + + public static CctvInfoResponse fromEntity(CctvEntity entity, String urlPrefix) { + return CctvInfoResponse.builder() + .id(entity.getId()) + .latitude(entity.getLatitude()) + .longitude(entity.getLongitude()) + .address(entity.getAddress()) + .liveUrl(urlPrefix + entity.getId() + ".m3u8") + .build(); + } + +} + diff --git a/backend/src/main/java/com/example/backend/dashboard/dto/DashboardResponse.java b/backend/src/main/java/com/example/backend/dashboard/dto/DashboardResponse.java index a09094cc..767ec0f0 100644 --- a/backend/src/main/java/com/example/backend/dashboard/dto/DashboardResponse.java +++ b/backend/src/main/java/com/example/backend/dashboard/dto/DashboardResponse.java @@ -19,7 +19,7 @@ public class DashboardResponse { @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime date; - private Integer level; + private int level; private CaseCategory category; private CaseState state; diff --git a/backend/src/main/java/com/example/backend/dashboard/repository/CctvInfoRepository.java b/backend/src/main/java/com/example/backend/dashboard/repository/CctvInfoRepository.java new file mode 100644 index 00000000..b6e25e9e --- /dev/null +++ b/backend/src/main/java/com/example/backend/dashboard/repository/CctvInfoRepository.java @@ -0,0 +1,10 @@ +package com.example.backend.dashboard.repository; + +import com.example.backend.common.domain.CctvEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CctvInfoRepository extends JpaRepository { + List findAllByOfficeIdOrderById(int officeId); +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/backend/dashboard/service/CctvService.java b/backend/src/main/java/com/example/backend/dashboard/service/CctvService.java new file mode 100644 index 00000000..fd29898b --- /dev/null +++ b/backend/src/main/java/com/example/backend/dashboard/service/CctvService.java @@ -0,0 +1,53 @@ +package com.example.backend.dashboard.service; + +import com.example.backend.common.domain.CctvEntity; +import com.example.backend.dashboard.dto.CctvInfoResponse; +import com.example.backend.dashboard.repository.CctvInfoRepository; +import com.example.backend.user.dto.UserResponseDto; +import jakarta.persistence.EntityNotFoundException; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class CctvService { + + private final CctvInfoRepository cctvRepository; + + @Value("${cctv.stream-url-prefix}") + private String streamUrlPrefix; + + // 세션에서 officeId 추출 + private int getAuthenticatedOfficeId(HttpSession session) { + UserResponseDto user = (UserResponseDto) session.getAttribute("user"); + if (user == null) { + throw new IllegalStateException("세션이 만료되었거나 로그인되지 않았습니다."); + } + return user.getOfficeId(); + } + + public List getCctvInfo(HttpSession session) { + int officeId = getAuthenticatedOfficeId(session); + List cctvs = cctvRepository.findAllByOfficeIdOrderById(officeId); + + return cctvs.stream() + .map(entity -> CctvInfoResponse.fromEntity(entity, streamUrlPrefix)) + .toList(); + } + + public CctvInfoResponse getCctvLive(int cctvId, HttpSession session) { + getAuthenticatedOfficeId(session); + + CctvEntity entity = cctvRepository.findById(cctvId) + .orElseThrow(() -> new EntityNotFoundException("해당 CCTV를 찾을 수 없습니다.")); + + return CctvInfoResponse.fromEntity(entity, streamUrlPrefix); + } + +} \ No newline at end of file