Skip to content

Commit

Permalink
Merge pull request #148 from ptkis/notifications-list
Browse files Browse the repository at this point in the history
Alarm controller
  • Loading branch information
thomedw authored Nov 9, 2023
2 parents 3c08234 + 5ca687f commit 4edb43a
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 3 deletions.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ dependencies {

testImplementation("org.junit.jupiter:junit-jupiter:5.9.1")

implementation("io.github.sercasti:spring-httpserver-timings:0.0.7")

implementation("org.ehcache:ehcache")
implementation("javax.cache:cache-api")
testRuntimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.84.Final:osx-aarch_64")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package com.katalisindonesia.banyuwangi.controller

import au.com.console.jpaspecificationdsl.and
import au.com.console.jpaspecificationdsl.get
import au.com.console.jpaspecificationdsl.greaterThanOrEqualTo
import au.com.console.jpaspecificationdsl.join
import au.com.console.jpaspecificationdsl.lessThan
import au.com.console.jpaspecificationdsl.where
import com.katalisindonesia.banyuwangi.model.Alarm
import com.katalisindonesia.banyuwangi.model.DetectionType
import com.katalisindonesia.banyuwangi.model.SnapshotCount
import com.katalisindonesia.banyuwangi.repo.AlarmRepo
import com.katalisindonesia.imageserver.service.StorageService
import io.github.sercasti.tracing.Traceable
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort
import org.springframework.data.domain.Sort.Direction
import org.springframework.data.jpa.domain.Specification
import org.springframework.format.annotation.DateTimeFormat
import org.springframework.http.ResponseEntity
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDate
import java.time.ZoneId
import javax.validation.Valid
import javax.validation.constraints.Min

@RestController
@RequestMapping("/v1/alarm")
@Tag(name = "alarm", description = "Alarm")
class AlarmController(
private val alarmRepo: AlarmRepo,
private val storageService: StorageService,
) {

@GetMapping("/list")
@Traceable
@PreAuthorize("hasAuthority('alarm:read')")
@Operation(
summary = "Get all alarms",
description = "Get all alarms",
)
@ApiResponses(
value = [
ApiResponse(responseCode = "200", description = "Successful operation"),
ApiResponse(responseCode = "400", description = "Invalid request parameter"),
ApiResponse(
responseCode = "403",
description = "You do not have required permission. Check token and scope.",
),
],
)
fun list(
@RequestParam(required = false)
detectionType: Set<DetectionType>?,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
beginning: LocalDate?,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
ending: LocalDate?,

@Parameter(description = "Sorted property")
@Valid
@RequestParam(
required = false,
defaultValue = "CREATED",
)
sort: AlarmSortProperty,

@Parameter(description = "Sort direction, ascending if omitted")
@Valid
@RequestParam(
required = false,
defaultValue = "DESC",
)
direction: Direction,

@Parameter(description = "Page number, defaults to 0")
@Valid
@RequestParam(
required = false,
defaultValue = "0",
)
@Min(0L)
page: Int,

@Parameter(description = "Size of a page")
@Valid
@RequestParam(
required = false,
defaultValue = "1000",
)
@Min(0L)
size: Int,
): ResponseEntity<WebResponse<List<DetectionResponse>>> {
val specs = specs(detectionType, beginning, ending)
val page1 = alarmRepo.findAll(
and(specs),
PageRequest.of(
page,
size,
Sort.by(
direction,
sort.propertyName,
),
),
)
val zone = ZoneId.systemDefault()
return ResponseEntity.ok(
WebResponse(
success = true,
message = "ok",
data = page1.map {
val camera = it.snapshotCount.snapshot.camera
DetectionResponse(
date = it.created.atZone(zone).toLocalDate(),
instant = it.created,
location = camera.location,
cameraName = camera.name,
type = it.snapshotCount.type,
value = it.snapshotCount.value,
imageSrc = storageService.uri(
it.snapshotCount.snapshotImageId,
)
.toString(),
annotations = emptyList(),
)
}
)
)
}

@GetMapping("/count")
@Traceable
@PreAuthorize("hasAuthority('alarm:read')")
@Operation(
summary = "Count all alarms",
description = "Count all alarms",
)
@ApiResponses(
value = [
ApiResponse(responseCode = "200", description = "Successful operation"),
ApiResponse(responseCode = "400", description = "Invalid request parameter"),
ApiResponse(
responseCode = "403",
description = "You do not have required permission. Check token and scope.",
),
],
)
fun count(
@RequestParam(required = false)
detectionType: Set<DetectionType>?,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
beginning: LocalDate?,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
ending: LocalDate?,
): ResponseEntity<WebResponse<Long>> {
val specs = specs(detectionType, beginning, ending)
val page1 = alarmRepo.countAll(
and(specs),
)
return ResponseEntity.ok(
WebResponse(
success = true,
message = "ok",
data = page1,
),
)
}

private fun specs(
detectionType: Set<DetectionType>?,
beginning: LocalDate?,
ending: LocalDate?,
): MutableList<Specification<Alarm>> {
val zone = ZoneId.systemDefault()
val specs = mutableListOf<Specification<Alarm>>()
if (!detectionType.isNullOrEmpty()) {
specs.add(
where {
it.join(Alarm::snapshotCount)
.get(SnapshotCount::type).`in`(
detectionType
)
}
)
}
if (beginning != null) {
specs.add(Alarm::created.greaterThanOrEqualTo(beginning.atStartOfDay(zone).toInstant()))
}
if (ending != null) {
specs.add(Alarm::created.lessThan(ending.plusDays(1).atStartOfDay(zone).toInstant()))
}

return specs
}
}

enum class AlarmSortProperty(val propertyName: String) {
CREATED(Alarm::created.name),
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ class CameraController(
scopes = ["camera:write"],
)
],
tags = ["chart"],
)
@ApiResponses(
value = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.katalisindonesia.banyuwangi.repo

import com.katalisindonesia.banyuwangi.model.Alarm
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.util.UUID

@Repository
interface AlarmRepo : JpaRepository<Alarm, UUID>
interface AlarmRepo : BaseRepository<Alarm, UUID>
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.katalisindonesia.banyuwangi.controller

import com.katalisindonesia.banyuwangi.repo.AlarmRepo
import com.katalisindonesia.banyuwangi.security.TokenManager
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.get

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class AlarmControllerTest(
@Autowired private val mockMvc: MockMvc,
@Autowired private val alarmRepo: AlarmRepo,
@Autowired private val tokenManager: TokenManager,

) {

@BeforeEach
@AfterEach
fun cleanup() {
alarmRepo.deleteAll()
}

@Test
fun `list no token`() {
mockMvc.get("/v1/alarm/list").andExpect {
status { is3xxRedirection() }
}
}

@Test
fun `list empty`() {
mockMvc.get("/v1/alarm/list") {
headers {
setBearerAuth(token())
accept = listOf(MediaType.APPLICATION_JSON)
}
}.andExpect {
status { isOk() }
content {
json(
"""{
"success": true,
"message": "ok",
"data": []
}""",
strict = false,
)
}
}
}

@Test
fun `list and count empty with parameters`() {
mockMvc.get(
"/v1/alarm/list?beginning=2022-01-01" +
"&ending=2022-01-02&detectionType=TRASH"
) {
headers {
setBearerAuth(token())
accept = listOf(MediaType.APPLICATION_JSON)
}
}.andExpect {
status { isOk() }
content {
json(
"""{
"success": true,
"message": "ok",
"data": []
}""",
strict = false,
)
}
}
mockMvc.get(
"/v1/alarm/count?beginning=2022-01-01" +
"&ending=2022-01-02&detectionType=TRASH"
) {
headers {
setBearerAuth(token())
accept = listOf(MediaType.APPLICATION_JSON)
}
}.andExpect {
status { isOk() }
content {
json(
"""{
"success": true,
"message": "ok",
"data": 0
}""",
strict = false,
)
}
}
}

private fun token(): String = tokenManager.accessToken("banyuwangi-test", "banyuwangi-test")
}

0 comments on commit 4edb43a

Please sign in to comment.