Skip to content

Commit

Permalink
Merge pull request #349 from it-at-m/139-implementierung-von-speicher…
Browse files Browse the repository at this point in the history
…n-und-lesen-für-handbuch-endpunkt

139 implementierung von speichern und lesen für handbuch endpunkt
  • Loading branch information
MrSebastian committed Jul 11, 2024
2 parents 05a14d0 + d3bf5fc commit a080819
Show file tree
Hide file tree
Showing 29 changed files with 1,215 additions and 2 deletions.
9 changes: 8 additions & 1 deletion docs/src/features/basisdaten-service/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ Service zur Bereitstellung folgender Basisdaten:
- Wahlbezirke
- Wahlvorschläge
- Kopfdaten
- Handbuch

Wahlen, Wahlbezirke und Kopfdaten können in der Service-Datenbank gespeichert werden.

## Abhängigkeiten

Folgende Services werden zum Betrieb benötigt:
- EAI-Service
- Infomanagement-Service
- Infomanagement-Service

## Handbuch

In dem Service werden Handbücher verwaltet. Je Wahl und Wahlbezirkart kann ein Handbuch hinterlegt werden.

Bei dem Handbuch soll es sich um ein PDF-Dokument handeln.
48 changes: 48 additions & 0 deletions stack/keycloak/migration/add-authorities-basisdaten-handbuch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
id: add authorities basisdaten handbuch
author: MrSebastian
realm: ${SSO_REALM}
changes:
- addRole:
name: Basisdaten_BUSINESSACTION_GetHandbuch
clientRole: true
clientId: ${SSO_CLIENT_ID}
- assignRoleToGroup:
group: allBasisdatenAuthorities
role: Basisdaten_BUSINESSACTION_GetHandbuch
clientId: ${SSO_CLIENT_ID}

- addRole:
name: Basisdaten_BUSINESSACTION_PostHandbuch
clientRole: true
clientId: ${SSO_CLIENT_ID}
- assignRoleToGroup:
group: allBasisdatenAuthorities
role: Basisdaten_BUSINESSACTION_PostHandbuch
clientId: ${SSO_CLIENT_ID}

- addRole:
name: Basisdaten_READ_Handbuch
clientRole: true
clientId: ${SSO_CLIENT_ID}
- assignRoleToGroup:
group: allBasisdatenAuthorities
role: Basisdaten_READ_Handbuch
clientId: ${SSO_CLIENT_ID}

- addRole:
name: Basisdaten_WRITE_Handbuch
clientRole: true
clientId: ${SSO_CLIENT_ID}
- assignRoleToGroup:
group: allBasisdatenAuthorities
role: Basisdaten_WRITE_Handbuch
clientId: ${SSO_CLIENT_ID}

- addRole:
name: Basisdaten_DELETE_Handbuch
clientRole: true
clientId: ${SSO_CLIENT_ID}
- assignRoleToGroup:
group: allBasisdatenAuthorities
role: Basisdaten_DELETE_Handbuch
clientId: ${SSO_CLIENT_ID}
3 changes: 2 additions & 1 deletion stack/keycloak/migration/keycloak-changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ includes:
- path: create-group-all-basisdaten-authorities.yml
- path: add-authorities-basisdaten-wahlvorschlaege.yml
- path: add-authorities-eai-wahlvorschlag.yml
- path: add-authorities-eai-wahldaten.yml
- path: add-authorities-eai-wahldaten.yml
- path: add-authorities-basisdaten-handbuch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.muenchen.oss.wahllokalsystem.basisdatenservice.domain;

import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.Lob;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString(onlyExplicitlyIncluded = true)
public class Handbuch {

@EmbeddedId
@ToString.Include
private WahltagIdUndWahlbezirksart wahltagIdUndWahlbezirksart;

@NotNull
@Lob
private byte[] handbuch;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package de.muenchen.oss.wahllokalsystem.basisdatenservice.domain;

import java.util.Optional;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.security.access.prepost.PreAuthorize;

@PreAuthorize("hasAuthority('Basisdaten_READ_Handbuch')")
public interface HandbuchRepository extends CrudRepository<Handbuch, WahltagIdUndWahlbezirksart> {

String CACHE = "HANDBUCH_CACHE";

@Override
Iterable<Handbuch> findAll();

@Override
@Cacheable(value = CACHE, key = "#p0")
Optional<Handbuch> findById(WahltagIdUndWahlbezirksart wahltagIdUndWahlbezirksart);

@Override
@CachePut(value = CACHE, key = "#p0.wahltagIdUndWahlbezirksart")
@PreAuthorize("hasAuthority('Basisdaten_WRITE_Handbuch')")
<S extends Handbuch> S save(S handbuch);

@Override
@CacheEvict(value = CACHE, key = "#p0")
@PreAuthorize("hasAuthority('Basisdaten_DELETE_Handbuch')")
void deleteById(WahltagIdUndWahlbezirksart wahltagIdUndWahlbezirksart);

@Override
@CacheEvict(value = CACHE, key = "#p0.wahltagIdUndWahlbezirksart")
@PreAuthorize("hasAuthority('Basisdaten_DELETE_Handbuch')")
void delete(Handbuch entity);

@Override
@CacheEvict(value = CACHE, allEntries = true)
@PreAuthorize("hasAuthority('Basisdaten_DELETE_Handbuch')")
void deleteAll(Iterable<? extends Handbuch> entities);

@Override
@CacheEvict(value = CACHE, allEntries = true)
@PreAuthorize("hasAuthority('Basisdaten_DELETE_Handbuch')")
void deleteAll();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.muenchen.oss.wahllokalsystem.basisdatenservice.domain;

public enum WahlbezirkArt {
UWB, BWB
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.muenchen.oss.wahllokalsystem.basisdatenservice.domain;

import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class WahltagIdUndWahlbezirksart {

@NotNull
@Size(max = 1024)
private String wahltagID;

@Enumerated(EnumType.STRING)
@NotNull
private WahlbezirkArt wahlbezirksart;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,11 @@ public class ExceptionConstants {
public static ExceptionDataWrapper FAILED_COMMUNICATION_WITH_EAI = new ExceptionDataWrapper("100",
"Bei der Kommunikation mit dem Aoueai-Service ist ein Fehler aufgetreten. Es konnten daher keine Daten geladen werden.");

public static ExceptionDataWrapper GETHANDBUCH_PARAMETER_UNVOLLSTAENDIG = new ExceptionDataWrapper("301", "getHandbuch: Suchkriterien unvollständig.");
public static ExceptionDataWrapper GETHANDBUCH_KEINE_DATEN = new ExceptionDataWrapper("302", "Das Handbuch konnte nicht geladen werden.");

public static ExceptionDataWrapper POSTHANDBUCH_PARAMETER_UNVOLLSTAENDIG = new ExceptionDataWrapper("315", "postHandbuch: Suchkriterien unvollständig.");
public static ExceptionDataWrapper POSTHANDBUCH_SPEICHERN_NICHT_ERFOLGREICH = new ExceptionDataWrapper("316",
"postHandbuch: Das speichern des Handbuches war nicht erfolgreich.");

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package de.muenchen.oss.wahllokalsystem.basisdatenservice.rest.handbuch;

import de.muenchen.oss.wahllokalsystem.basisdatenservice.exception.ExceptionConstants;
import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.handbuch.HandbuchService;
import de.muenchen.oss.wahllokalsystem.wls.common.exception.rest.model.WlsExceptionDTO;
import de.muenchen.oss.wahllokalsystem.wls.common.exception.util.ExceptionFactory;
import io.swagger.v3.oas.annotations.Operation;
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 java.io.IOException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartHttpServletRequest;

@RestController
@RequestMapping("/businessActions/handbuch")
@RequiredArgsConstructor
@Slf4j
public class HandbuchController {

@Value("${service.config.manual.filenamesuffix:Handbuch.pdf}")
String manualFileNameSuffix;

private final HandbuchService handbuchService;

private final ExceptionFactory exceptionFactory;

private final HandbuchDTOMapper handbuchDTOMapper;

@GetMapping("{wahltagID}/{wahlbezirksart}")
@Operation(
description = "Abrufen des Handbuches einer Wahl für eine bestimmte Wahlbezirksart",
responses = {
@ApiResponse(
responseCode = "500", description = "Handbuch ist nicht abrufbar. Entweder fehlt es oder es gab technische Probleme",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = WlsExceptionDTO.class))
),
@ApiResponse(
responseCode = "400", description = "Anfrageparameter sind fehlerhaft",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = WlsExceptionDTO.class))
)
}
)
public ResponseEntity<byte[]> getHandbuch(@PathVariable("wahltagID") String wahltagID,
@PathVariable("wahlbezirksart") WahlbezirkArtDTO wahlbezirkArtDTO) {
val handbuchData = handbuchService.getHandbuch(handbuchDTOMapper.toModel(wahltagID, wahlbezirkArtDTO));
return createPDFResponse(handbuchData, wahlbezirkArtDTO);
}

@PostMapping("{wahltagID}/{wahlbezirksart}")
@Operation(
description = "Speichern eines Handbuches einer Wahl für eine bestimmte Wahlbezirksart",
responses = {
@ApiResponse(responseCode = "500", description = "Handbuch kann nicht gespeichert werden"),
@ApiResponse(
responseCode = "400", description = "Anfrageparameter sind fehlerhaft",
content = @Content(mediaType = "application/json", schema = @Schema(implementation = WlsExceptionDTO.class))
)
}
)
public void setHandbuch(@PathVariable("wahltagID") String wahltagID, @PathVariable("wahlbezirksart") WahlbezirkArtDTO wahlbezirkArtDTO,
final MultipartHttpServletRequest request) {
try {
val handbuchData = getHandbuchFromRequest(request);
val modelToSet = handbuchDTOMapper.toModel(handbuchDTOMapper.toModel(wahltagID, wahlbezirkArtDTO), handbuchData);
handbuchService.setHandbuch(modelToSet);
} catch (final IOException e) {
throw exceptionFactory.createTechnischeWlsException(ExceptionConstants.POSTHANDBUCH_SPEICHERN_NICHT_ERFOLGREICH);
}
}

private byte[] getHandbuchFromRequest(final MultipartHttpServletRequest request) throws IOException {
val fileName = request.getFileNames().next();
log.debug("using filename > {}", fileName);
val file = request.getFile(fileName);

if (file == null) {
throw new IOException("No file was uploaded");
}

return file.getBytes();
}

private ResponseEntity<byte[]> createPDFResponse(final byte[] responseBody, final WahlbezirkArtDTO wahlbezirkArtDTO) {
val responseHeaders = new HttpHeaders();
responseHeaders.add(HttpHeaders.CONTENT_TYPE, "application/pdf");
responseHeaders.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + wahlbezirkArtDTO + manualFileNameSuffix);

return new ResponseEntity<>(responseBody, responseHeaders, HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package de.muenchen.oss.wahllokalsystem.basisdatenservice.rest.handbuch;

import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.handbuch.HandbuchReferenceModel;
import de.muenchen.oss.wahllokalsystem.basisdatenservice.services.handbuch.HandbuchWriteModel;
import org.mapstruct.Mapper;

@Mapper
public interface HandbuchDTOMapper {

HandbuchWriteModel toModel(HandbuchReferenceModel handbuchReferenceModel, byte[] handbuchData);

HandbuchReferenceModel toModel(String wahltagID, WahlbezirkArtDTO wahlbezirksart);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.muenchen.oss.wahllokalsystem.basisdatenservice.rest.handbuch;

public enum WahlbezirkArtDTO {
UWB, BWB
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.handbuch;

import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.Handbuch;
import de.muenchen.oss.wahllokalsystem.basisdatenservice.domain.WahltagIdUndWahlbezirksart;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper
public interface HandbuchModelMapper {

WahltagIdUndWahlbezirksart toEntityID(HandbuchReferenceModel handbuchReferenceModel);

@Mapping(target = "wahltagIdUndWahlbezirksart.wahltagID", source = "handbuchReferenceModel.wahltagID")
@Mapping(target = "wahltagIdUndWahlbezirksart.wahlbezirksart", source = "handbuchReferenceModel.wahlbezirksart")
@Mapping(target = "handbuch", source = "handbuchData")
Handbuch toEntity(HandbuchWriteModel handbuchWriteModel);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package de.muenchen.oss.wahllokalsystem.basisdatenservice.services.handbuch;

import jakarta.validation.constraints.NotNull;
import lombok.Builder;

@Builder
public record HandbuchReferenceModel(@NotNull String wahltagID,
@NotNull WahlbezirkArtModel wahlbezirksart) {
}
Loading

0 comments on commit a080819

Please sign in to comment.