From d321ed85c961f86885bd2bb723dfc06588a0aee8 Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Sat, 19 Aug 2023 03:29:01 +0200 Subject: [PATCH] Dump commit --- .../main/kotlin/twomartens.kotlin.gradle.kts | 13 +- gradle/libs.versions.toml | 7 +- .../twomartens/wahlrecht/MainApplication.kt | 2 + .../controller/v1/CalculationController.kt | 5 +- .../controller/v1/ElectionController.kt | 145 +++++++++-------- .../controller/v1/ElectionResultController.kt | 115 +++++++------ .../controller/v1/PartyController.kt | 152 ++++++++++++------ .../wahlrecht/model/db/Nomination.kt | 31 ++-- .../repository/CandidateRepository.kt | 29 +++- .../repository/ConstituencyRepository.kt | 25 ++- .../repository/ElectionRepository.kt | 25 ++- .../repository/ElectionResultRepository.kt | 23 ++- .../repository/NominationRepository.kt | 29 +++- .../wahlrecht/repository/PartyRepository.kt | 28 +++- .../wahlrecht/service/CalculationService.kt | 8 +- .../wahlrecht/service/CandidateService.kt | 32 +--- .../wahlrecht/service/ConstituencyService.kt | 34 +--- .../service/ElectionResultService.kt | 47 ++---- .../wahlrecht/service/ElectionService.kt | 61 +++---- .../wahlrecht/service/NominationService.kt | 74 +++------ .../wahlrecht/service/PartyService.kt | 68 ++------ 21 files changed, 517 insertions(+), 436 deletions(-) diff --git a/buildSrc/src/main/kotlin/twomartens.kotlin.gradle.kts b/buildSrc/src/main/kotlin/twomartens.kotlin.gradle.kts index 3b58353..b519c43 100644 --- a/buildSrc/src/main/kotlin/twomartens.kotlin.gradle.kts +++ b/buildSrc/src/main/kotlin/twomartens.kotlin.gradle.kts @@ -1,18 +1,19 @@ import org.gradle.accessors.dm.LibrariesForLibs plugins { - kotlin("jvm") + kotlin("jvm") } val libs = the() dependencies { - implementation(libs.kotlin.logging) - implementation(kotlin("reflect")) + implementation(libs.kotlin.logging) + implementation(libs.kotlin.reactor) + implementation(kotlin("reflect")) } kotlin { - compilerOptions { - freeCompilerArgs.add("-Xjvm-default=all") - } + compilerOptions { + freeCompilerArgs.add("-Xjvm-default=all") + } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 06c7c5e..f43f291 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,7 @@ keycloak = "22.0.1" kotlin-logging = "3.0.5" kotlin-reflect = "1.9.0" kotlin-lombok = "1.9.0" +kotlin-reactor = "1.2.2" plugin-nebula-release = "17.2.2" plugin-lombok = "8.0.1" plugin-gradle-versions = "0.46.0" @@ -31,10 +32,12 @@ spring-boot-web = { module = "org.springframework.boot:spring-boot-starter-web" spring-boot-log4j = { module = "org.springframework.boot:spring-boot-starter-log4j2" } spring-boot-thymeleaf = { module = "org.springframework.boot:spring-boot-starter-thymeleaf" } spring-boot-mongo = { module = "org.springframework.boot:spring-boot-starter-data-mongodb" } +spring-boot-mongo-reactive = { module = "org.springframework.boot:spring-boot-starter-data-mongodb-reactive" } spring-boot-validation = { module = "org.springframework.boot:spring-boot-starter-validation" } spring-boot-test = { module = "org.springframework.boot:spring-boot-starter-test" } spring-boot-security = { module = "org.springframework.boot:spring-boot-starter-security" } spring-boot-oauth2-resource-server = { module = "org.springframework.boot:spring-boot-starter-oauth2-resource-server" } +spring-boot-webflux = { module = "org.springframework.boot:spring-boot-starter-webflux" } spring-boot-config = { module = "org.springframework.boot:spring-boot-configuration-processor", version.ref = "spring-boot" } spring-cloud = { module = "org.springframework.cloud:spring-cloud-dependencies", version.ref = "spring-cloud" } spring-cloud-starter = { module = "org.springframework.cloud:spring-cloud-starter" } @@ -78,6 +81,7 @@ mockito-inline = "org.mockito:mockito-inline:5.2.0" mockito-junit = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" } kotlin-logging = { module = "io.github.microutils:kotlin-logging-jvm", version.ref = "kotlin-logging" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin-reflect" } +kotlin-reactor = { module = "io.projectreactor.kotlin:reactor-kotlin-extensions", version.ref = "kotlin.reactor" } plugin-nebula-release = { module = "com.netflix.nebula:nebula-release-plugin", version.ref = "plugin-nebula-release" } plugin-springboot = { module = "org.springframework.boot:spring-boot-gradle-plugin", version.ref = "spring-boot" } plugin-lombok = { module = "io.freefair.gradle:lombok-plugin", version.ref = "plugin-lombok" } @@ -112,10 +116,11 @@ spring-boot = [ spring-boot-server = [ "spring-boot-actuator", "spring-boot-log4j", - "spring-boot-mongo", + "spring-boot-mongo-reactive", "spring-boot-thymeleaf", "spring-boot-validation", "spring-boot-web", + "spring-boot-webflux", "spring-cloud-starter", "spring-sec", "spring-ui", diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/MainApplication.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/MainApplication.kt index c65ae75..1124158 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/MainApplication.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/MainApplication.kt @@ -3,11 +3,13 @@ package de.twomartens.wahlrecht import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication import org.springframework.data.mongodb.config.EnableMongoAuditing +import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories import org.springframework.scheduling.annotation.EnableScheduling @EnableMongoAuditing @EnableScheduling @SpringBootApplication +@EnableReactiveMongoRepositories open class MainApplication fun main(args: Array) { diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/CalculationController.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/CalculationController.kt index 059a8ff..fc347e9 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/CalculationController.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/CalculationController.kt @@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import reactor.core.publisher.Mono @RestController @RequestMapping(value = ["/wahlrecht/v1"]) @@ -47,12 +48,12 @@ class CalculationController(private val service: CalculationService) { @SecurityRequirement(name = "oauth2") fun calculateResult( @RequestBody electionResult: ElectionResult - ): ResponseEntity { + ): ResponseEntity> { val result = electedCandidatesMapper.mapToExternal( service.determineElectedCandidates(electionResultMapper.mapToInternal(electionResult)) ) return ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) - .body(result) + .body(Mono.just(result)) } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionController.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionController.kt index 97f0791..0c88083 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionController.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionController.kt @@ -12,80 +12,97 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.security.SecurityRequirement import io.swagger.v3.oas.annotations.tags.Tag import org.mapstruct.factory.Mappers -import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import org.springframework.web.util.UriComponentsBuilder +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.util.function.Tuples @RestController @RequestMapping(value = ["/wahlrecht/v1"]) @Tag(name = "Elections", description = "all requests relating to elections") class ElectionController(private val service: ElectionService) { - private val mapper = Mappers.getMapper(ElectionMapper::class.java) + private val mapper = Mappers.getMapper(ElectionMapper::class.java) - @SecurityRequirement(name = "oauth2") - @SecurityRequirement(name = "bearerAuth") - @GetMapping(value = ["/elections"], produces = [MediaType.APPLICATION_JSON_VALUE]) - @Operation( - summary = "Returns all stored elections", - responses = [ApiResponse( - responseCode = "200", - description = "Returns all stored elections", - content = [Content( - mediaType = "application/json", - array = ArraySchema( - schema = Schema( - implementation = Election::class - ) - ) - )] - )] - ) - fun getElections(): ResponseEntity> { - val elections = service.elections.asSequence() - .map { mapper.mapToExternal(it.value) } - .toList() - return ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(elections) + @SecurityRequirement(name = "oauth2") + @SecurityRequirement(name = "bearerAuth") + @GetMapping(value = ["/elections"]) + @Operation( + summary = "Returns all stored elections", + responses = [ApiResponse( + responseCode = "200", + description = "Returns all stored elections", + content = [Content( + mediaType = "application/json", + array = ArraySchema( + schema = Schema( + implementation = Election::class + ) + ) + )] + )] + ) + fun getElections(): Flux { + val elections = service.getElections() + .map { + mapper.mapToExternal(it) } + return elections + } - @Operation( - summary = "Returns election matching provided name", - responses = [ApiResponse( - responseCode = "200", - description = "Returns matching election", - content = [Content(mediaType = "application/json", schema = Schema(implementation = Election::class))] - )] - ) - @GetMapping(value = ["/elections/by-name/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE]) - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - fun getElectionByName( - @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String? - ): ResponseEntity { - val election = mapper.mapToExternal(service.elections[electionName]!!) - return ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(election) - } + @Operation( + summary = "Returns election matching provided name", + responses = [ApiResponse( + responseCode = "200", + description = "Returns matching election", + content = [Content(mediaType = "application/json", schema = Schema(implementation = Election::class))] + )] + ) + @GetMapping(value = ["/elections/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE]) + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun getElectionByName( + @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String + ): ResponseEntity> { + val election = service.findElectionByName(electionName) + .map { mapper.mapToExternal(it) } + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(election) + } - @Operation( - summary = "Stores a new election", - responses = [ApiResponse( - responseCode = "201", - description = "Election was created" - ), ApiResponse(responseCode = "200", description = "Election was modified"), ApiResponse( - responseCode = "401", - description = "Unauthorized", - content = [Content(mediaType = "plain/text")] - )] - ) - @PutMapping("/election") - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - fun putElection(@RequestBody election: Election?): ResponseEntity<*> { - val createdNew = service.storeElection(mapper.mapToDB(election!!)) - return if (createdNew) ResponseEntity(HttpStatus.CREATED) else ResponseEntity(HttpStatus.OK) - } + @Operation( + summary = "Stores a new election", + responses = [ApiResponse( + responseCode = "201", + description = "Election was created" + ), ApiResponse(responseCode = "200", description = "Election was modified"), ApiResponse( + responseCode = "401", + description = "Unauthorized", + content = [Content(mediaType = "plain/text")] + )] + ) + @PutMapping("/election") + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun putElection(@RequestBody election: Election, uriComponentsBuilder: UriComponentsBuilder): Mono> { + return service.storeElection(mapper.mapToDB(election)) + .map { tuple -> + Tuples.of(tuple.t1, mapper.mapToExternal(tuple.t2)) + } + .map { tuple -> + if (tuple.t1) { + ResponseEntity.created(uriComponentsBuilder.path("/elections/{electionName}") + .buildAndExpand(tuple.t2.name).toUri()) + .contentType(MediaType.APPLICATION_JSON) + .body(tuple.t2) + } else { + ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(tuple.t2) + } + } + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionResultController.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionResultController.kt index 091ce84..4ace049 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionResultController.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionResultController.kt @@ -14,10 +14,12 @@ import io.swagger.v3.oas.annotations.tags.Tag import lombok.RequiredArgsConstructor import lombok.extern.slf4j.Slf4j import org.mapstruct.factory.Mappers -import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import org.springframework.web.util.UriComponentsBuilder +import reactor.core.publisher.Mono +import reactor.util.function.Tuples @Slf4j @RestController @@ -25,51 +27,72 @@ import org.springframework.web.bind.annotation.* @RequestMapping(value = ["/wahlrecht/v1"]) @Tag(name = "ElectionResults", description = "all requests relating to election results") class ElectionResultController(private val service: ElectionResultService) { - private val mapper = Mappers.getMapper(ElectionResultMapper::class.java) + private val mapper = Mappers.getMapper(ElectionResultMapper::class.java) - @Operation( - summary = "Returns election result for given election name", - responses = [ApiResponse( - responseCode = "200", - description = "Returns stored election result for given election name", - content = [Content(mediaType = "application/json", schema = Schema(implementation = ElectionResult::class))] - ), ApiResponse( - responseCode = "404", - description = "No election result found", - content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorMessage::class))] - )] - ) - @GetMapping( - value = ["/electionResult/by-election-name/{electionName}"], - produces = [MediaType.APPLICATION_JSON_VALUE] - ) - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - fun getElectionByName( - @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String - ): ResponseEntity { - val result = mapper.mapToExternal(service.getElectionResult(electionName)) - return ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(result) - } + @Operation( + summary = "Returns election result for given election name", + responses = [ApiResponse( + responseCode = "200", + description = "Returns stored election result for given election name", + content = [Content(mediaType = "application/json", schema = Schema(implementation = ElectionResult::class))] + ), ApiResponse( + responseCode = "404", + description = "No election result found", + content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorMessage::class))] + )] + ) + @GetMapping( + value = ["/electionResult/{electionName}"], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun getElectionByName( + @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String + ): Mono> { + return service.getElectionResult(electionName) + .map { + mapper.mapToExternal(it) + } + .map { + ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(it) + } + } - @Operation( - summary = "Stores a new election", - responses = [ApiResponse( - responseCode = "201", - description = "Election was created" - ), ApiResponse(responseCode = "200", description = "Election was modified"), ApiResponse( - responseCode = "401", - description = "Unauthorized", - content = [Content(mediaType = "plain/text")] - )] - ) - @PutMapping("/electionResult") - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - fun putElection(@RequestBody electionResult: ElectionResult?): ResponseEntity<*> { - val createdNew = service.storeResult(mapper.mapToDB(electionResult!!)) - return if (createdNew) ResponseEntity(HttpStatus.CREATED) else ResponseEntity(HttpStatus.OK) - } + @Operation( + summary = "Stores a new election", + responses = [ApiResponse( + responseCode = "201", + description = "Election was created" + ), ApiResponse(responseCode = "200", description = "Election was modified"), ApiResponse( + responseCode = "401", + description = "Unauthorized", + content = [Content(mediaType = "plain/text")] + )] + ) + @PutMapping("/electionResult") + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun putElection(@RequestBody electionResult: ElectionResult, + uriComponentsBuilder: UriComponentsBuilder): Mono> { + val storedElection = service.storeResult(mapper.mapToDB(electionResult)) + return storedElection + .map { tuple -> + Tuples.of(tuple.t1, mapper.mapToExternal(tuple.t2)) + } + .map { tuple -> + if (tuple.t1) { + ResponseEntity.created(uriComponentsBuilder.path("/electionResult/{electionName}") + .buildAndExpand(tuple.t2.electionName).toUri()) + .contentType(MediaType.APPLICATION_JSON) + .body(tuple.t2) + } else { + ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(tuple.t2) + } + } + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/PartyController.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/PartyController.kt index bea793b..af7aa0c 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/PartyController.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/PartyController.kt @@ -13,65 +13,115 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.security.SecurityRequirement import io.swagger.v3.oas.annotations.tags.Tag import org.mapstruct.factory.Mappers -import org.springframework.http.HttpStatus import org.springframework.http.MediaType import org.springframework.http.ResponseEntity +import org.springframework.http.ResponseEntity.created +import org.springframework.http.ResponseEntity.ok import org.springframework.stereotype.Controller import org.springframework.web.bind.annotation.* +import org.springframework.web.util.UriComponentsBuilder +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.util.function.Tuples @Controller @RequestMapping(value = ["/wahlrecht/v1"]) @Tag(name = "Parties", description = "all requests relating to parties") class PartyController(private val service: PartyService) { - private val mapper = Mappers.getMapper(PartyInElectionMapper::class.java) - @Operation( - summary = "Returns all stored parties for given election name", - responses = [ApiResponse( - responseCode = "200", - description = "Returns all stored parties", - content = [Content( - mediaType = "application/json", - array = ArraySchema(schema = Schema(implementation = PartyInElection::class)) - )] - ), ApiResponse( - responseCode = "404", - description = "No parties found", - content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorMessage::class))] - )] - ) - @GetMapping(value = ["/parties/by-election-name/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE]) - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - fun getPartiesByElectionName( - @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String - ): ResponseEntity> { - val parties = service.getPartiesByElectionName(electionName).stream() - .map { election: de.twomartens.wahlrecht.model.db.PartyInElection? -> - mapper.mapToExternal( - election!! - ) - } - .toList() - return ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(parties) - } + private val mapper = Mappers.getMapper(PartyInElectionMapper::class.java) - @Operation( - summary = "Stores a new party in an election", - responses = [ApiResponse( - responseCode = "201", - description = "Party was created" - ), ApiResponse(responseCode = "200", description = "Party was modified"), ApiResponse( - responseCode = "401", - description = "Unauthorized" - )] - ) - @PutMapping("/party") - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - fun putParty(@RequestBody party: PartyInElection?): ResponseEntity<*> { - val createdNew = service.storeParty(mapper.mapToDB(party!!)) - return if (createdNew) ResponseEntity(HttpStatus.CREATED) else ResponseEntity(HttpStatus.OK) - } + @Operation( + summary = "Returns all stored parties for given election name", + responses = [ApiResponse( + responseCode = "200", + description = "Returns all stored parties", + content = [Content( + mediaType = "application/json", + array = ArraySchema(schema = Schema(implementation = PartyInElection::class)) + )] + ), ApiResponse( + responseCode = "404", + description = "No parties found", + content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorMessage::class))] + )] + ) + @GetMapping(value = ["/parties/by-election-name/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE]) + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun getPartiesByElectionName( + @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String + ): ResponseEntity> { + val parties = service.getPartiesByElectionName(electionName) + .map { election: de.twomartens.wahlrecht.model.db.PartyInElection -> + mapper.mapToExternal(election) + } + return ok() + .contentType(MediaType.APPLICATION_JSON) + .body(parties) + } + + @Operation( + summary = "Returns party stored for given election name and abbreviation", + responses = [ApiResponse( + responseCode = "200", + description = "Returns stored party", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = PartyInElection::class) + )] + ), ApiResponse( + responseCode = "404", + description = "No party found", + content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorMessage::class))] + )] + ) + @GetMapping(value = ["/parties/{electionName}/{abbreviation}"], produces = [MediaType.APPLICATION_JSON_VALUE]) + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun getPartyByElectionNameAndAbbreviation( + @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String, + @PathVariable @Parameter(description = "the party abbreviation", example = "SPD") abbreviation: String + ): ResponseEntity> { + val party = service.getPartyByElectionNameAndAbbreviation(electionName, abbreviation) + .map { stored: de.twomartens.wahlrecht.model.db.PartyInElection -> + mapper.mapToExternal(stored) + } + return ok() + .contentType(MediaType.APPLICATION_JSON) + .body(party) + } + + @Operation( + summary = "Stores a new party in an election", + responses = [ApiResponse( + responseCode = "201", + description = "Party was created" + ), ApiResponse(responseCode = "200", description = "Party was modified"), ApiResponse( + responseCode = "401", + description = "Unauthorized" + )] + ) + @PutMapping("/party") + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun putParty(@RequestBody party: PartyInElection, + uriComponentsBuilder: UriComponentsBuilder): Mono> { + val storedParty = service.storeParty(mapper.mapToDB(party)) + return storedParty + .map { tuple -> + Tuples.of(tuple.t1, mapper.mapToExternal(tuple.t2)) + } + .map { tuple -> + if (tuple.t1) { + created(uriComponentsBuilder.path("/parties/{electionName}/{abbreviation}") + .buildAndExpand(tuple.t2.electionName, tuple.t2.abbreviation).toUri()) + .contentType(MediaType.APPLICATION_JSON) + .body(tuple.t2) + } else { + ok() + .contentType(MediaType.APPLICATION_JSON) + .body(tuple.t2) + } + } + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Nomination.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Nomination.kt index e5eaa68..cc6202a 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Nomination.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Nomination.kt @@ -7,27 +7,30 @@ import org.springframework.data.annotation.Id import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.mongodb.core.index.CompoundIndex import org.springframework.data.mongodb.core.mapping.Document +import org.springframework.data.mongodb.core.mapping.DocumentReference import java.time.Instant @Document @EqualsAndHashCode @CompoundIndex(def = "{'name': 1, 'partyAbbreviation': 1, 'electionName': 1}") class Nomination { - @EqualsAndHashCode.Exclude - @Id - var id: ObjectId? = null + @EqualsAndHashCode.Exclude + @Id + var id: ObjectId? = null - @EqualsAndHashCode.Exclude - @CreatedDate - var created: Instant? = null + @EqualsAndHashCode.Exclude + @CreatedDate + var created: Instant? = null - @EqualsAndHashCode.Exclude - @LastModifiedDate - var lastModified: Instant? = null + @EqualsAndHashCode.Exclude + @LastModifiedDate + var lastModified: Instant? = null - var name: String = "" - var partyAbbreviation: String = "" - var electionName: String = "" - var candidates: Collection = emptyList() - var supportVotesOnNomination = false + var name: String = "" + var partyAbbreviation: String = "" + var electionName: String = "" + + @DocumentReference + var candidates: Collection = emptyList() + var supportVotesOnNomination = false } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/CandidateRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/CandidateRepository.kt index f36e3c3..f9090ed 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/CandidateRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/CandidateRepository.kt @@ -1,6 +1,31 @@ package de.twomartens.wahlrecht.repository import de.twomartens.wahlrecht.model.db.Candidate -import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.data.mongodb.repository.ReactiveMongoRepository +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono -interface CandidateRepository : MongoRepository \ No newline at end of file +interface CandidateRepository : ReactiveMongoRepository { + fun findByName(name: String): Mono + + fun findAndModify(new: Candidate): Mono { + val existing = findByName(new.name) + return existing.hasElement().flatMap { hasElement -> + if (hasElement) { + existing.flatMap { old -> + old.profession = new.profession + save(old) + } + } else { + save(new) + } + } + } + + fun findAllAndModify(new: Collection): Flux { + val existing = new.map { + findAndModify(it) + } + return Flux.merge(existing) + } +} \ No newline at end of file diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ConstituencyRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ConstituencyRepository.kt index 3ee0dba..b37c76a 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ConstituencyRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ConstituencyRepository.kt @@ -1,6 +1,27 @@ package de.twomartens.wahlrecht.repository import de.twomartens.wahlrecht.model.db.Constituency -import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.data.mongodb.repository.ReactiveMongoRepository +import reactor.core.publisher.Mono -interface ConstituencyRepository : MongoRepository +interface ConstituencyRepository : ReactiveMongoRepository { + fun findByElectionNameAndNumber(electionName: String, number: Int): Mono + + fun findAndModify(new: Constituency): Mono { + val existing = findByElectionNameAndNumber( + new.electionName, + new.number) + + return existing.hasElement().flatMap { hasElement -> + if (hasElement) { + existing.flatMap { old -> + old.name = new.name + old.numberOfSeats = new.numberOfSeats + save(old) + } + } else { + save(new) + } + } + } +} diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionRepository.kt index a4218ba..dabf7bc 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionRepository.kt @@ -1,6 +1,27 @@ package de.twomartens.wahlrecht.repository import de.twomartens.wahlrecht.model.db.Election -import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.data.mongodb.repository.ReactiveMongoRepository +import reactor.core.publisher.Mono +import reactor.util.function.Tuple2 -interface ElectionRepository : MongoRepository +interface ElectionRepository : ReactiveMongoRepository { + fun findByName(name: String): Mono + + fun findAndModify(new: Election): Mono> { + val electionName = new.name + val existing = findByName(electionName) + return existing.hasElement().flatMap { hasElement -> + if (hasElement) { + existing.flatMap { old -> + old.constituencies = new.constituencies + old.totalNumberOfSeats = new.totalNumberOfSeats + old.votingThreshold = new.votingThreshold + Mono.just(false).zipWith(save(old)) + } + } else { + Mono.just(true).zipWith(save(new)) + } + } + } +} diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionResultRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionResultRepository.kt index 8e95a33..23bafef 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionResultRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionResultRepository.kt @@ -1,6 +1,25 @@ package de.twomartens.wahlrecht.repository import de.twomartens.wahlrecht.model.db.ElectionResult -import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.data.mongodb.repository.ReactiveMongoRepository +import reactor.core.publisher.Mono +import reactor.util.function.Tuple2 -interface ElectionResultRepository : MongoRepository +interface ElectionResultRepository : ReactiveMongoRepository { + fun findByElectionName(electionName: String): Mono + + fun findAndModify(new: ElectionResult): Mono> { + val existing = findByElectionName(new.electionName) + return existing.hasElement().flatMap { hasElement -> + if (hasElement) { + existing.flatMap { old -> + old.overallResults = new.overallResults + old.constituencyResults = new.constituencyResults + Mono.just(false).zipWith(save(old)) + } + } else { + Mono.just(true).zipWith(save(new)) + } + } + } +} diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/NominationRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/NominationRepository.kt index b10d410..4d42258 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/NominationRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/NominationRepository.kt @@ -1,6 +1,31 @@ package de.twomartens.wahlrecht.repository import de.twomartens.wahlrecht.model.db.Nomination -import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.data.mongodb.repository.ReactiveMongoRepository +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers -interface NominationRepository : MongoRepository +interface NominationRepository : ReactiveMongoRepository { + fun findByElectionNameAndPartyAbbreviationAndName(electionName: String, + abbreviation: String, + name: String): Mono + + fun findAndModify(new: Nomination): Mono { + val existing = findByElectionNameAndPartyAbbreviationAndName( + new.electionName, + new.partyAbbreviation, + new.name + ) + return existing.hasElement().flatMap { hasElement -> + if (hasElement) { + existing.publishOn(Schedulers.boundedElastic()).publishOn(Schedulers.boundedElastic()).flatMap { old -> + old.supportVotesOnNomination = new.supportVotesOnNomination + old.candidates = new.candidates + save(old) + } + } else { + save(new) + } + } + } +} diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/PartyRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/PartyRepository.kt index eeb8007..f4cf89f 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/PartyRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/PartyRepository.kt @@ -1,8 +1,30 @@ package de.twomartens.wahlrecht.repository import de.twomartens.wahlrecht.model.db.PartyInElection -import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.data.mongodb.repository.ReactiveMongoRepository +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.util.function.Tuple2 -interface PartyRepository : MongoRepository { - fun findByElectionName(name: String): Collection +interface PartyRepository : ReactiveMongoRepository { + + fun findByElectionName(name: String): Flux + + fun findByElectionNameAndAbbreviation(electionName: String, abbreviation: String): Mono + + fun findAndModify(party: PartyInElection): Mono> { + val existing = findByElectionNameAndAbbreviation(party.electionName, party.abbreviation) + return existing.hasElement().flatMap { hasElement -> + if (hasElement) { + existing.flatMap { old -> + old.name = party.name + old.overallNomination = party.overallNomination + old.constituencyNominations = party.constituencyNominations + Mono.just(false).zipWith(save(old)) + } + } else { + Mono.just(true).zipWith(save(party)) + } + } + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CalculationService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CalculationService.kt index c49f6d6..ff8b270 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CalculationService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CalculationService.kt @@ -17,9 +17,9 @@ class CalculationService( log.info("Calculate election result for election {}", electionResult.electionName) val stopWatch = StopWatch() stopWatch.start("determineElectedCandidates") - val election = electionService.getElectionInternal(electionResult.electionName) + val election = electionService.getElectionInternal(electionResult.electionName).block() val seatResult = calculateOverallSeatDistribution( - election, + election!!, electionResult.overallResults ) val constituencyResults: MutableMap = HashMap() @@ -192,8 +192,8 @@ class CalculationService( .toMap() .toMutableMap() val nominationId = votingResult.nominationId - val nomination = nominationService.getNominationInternal(nominationId) - if (nomination.supportsVotesOnNomination()) { + val nomination = nominationService.getNominationInternal(nominationId).block() + if (nomination!!.supportsVotesOnNomination()) { val seatsByNomination = calculateSeatsByNominationOrder(votingResult, numberOfSeats) seatsByVoteOrder = numberOfSeats - seatsByNomination val candidates = nomination.getCandidates() diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CandidateService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CandidateService.kt index df3e9c5..8514e1c 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CandidateService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CandidateService.kt @@ -3,33 +3,17 @@ package de.twomartens.wahlrecht.service import de.twomartens.wahlrecht.model.db.Candidate import de.twomartens.wahlrecht.repository.CandidateRepository import org.springframework.stereotype.Service +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono @Service class CandidateService(private val repository: CandidateRepository) { - private val candidates: MutableMap by lazy { - fetchCandidates() - } - fun storeCandidate(candidate: Candidate): Candidate { - var result = candidate - val existing = candidates[candidate.name] - val needsUpdate = candidate != existing - if (!needsUpdate && existing != null) { - return existing - } - if (existing != null) { - existing.profession = candidate.profession - result = existing - } - result = repository.save(result) - candidates[candidate.name] = candidate - return result - } + fun storeCandidate(candidate: Candidate): Mono { + return repository.findAndModify(candidate) + } - private fun fetchCandidates(): MutableMap { - return repository.findAll().asSequence() - .map { Pair(it.name, it) } - .toMap() - .toMutableMap() - } + fun storeCandidates(candidates: Collection): Flux { + return Flux.merge(candidates.map { storeCandidate(it) }) + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ConstituencyService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ConstituencyService.kt index ced68e6..002be17 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ConstituencyService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ConstituencyService.kt @@ -1,40 +1,14 @@ package de.twomartens.wahlrecht.service import de.twomartens.wahlrecht.model.db.Constituency -import de.twomartens.wahlrecht.model.internal.ConstituencyId import de.twomartens.wahlrecht.repository.ConstituencyRepository import org.springframework.stereotype.Service +import reactor.core.publisher.Flux @Service class ConstituencyService(private val repository: ConstituencyRepository) { - private val constituencies: MutableMap by lazy { - fetchConstituencies() - } - fun storeConstituency(constituency: Constituency): Constituency { - var result = constituency - val constituencyId = ConstituencyId( - constituency.electionName, - constituency.number - ) - val existing = constituencies[constituencyId] - if (constituency == existing) { - return existing - } - if (existing != null) { - existing.numberOfSeats = constituency.numberOfSeats - existing.name = constituency.name - result = existing - } - val stored = repository.save(result) - constituencies[constituencyId] = stored - return stored - } - - private fun fetchConstituencies(): MutableMap { - return repository.findAll().asSequence() - .map { Pair(ConstituencyId(electionName = it.electionName, number = it.number), it) } - .toMap() - .toMutableMap() - } + fun storeConstituencies(constituencies: Collection): Flux { + return Flux.merge(constituencies.map { repository.findAndModify(it) }) + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionResultService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionResultService.kt index dc6a051..2f436fc 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionResultService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionResultService.kt @@ -5,45 +5,20 @@ import de.twomartens.wahlrecht.repository.ElectionResultRepository import org.springframework.http.HttpStatus import org.springframework.stereotype.Service import org.springframework.web.server.ResponseStatusException +import reactor.core.publisher.Mono +import reactor.util.function.Tuple2 @Service class ElectionResultService(private val repository: ElectionResultRepository) { - private val electionResults: MutableMap by lazy { - fetchResults() - } - fun getElectionResult(electionName: String): ElectionResult { - return electionResults[electionName] - ?: throw ResponseStatusException( - HttpStatus.NOT_FOUND, - "no election result found for $electionName" - ) - } - fun storeResult(electionResult: ElectionResult): Boolean { - var result = electionResult + fun getElectionResult(electionName: String): Mono { + return repository.findByElectionName(electionName) + .switchIfEmpty(Mono.error( + ResponseStatusException(HttpStatus.NOT_FOUND, "no election result found for $electionName") + )) + } - val id = result.electionName - val existing = electionResults[id] - val needsUpdate = result != existing - if (!needsUpdate) { - return false - } - var createdNew = true - if (existing != null) { - existing.overallResults = result.overallResults - existing.constituencyResults = result.constituencyResults - result = existing - createdNew = false - } - val stored = repository.save(result) - electionResults[id] = stored - return createdNew - } - - private fun fetchResults(): MutableMap { - return repository.findAll().asSequence() - .map { Pair(it.electionName, it) } - .toMap() - .toMutableMap() - } + fun storeResult(new: ElectionResult): Mono> { + return repository.findAndModify(new) + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionService.kt index 6e16e36..b6928aa 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionService.kt @@ -1,60 +1,35 @@ package de.twomartens.wahlrecht.service import de.twomartens.wahlrecht.mapper.v1.ElectionMapper -import de.twomartens.wahlrecht.model.db.Constituency import de.twomartens.wahlrecht.model.db.Election import de.twomartens.wahlrecht.repository.ElectionRepository import org.mapstruct.factory.Mappers import org.springframework.stereotype.Service +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.util.function.Tuple2 @Service class ElectionService( - private val electionRepository: ElectionRepository, - private val constituencyService: ConstituencyService + private val electionRepository: ElectionRepository ) { - private val electionMapper = Mappers.getMapper(ElectionMapper::class.java) - private val _elections: MutableMap by lazy { - fetchElections() - } + private val electionMapper = Mappers.getMapper(ElectionMapper::class.java) - val elections: Map = _elections + fun getElections(): Flux { + return electionRepository.findAll() + } - private fun getElection(electionName: String): Election? { - return elections[electionName] - } + fun findElectionByName(electionName: String): Mono { + return electionRepository.findByName(electionName) + } - fun getElectionInternal(electionName: String): de.twomartens.wahlrecht.model.internal.Election { - return electionMapper.mapToInternal(getElection(electionName)!!) + fun getElectionInternal(electionName: String): Mono { + return findElectionByName(electionName).map { + electionMapper.mapToInternal(it) } + } - fun storeElection(election: Election): Boolean { - var result = election - var createdNew = true - val electionName = election.name - val existing = elections[electionName] - if (election == existing) { - return false - } - val constituencies: MutableCollection = mutableListOf() - result.constituencies - .forEach { constituencies.add(constituencyService.storeConstituency(it)) } - if (existing != null) { - existing.day = election.day - existing.totalNumberOfSeats = election.totalNumberOfSeats - existing.votingThreshold = election.votingThreshold - result = existing - createdNew = false - } - result.constituencies = constituencies - val stored = electionRepository.save(result) - _elections[electionName] = stored - return createdNew - } - - private fun fetchElections(): MutableMap { - return electionRepository.findAll().asSequence() - .map { Pair(it.name, it) } - .toMap() - .toMutableMap() - } + fun storeElection(new: Election): Mono> { + return electionRepository.findAndModify(new) + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/NominationService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/NominationService.kt index 184ee2e..750b97d 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/NominationService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/NominationService.kt @@ -1,69 +1,41 @@ package de.twomartens.wahlrecht.service import de.twomartens.wahlrecht.mapper.v1.NominationMapper -import de.twomartens.wahlrecht.model.db.Candidate import de.twomartens.wahlrecht.model.db.Nomination import de.twomartens.wahlrecht.model.internal.NominationId import de.twomartens.wahlrecht.repository.NominationRepository import org.mapstruct.factory.Mappers import org.springframework.stereotype.Service +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono @Service class NominationService( - private val repository: NominationRepository, - private val candidateService: CandidateService + private val repository: NominationRepository ) { - private val mapper = Mappers.getMapper(NominationMapper::class.java) - private val nominations: MutableMap by lazy { - fetchNominations() - } + private val mapper = Mappers.getMapper(NominationMapper::class.java) - fun getNominationInternal(id: NominationId): de.twomartens.wahlrecht.model.internal.Nomination { - return mapper.mapToInternal(getNomination(id)!!) + fun getNominationInternal(id: NominationId): Mono { + return getNomination(id).map { + mapper.mapToInternal(it) } + } - fun storeNomination(nomination: Nomination?): Nomination? { - if (nomination == null) return null - val nominationId = NominationId( - nomination.electionName, - nomination.partyAbbreviation, - nomination.name - ) - val existing = nominations[nominationId] - val needsUpdate = nomination != existing - if (!needsUpdate) { - return existing - } - var result: Nomination = nomination - val candidates: MutableCollection = ArrayList() - result.candidates - .forEach { candidates.add(candidateService.storeCandidate(it)) } - if (existing != null) { - result = existing - } - result.candidates = candidates - result = repository.save(nomination) - nominations[nominationId] = result - return result - } + fun storeNomination(new: Nomination?): Mono { + if (new == null) return Mono.empty() - private fun getNomination(id: NominationId): Nomination? { - return nominations[id] - } + return repository.findAndModify(new) + } - private fun fetchNominations(): MutableMap { - return repository.findAll().asSequence() - .map { - Pair( - NominationId( - electionName = it.electionName, - partyAbbreviation = it.partyAbbreviation, - name = it.name - ), - it - ) - } - .toMap() - .toMutableMap() - } + fun storeNominations(new: Collection): Flux { + return Flux.merge(new.map { storeNomination(it) }) + } + + private fun getNomination(id: NominationId): Mono { + return repository.findByElectionNameAndPartyAbbreviationAndName( + id.electionName, + id.partyAbbreviation, + id.name + ) + } } \ No newline at end of file diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PartyService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PartyService.kt index 99b0fa0..d950669 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PartyService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PartyService.kt @@ -1,66 +1,32 @@ package de.twomartens.wahlrecht.service -import de.twomartens.wahlrecht.model.db.Nomination import de.twomartens.wahlrecht.model.db.PartyInElection -import de.twomartens.wahlrecht.model.internal.PartyId import de.twomartens.wahlrecht.repository.PartyRepository import org.springframework.http.HttpStatus import org.springframework.stereotype.Service import org.springframework.web.server.ResponseStatusException +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.util.function.Tuple2 @Service class PartyService( - private val partyRepository: PartyRepository, - private val nominationService: NominationService + private val partyRepository: PartyRepository ) { - private val parties: MutableMap by lazy { - fetchParties() - } + fun getPartiesByElectionName(electionName: String): Flux { + return partyRepository.findByElectionName(electionName) + .switchIfEmpty(Flux.error( + ResponseStatusException(HttpStatus.NOT_FOUND, "no party found for $electionName") + )) + } - fun getPartiesByElectionName(electionName: String): Collection { - val parties = partyRepository.findByElectionName(electionName) - if (parties.isEmpty()) { - throw ResponseStatusException(HttpStatus.NOT_FOUND, "no party found for $electionName") - } - return parties - } + fun getPartyByElectionNameAndAbbreviation(electionName: String, + abbreviation: String): Mono { + return partyRepository.findByElectionNameAndAbbreviation(electionName, abbreviation) + } - fun storeParty(partyInElection: PartyInElection): Boolean { - var result = partyInElection - var createdNew = true - val partyId = PartyId( - partyInElection.electionName, - partyInElection.abbreviation - ) - val existing = parties[partyId] - val needsUpdate = partyInElection != existing - if (!needsUpdate) { - return false - } - val constituencyNominations: MutableMap = mutableMapOf() - result.constituencyNominations - .forEach { constituencyNominations[it.key] = nominationService.storeNomination(it.value)!! } - if (existing != null) { - existing.name = partyInElection.name - if (constituencyNominations.isEmpty()) { - constituencyNominations.putAll(existing.constituencyNominations) - } - result = existing - createdNew = false - } - partyInElection.constituencyNominations = constituencyNominations - partyInElection.overallNomination = nominationService.storeNomination(partyInElection.overallNomination)!! - result = partyRepository.save(result) - parties[partyId] = result - return createdNew - } - - private fun fetchParties(): MutableMap { - val allParties = partyRepository.findAll() - return allParties.asSequence() - .map { Pair(PartyId(electionName = it.electionName, abbreviation = it.abbreviation), it) } - .toMap() - .toMutableMap() - } + fun storeParty(new: PartyInElection): Mono> { + return partyRepository.findAndModify(new) + } }