Skip to content

Commit

Permalink
feat: date range and big data support for sql reports (#17)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomwwinter authored Jul 23, 2024
1 parent 28ba520 commit c86abde
Show file tree
Hide file tree
Showing 55 changed files with 999 additions and 633 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import org.springframework.boot.runApplication
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import java.util.*

@SpringBootApplication
@ConfigurationPropertiesScan
@EnableScheduling
class Application

fun main(args: Array<String>) {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"))

runApplication<Application>(*args)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,187 +3,65 @@ package com.aamdigital.aambackendservice.couchdb.core
import com.aamdigital.aambackendservice.couchdb.dto.CouchDbChangesResponse
import com.aamdigital.aambackendservice.couchdb.dto.DocSuccess
import com.aamdigital.aambackendservice.couchdb.dto.FindResponse
import com.aamdigital.aambackendservice.error.InternalServerException
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
import org.slf4j.LoggerFactory
import org.springframework.core.ParameterizedTypeReference
import org.springframework.core.io.buffer.DataBuffer
import org.springframework.http.HttpHeaders
import org.springframework.http.MediaType
import org.springframework.util.MultiValueMap
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.ClientResponse
import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import kotlin.reflect.KClass

class CouchDbClient(
private val webClient: WebClient,
private val objectMapper: ObjectMapper
) : CouchDbStorage {
interface CouchDbClient {
fun allDatabases(): Mono<List<String>>
fun changes(database: String, queryParams: MultiValueMap<String, String>): Mono<CouchDbChangesResponse>

private val logger = LoggerFactory.getLogger(javaClass)

companion object {
private const val CHANGES_URL = "/_changes"
private const val FIND_URL = "/_find"
}

override fun allDatabases(): Mono<List<String>> {
return webClient
.get()
.uri("/_all_dbs")
.accept(MediaType.APPLICATION_JSON)
.exchangeToMono { response ->
response.bodyToMono(object : ParameterizedTypeReference<List<String>>() {})
}
}

override fun changes(
database: String, queryParams: MultiValueMap<String, String>
): Mono<CouchDbChangesResponse> {
return webClient.get().uri {
it.path("/$database/$CHANGES_URL")
it.queryParams(queryParams)
it.build()
}.accept(MediaType.APPLICATION_JSON).exchangeToMono { response ->
response.bodyToMono(CouchDbChangesResponse::class.java).mapNotNull {
it
}
}
}

override fun <T : Any> find(
database: String, body: Map<String, Any>, queryParams: MultiValueMap<String, String>, kClass: KClass<T>
): Mono<FindResponse<T>> {
return webClient.post().uri {
it.path("/$database/$FIND_URL")
it.queryParams(queryParams)
it.build()
}.contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(body)).exchangeToMono {
it.bodyToMono(ObjectNode::class.java).map { objectNode ->
val data =
(objectMapper.convertValue(objectNode, Map::class.java)["docs"] as Iterable<*>).map { entry ->
objectMapper.convertValue(entry, kClass.java)
}

FindResponse(docs = data)
}
}
}
fun <T : Any> find(
database: String,
body: Map<String, Any>,
queryParams: MultiValueMap<String, String> = getEmptyQueryParams(),
kClass: KClass<T>
): Mono<FindResponse<T>>

override fun headDatabaseDocument(
fun headDatabaseDocument(
database: String,
documentId: String,
): Mono<HttpHeaders> {
return webClient
.head()
.uri {
it.path("/$database/$documentId")
it.build()
}
.accept(MediaType.APPLICATION_JSON).exchangeToMono {
if (it.statusCode().is2xxSuccessful) {
Mono.just(it.headers().asHttpHeaders())
} else if (it.statusCode().is4xxClientError) {
Mono.just(HttpHeaders())
} else {
throw InternalServerException()
}
}
}
): Mono<HttpHeaders>

override fun <T : Any> getDatabaseDocument(
fun <T : Any> getDatabaseDocument(
database: String,
documentId: String,
queryParams: MultiValueMap<String, String>,
queryParams: MultiValueMap<String, String> = getEmptyQueryParams(),
kClass: KClass<T>,
): Mono<T> {
return webClient.get().uri {
it.path("/$database/$documentId")
it.queryParams(queryParams)
it.build()
}.accept(MediaType.APPLICATION_JSON).exchangeToMono {
handleResponse(it, kClass)
}
}
): Mono<T>

override fun putDatabaseDocument(
fun putDatabaseDocument(
database: String,
documentId: String,
body: Any,
): Mono<DocSuccess> {
return headDatabaseDocument(
database = database,
documentId = documentId
).flatMap { httpHeaders ->
val etag = httpHeaders.eTag?.replace("\"", "")

webClient.put().uri {
it.path("/$database/$documentId")
it.build()
}.body(BodyInserters.fromValue(body)).headers {
if (etag.isNullOrBlank().not()) {
it.set("If-Match", etag)
}
}.accept(MediaType.APPLICATION_JSON).exchangeToMono {
handleResponse(it, DocSuccess::class)
}
}
}
body: Any
): Mono<DocSuccess>

override fun <T : Any> getPreviousDocRev(
fun <T : Any> getPreviousDocRev(
database: String,
documentId: String,
rev: String,
kClass: KClass<T>,
): Mono<T> {
val allRevsInfoQueryParams = getEmptyQueryParams()
allRevsInfoQueryParams.set("revs_info", "true")

return getDatabaseDocument(
database = database,
documentId = documentId,
queryParams = allRevsInfoQueryParams,
kClass = ObjectNode::class
).flatMap { currentDoc ->
val revInfo = currentDoc.get("_revs_info") ?: return@flatMap Mono.empty()

if (!revInfo.isArray) {
return@flatMap Mono.empty()
}

val revIndex = revInfo.indexOfFirst { jsonNode -> jsonNode.get("rev").textValue().equals(rev) }
): Mono<T>

if (revIndex == -1) {
return@flatMap Mono.empty()
}

if (revIndex + 1 >= revInfo.size()) {
return@flatMap Mono.empty()
}

val previousRef = revInfo.get(revIndex + 1).get("rev").textValue()

val previousRevQueryParams = getEmptyQueryParams()
previousRevQueryParams.set("rev", previousRef)
fun headAttachment(
database: String,
documentId: String,
attachmentId: String,
): Mono<HttpHeaders>

getDatabaseDocument(
database = database,
documentId = documentId,
queryParams = previousRevQueryParams,
kClass = ObjectNode::class
).map { previousDoc ->
objectMapper.convertValue(previousDoc, kClass.java)
}
}
}
fun getAttachment(
database: String,
documentId: String,
attachmentId: String,
): Flux<DataBuffer>

private fun <T : Any> handleResponse(
response: ClientResponse, typeReference: KClass<T>
): Mono<T> {
return response.bodyToMono(typeReference.java).mapNotNull {
it
}
}
fun putAttachment(
database: String,
documentId: String,
attachmentId: String,
file: Flux<DataBuffer>
): Mono<DocSuccess>
}

This file was deleted.

Loading

0 comments on commit c86abde

Please sign in to comment.