Compare commits

...
49 Commits
Author SHA1 Message Date
2martens a02dfa100c chore: Version update
continuous-integration/drone/tag Build is passing
2024-03-06 15:19:53 +01:00
2martens 0082305a5a fix: Remove kafka
continuous-integration/drone/tag Build is passing
2024-03-06 15:06:32 +01:00
2martens 11345c94a5 fix: Update values in correct repository 2024-01-06 15:41:51 +01:00
2martens f5f773a90b Added logging and reformatted, updated drone config
continuous-integration/drone/tag Build is passing
continuous-integration/drone/promote/test Build is passing
2023-10-30 13:30:17 +01:00
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
2martens 56c6fef178 Remove constructor binding
continuous-integration/drone/push Build is failing
2023-08-17 02:02:19 +02:00
2martens 46b1b32708 Properties must be changeable with setters
continuous-integration/drone/push Build is failing
2023-08-17 01:52:50 +02:00
2martens 519101023c Change message [CI SKIP] 2023-08-17 01:42:01 +02:00
2martens 82b4f51390 Require configserver on prod
continuous-integration/drone/push Build is failing
2023-08-17 01:38:26 +02:00
2martens 3256939ac7 Change message [CI SKIP] 2023-08-17 01:20:17 +02:00
2martens 02e4c5318d Fix bootstrap servers on prod
continuous-integration/drone/push Build is failing
2023-08-17 01:14:03 +02:00
23 changed files with 339 additions and 239 deletions
+60 -7
View File
@@ -30,7 +30,6 @@ volumes:
trigger:
event:
include:
- push
- pull_request
- custom
@@ -51,7 +50,7 @@ steps:
image: alpine/git
environment:
SSH_KEY:
from_secret: ssh_key
from_secret: private_ssh_key
commands:
- mkdir $HOME/.ssh
- echo "$SSH_KEY" > $HOME/.ssh/id_ed25519
@@ -69,7 +68,7 @@ steps:
PASSWORD:
from_secret: docker_password
SSH_KEY:
from_secret: ssh_key
from_secret: private_ssh_key
volumes:
- name: cache
path: /home/gradle/.gradle
@@ -88,10 +87,64 @@ volumes:
path: /var/lib/drone/cache/.gradle
trigger:
branches:
- main
event:
include:
- push
- custom
- tag
---
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: update image version
image: alpine
commands:
- mkdir $HOME/bin
- wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_arm64 -O $HOME/bin/yq && chmod +x $HOME/bin/yq
- sed -i -r "s/(tag:).*/\1\ \"${DRONE_SEMVER}\"/" argocd/wahlrecht/${DRONE_DEPLOY_TO}/overwrite_values.yaml
- cd argocd/wahlrecht/${DRONE_DEPLOY_TO} && $HOME/bin/yq '. *= load("overwrite_values.yaml")' default_values.yaml > values.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 argocd/wahlrecht/${DRONE_DEPLOY_TO}/*
- git diff-index --quiet HEAD || git commit -m "[Drone] Changed wahlrecht_image_version to ${DRONE_SEMVER}"
- git push origin main
trigger:
target:
- test
event:
include:
- promote
- rollback
+3 -3
View File
@@ -543,9 +543,9 @@ ij_html_space_inside_empty_tag = false
ij_html_text_wrap = normal
[{*.kt,*.kts}]
indent_size = 4
tab_width = 4
ij_continuation_indent_size = 8
indent_size = 2
tab_width = 2
ij_continuation_indent_size = 4
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = 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") {
delete("${buildDir}/libs")
delete("${layout.buildDirectory}/libs")
}
tasks.build {
@@ -9,10 +9,10 @@ apply(plugin="com.netflix.nebula.release")
tasks.register("writeVersionProperties") {
group = "version"
mustRunAfter("release")
outputs.file("$buildDir/version.properties")
val directory = buildDir
outputs.file("${layout.buildDirectory}/version.properties")
val directory = layout.buildDirectory.asFile
doLast {
Files.createDirectories(directory.toPath())
File("$buildDir/version.properties").writeText("VERSION=${project.version}\n")
Files.createDirectories(directory.get().toPath())
File("${layout.buildDirectory}/version.properties").writeText("VERSION=${project.version}\n")
}
}
+12 -12
View File
@@ -1,21 +1,21 @@
[versions]
spring-boot = "3.1.2"
spring-doc = "2.1.0"
spring-cloud = "2022.0.4"
spring-boot = "3.2.3"
spring-doc = "2.3.0"
spring-cloud = "2023.0.0"
spring-grpc = "2.14.0.RELEASE"
grpc = "1.57.0"
tomcat-annotations = "6.0.53"
httpclient = "5.2.1"
slf4j = "2.0.7"
log4j = "2.20.0"
log4j-ecs = "1.5.0"
httpclient = "5.3.1"
slf4j = "2.0.12"
log4j = "2.23.0"
log4j-ecs = "1.6.0"
mapstruct = "1.5.5.Final"
junit = "5.10.0"
assertj = "3.24.2"
mockito = "5.4.0"
keycloak = "22.0.1"
junit = "5.10.2"
assertj = "3.25.3"
mockito = "5.11.0"
keycloak = "24.0.1"
kotlin-logging = "3.0.5"
kotlin-reflect = "1.9.0"
kotlin-reflect = "1.9.22"
kotlin-lombok = "1.9.0"
plugin-nebula-release = "17.2.2"
plugin-lombok = "8.0.1"
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists
+1 -1
View File
@@ -8,7 +8,7 @@ dependencies {
jib {
from {
image = "amazoncorretto:" + properties["projectSourceCompatibility"]
image = "amazoncorretto:" + properties["projectSourceCompatibility"] + "-alpine"
platforms {
platform {
architecture = "amd64"
-1
View File
@@ -10,5 +10,4 @@ dependencies {
annotationProcessor(libs.mapstruct.processor)
kapt(libs.mapstruct.processor)
implementation(libs.spring.cloud.starter.config)
implementation(libs.spring.cloud.starter.bus.kafka)
}
@@ -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
open class InterceptorConfiguration {
@Bean
open fun loggingInterceptorRest(clock: Clock): LoggingInterceptorRest {
return LoggingInterceptorRest(clock)
}
@Bean
open fun loggingInterceptorRest(clock: Clock): LoggingInterceptorRest {
return LoggingInterceptorRest(clock)
}
@Bean
open fun headerInterceptorRest(): HeaderInterceptorRest {
return HeaderInterceptorRest()
}
@Bean
open fun headerInterceptorRest(): 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.models.OpenAPI
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.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@@ -36,7 +35,5 @@ open class OpenApiConfiguration {
.version(apiVersion)
.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.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.web.bind.annotation.*
import org.springframework.web.util.UriComponentsBuilder
@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<Collection<Election>> {
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"], 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<Collection<Election>> {
val elections = service.elections.asSequence()
.map { mapper.mapToExternal(it.value) }
.toList()
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(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<Election> {
val election = mapper.mapToExternal(service.elections[electionName]!!)
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<Any>(HttpStatus.CREATED) else ResponseEntity<Any>(HttpStatus.OK)
@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<Election> {
val election = mapper.mapToExternal(service.elections[electionName]!!)
return 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?, uriComponentsBuilder: UriComponentsBuilder):
ResponseEntity<Void> {
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.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.http.ResponseEntity.created
import org.springframework.http.ResponseEntity.ok
import org.springframework.web.bind.annotation.*
import org.springframework.web.util.UriComponentsBuilder
@Slf4j
@RestController
@@ -40,16 +42,16 @@ class ElectionResultController(private val service: ElectionResultService) {
)]
)
@GetMapping(
value = ["/electionResult/by-election-name/{electionName}"],
value = ["/electionResults/{electionName}"],
produces = [MediaType.APPLICATION_JSON_VALUE]
)
@SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2")
fun getElectionByName(
fun getElectionResultByElectionName(
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String
): ResponseEntity<ElectionResult> {
val result = mapper.mapToExternal(service.getElectionResult(electionName))
return ResponseEntity.ok()
return ok()
.contentType(MediaType.APPLICATION_JSON)
.body(result)
}
@@ -68,8 +70,14 @@ class ElectionResultController(private val service: ElectionResultService) {
@PutMapping("/electionResult")
@SecurityRequirement(name = "bearerAuth")
@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!!))
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.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
@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<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)
}
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"
)]
@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/{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")
@SecurityRequirement(name = "bearerAuth")
@SecurityRequirement(name = "oauth2")
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)
}
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): 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()
}
}
@@ -2,11 +2,13 @@ package de.twomartens.wahlrecht.property
import io.swagger.v3.oas.annotations.media.Schema
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.ConstructorBinding
import org.springframework.cloud.context.config.annotation.RefreshScope
import java.time.ZoneId
@RefreshScope
@ConfigurationProperties(prefix = "de.twomartens.wahlrecht")
@Schema(description = "Properties, to configure this Application")
data class ServiceProperties @ConstructorBinding constructor(val defaultTimeZone: ZoneId, val 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> {
fun findByElectionName(name: String): Collection<PartyInElection>
fun findByElectionNameAndAbbreviation(electionName: String, abbreviation: String): PartyInElection?
}
@@ -13,53 +13,54 @@ import org.keycloak.representations.idm.authorization.Permission
class SpringPolicyEnforcer(private val policyEnforcer: PolicyEnforcer,
private val policyEnforcerConfig: PolicyEnforcerConfig) {
fun enforce(request: HttpRequest, response: HttpResponse): AuthorizationContext {
if (log.isDebugEnabled) {
log.debug("Policy enforcement is enabled. Enforcing policy decisions for path [{}].", request.uri)
}
val context = authorize(request, response)
if (log.isDebugEnabled) {
log.debug("Policy enforcement result for path [{}] is : {}", request.uri, if (context.isGranted) "GRANTED" else "DENIED")
log.debug("Returning authorization context with permissions:")
for (permission in context.permissions) {
log.debug(permission.toString())
}
}
return context
fun enforce(request: HttpRequest, response: HttpResponse): AuthorizationContext {
if (log.isDebugEnabled) {
log.debug("Policy enforcement is enabled. Enforcing policy decisions for path [{}].", request.uri)
}
private fun authorize(request: HttpRequest, response: HttpResponse): AuthorizationContext {
val enforcementMode = policyEnforcerConfig.enforcementMode
return if (EnforcementMode.DISABLED == enforcementMode) {
createAuthorizedContext()
} else policyEnforcer.enforce(request, response)
val context = authorize(request, response)
if (log.isDebugEnabled) {
log.debug("Policy enforcement result for path [{}] is : {}", request.uri, if (context.isGranted) "GRANTED" else "DENIED")
log.debug("Returning authorization context with permissions:")
for (permission in context.permissions) {
log.debug(permission.toString())
}
}
return context
}
private fun createAuthorizedContext(): AuthorizationContext {
return object : ClientAuthorizationContext(policyEnforcer.authzClient) {
override fun hasPermission(resourceName: String, scopeName: String): Boolean {
return true
}
private fun authorize(request: HttpRequest, response: HttpResponse): AuthorizationContext {
val enforcementMode = policyEnforcerConfig.enforcementMode
log.debug("Authorize with enforcement mode: [{}]", enforcementMode)
return if (EnforcementMode.DISABLED == enforcementMode) {
createAuthorizedContext()
} else policyEnforcer.enforce(request, response)
}
override fun hasResourcePermission(resourceName: String): Boolean {
return true
}
private fun createAuthorizedContext(): AuthorizationContext {
return object : ClientAuthorizationContext(policyEnforcer.authzClient) {
override fun hasPermission(resourceName: String, scopeName: String): Boolean {
return true
}
override fun hasScopePermission(scopeName: String): Boolean {
return true
}
override fun hasResourcePermission(resourceName: String): Boolean {
return true
}
override fun getPermissions(): List<Permission> {
return emptyList()
}
override fun hasScopePermission(scopeName: String): Boolean {
return true
}
override fun isGranted(): Boolean {
return true
}
}
override fun getPermissions(): List<Permission> {
return emptyList()
}
override fun isGranted(): Boolean {
return true
}
}
}
companion object {
private val log = KotlinLogging.logger {}
}
companion object {
private val log = KotlinLogging.logger {}
}
}
@@ -18,6 +18,13 @@ class PartyService(
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> {
val parties = partyRepository.findByElectionName(electionName)
if (parties.isEmpty()) {
@@ -1,9 +1,5 @@
server.port: 12000
management.server.port: 12001
spring:
config:
import:
- "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,5 +1,4 @@
spring:
config:
import:
- "classpath:config/wahlrecht.yaml"
- "optional:configserver:http://configserver:8888/config"
- "configserver:${CONFIGSERVER_SCHEME:http}://${CONFIGSERVER_HOST:configserver}:${CONFIGSERVER_PORT:8888}${CONFIGSERVER_PREFIX:/config}"
@@ -1,11 +1,12 @@
server:
port: 12000
port: ${PORT:12000}
shutdown: graceful
forward-headers-strategy: framework
### technical configurations ###
management:
# for security, don't use same port as application
server.port: 12001
server.port: ${ACTUATOR_PORT:12001}
health:
livenessState.enabled: true
readinessState.enabled: true
@@ -28,10 +29,6 @@ spring:
name: wahlrecht
main:
banner-mode: off
kafka:
bootstrap-servers:
- localhost:9092
- kafka:9092
profiles:
default: dev
data.mongodb:
@@ -41,7 +38,7 @@ spring:
oauth2:
resourceserver:
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:
name: wahlrecht
@@ -1,2 +1,2 @@
de.twomartens.wahlrecht:
greeting: "Moin %s, you are prod"
greeting: "Moin %s, you are test"