Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

139 implementierung von speichern und lesen für handbuch endpunkt #349

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
dfb99db
🚧 draft of get post handbuch implementation
MrSebastian Jul 9, 2024
4debc0b
add flyway scripts for handbuch
MrSebastian Jul 9, 2024
f0a7275
finalize implementation of get and post handbuch
MrSebastian Jul 9, 2024
2b831e7
add example pdf file for upload request
MrSebastian Jul 9, 2024
dcd2e2f
create example http requests for handbuch
MrSebastian Jul 9, 2024
992bc80
add PreAuthorize to Service
MrSebastian Jul 9, 2024
340955d
fix methodename that handles post request
MrSebastian Jul 10, 2024
5ef15c1
add lombok No/AllArgs and builder
MrSebastian Jul 10, 2024
42ebf96
create tests for handbuche classes in service package
MrSebastian Jul 10, 2024
df68f75
draft handbuch controller unit test
MrSebastian Jul 10, 2024
7306176
Finalize controller unit test
MrSebastian Jul 10, 2024
92ad04c
implemented controller integration test
MrSebastian Jul 10, 2024
736ffd4
updated securityconfigurationtest for handbuch
MrSebastian Jul 10, 2024
bf15e35
spotless:apply
MrSebastian Jul 10, 2024
1e2a703
add openApiDoc for handbuch
MrSebastian Jul 10, 2024
f32fe42
add handbuch keycloak migration authorities
MrSebastian Jul 10, 2024
54f6afe
add oracle flyway file for handbuch
MrSebastian Jul 10, 2024
5848a47
add doc for manual
MrSebastian Jul 10, 2024
ded39f5
Merge branch 'refs/heads/dev' into 139-implementierung-von-speichern-…
MrSebastian Jul 10, 2024
a91419c
feedback: improve endpoint description
MrSebastian Jul 11, 2024
ec65d7f
fix typo in testclassname
MrSebastian Jul 11, 2024
6ae8b5b
Apply suggestions from code review
MrSebastian Jul 11, 2024
f0f558d
Apply suggestions from code review: fix id -> ID
MrSebastian Jul 11, 2024
12eb3a6
fix: use same column name in primary key
MrSebastian Jul 11, 2024
f3bed1a
feedback: add check that handbuchdata contains data
MrSebastian Jul 11, 2024
d3bf5fc
remove empty line at end of method
MrSebastian Jul 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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