diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/controller/v1/ElectionController.java b/module-server/src/main/java/de/twomartens/wahlrecht/controller/v1/ElectionController.java new file mode 100644 index 0000000..3f6af22 --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/controller/v1/ElectionController.java @@ -0,0 +1,78 @@ +package de.twomartens.wahlrecht.controller.v1; + +import de.twomartens.wahlrecht.mapper.v1.ElectionMapper; +import de.twomartens.wahlrecht.model.dto.v1.Election; +import de.twomartens.wahlrecht.service.ElectionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; +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 java.util.Collection; +import java.util.List; +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.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 = "Elections", description = "all requests relating to elections") +public class ElectionController { + + private final ElectionMapper mapper = Mappers.getMapper(ElectionMapper.class); + private final ElectionService service; + + @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)))} + ) + } + ) + @GetMapping(value = "/elections", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> getElections() { + List elections = service.getElections().stream().map(mapper::mapToExternal).toList(); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(elections); + } + + @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 = "basicAuth") + public ResponseEntity putElection(@RequestBody Election election) { + boolean createdNew = service.storeElection(mapper.mapToDB(election)); + return createdNew + ? new ResponseEntity<>(HttpStatus.CREATED) + : new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/controller/v1/PartyController.java b/module-server/src/main/java/de/twomartens/wahlrecht/controller/v1/PartyController.java new file mode 100644 index 0000000..8721a23 --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/controller/v1/PartyController.java @@ -0,0 +1,94 @@ +package de.twomartens.wahlrecht.controller.v1; + +import de.twomartens.wahlrecht.mapper.v1.PartyInElectionMapper; +import de.twomartens.wahlrecht.model.dto.ErrorMessage; +import de.twomartens.wahlrecht.model.dto.v1.PartyInElection; +import de.twomartens.wahlrecht.service.PartyService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +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 java.util.Collection; +import java.util.List; +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 = "Parties", description = "all requests relating to parties") +public class PartyController { + + private final PartyInElectionMapper mapper = Mappers.getMapper(PartyInElectionMapper.class); + private final PartyService service; + + @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) + public ResponseEntity> getPartiesByElectionName( + @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") String electionName) { + List parties = service.getPartiesByElectionName(electionName).stream() + .map(mapper::mapToExternal) + .toList(); + + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(parties); + } + + @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", + content = {@Content( + mediaType = "plain/text")} + ) + } + ) + @PutMapping("/party") + @SecurityRequirement(name = "basicAuth") + public ResponseEntity putElection(@RequestBody PartyInElection party) { + boolean createdNew = service.storeParty(mapper.mapToDB(party)); + return createdNew + ? new ResponseEntity<>(HttpStatus.CREATED) + : new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/service/CandidateService.java b/module-server/src/main/java/de/twomartens/wahlrecht/service/CandidateService.java new file mode 100644 index 0000000..63dde6a --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/service/CandidateService.java @@ -0,0 +1,27 @@ +package de.twomartens.wahlrecht.service; + +import de.twomartens.wahlrecht.model.db.Candidate; +import de.twomartens.wahlrecht.repository.CandidateRepository; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class CandidateService { + + private final CandidateRepository repository; + + public Candidate storeCandidate(@NonNull Candidate candidate) { + Optional foundOptional = repository.findByName(candidate.getName()); + + if (foundOptional.isPresent()) { + Candidate found = foundOptional.get(); + found.setProfession(candidate.getProfession()); + return repository.save(found); + } else { + return repository.save(candidate); + } + } +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/service/ConstituencyService.java b/module-server/src/main/java/de/twomartens/wahlrecht/service/ConstituencyService.java new file mode 100644 index 0000000..c749a58 --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/service/ConstituencyService.java @@ -0,0 +1,33 @@ +package de.twomartens.wahlrecht.service; + +import de.twomartens.wahlrecht.model.db.Constituency; +import de.twomartens.wahlrecht.repository.ConstituencyRepository; +import java.util.Collection; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ConstituencyService { + + private final ConstituencyRepository repository; + + public Collection getConstituencies() { + return repository.findAll(); + } + + public Constituency storeConstituency(@NonNull Constituency constituency) { + Optional foundOptional = repository + .findByElectionNameAndNumber(constituency.getElectionName(), constituency.getNumber()); + + if (foundOptional.isPresent()) { + Constituency found = foundOptional.get(); + found.setNumberOfSeats(constituency.getNumberOfSeats()); + found.setName(constituency.getName()); + constituency = found; + } + return repository.save(constituency); + } +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/service/ElectionService.java b/module-server/src/main/java/de/twomartens/wahlrecht/service/ElectionService.java new file mode 100644 index 0000000..844db56 --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/service/ElectionService.java @@ -0,0 +1,47 @@ +package de.twomartens.wahlrecht.service; + +import de.twomartens.wahlrecht.model.db.Constituency; +import de.twomartens.wahlrecht.model.db.Election; +import de.twomartens.wahlrecht.repository.ElectionRepository; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class ElectionService { + + private final ElectionRepository electionRepository; + private final ConstituencyService constituencyService; + + public Collection getElections() { + return electionRepository.findAll(); + } + + public boolean storeElection(@NonNull Election election) { + boolean createdNew = true; + String electionName = election.getName(); + Optional existingElectionOptional = electionRepository.findByName(electionName); + Collection constituencies = new ArrayList<>(); + election.getConstituencies() + .forEach(constituency -> constituencies.add( + constituencyService.storeConstituency(constituency))); + + if (existingElectionOptional.isPresent()) { + Election existing = existingElectionOptional.get(); + existing.setDay(election.getDay()); + existing.setTotalNumberOfSeats(election.getTotalNumberOfSeats()); + existing.setVotingThreshold(election.getVotingThreshold()); + election = existing; + createdNew = false; + } + + election.setConstituencies(constituencies); + electionRepository.save(election); + + return createdNew; + } +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/service/NominationService.java b/module-server/src/main/java/de/twomartens/wahlrecht/service/NominationService.java new file mode 100644 index 0000000..f0ecbc2 --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/service/NominationService.java @@ -0,0 +1,32 @@ +package de.twomartens.wahlrecht.service; + +import de.twomartens.wahlrecht.model.db.Candidate; +import de.twomartens.wahlrecht.model.db.Nomination; +import de.twomartens.wahlrecht.repository.NominationRepository; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class NominationService { + + private final NominationRepository repository; + private final CandidateService candidateService; + + public Nomination storeNomination(Nomination nomination) { + Optional existingOptional = repository + .findByNameAndElectionNameAndPartyAbbreviation(nomination.getName(), + nomination.getElectionName(), nomination.getPartyAbbreviation()); + Collection candidates = new ArrayList<>(); + nomination.getCandidates() + .forEach(candidate -> candidates.add(candidateService.storeCandidate(candidate))); + if (existingOptional.isPresent()) { + nomination = existingOptional.get(); + } + nomination.setCandidates(candidates); + return repository.save(nomination); + } +} diff --git a/module-server/src/main/java/de/twomartens/wahlrecht/service/PartyService.java b/module-server/src/main/java/de/twomartens/wahlrecht/service/PartyService.java new file mode 100644 index 0000000..b5e92ec --- /dev/null +++ b/module-server/src/main/java/de/twomartens/wahlrecht/service/PartyService.java @@ -0,0 +1,53 @@ +package de.twomartens.wahlrecht.service; + +import de.twomartens.wahlrecht.model.db.Nomination; +import de.twomartens.wahlrecht.model.db.PartyInElection; +import de.twomartens.wahlrecht.repository.PartyRepository; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +@RequiredArgsConstructor +@Service +public class PartyService { + + private final PartyRepository partyRepository; + private final NominationService nominationService; + + public Collection getPartiesByElectionName(String electionName) { + Collection parties = partyRepository.findByElectionName(electionName); + if (parties.isEmpty()) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "no party found for " + electionName); + } + + return parties; + } + + public boolean storeParty(PartyInElection partyInElection) { + boolean createdNew = true; + Optional existingOptional = partyRepository + .findByAbbreviationAndElectionName(partyInElection.getName(), + partyInElection.getElectionName()); + Map constituencyNominations = new HashMap<>(); + partyInElection.getConstituencyNominations() + .forEach((key, value) -> constituencyNominations.put(key, + nominationService.storeNomination(value))); + if (existingOptional.isPresent()) { + PartyInElection existing = existingOptional.get(); + existing.setName(partyInElection.getName()); + partyInElection = existing; + createdNew = false; + } + partyInElection.setConstituencyNominations(constituencyNominations); + partyInElection.setOverallNomination( + nominationService.storeNomination(partyInElection.getOverallNomination())); + partyRepository.save(partyInElection); + + return createdNew; + } +}