Skip to content

Commit 75184da

Browse files
authored
Merge pull request #8 from ctrl-hub/feat/retrieve-submissions
feat: implement FormSubmission and FormSubmissionVersionsRouter with …
2 parents 32bcff7 + f3fc562 commit 75184da

File tree

10 files changed

+670
-26
lines changed

10 files changed

+670
-26
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.ctrlhub.core.datacapture
2+
3+
import com.ctrlhub.core.Api
4+
import com.ctrlhub.core.datacapture.resource.FormSubmissionVersion
5+
import com.ctrlhub.core.datacapture.response.FormSchema
6+
import com.ctrlhub.core.router.Router
7+
import io.ktor.client.HttpClient
8+
import io.ktor.http.ContentType
9+
import com.ctrlhub.core.api.response.PaginatedList
10+
11+
class FormSubmissionVersionsRouter(httpClient: HttpClient) : Router(httpClient) {
12+
/**
13+
* Create a new form submission version
14+
*/
15+
suspend fun create(organisationId: String, formId: String, schemaId: String, payload: Map<String, Any>): FormSubmissionVersion {
16+
return postJsonApiResource(
17+
"/v3/orgs/$organisationId/data-capture/forms/$formId/submissions",
18+
requestBody = FormSubmissionVersion(
19+
payload = payload,
20+
id = "",
21+
schema = FormSchema(
22+
id = schemaId,
23+
rawSchema = null,
24+
)
25+
),
26+
queryParameters = emptyMap(),
27+
contentType = ContentType.parse("application/vnd.api+json"),
28+
FormSubmissionVersion::class.java,
29+
FormSchema::class.java
30+
)
31+
}
32+
33+
/**
34+
* Get all submission versions for a given form (paginated)
35+
*/
36+
suspend fun all(organisationId: String, formId: String, submissionId: String): PaginatedList<FormSubmissionVersion> {
37+
return fetchPaginatedJsonApiResources(
38+
"/v3/orgs/$organisationId/data-capture/forms/$formId/submissions/$submissionId/versions",
39+
queryParameters = emptyMap(),
40+
FormSubmissionVersion::class.java,
41+
FormSchema::class.java
42+
)
43+
}
44+
45+
/**
46+
* Get a single submission version
47+
*/
48+
suspend fun one(organisationId: String, formId: String, submissionId: String, versionId: String): FormSubmissionVersion {
49+
return fetchJsonApiResource(
50+
"/v3/orgs/$organisationId/data-capture/forms/$formId/submissions/$submissionId/versions/$versionId",
51+
queryParameters = emptyMap(),
52+
FormSubmissionVersion::class.java,
53+
FormSchema::class.java
54+
)
55+
}
56+
}
57+
58+
val Api.formSubmissionVersions
59+
get() = FormSubmissionVersionsRouter(httpClient = httpClient)

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

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
11
package com.ctrlhub.core.datacapture
22

33
import com.ctrlhub.core.Api
4-
import com.ctrlhub.core.datacapture.resource.FormSubmissionVersion
5-
import com.ctrlhub.core.datacapture.response.FormSchema
4+
import com.ctrlhub.core.datacapture.response.FormSubmission
5+
import com.ctrlhub.core.datacapture.response.Form
6+
import com.ctrlhub.core.iam.response.User
7+
import com.ctrlhub.core.projects.response.Organisation
68
import com.ctrlhub.core.router.Router
79
import io.ktor.client.HttpClient
8-
import io.ktor.http.ContentType
10+
import com.ctrlhub.core.api.response.PaginatedList
911

1012
class FormSubmissionsRouter(httpClient: HttpClient) : Router(httpClient) {
11-
suspend fun create(organisationId: String, formId: String, schemaId: String, payload: Map<String, Any>): FormSubmissionVersion {
12-
return postJsonApiResource("/v3/orgs/$organisationId/data-capture/forms/$formId/submissions", requestBody = FormSubmissionVersion(
13-
payload = payload,
14-
id = "",
15-
schema = FormSchema(
16-
id = schemaId,
17-
rawSchema = null,
18-
)
19-
), queryParameters = emptyMap(), contentType = ContentType.parse("application/vnd.api+json"), FormSubmissionVersion::class.java,
20-
FormSchema::class.java)
13+
/**
14+
* Get paginated form-submission resources (hydrated with relationships)
15+
*
16+
* @return PaginatedList of FormSubmission
17+
*/
18+
suspend fun all(organisationId: String, formId: String): PaginatedList<FormSubmission> {
19+
return fetchPaginatedJsonApiResources(
20+
"/v3/orgs/$organisationId/data-capture/forms/$formId/submissions",
21+
queryParameters = emptyMap<String, String>(),
22+
FormSubmission::class.java,
23+
User::class.java,
24+
Form::class.java,
25+
Organisation::class.java,
26+
com.ctrlhub.core.datacapture.resource.FormSubmissionVersion::class.java
27+
)
2128
}
2229
}
2330

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.ctrlhub.core.datacapture.response
2+
3+
import com.ctrlhub.core.datacapture.resource.FormSubmissionVersion
4+
import com.ctrlhub.core.projects.response.Organisation
5+
import com.ctrlhub.core.iam.response.User
6+
import com.fasterxml.jackson.annotation.JsonCreator
7+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
8+
import com.fasterxml.jackson.annotation.JsonProperty
9+
import com.github.jasminb.jsonapi.StringIdHandler
10+
import com.github.jasminb.jsonapi.annotations.Id
11+
import com.github.jasminb.jsonapi.annotations.Meta
12+
import com.github.jasminb.jsonapi.annotations.Relationship
13+
import com.github.jasminb.jsonapi.annotations.Type
14+
import java.time.LocalDateTime
15+
16+
@Type("form-submissions")
17+
@JsonIgnoreProperties(ignoreUnknown = true)
18+
class FormSubmission @JsonCreator constructor(
19+
@Id(StringIdHandler::class) var id: String = "",
20+
21+
@Relationship("contributors")
22+
var contributors: java.util.List<User>? = null,
23+
24+
@Relationship("creator")
25+
var creator: User? = null,
26+
27+
@Relationship("form")
28+
var form: Form? = null,
29+
30+
@Relationship("organisation")
31+
var organisation: Organisation? = null,
32+
33+
@Relationship("versions")
34+
var versions: java.util.List<FormSubmissionVersion>? = null,
35+
36+
@Meta
37+
var meta: FormSubmissionMeta? = null
38+
) {
39+
constructor(): this(id = "")
40+
}
41+
42+
@JsonIgnoreProperties(ignoreUnknown = true)
43+
class FormSubmissionMeta @JsonCreator constructor(
44+
@JsonProperty("created_at") var createdAt: LocalDateTime? = null,
45+
@JsonProperty("modified_at") var modifiedAt: LocalDateTime? = null,
46+
@JsonProperty("counts") var counts: FormSubmissionCounts? = null
47+
)
48+
49+
@JsonIgnoreProperties(ignoreUnknown = true)
50+
data class FormSubmissionCounts(
51+
@JsonProperty("versions") val versions: Int = 0,
52+
@JsonProperty("contributors") val contributors: Int = 0
53+
)
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.ctrlhub.core.datacapture
2+
3+
import com.ctrlhub.core.configureForTest
4+
import com.ctrlhub.core.datacapture.resource.FormSubmissionVersion
5+
import com.ctrlhub.core.api.response.PaginatedList
6+
import io.ktor.client.*
7+
import io.ktor.client.engine.mock.*
8+
import io.ktor.http.*
9+
import kotlinx.coroutines.runBlocking
10+
import org.junit.jupiter.api.Test
11+
import java.nio.file.Files
12+
import java.nio.file.Paths
13+
import kotlin.test.assertEquals
14+
import kotlin.test.assertIs
15+
import kotlin.test.assertNotNull
16+
import kotlin.test.assertTrue
17+
18+
class FormSubmissionVersionsRouterTest {
19+
20+
@Test
21+
fun `can create form submission versions`() {
22+
val jsonFilePath = Paths.get("src/test/resources/datacapture/form-submission-version-response.json")
23+
val jsonContent = Files.readString(jsonFilePath)
24+
25+
val mockEngine = MockEngine { _ ->
26+
respond(
27+
content = jsonContent,
28+
status = HttpStatusCode.Created,
29+
headers = headersOf(HttpHeaders.ContentType, "application/vnd.api+json")
30+
)
31+
}
32+
33+
val formSubmissionVersionsRouter = FormSubmissionVersionsRouter(httpClient = HttpClient(mockEngine).configureForTest())
34+
35+
runBlocking {
36+
val result = formSubmissionVersionsRouter.create(
37+
organisationId = "org-123",
38+
formId = "form-456",
39+
schemaId = "schema-789",
40+
payload = mapOf("Test" to "value")
41+
)
42+
43+
assertIs<FormSubmissionVersion>(result)
44+
assertNotNull(result.id)
45+
}
46+
}
47+
48+
@Test
49+
fun `can get all form submission versions and hydrate`() {
50+
val jsonFilePath = Paths.get("src/test/resources/datacapture/all-form-submission-versions-response.json")
51+
val jsonContent = Files.readString(jsonFilePath)
52+
53+
val mockEngine = MockEngine { _ ->
54+
respond(
55+
content = jsonContent,
56+
status = HttpStatusCode.OK,
57+
headers = headersOf(HttpHeaders.ContentType, "application/vnd.api+json")
58+
)
59+
}
60+
61+
val formSubmissionVersionsRouter = FormSubmissionVersionsRouter(httpClient = HttpClient(mockEngine).configureForTest())
62+
63+
runBlocking {
64+
val result = formSubmissionVersionsRouter.all(
65+
organisationId = "org-123",
66+
formId = "form-456",
67+
submissionId = "d3c2b1a0-9f8e-7d6c-5b4a-3e2f1d0c9b8a"
68+
)
69+
70+
// verify the paginated wrapper
71+
assertNotNull(result)
72+
assertTrue(result.data.isNotEmpty())
73+
74+
// verify hydration of the first item
75+
val first = result.data.first()
76+
assertIs<FormSubmissionVersion>(first)
77+
assertNotNull(first.id)
78+
}
79+
}
80+
81+
@Test
82+
fun `can get one form submission version and hydrate`() {
83+
val jsonFilePath = Paths.get("src/test/resources/datacapture/one-form-submission-version-response.json")
84+
val jsonContent = Files.readString(jsonFilePath)
85+
86+
val mockEngine = MockEngine { _ ->
87+
respond(
88+
content = jsonContent,
89+
status = HttpStatusCode.OK,
90+
headers = headersOf(HttpHeaders.ContentType, "application/vnd.api+json")
91+
)
92+
}
93+
94+
val formSubmissionVersionsRouter = FormSubmissionVersionsRouter(httpClient = HttpClient(mockEngine).configureForTest())
95+
96+
runBlocking {
97+
val result = formSubmissionVersionsRouter.one(
98+
organisationId = "org-123",
99+
formId = "form-456",
100+
submissionId = "d4c3b2a1-0f9e-8d7c-6b5a-4e3f2d1c0b9a",
101+
versionId = "4a8b7c6d-2e3f-4a1b-9c2d-0e1f2a3b4c5d"
102+
)
103+
104+
assertIs<FormSubmissionVersion>(result)
105+
assertEquals("4a8b7c6d-2e3f-4a1b-9c2d-0e1f2a3b4c5d", result.id)
106+
// check payload exists and contains expected keys
107+
assertNotNull(result.payload)
108+
assertTrue(result.payload!!.containsKey("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"))
109+
assertTrue(result.payload!!.containsKey("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"))
110+
}
111+
}
112+
}
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.ctrlhub.core.datacapture
22

33
import com.ctrlhub.core.configureForTest
4-
import com.ctrlhub.core.datacapture.resource.FormSubmissionVersion
4+
import com.ctrlhub.core.datacapture.response.FormSubmission
5+
import com.ctrlhub.core.api.response.PaginatedList
56
import io.ktor.client.*
67
import io.ktor.client.engine.mock.*
78
import io.ktor.http.*
@@ -16,30 +17,29 @@ import kotlin.test.assertIs
1617
class FormSubmissionsRouterTest {
1718

1819
@Test
19-
fun `can create form submission`() {
20-
val jsonFilePath = Paths.get("src/test/resources/datacapture/form-submission-response.json")
20+
fun `can retrieve and hydrate submissions`() {
21+
val jsonFilePath = Paths.get("src/test/resources/datacapture/all-form-submissions-response.json")
2122
val jsonContent = Files.readString(jsonFilePath)
2223

2324
val mockEngine = MockEngine { request ->
2425
respond(
2526
content = jsonContent,
26-
status = HttpStatusCode.Created,
27+
status = HttpStatusCode.OK,
2728
headers = headersOf(HttpHeaders.ContentType, "application/vnd.api+json")
2829
)
2930
}
3031

3132
val formSubmissionsRouter = FormSubmissionsRouter(httpClient = HttpClient(mockEngine).configureForTest())
3233

3334
runBlocking {
34-
val result = formSubmissionsRouter.create(
35-
organisationId = "org-123",
36-
formId = "form-456",
37-
schemaId = "schema-789",
38-
payload = mapOf("Test" to "value")
39-
)
35+
val response = formSubmissionsRouter.all(organisationId = "org-123", formId = "form-456")
4036

41-
assertIs<FormSubmissionVersion>(result)
42-
assertNotNull(result.id)
37+
assertIs<PaginatedList<FormSubmission>>(response)
38+
assertEquals(4, response.data.size)
39+
assertNotNull(response.data[0].id)
40+
// verify pagination counts parsed from meta
41+
assertEquals(4, response.pagination.counts.resources)
42+
assertEquals(1, response.pagination.counts.pages)
4343
}
4444
}
4545
}

0 commit comments

Comments
 (0)