-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #148 from ptkis/notifications-list
Alarm controller
- Loading branch information
Showing
5 changed files
with
317 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
208 changes: 208 additions & 0 deletions
208
src/main/kotlin/com/katalisindonesia/banyuwangi/controller/AlarmController.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 1 addition & 2 deletions
3
src/main/kotlin/com/katalisindonesia/banyuwangi/repo/AlarmRepo.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
106 changes: 106 additions & 0 deletions
106
src/test/kotlin/com/katalisindonesia/banyuwangi/controller/AlarmControllerTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |