From 0336825cd190211dfece3320bbae67e914a33109 Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Thu, 13 Jul 2023 19:45:22 +0200 Subject: [PATCH] Added ability to add election results to DB --- .../v1/ElectionResultController.java | 88 +++++++++++++++++++ .../mapper/v1/ElectionResultMapper.java | 44 ++++++++++ .../wahlrecht/model/db/ElectionResult.java | 44 ++++++++++ .../wahlrecht/model/db/VotingResult.java | 29 ++++++ .../model/dto/v1/ElectionResult.java | 9 ++ .../model/internal/ElectionResult.java | 9 ++ .../repository/ElectionResultRepository.java | 9 ++ .../repository/VotingResultRepository.java | 8 ++ .../service/ElectionResultService.java | 66 ++++++++++++++ 9 files changed, 306 insertions(+) create mode 100644 module-server/src/main/java/de/twomartens/wahlrecht/controller/v1/ElectionResultController.java create mode 100644 module-server/src/main/java/de/twomartens/wahlrecht/mapper/v1/ElectionResultMapper.java create mode 100644 module-server/src/main/java/de/twomartens/wahlrecht/model/db/ElectionResult.java create mode 100644 module-server/src/main/java/de/twomartens/wahlrecht/model/db/VotingResult.java create mode 100644 module-server/src/main/java/de/twomartens/wahlrecht/model/dto/v1/ElectionResult.java create mode 100644 module-server/src/main/java/de/twomartens/wahlrecht/model/internal/ElectionResult.java create mode 100644 module-server/src/main/java/de/twomartens/wahlrecht/repository/ElectionResultRepository.java create mode 100644 module-server/src/main/java/de/twomartens/wahlrecht/repository/VotingResultRepository.java create mode 100644 module-server/src/main/java/de/twomartens/wahlrecht/service/ElectionResultService.java diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/controller/v1/ElectionResultController.java b/module-server/src/main/java/de/twomartens/wahlrecht/controller/v1/ElectionResultController.java new file mode 100644 index 0000000..f5a9c2f --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/controller/v1/ElectionResultController.java @@ -0,0 +1,88 @@ +package de.twomartens.wahlrecht.controller.v1; + +import de.twomartens.wahlrecht.mapper.v1.ElectionResultMapper; +import de.twomartens.wahlrecht.model.dto.ErrorMessage; +import de.twomartens.wahlrecht.model.dto.v1.ElectionResult; +import de.twomartens.wahlrecht.service.ElectionResultService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +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 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.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping(value = "/wahlrecht/v1") +@Tag(name = "ElectionResults", description = "all requests relating to election results") +public class ElectionResultController { + + private final ElectionResultMapper mapper = Mappers.getMapper(ElectionResultMapper.class); + private final ElectionResultService service; + + @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) + public ResponseEntity getElectionByName( + @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") String electionName) { + ElectionResult result = mapper.mapToExternal(service.getElectionResult(electionName)); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(result); + } + + @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 = "basicAuth") + public ResponseEntity putElection(@RequestBody ElectionResult electionResult) { + boolean createdNew = service.storeResult(mapper.mapToDB(electionResult)); + return createdNew + ? new ResponseEntity<>(HttpStatus.CREATED) + : new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/mapper/v1/ElectionResultMapper.java b/module-server/src/main/java/de/twomartens/wahlrecht/mapper/v1/ElectionResultMapper.java new file mode 100644 index 0000000..11553a6 --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/mapper/v1/ElectionResultMapper.java @@ -0,0 +1,44 @@ +package de.twomartens.wahlrecht.mapper.v1; + +import de.twomartens.wahlrecht.model.internal.ElectionResult; +import de.twomartens.wahlrecht.model.internal.VotingResult; +import java.util.Collection; +import java.util.Map; +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.ReportingPolicy; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED, + nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface ElectionResultMapper { + + de.twomartens.wahlrecht.model.dto.v1.ElectionResult mapToExternal(de.twomartens.wahlrecht.model.db.ElectionResult result); + + Collection mapToExternal( + Collection results); + + Map> mapToExternal( + Map> results); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "created", ignore = true) + @Mapping(target = "lastModified", ignore = true) + de.twomartens.wahlrecht.model.db.ElectionResult mapToDB(de.twomartens.wahlrecht.model.dto.v1.ElectionResult result); + + Collection mapToDB( + Collection results); + + Map> mapToDB( + Map> results); + + ElectionResult mapToInternal(de.twomartens.wahlrecht.model.dto.v1.ElectionResult result); + + Collection mapToInternal( + Collection results); + + Map> mapToInternal( + Map> results); +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/model/db/ElectionResult.java b/module-server/src/main/java/de/twomartens/wahlrecht/model/db/ElectionResult.java new file mode 100644 index 0000000..2ff58bf --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/model/db/ElectionResult.java @@ -0,0 +1,44 @@ +package de.twomartens.wahlrecht.model.db; + +import java.time.Instant; +import java.util.Collection; +import java.util.Map; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.FieldDefaults; +import org.bson.types.ObjectId; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +@Getter +@Setter +@Builder +@EqualsAndHashCode +@ToString +@FieldDefaults(level = AccessLevel.PRIVATE) +@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ElectionResult { + @EqualsAndHashCode.Exclude + @Id + ObjectId id; + @EqualsAndHashCode.Exclude + @CreatedDate + Instant created; + @EqualsAndHashCode.Exclude + @LastModifiedDate + Instant lastModified; + + String electionName; + Collection overallResults; + Map> constituencyResults; +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/model/db/VotingResult.java b/module-server/src/main/java/de/twomartens/wahlrecht/model/db/VotingResult.java new file mode 100644 index 0000000..b3052f6 --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/model/db/VotingResult.java @@ -0,0 +1,29 @@ +package de.twomartens.wahlrecht.model.db; + +import java.util.Map; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.FieldDefaults; + +@Getter +@Setter +@Builder +@EqualsAndHashCode +@ToString +@FieldDefaults(level = AccessLevel.PRIVATE) +@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class VotingResult { + String electionName; + String partyAbbreviation; + String nominationName; + int votesOnNomination; + int votesThroughHealing; + Map votesPerPosition; +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/model/dto/v1/ElectionResult.java b/module-server/src/main/java/de/twomartens/wahlrecht/model/dto/v1/ElectionResult.java new file mode 100644 index 0000000..11eba7f --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/model/dto/v1/ElectionResult.java @@ -0,0 +1,9 @@ +package de.twomartens.wahlrecht.model.dto.v1; + +import java.util.Collection; +import java.util.Map; + +public record ElectionResult(String electionName, Collection overallResults, + Map> constituencyResults) { + +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/model/internal/ElectionResult.java b/module-server/src/main/java/de/twomartens/wahlrecht/model/internal/ElectionResult.java new file mode 100644 index 0000000..791c9ad --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/model/internal/ElectionResult.java @@ -0,0 +1,9 @@ +package de.twomartens.wahlrecht.model.internal; + +import java.util.Collection; +import java.util.Map; + +public record ElectionResult(String electionName, Collection overallResults, + Map> constituencyResults) { + +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/repository/ElectionResultRepository.java b/module-server/src/main/java/de/twomartens/wahlrecht/repository/ElectionResultRepository.java new file mode 100644 index 0000000..51dd244 --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/repository/ElectionResultRepository.java @@ -0,0 +1,9 @@ +package de.twomartens.wahlrecht.repository; + +import de.twomartens.wahlrecht.model.db.ElectionResult; +import java.util.Optional; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface ElectionResultRepository extends MongoRepository { + Optional findByElectionName(String name); +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/repository/VotingResultRepository.java b/module-server/src/main/java/de/twomartens/wahlrecht/repository/VotingResultRepository.java new file mode 100644 index 0000000..8d37ecb --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/repository/VotingResultRepository.java @@ -0,0 +1,8 @@ +package de.twomartens.wahlrecht.repository; + +import de.twomartens.wahlrecht.model.db.VotingResult; +import org.springframework.data.mongodb.repository.MongoRepository; + +public interface VotingResultRepository extends MongoRepository { + +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/service/ElectionResultService.java b/module-server/src/main/java/de/twomartens/wahlrecht/service/ElectionResultService.java new file mode 100644 index 0000000..8162ce6 --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/service/ElectionResultService.java @@ -0,0 +1,66 @@ +package de.twomartens.wahlrecht.service; + +import de.twomartens.wahlrecht.model.db.ElectionResult; +import de.twomartens.wahlrecht.repository.ElectionResultRepository; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +@RequiredArgsConstructor +@Service +public class ElectionResultService { + + private final ElectionResultRepository repository; + + private Map electionResults; + + public ElectionResult getElectionResult(String electionName) { + if (electionResults == null) { + fetchResults(); + } + ElectionResult electionResult = electionResults.get(electionName); + if (electionResult == null) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, + "no election result found for %s".formatted(electionName)); + } + return electionResult; + } + + public boolean storeResult(ElectionResult result) { + if (electionResults == null) { + fetchResults(); + } + + String id = result.getElectionName(); + ElectionResult existing = electionResults.get(id); + boolean needsUpdate = !result.equals(existing); + + if (!needsUpdate) { + return false; + } + + boolean createdNew = true; + if (existing != null) { + existing.setOverallResults(result.getOverallResults()); + existing.setConstituencyResults(result.getConstituencyResults()); + result = existing; + createdNew = false; + } + + ElectionResult stored = repository.save(result); + electionResults.put(id, stored); + + return createdNew; + } + + private void fetchResults() { + electionResults = repository.findAll().stream() + .collect(Collectors.toMap( + ElectionResult::getElectionName, + Function.identity())); + } +}