Compare commits

..
39 Commits
Author SHA1 Message Date
2martens 323337ed64 Added quotes around bootstrap server property
continuous-integration/drone/tag Build is passing
2023-10-30 00:32:19 +01:00
2martens b04d6f4389 Added support for passing ports in via environment variables 2023-10-29 18:07:50 +01:00
2martens 517f2b4f1b Fixed kafka errors
continuous-integration/drone/tag Build is passing
2023-10-29 01:07:02 +02:00
2martens f089d70d5f Remove deprecated usage 2023-10-29 00:50:55 +02:00
2martens 2a78b47d83 Added configs to producer and consumer as well
continuous-integration/drone/tag Build is passing
2023-10-29 00:42:47 +02:00
2martens 84baee6ebb Upgrade gradle version 2023-10-28 11:00:50 +02:00
2martens 35a1b49412 Removed chart 2023-08-23 08:28:47 +02:00
2martens 9453e10803 Fixed prefix 2023-08-20 19:55:11 +02:00
2martens a2b85f402d Added environment variables to configure in Docker environment 2023-08-20 19:49:57 +02:00
2martens 3a9c70932d Use alpine base image 2023-08-20 12:14:43 +02:00
2martens 7e87623b0e Created skeleton chart 2023-08-20 10:45:22 +02:00
2martens 1b71ef0614 Use AmazonLinux 2023 2023-08-20 10:44:57 +02:00
2martens 9aa199103d Don't add servers item 2023-08-19 18:34:25 +02:00
2martens 904bc60ed7 Added forwarded header filter as bean 2023-08-19 18:18:28 +02:00
2martens d5691e3081 Use framework handling of forward headers 2023-08-19 17:33:03 +02:00
2martens 538b1badec Add forward headers strategy 2023-08-19 16:59:05 +02:00
2martens d34ca9aff2 Removed not working step 2023-08-19 16:12:09 +02:00
2martens cd79f15d5b Enabled target and removed custom trigger 2023-08-19 03:54:43 +02:00
2martens 37506b6c83 Added promotion step 2023-08-19 03:54:11 +02:00
2martens 406a891849 Improved code style of Kotlin 2023-08-19 03:53:40 +02:00
2martens b411c97f53 Adjusted API to be more in line with resource orientation 2023-08-19 03:52:50 +02:00
2martens 37f57f788b Do not overwrite existing collections
continuous-integration/drone Build is failing
continuous-integration/drone/promote/production Build is passing
2023-08-17 23:56:56 +02:00
2martens d5c7597a04 Add cache to minimize time for installing ansible collections
continuous-integration/drone Build is failing
2023-08-17 23:53:39 +02:00
2martens 8f31d2dd3d Move to private_ssh_key as secret
continuous-integration/drone Build is failing
2023-08-17 23:49:06 +02:00
2martens 5ff47de10a Prevent commit if nothing changed
continuous-integration/drone Build is failing
2023-08-17 23:39:39 +02:00
2martens 794a08c9b0 Only run code quality on pull request
continuous-integration/drone Build is failing
2023-08-17 23:24:41 +02:00
2martens d579004653 Don't limit to target
continuous-integration/drone/push Build was killed
continuous-integration/drone Build was killed
2023-08-17 23:22:30 +02:00
2martens e127d66a1b Remove custom trigger for building docker container
continuous-integration/drone/push Build is failing
2023-08-17 23:21:36 +02:00
2martens 21fa17e62b Added galaxy requirements
continuous-integration/drone/push Build was killed
continuous-integration/drone Build is failing
2023-08-17 23:20:14 +02:00
2martens 8381c1ba7d Use Drone semver
continuous-integration/drone/push Build is failing
2023-08-17 23:07:16 +02:00
2martens 4308130cd1 Fixed usage of image version
continuous-integration/drone/push Build is failing
continuous-integration/drone/promote/production Build is failing
2023-08-17 23:03:00 +02:00
2martens 721cdac95d Use latest ansible plugin version
continuous-integration/drone/push Build is failing
2023-08-17 22:59:49 +02:00
2martens 395fa52962 Fixed pipeline
continuous-integration/drone/push Build is failing
continuous-integration/drone/promote/production Build is failing
2023-08-17 22:50:20 +02:00
2martens 45e5c473ff Added pipeline to deploy via Ansible
continuous-integration/drone/push Build is failing
continuous-integration/drone/promote/production Build is failing
2023-08-17 21:11:44 +02:00
2martens 1c3648f0f4 Update [CI SKIP] 2023-08-17 12:51:18 +02:00
2martens 5b707728b8 Change properties [CI SKIP] 2023-08-17 02:18:08 +02:00
2martens c19c211839 Make properties refreshable
continuous-integration/drone/push Build is failing
2023-08-17 02:15:23 +02:00
2martens aace535914 Change message [CI SKIP] 2023-08-17 02:13:07 +02:00
2martens 59982ef762 Remove refresh scope from Clock
continuous-integration/drone/push Build is failing
2023-08-17 02:08:12 +02:00
21 changed files with 303 additions and 183 deletions
+76 -7
View File
@@ -30,7 +30,6 @@ volumes:
trigger: trigger:
event: event:
include: include:
- push
- pull_request - pull_request
- custom - custom
@@ -51,7 +50,7 @@ steps:
image: alpine/git image: alpine/git
environment: environment:
SSH_KEY: SSH_KEY:
from_secret: ssh_key from_secret: private_ssh_key
commands: commands:
- mkdir $HOME/.ssh - mkdir $HOME/.ssh
- echo "$SSH_KEY" > $HOME/.ssh/id_ed25519 - echo "$SSH_KEY" > $HOME/.ssh/id_ed25519
@@ -69,7 +68,7 @@ steps:
PASSWORD: PASSWORD:
from_secret: docker_password from_secret: docker_password
SSH_KEY: SSH_KEY:
from_secret: ssh_key from_secret: private_ssh_key
volumes: volumes:
- name: cache - name: cache
path: /home/gradle/.gradle path: /home/gradle/.gradle
@@ -88,10 +87,80 @@ volumes:
path: /var/lib/drone/cache/.gradle path: /var/lib/drone/cache/.gradle
trigger: trigger:
branches:
- main
event: event:
include: include:
- push - tag
- custom
---
kind: pipeline
name: deploy image
type: docker
platform:
os: linux
arch: arm64
clone:
disable: true
steps:
- name: clone
image: alpine/git
environment:
SSH_KEY:
from_secret: private_ssh_key
commands:
- mkdir $HOME/.ssh
- echo "$SSH_KEY" > $HOME/.ssh/id_ed25519
- chmod 600 $HOME/.ssh/id_ed25519
- touch $HOME/.ssh/known_hosts
- chmod 600 $HOME/.ssh/known_hosts
- ssh-keyscan -H git.2martens.de > $HOME/.ssh/known_hosts 2>/dev/null
- git clone ssh://giteajim@git.2martens.de:22/2martens/cloud-configuration.git .
- git checkout main
- name: modify deployed image
image: alpine
commands:
- sed -i -r "s/(wahlrecht_image_version:).*/\1\ ${DRONE_SEMVER}/" ansible/inventories/host_vars/api-server/public.yaml
- name: save modified variable file
image: alpine/git
environment:
SSH_KEY:
from_secret: private_ssh_key
commands:
- mkdir $HOME/.ssh
- echo "$SSH_KEY" > $HOME/.ssh/id_ed25519
- chmod 600 $HOME/.ssh/id_ed25519
- touch $HOME/.ssh/known_hosts
- chmod 600 $HOME/.ssh/known_hosts
- ssh-keyscan -H git.2martens.de > $HOME/.ssh/known_hosts 2>/dev/null
- git add ansible/inventories/host_vars/api-server/public.yaml
- git diff-index --quiet HEAD || git commit -m "[Drone] Changed wahlrecht_image_version to ${DRONE_SEMVER}"
- git push origin main
- name: deploy image
image: plugins/ansible:latest
volumes:
- name: cache
path: /root/.ansible
settings:
playbook: ansible/deploy_spring.yaml
inventory: ansible/inventories/production
galaxy: ansible/requirements.yaml
galaxy_force: false
private_key:
from_secret: private_ssh_key
vault_password:
from_secret: vault_password
volumes:
- name: cache
host:
path: /var/lib/drone/cache/.ansible
trigger:
target:
- production
event:
include:
- promote
- rollback
+3 -3
View File
@@ -543,9 +543,9 @@ ij_html_space_inside_empty_tag = false
ij_html_text_wrap = normal ij_html_text_wrap = normal
[{*.kt,*.kts}] [{*.kt,*.kts}]
indent_size = 4 indent_size = 2
tab_width = 4 tab_width = 2
ij_continuation_indent_size = 8 ij_continuation_indent_size = 4
ij_kotlin_align_in_columns_case_branch = false ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false ij_kotlin_align_multiline_binary_operation = false
ij_kotlin_align_multiline_extends_list = false ij_kotlin_align_multiline_extends_list = false
-20
View File
@@ -1,20 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="MainApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
<module name="wahlrecht.server.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="de.twomartens.wahlrecht.MainApplication" />
<extension name="net.ashald.envfile">
<option name="IS_ENABLED" value="true" />
<option name="IS_SUBST" value="false" />
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
<option name="IS_IGNORE_MISSING_FILES" value="false" />
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
<ENTRIES>
<ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
<ENTRY IS_ENABLED="true" PARSER="env" IS_EXECUTABLE="false" PATH=".env" />
</ENTRIES>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
@@ -54,7 +54,7 @@ normalization.runtimeClasspath.metaInf {
} }
tasks.register("cleanLibs") { tasks.register("cleanLibs") {
delete("${buildDir}/libs") delete("${layout.buildDirectory}/libs")
} }
tasks.build { tasks.build {
@@ -9,10 +9,10 @@ apply(plugin="com.netflix.nebula.release")
tasks.register("writeVersionProperties") { tasks.register("writeVersionProperties") {
group = "version" group = "version"
mustRunAfter("release") mustRunAfter("release")
outputs.file("$buildDir/version.properties") outputs.file("${layout.buildDirectory}/version.properties")
val directory = buildDir val directory = layout.buildDirectory.asFile
doLast { doLast {
Files.createDirectories(directory.toPath()) Files.createDirectories(directory.get().toPath())
File("$buildDir/version.properties").writeText("VERSION=${project.version}\n") File("${layout.buildDirectory}/version.properties").writeText("VERSION=${project.version}\n")
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
+1 -1
View File
@@ -8,7 +8,7 @@ dependencies {
jib { jib {
from { from {
image = "amazoncorretto:" + properties["projectSourceCompatibility"] image = "amazoncorretto:" + properties["projectSourceCompatibility"] + "-alpine"
platforms { platforms {
platform { platform {
architecture = "amd64" architecture = "amd64"
@@ -1,7 +1,6 @@
package de.twomartens.wahlrecht.configuration package de.twomartens.wahlrecht.configuration
import de.twomartens.wahlrecht.property.ServiceProperties import de.twomartens.wahlrecht.property.ServiceProperties
import org.springframework.cloud.context.config.annotation.RefreshScope
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import java.time.Clock import java.time.Clock
@@ -10,7 +9,6 @@ import java.time.Clock
open class ClockConfiguration(private val serviceProperties: ServiceProperties) { open class ClockConfiguration(private val serviceProperties: ServiceProperties) {
@Bean @Bean
@RefreshScope
open fun clock(): Clock { open fun clock(): Clock {
return Clock.system(serviceProperties.defaultTimeZone) return Clock.system(serviceProperties.defaultTimeZone)
} }
@@ -0,0 +1,13 @@
package de.twomartens.wahlrecht.configuration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.filter.ForwardedHeaderFilter
@Configuration
open class FilterConfiguration {
@Bean
open fun forwardedFilter(): ForwardedHeaderFilter {
return ForwardedHeaderFilter()
}
}
@@ -8,13 +8,13 @@ import java.time.Clock
@Configuration @Configuration
open class InterceptorConfiguration { open class InterceptorConfiguration {
@Bean @Bean
open fun loggingInterceptorRest(clock: Clock): LoggingInterceptorRest { open fun loggingInterceptorRest(clock: Clock): LoggingInterceptorRest {
return LoggingInterceptorRest(clock) return LoggingInterceptorRest(clock)
} }
@Bean @Bean
open fun headerInterceptorRest(): HeaderInterceptorRest { open fun headerInterceptorRest(): HeaderInterceptorRest {
return HeaderInterceptorRest() return HeaderInterceptorRest()
} }
} }
@@ -6,7 +6,6 @@ import io.swagger.v3.oas.annotations.security.OAuthFlows
import io.swagger.v3.oas.annotations.security.SecurityScheme import io.swagger.v3.oas.annotations.security.SecurityScheme
import io.swagger.v3.oas.models.OpenAPI import io.swagger.v3.oas.models.OpenAPI
import io.swagger.v3.oas.models.info.Info import io.swagger.v3.oas.models.info.Info
import io.swagger.v3.oas.models.servers.Server
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
@@ -36,7 +35,5 @@ open class OpenApiConfiguration {
.version(apiVersion) .version(apiVersion)
.description(apiDescription) .description(apiDescription)
) )
.addServersItem(Server().url("http://localhost:12000"))
.addServersItem(Server().url("https://api.2martens.de"))
} }
} }
@@ -12,80 +12,88 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.security.SecurityRequirement import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.tags.Tag
import org.mapstruct.factory.Mappers import org.mapstruct.factory.Mappers
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.created
import org.springframework.http.ResponseEntity.ok
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
import org.springframework.web.util.UriComponentsBuilder
@RestController @RestController
@RequestMapping(value = ["/wahlrecht/v1"]) @RequestMapping(value = ["/wahlrecht/v1"])
@Tag(name = "Elections", description = "all requests relating to elections") @Tag(name = "Elections", description = "all requests relating to elections")
class ElectionController(private val service: ElectionService) { 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 = "oauth2")
@SecurityRequirement(name = "bearerAuth") @SecurityRequirement(name = "bearerAuth")
@GetMapping(value = ["/elections"], produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping(value = ["/elections"], produces = [MediaType.APPLICATION_JSON_VALUE])
@Operation( @Operation(
summary = "Returns all stored elections", summary = "Returns all stored elections",
responses = [ApiResponse( responses = [ApiResponse(
responseCode = "200", responseCode = "200",
description = "Returns all stored elections", description = "Returns all stored elections",
content = [Content( content = [Content(
mediaType = "application/json", mediaType = "application/json",
array = ArraySchema( array = ArraySchema(
schema = Schema( schema = Schema(
implementation = Election::class implementation = Election::class
) )
) )
)] )]
)] )]
) )
fun getElections(): ResponseEntity<Collection<Election>> { fun getElections(): ResponseEntity<Collection<Election>> {
val elections = service.elections.asSequence() val elections = service.elections.asSequence()
.map { mapper.mapToExternal(it.value) } .map { mapper.mapToExternal(it.value) }
.toList() .toList()
return ResponseEntity.ok() return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.body(elections) .body(elections)
} }
@Operation( @Operation(
summary = "Returns election matching provided name", summary = "Returns election matching provided name",
responses = [ApiResponse( responses = [ApiResponse(
responseCode = "200", responseCode = "200",
description = "Returns matching election", description = "Returns matching election",
content = [Content(mediaType = "application/json", schema = Schema(implementation = Election::class))] content = [Content(mediaType = "application/json", schema = Schema(implementation = Election::class))]
)] )]
) )
@GetMapping(value = ["/elections/by-name/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping(value = ["/elections/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE])
@SecurityRequirement(name = "bearerAuth") @SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
fun getElectionByName( fun getElectionByName(
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String? @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String?
): ResponseEntity<Election> { ): ResponseEntity<Election> {
val election = mapper.mapToExternal(service.elections[electionName]!!) val election = mapper.mapToExternal(service.elections[electionName]!!)
return ResponseEntity.ok() return ok()
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.body(election) .body(election)
} }
@Operation( @Operation(
summary = "Stores a new election", summary = "Stores a new election",
responses = [ApiResponse( responses = [ApiResponse(
responseCode = "201", responseCode = "201",
description = "Election was created" description = "Election was created"
), ApiResponse(responseCode = "200", description = "Election was modified"), ApiResponse( ), ApiResponse(responseCode = "200", description = "Election was modified"), ApiResponse(
responseCode = "401", responseCode = "401",
description = "Unauthorized", description = "Unauthorized",
content = [Content(mediaType = "plain/text")] content = [Content(mediaType = "plain/text")]
)] )]
) )
@PutMapping("/election") @PutMapping("/election")
@SecurityRequirement(name = "bearerAuth") @SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
fun putElection(@RequestBody election: Election?): ResponseEntity<*> { fun putElection(@RequestBody election: Election?, uriComponentsBuilder: UriComponentsBuilder):
val createdNew = service.storeElection(mapper.mapToDB(election!!)) ResponseEntity<Void> {
return if (createdNew) ResponseEntity<Any>(HttpStatus.CREATED) else ResponseEntity<Any>(HttpStatus.OK) val createdNew = service.storeElection(mapper.mapToDB(election!!))
return if (createdNew) {
created(uriComponentsBuilder.path("/elections/{electionName}")
.buildAndExpand(election.name).toUri())
.build()
} }
else ok().build()
}
} }
@@ -14,10 +14,12 @@ import io.swagger.v3.oas.annotations.tags.Tag
import lombok.RequiredArgsConstructor import lombok.RequiredArgsConstructor
import lombok.extern.slf4j.Slf4j import lombok.extern.slf4j.Slf4j
import org.mapstruct.factory.Mappers import org.mapstruct.factory.Mappers
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.http.ResponseEntity.created
import org.springframework.http.ResponseEntity.ok
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
import org.springframework.web.util.UriComponentsBuilder
@Slf4j @Slf4j
@RestController @RestController
@@ -40,16 +42,16 @@ class ElectionResultController(private val service: ElectionResultService) {
)] )]
) )
@GetMapping( @GetMapping(
value = ["/electionResult/by-election-name/{electionName}"], value = ["/electionResults/{electionName}"],
produces = [MediaType.APPLICATION_JSON_VALUE] produces = [MediaType.APPLICATION_JSON_VALUE]
) )
@SecurityRequirement(name = "bearerAuth") @SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
fun getElectionByName( fun getElectionResultByElectionName(
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String
): ResponseEntity<ElectionResult> { ): ResponseEntity<ElectionResult> {
val result = mapper.mapToExternal(service.getElectionResult(electionName)) val result = mapper.mapToExternal(service.getElectionResult(electionName))
return ResponseEntity.ok() return ok()
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.body(result) .body(result)
} }
@@ -68,8 +70,14 @@ class ElectionResultController(private val service: ElectionResultService) {
@PutMapping("/electionResult") @PutMapping("/electionResult")
@SecurityRequirement(name = "bearerAuth") @SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2") @SecurityRequirement(name = "oauth2")
fun putElection(@RequestBody electionResult: ElectionResult?): ResponseEntity<*> { fun putElection(@RequestBody electionResult: ElectionResult?,
uriComponentsBuilder: UriComponentsBuilder): ResponseEntity<Void> {
val createdNew = service.storeResult(mapper.mapToDB(electionResult!!)) val createdNew = service.storeResult(mapper.mapToDB(electionResult!!))
return if (createdNew) ResponseEntity<Any>(HttpStatus.CREATED) else ResponseEntity<Any>(HttpStatus.OK) return if (createdNew) {
created(uriComponentsBuilder.path("/electionResult/{electionName}")
.buildAndExpand(electionResult.electionName).toUri())
.build()
}
else ok().build()
} }
} }
@@ -13,65 +13,103 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.security.SecurityRequirement import io.swagger.v3.oas.annotations.security.SecurityRequirement
import io.swagger.v3.oas.annotations.tags.Tag import io.swagger.v3.oas.annotations.tags.Tag
import org.mapstruct.factory.Mappers import org.mapstruct.factory.Mappers
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity 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.stereotype.Controller
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
import org.springframework.web.util.UriComponentsBuilder
@Controller @Controller
@RequestMapping(value = ["/wahlrecht/v1"]) @RequestMapping(value = ["/wahlrecht/v1"])
@Tag(name = "Parties", description = "all requests relating to parties") @Tag(name = "Parties", description = "all requests relating to parties")
class PartyController(private val service: PartyService) { class PartyController(private val service: PartyService) {
private val mapper = Mappers.getMapper(PartyInElectionMapper::class.java) 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<Collection<PartyInElection>> {
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)
}
@Operation( @Operation(
summary = "Stores a new party in an election", summary = "Returns all stored parties for given election name",
responses = [ApiResponse( responses = [ApiResponse(
responseCode = "201", responseCode = "200",
description = "Party was created" description = "Returns all stored parties",
), ApiResponse(responseCode = "200", description = "Party was modified"), ApiResponse( content = [Content(
responseCode = "401", mediaType = "application/json",
description = "Unauthorized" 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/{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<Collection<PartyInElection>> {
val parties = service.getPartiesByElectionName(electionName).stream()
.map { election: de.twomartens.wahlrecht.model.db.PartyInElection? ->
mapper.mapToExternal(
election!!
)
}
.toList()
return ok()
.contentType(MediaType.APPLICATION_JSON)
.body(parties)
}
@Operation(
summary = "Returns stored party for given election name and party 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<PartyInElection> {
val party = mapper.mapToExternal(
service.getPartyByElectionNameAndAbbreviation(electionName, abbreviation)
) )
@PutMapping("/party") return ok()
@SecurityRequirement(name = "bearerAuth") .contentType(MediaType.APPLICATION_JSON)
@SecurityRequirement(name = "oauth2") .body(party)
fun putParty(@RequestBody party: PartyInElection?): ResponseEntity<*> { }
val createdNew = service.storeParty(mapper.mapToDB(party!!))
return if (createdNew) ResponseEntity<Any>(HttpStatus.CREATED) else ResponseEntity<Any>(HttpStatus.OK) @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): ResponseEntity<Void> {
val createdNew = service.storeParty(mapper.mapToDB(party!!))
return if (createdNew) {
created(uriComponentsBuilder.path("/parties/{electionName}/{abbreviation}")
.buildAndExpand(party.electionName, party.abbreviation).toUri())
.build()
} else ok().build()
}
} }
@@ -8,4 +8,7 @@ import java.time.ZoneId
@RefreshScope @RefreshScope
@ConfigurationProperties(prefix = "de.twomartens.wahlrecht") @ConfigurationProperties(prefix = "de.twomartens.wahlrecht")
@Schema(description = "Properties, to configure this Application") @Schema(description = "Properties, to configure this Application")
data class ServiceProperties(var defaultTimeZone: ZoneId, var greeting: String) class ServiceProperties {
lateinit var defaultTimeZone: ZoneId
lateinit var greeting: String
}
@@ -5,4 +5,6 @@ import org.springframework.data.mongodb.repository.MongoRepository
interface PartyRepository : MongoRepository<PartyInElection, String> { interface PartyRepository : MongoRepository<PartyInElection, String> {
fun findByElectionName(name: String): Collection<PartyInElection> fun findByElectionName(name: String): Collection<PartyInElection>
fun findByElectionNameAndAbbreviation(electionName: String, abbreviation: String): PartyInElection?
} }
@@ -18,6 +18,13 @@ class PartyService(
fetchParties() fetchParties()
} }
fun getPartyByElectionNameAndAbbreviation(electionName: String,
abbreviation: String): PartyInElection {
return partyRepository.findByElectionNameAndAbbreviation(electionName, abbreviation)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND,
"no party found for $electionName and $abbreviation")
}
fun getPartiesByElectionName(electionName: String): Collection<PartyInElection> { fun getPartiesByElectionName(electionName: String): Collection<PartyInElection> {
val parties = partyRepository.findByElectionName(electionName) val parties = partyRepository.findByElectionName(electionName)
if (parties.isEmpty()) { if (parties.isEmpty()) {
@@ -1,9 +1,5 @@
server.port: 12000
management.server.port: 12001
spring: spring:
config: config:
import: import:
- "classpath:config/wahlrecht.yaml" - "classpath:config/wahlrecht.yaml"
- "optional:configserver:http://localhost:8888/config" - "optional:configserver:${CONFIGSERVER_SCHEME:http}://${CONFIGSERVER_HOST:configserver}:${CONFIGSERVER_PORT:8888}${CONFIGSERVER_PREFIX:/config}"
@@ -1,7 +1,7 @@
spring: spring:
config: config:
import: import:
- "configserver:http://configserver:8888/config" - "configserver:${CONFIGSERVER_SCHEME:http}://${CONFIGSERVER_HOST:configserver}:${CONFIGSERVER_PORT:8888}${CONFIGSERVER_PREFIX:/config}"
kafka: kafka:
bootstrap-servers: bootstrap-servers:
- kafka:9092 - "${KAFKA_HOST:kafka}:${KAFKA_PORT:9092}"
@@ -1,11 +1,12 @@
server: server:
port: 12000 port: ${PORT:12000}
shutdown: graceful shutdown: graceful
forward-headers-strategy: framework
### technical configurations ### ### technical configurations ###
management: management:
# for security, don't use same port as application # for security, don't use same port as application
server.port: 12001 server.port: ${ACTUATOR_PORT:12001}
health: health:
livenessState.enabled: true livenessState.enabled: true
readinessState.enabled: true readinessState.enabled: true
@@ -37,7 +38,7 @@ spring:
oauth2: oauth2:
resourceserver: resourceserver:
jwt: jwt:
jwk-set-uri: https://id.2martens.de/realms/2martens/protocol/openid-connect/certs jwk-set-uri: ${KEYCLOAK_URL:https://id.2martens.de}/realms/${KEYCLOAK_REALM:2martens}/protocol/openid-connect/certs
application: application:
name: wahlrecht name: wahlrecht
@@ -1,2 +1,2 @@
de.twomartens.wahlrecht: de.twomartens.wahlrecht:
greeting: "Moin %s, you are stage" greeting: "Moin %s, you are test"