Skip to content

Commit 19bc1ab

Browse files
committed
feat: support paginated responses on API endpoints
1 parent a755a2d commit 19bc1ab

File tree

8 files changed

+160
-10
lines changed

8 files changed

+160
-10
lines changed

src/main/kotlin/com/ctrlhub/core/assets/equipment/exposures/EquipmentExposuresRouter.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.ctrlhub.core.assets.equipment.exposures
22

3+
import com.ctrlhub.core.api.response.PaginatedList
34
import com.ctrlhub.core.assets.equipment.EquipmentRouter
45
import com.ctrlhub.core.assets.equipment.exposures.resource.EquipmentExposureResource
56
import com.ctrlhub.core.assets.equipment.resource.EquipmentItem
@@ -45,8 +46,8 @@ class EquipmentExposuresRouter(httpClient: HttpClient) : Router(httpClient) {
4546
organisationId: String,
4647
equipmentId: String,
4748
requestParameters: EquipmentExposureRequestParameters = EquipmentExposureRequestParameters()
48-
): List<EquipmentExposureResource> {
49-
return fetchJsonApiResources(
49+
): PaginatedList<EquipmentExposureResource> {
50+
return fetchPaginatedJsonApiResources(
5051
"/v3/orgs/$organisationId/assets/equipment/$equipmentId/exposures",
5152
requestParameters.toMap()
5253
)

src/main/kotlin/com/ctrlhub/core/datacapture/FormsRouter.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@ import com.ctrlhub.core.Api
44
import com.ctrlhub.core.api.response.PaginatedList
55
import com.ctrlhub.core.datacapture.response.Form
66
import com.ctrlhub.core.router.Router
7+
import com.ctrlhub.core.router.request.RequestParameters
78
import io.ktor.client.HttpClient
89

910
class FormsRouter(httpClient: HttpClient) : Router(httpClient) {
10-
suspend fun all(organisationId: String): PaginatedList<Form> {
11-
return fetchPaginatedJsonApiResources("/v3/orgs/${organisationId}/data-capture/forms", emptyMap(), Form::class.java)
11+
suspend fun all(
12+
organisationId: String,
13+
requestParameters: RequestParameters = RequestParameters()
14+
): PaginatedList<Form> {
15+
return fetchPaginatedJsonApiResources(
16+
"/v3/orgs/${organisationId}/data-capture/forms",
17+
requestParameters.toMap(),
18+
Form::class.java
19+
)
1220
}
1321

1422
suspend fun one(organisationId: String, formId: String): Form {

src/main/kotlin/com/ctrlhub/core/governance/schemes/SchemesRouter.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.ctrlhub.core.governance.schemes
22

33
import com.ctrlhub.core.Api
4+
import com.ctrlhub.core.api.response.PaginatedList
45
import com.ctrlhub.core.governance.schemes.response.Scheme
56
import com.ctrlhub.core.governance.schemes.workorders.response.WorkOrder
67
import com.ctrlhub.core.iam.response.User
@@ -36,8 +37,8 @@ class SchemesRouter(httpClient: HttpClient) : Router(httpClient) {
3637
*
3738
* @return A list of all schemes
3839
*/
39-
suspend fun all(organisationId: String, requestParameters: SchemeRequestParameters = SchemeRequestParameters()): List<Scheme> {
40-
return fetchJsonApiResources("/v3/orgs/$organisationId/governance/schemes", requestParameters.toMap(), Scheme::class.java,
40+
suspend fun all(organisationId: String, requestParameters: SchemeRequestParameters = SchemeRequestParameters()): PaginatedList<Scheme> {
41+
return fetchPaginatedJsonApiResources("/v3/orgs/$organisationId/governance/schemes", requestParameters.toMap(), Scheme::class.java,
4142
WorkOrder::class.java, User::class.java)
4243
}
4344

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.ctrlhub.core
2+
3+
import com.ctrlhub.core.router.request.RequestParameters
4+
import kotlin.test.Test
5+
import kotlin.test.assertEquals
6+
7+
class PaginatedRequestTest {
8+
@Test
9+
fun `can set limit correctly in request`() {
10+
val request = RequestParameters(
11+
limit = 10
12+
)
13+
14+
val queryParams = request.toMap()
15+
assertEquals("10", queryParams["limit"])
16+
}
17+
18+
@Test
19+
fun `can set offset correctly in request`() {
20+
val request = RequestParameters(
21+
offset = 10
22+
)
23+
24+
val queryParams = request.toMap()
25+
assertEquals("10", queryParams["offset"])
26+
}
27+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.ctrlhub.core
2+
3+
import com.ctrlhub.core.datacapture.FormsRouter
4+
import io.ktor.client.*
5+
import io.ktor.client.engine.mock.*
6+
import io.ktor.http.*
7+
import kotlinx.coroutines.runBlocking
8+
import java.nio.file.Files
9+
import java.nio.file.Paths
10+
import kotlin.test.Test
11+
import kotlin.test.assertEquals
12+
13+
class PaginatedResponseTest {
14+
@Test
15+
fun `can retrieve pagination meta correctly`() {
16+
val jsonFilePath = Paths.get("src/test/resources/paginated-response.json")
17+
val jsonContent = Files.readString(jsonFilePath)
18+
19+
val mockEngine = MockEngine { request ->
20+
respond(
21+
content = jsonContent,
22+
status = HttpStatusCode.OK,
23+
headers = headersOf(HttpHeaders.ContentType, "application/json")
24+
)
25+
}
26+
27+
val formsRouter = FormsRouter(httpClient = HttpClient(mockEngine).configureForTest())
28+
29+
runBlocking {
30+
val response = formsRouter.all(organisationId = "123")
31+
assertEquals(1, response.pagination.page.currentPage)
32+
assertEquals(10, response.pagination.counts.resources)
33+
assertEquals(5, response.pagination.counts.pages)
34+
assertEquals(0, response.pagination.requested.offset)
35+
assertEquals(100, response.pagination.requested.limit)
36+
assertEquals(2, response.pagination.offsets.previous)
37+
assertEquals(3, response.pagination.offsets.next)
38+
}
39+
}
40+
}

src/test/kotlin/com/ctrlhub/core/assets/equipment/exposures/EquipmentExposuresRouterTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.ctrlhub.core.assets.equipment.exposures
22

3+
import com.ctrlhub.core.api.response.PaginatedList
34
import com.ctrlhub.core.assets.equipment.exposures.resource.EquipmentExposureResource
45
import com.ctrlhub.core.configureForTest
56
import io.ktor.client.HttpClient
@@ -33,8 +34,8 @@ class EquipmentExposuresRouterTest {
3334

3435
runBlocking {
3536
val response = exposuresRouter.all(organisationId = "000", equipmentId = "000")
36-
assertIs<List<EquipmentExposureResource>>(response)
37-
assertNotNull(response[0].id)
37+
assertIs<PaginatedList<EquipmentExposureResource>>(response)
38+
assertNotNull(response.data[0].id)
3839
}
3940
}
4041

src/test/kotlin/com/ctrlhub/core/governance/schemes/SchemesRouterTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.ctrlhub.core.governance.schemes
22

3+
import com.ctrlhub.core.api.response.PaginatedList
34
import com.ctrlhub.core.configureForTest
45
import com.ctrlhub.core.governance.schemes.response.Scheme
56
import io.ktor.client.HttpClient
@@ -40,8 +41,8 @@ class SchemesRouterTest {
4041
)
4142
))
4243

43-
assertIs<List<Scheme>>(response)
44-
assertNotNull(response[0].id)
44+
assertIs<PaginatedList<Scheme>>(response)
45+
assertNotNull(response.data[0].id)
4546
}
4647
}
4748

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
{
2+
"data": [
3+
{
4+
"id": "0c8ae6a0-5617-4205-b11d-9ae0e74e88b7",
5+
"type": "forms",
6+
"attributes": {
7+
"description": "...",
8+
"name": "Form 3",
9+
"status": "active"
10+
},
11+
"relationships": {
12+
"categories": {
13+
"data": [
14+
{
15+
"id": "0ebac71d-bb15-4936-9b65-2d8089050997",
16+
"type": "form-categories"
17+
}
18+
]
19+
},
20+
"organisation": {
21+
"data": {
22+
"id": "bc7b2a6f-3b0c-4f4c-b63e-25dca7bc656e",
23+
"type": "organisations"
24+
}
25+
},
26+
"schemas": {
27+
"data": []
28+
}
29+
},
30+
"meta": {
31+
"created_at": "2025-05-07T07:31:52.473Z",
32+
"updated_at": "2025-05-12T11:49:20.536Z",
33+
"counts": {
34+
"schemas": 0,
35+
"categories": 1
36+
}
37+
}
38+
}
39+
],
40+
"meta": {
41+
"pagination": {
42+
"current_page": 1,
43+
"counts": {
44+
"resources": 10,
45+
"pages": 5
46+
},
47+
"requested": {
48+
"offset": 0,
49+
"limit": 100
50+
},
51+
"offsets": {
52+
"previous": 2,
53+
"next": 3
54+
}
55+
},
56+
"features": {
57+
"params": {
58+
"include": {
59+
"options": null
60+
},
61+
"sort": {
62+
"default": "",
63+
"options": null
64+
}
65+
}
66+
}
67+
},
68+
"jsonapi": {
69+
"version": "1.0"
70+
}
71+
}

0 commit comments

Comments
 (0)