Skip to content

Commit a48db06

Browse files
committed
Alarm controller
1 parent 3c08234 commit a48db06

File tree

5 files changed

+271
-3
lines changed

5 files changed

+271
-3
lines changed

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ dependencies {
112112

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

115+
implementation("io.github.sercasti:spring-httpserver-timings:0.0.7")
116+
115117
implementation("org.ehcache:ehcache")
116118
implementation("javax.cache:cache-api")
117119
testRuntimeOnly("io.netty:netty-resolver-dns-native-macos:4.1.84.Final:osx-aarch_64")
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package com.katalisindonesia.banyuwangi.controller
2+
3+
import au.com.console.jpaspecificationdsl.and
4+
import au.com.console.jpaspecificationdsl.get
5+
import au.com.console.jpaspecificationdsl.greaterThanOrEqualTo
6+
import au.com.console.jpaspecificationdsl.join
7+
import au.com.console.jpaspecificationdsl.lessThan
8+
import au.com.console.jpaspecificationdsl.where
9+
import com.katalisindonesia.banyuwangi.model.Alarm
10+
import com.katalisindonesia.banyuwangi.model.DetectionType
11+
import com.katalisindonesia.banyuwangi.model.SnapshotCount
12+
import com.katalisindonesia.banyuwangi.repo.AlarmRepo
13+
import com.katalisindonesia.imageserver.service.StorageService
14+
import io.github.sercasti.tracing.Traceable
15+
import io.swagger.v3.oas.annotations.Operation
16+
import io.swagger.v3.oas.annotations.Parameter
17+
import io.swagger.v3.oas.annotations.responses.ApiResponse
18+
import io.swagger.v3.oas.annotations.responses.ApiResponses
19+
import io.swagger.v3.oas.annotations.tags.Tag
20+
import org.springframework.data.domain.PageRequest
21+
import org.springframework.data.domain.Sort
22+
import org.springframework.data.domain.Sort.Direction
23+
import org.springframework.data.jpa.domain.Specification
24+
import org.springframework.format.annotation.DateTimeFormat
25+
import org.springframework.http.ResponseEntity
26+
import org.springframework.security.access.prepost.PreAuthorize
27+
import org.springframework.web.bind.annotation.GetMapping
28+
import org.springframework.web.bind.annotation.RequestMapping
29+
import org.springframework.web.bind.annotation.RequestParam
30+
import org.springframework.web.bind.annotation.RestController
31+
import java.time.LocalDate
32+
import java.time.ZoneId
33+
import javax.validation.Valid
34+
import javax.validation.constraints.Min
35+
36+
@RestController
37+
@RequestMapping("/v1/alarm")
38+
@Tag(name = "alarm", description = "Alarm")
39+
class AlarmController(
40+
private val alarmRepo: AlarmRepo,
41+
private val storageService: StorageService,
42+
) {
43+
44+
@GetMapping("/list")
45+
@Traceable
46+
@PreAuthorize("hasAuthority('alarm:read')")
47+
@Operation(
48+
summary = "Get all alarms",
49+
description = "Get all alarms",
50+
)
51+
@ApiResponses(
52+
value = [
53+
ApiResponse(responseCode = "200", description = "Successful operation"),
54+
ApiResponse(responseCode = "400", description = "Invalid request parameter"),
55+
ApiResponse(
56+
responseCode = "403",
57+
description = "You do not have required permission. Check token and scope.",
58+
),
59+
],
60+
)
61+
fun list(
62+
@RequestParam(required = false)
63+
detectionType: Set<DetectionType>?,
64+
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
65+
beginning: LocalDate?,
66+
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
67+
ending: LocalDate?,
68+
69+
@Parameter(description = "Sorted property")
70+
@Valid
71+
@RequestParam(
72+
required = false,
73+
defaultValue = "CREATED",
74+
)
75+
sort: AlarmSortProperty,
76+
77+
@Parameter(description = "Sort direction, ascending if omitted")
78+
@Valid
79+
@RequestParam(
80+
required = false,
81+
defaultValue = "DESC",
82+
)
83+
direction: Direction,
84+
85+
@Parameter(description = "Page number, defaults to 0")
86+
@Valid
87+
@RequestParam(
88+
required = false,
89+
defaultValue = "0",
90+
)
91+
@Min(0L)
92+
page: Int,
93+
94+
@Parameter(description = "Size of a page")
95+
@Valid
96+
@RequestParam(
97+
required = false,
98+
defaultValue = "1000",
99+
)
100+
@Min(0L)
101+
size: Int,
102+
): ResponseEntity<WebResponse<List<DetectionResponse>>> {
103+
val specs = specs(detectionType, beginning, ending)
104+
val page1 = alarmRepo.findAll(
105+
and(specs),
106+
PageRequest.of(
107+
page,
108+
size,
109+
Sort.by(
110+
direction,
111+
sort.propertyName,
112+
),
113+
),
114+
)
115+
val zone = ZoneId.systemDefault()
116+
return ResponseEntity.ok(
117+
WebResponse(
118+
success = true,
119+
message = "ok",
120+
data = page1.map {
121+
val camera = it.snapshotCount.snapshot.camera
122+
DetectionResponse(
123+
date = it.created.atZone(zone).toLocalDate(),
124+
instant = it.created,
125+
location = camera.location,
126+
cameraName = camera.name,
127+
type = it.snapshotCount.type,
128+
value = it.snapshotCount.value,
129+
imageSrc = storageService.uri(
130+
it.snapshotCount.snapshotImageId,
131+
)
132+
.toString(),
133+
annotations = emptyList(),
134+
)
135+
}
136+
)
137+
)
138+
}
139+
140+
@GetMapping("/count")
141+
@Traceable
142+
@PreAuthorize("hasAuthority('alarm:read')")
143+
@Operation(
144+
summary = "Count all alarms",
145+
description = "Count all alarms",
146+
)
147+
@ApiResponses(
148+
value = [
149+
ApiResponse(responseCode = "200", description = "Successful operation"),
150+
ApiResponse(responseCode = "400", description = "Invalid request parameter"),
151+
ApiResponse(
152+
responseCode = "403",
153+
description = "You do not have required permission. Check token and scope.",
154+
),
155+
],
156+
)
157+
fun count(
158+
@RequestParam(required = false)
159+
detectionType: Set<DetectionType>?,
160+
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
161+
beginning: LocalDate?,
162+
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
163+
ending: LocalDate?,
164+
): ResponseEntity<WebResponse<Long>> {
165+
val specs = specs(detectionType, beginning, ending)
166+
val page1 = alarmRepo.countAll(
167+
and(specs),
168+
)
169+
return ResponseEntity.ok(
170+
WebResponse(
171+
success = true,
172+
message = "ok",
173+
data = page1,
174+
),
175+
)
176+
}
177+
178+
private fun specs(
179+
detectionType: Set<DetectionType>?,
180+
beginning: LocalDate?,
181+
ending: LocalDate?,
182+
): MutableList<Specification<Alarm>> {
183+
val zone = ZoneId.systemDefault()
184+
val specs = mutableListOf<Specification<Alarm>>()
185+
if (!detectionType.isNullOrEmpty()) {
186+
specs.add(
187+
where {
188+
it.join(Alarm::snapshotCount)
189+
.get(SnapshotCount::type).`in`(
190+
detectionType
191+
)
192+
}
193+
)
194+
}
195+
if (beginning != null) {
196+
specs.add(Alarm::created.greaterThanOrEqualTo(beginning.atStartOfDay(zone).toInstant()))
197+
}
198+
if (ending != null) {
199+
specs.add(Alarm::created.lessThan(ending.plusDays(1).atStartOfDay(zone).toInstant()))
200+
}
201+
202+
return specs
203+
}
204+
}
205+
206+
enum class AlarmSortProperty(val propertyName: String) {
207+
CREATED(Alarm::created.name),
208+
}

src/main/kotlin/com/katalisindonesia/banyuwangi/controller/CameraController.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ class CameraController(
109109
scopes = ["camera:write"],
110110
)
111111
],
112-
tags = ["chart"],
113112
)
114113
@ApiResponses(
115114
value = [
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package com.katalisindonesia.banyuwangi.repo
22

33
import com.katalisindonesia.banyuwangi.model.Alarm
4-
import org.springframework.data.jpa.repository.JpaRepository
54
import org.springframework.stereotype.Repository
65
import java.util.UUID
76

87
@Repository
9-
interface AlarmRepo : JpaRepository<Alarm, UUID>
8+
interface AlarmRepo : BaseRepository<Alarm, UUID>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.katalisindonesia.banyuwangi.controller
2+
3+
import com.katalisindonesia.banyuwangi.repo.AlarmRepo
4+
import com.katalisindonesia.banyuwangi.security.TokenManager
5+
import org.junit.jupiter.api.AfterEach
6+
import org.junit.jupiter.api.BeforeEach
7+
import org.junit.jupiter.api.Test
8+
import org.springframework.beans.factory.annotation.Autowired
9+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
10+
import org.springframework.boot.test.context.SpringBootTest
11+
import org.springframework.http.MediaType
12+
import org.springframework.test.web.servlet.MockMvc
13+
import org.springframework.test.web.servlet.get
14+
15+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
16+
@AutoConfigureMockMvc
17+
class AlarmControllerTest(
18+
@Autowired private val mockMvc: MockMvc,
19+
@Autowired private val alarmRepo: AlarmRepo,
20+
@Autowired private val tokenManager: TokenManager,
21+
22+
) {
23+
24+
@BeforeEach
25+
@AfterEach
26+
fun cleanup() {
27+
alarmRepo.deleteAll()
28+
}
29+
30+
@Test
31+
fun `list no token`() {
32+
mockMvc.get("/v1/alarm/list").andExpect {
33+
status { is3xxRedirection() }
34+
}
35+
}
36+
37+
@Test
38+
fun `list empty`() {
39+
mockMvc.get("/v1/alarm/list") {
40+
headers {
41+
setBearerAuth(token())
42+
accept = listOf(MediaType.APPLICATION_JSON)
43+
}
44+
}.andExpect {
45+
status { isOk() }
46+
content {
47+
json(
48+
"""{
49+
"success": true,
50+
"message": "ok",
51+
"data": []
52+
}""",
53+
strict = false,
54+
)
55+
}
56+
}
57+
}
58+
59+
private fun token(): String = tokenManager.accessToken("banyuwangi-test", "banyuwangi-test")
60+
}

0 commit comments

Comments
 (0)