diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditController.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditController.java index a1c0a45d1..21bc13a36 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditController.java @@ -39,6 +39,8 @@ @Tag(name = "Audit") @RequiredArgsConstructor public class AuditController { + + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk og *Repository-aksess til tjenestelaget. private final AuditVersionRepository repository; private final StorageService storage; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditService.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditService.java index f27966f0c..e541e4b8e 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditService.java @@ -1,5 +1,6 @@ package no.nav.data.common.auditing; +import lombok.RequiredArgsConstructor; import no.nav.data.common.auditing.domain.AuditVersionRepository; import no.nav.data.common.auditing.dto.AuditMetadata; import org.springframework.stereotype.Service; @@ -7,14 +8,11 @@ import java.util.List; @Service +@RequiredArgsConstructor public class AuditService { private final AuditVersionRepository auditVersionRepository; - public AuditService(AuditVersionRepository auditVersionRepository) { - this.auditVersionRepository = auditVersionRepository; - } - public List lastEditedProcessesByUser(String user) { return auditVersionRepository.getLastChangedProcessesByUser(user, 20); } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditVersionListener.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditVersionListener.java index 971273f2c..d035d7b9f 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditVersionListener.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/AuditVersionListener.java @@ -25,6 +25,8 @@ import no.nav.data.common.utils.MdcUtils; import no.nav.data.polly.codelist.domain.Codelist; import org.hibernate.proxy.HibernateProxy; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import java.util.Optional; @@ -33,7 +35,11 @@ @Slf4j public class AuditVersionListener { - private static AuditVersionRepository repository; + // Merk: Denne klassen er IKKE håndtert av Spring (siden den instansieres av JPA), men den avhenger av en Spring-managed bønne. + // Dette er løst med et hæck der Spring kaller en statisk metode i denne klassen for å tilby bønna. + // Men dette hacket virker ikke alltid for integrasjonstesting, så det er et hæck til i IntegrationTestBase for å få det til å gå. + + private static volatile AuditVersionRepository repository; private static final ObjectWriter wr; @@ -64,16 +70,17 @@ public void preRemove(Object entity) { audit(entity, Action.DELETE); } + @Transactional(propagation = Propagation.MANDATORY) private void audit(Object entity, Action action) { try { Assert.isTrue(entity instanceof Auditable, "Invalid object"); - if (entity instanceof GenericStorage && !((GenericStorage) entity).getType().isAudit()) { + if (entity instanceof GenericStorage genStorage && !genStorage.getType().isAudit()) { return; } String tableName = AuditVersion.tableName(((Auditable) entity).getClass()); String id = getIdForObject(entity); String data = wr.writeValueAsString(entity); - String user = Optional.ofNullable(MdcUtils.getUser()).orElse("no user set"); + String user = Optional.ofNullable(MdcUtils.getUser()).orElse("no-user-set"); AuditVersion auditVersion = AuditVersion.builder() .action(action).table(tableName).tableId(id).data(data).user(user) .build(); @@ -85,8 +92,8 @@ private void audit(Object entity, Action action) { public static String getIdForObject(Object entity) { String id; - if (entity instanceof Codelist) { - id = ((Codelist) entity).getList() + "-" + ((Codelist) entity).getCode(); + if (entity instanceof Codelist clEntity) { + id = clEntity.getList() + "-" + clEntity.getCode(); } else { UUID uuid = HibernateUtils.getId(entity); Assert.notNull(uuid, "entity has not set id"); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/AuditVersion.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/AuditVersion.java index 284d287e3..ca246b1b3 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/AuditVersion.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/AuditVersion.java @@ -67,6 +67,7 @@ public class AuditVersion { @Column(name = "DATA", nullable = false, updatable = false) private String data; + // TODO: Snu avhengigheten innover public AuditResponse convertToResponse() { return AuditResponse.builder() .id(id.toString()) @@ -79,6 +80,7 @@ public AuditResponse convertToResponse() { .build(); } + // TODO: Snu avhengigheten innover (ikke triviell). Flytt all forretningslogikk i domeneklassen ut til tjenestelaget. public EventResponse convertToEventResponse() { return EventResponse.builder() .id(id.toString()) diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/AuditVersionRepository.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/AuditVersionRepository.java index 4d209a9af..4cbc95be8 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/AuditVersionRepository.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/AuditVersionRepository.java @@ -24,29 +24,30 @@ static Example exampleFrom(AuditVersion example) { @Query(value = """ select * from audit_version av - where table_name = ?1 and action = ?2 + where table_name = ?1 and action = ?2 and (?2 = 'DELETE' or not exists (select 1 from audit_version av2 where av2.table_id = av.table_id and av2.action = 'DELETE')) - """, - countQuery = """ - select count(1) from audit_version av - where table_name = ?1 and action = ?2 - and (?2 = 'DELETE' or not exists (select 1 from audit_version av2 where av2.table_id = av.table_id and av2.action = 'DELETE')) - """, nativeQuery = true) + """, + countQuery = """ + select count(1) from audit_version av + where table_name = ?1 and action = ?2 + and (?2 = 'DELETE' or not exists (select 1 from audit_version av2 where av2.table_id = av.table_id and av2.action = 'DELETE')) + """, + nativeQuery = true) Page findForTableAndAction(String table, String action, Pageable page); @Query(value = """ select cast(audit_id as text) as id, time, action, table_name as tableName, table_id as tableId - from audit_version where audit_id in ( - select distinct on (table_id) audit_id - from audit_version - where table_name = 'PROCESS' - and user_id = ?1 - and exists (select 1 from process where process_id = cast(table_id as uuid)) - order by table_id, time desc - ) - order by time desc - limit ?2 - """, nativeQuery = true) + from audit_version where audit_id in ( + select distinct on (table_id) audit_id + from audit_version + where table_name = 'PROCESS' + and user_id = ?1 + and exists (select 1 from process where process_id = cast(table_id as uuid)) + order by table_id, time desc + ) + order by time desc + limit ?2 + """, nativeQuery = true) List getLastChangedProcessesByUser(String userId, int limit); } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/Auditable.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/Auditable.java index 5d0ee1b4c..e9d35b4e1 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/Auditable.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/Auditable.java @@ -46,6 +46,7 @@ public abstract class Auditable { @Column(name = "LAST_MODIFIED_DATE") protected LocalDateTime lastModifiedDate; + // TODO: Snu avhengigheten innover public ChangeStampResponse convertChangeStampResponse() { return ChangeStampResponse.builder() .lastModifiedBy(getLastModifiedBy()) diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/MailLogRepository.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/MailLogRepository.java index a8dd2ad65..54787499e 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/MailLogRepository.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/domain/MailLogRepository.java @@ -11,9 +11,10 @@ public interface MailLogRepository extends JpaRepository { + @Override @Query(value = "select * from generic_storage where type = 'MAIL_LOG' order by created_date desc", - countQuery = "select count(1) from generic_storage where type = 'MAIL_LOG'" - , nativeQuery = true) + countQuery = "select count(1) from generic_storage where type = 'MAIL_LOG'", + nativeQuery = true) Page findAll(Pageable pageable); @Query(value = "select * from generic_storage where type = 'MAIL_LOG' and data ->> 'to' = ?1 order by created_date desc", nativeQuery = true) diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/event/EventController.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/event/EventController.java index 1ed66fca8..b2123ca2d 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/event/EventController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/auditing/event/EventController.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import no.nav.data.common.auditing.domain.Action; import no.nav.data.common.auditing.domain.AuditVersion; @@ -21,6 +22,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -31,20 +33,19 @@ import static no.nav.data.common.utils.StreamUtils.convert; @Slf4j +@RequiredArgsConstructor @RestController @RequestMapping("/event") @Tag(name = "Event", description = "Domain object events") public class EventController { + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk, *Repository-aksess og @Transactional til tjenestelaget. + private final AuditVersionRepository repository; private final List allowedTables = convert( List.of(InformationType.class, Process.class, Policy.class, Disclosure.class, Document.class), AuditVersion::tableName); - public EventController(AuditVersionRepository repository) { - this.repository = repository; - } - @Operation(summary = "Get events") @ApiResponse(description = "Events fetched") @GetMapping @@ -63,7 +64,6 @@ private void validateTable(String table) { } static class EventPage extends RestResponsePage { - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/mail/EmailServiceImpl.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/mail/EmailServiceImpl.java index 5c708b6a6..80f0a8907 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/mail/EmailServiceImpl.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/mail/EmailServiceImpl.java @@ -2,10 +2,12 @@ import no.nav.data.common.security.SecurityProperties; import no.nav.data.common.storage.StorageService; +import no.nav.data.common.storage.domain.GenericStorage; import no.nav.data.common.storage.domain.GenericStorageRepository; import no.nav.data.common.storage.domain.StorageType; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class EmailServiceImpl implements EmailService { @@ -30,6 +32,7 @@ public void sendMail(MailTask mailTask) { } @Override + @Transactional public void scheduleMail(MailTask mailTask) { storage.save(mailTask); } @@ -37,10 +40,13 @@ public void scheduleMail(MailTask mailTask) { @Scheduled(initialDelayString = "PT3M", fixedRateString = "PT5M") public void sendMails() { var tasks = storageRepository.findAllByType(StorageType.MAIL_TASK); - - tasks.forEach(task -> { - sendMail(task.getDataObject(MailTask.class)); - storageRepository.delete(task); - }); + tasks.forEach(this::sendMailAndDeleteTask); + } + + @Transactional // Viktig med en transaksjon per mail, slik at ikke allerede sendte mails blir rullet tilbake til repo ved feil + private void sendMailAndDeleteTask(GenericStorage task) { + storageRepository.delete(task); + sendMail(task.getDataObject(MailTask.class)); } + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/rest/ChangeStampResponse.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/rest/ChangeStampResponse.java index c849b0340..4a3888250 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/rest/ChangeStampResponse.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/rest/ChangeStampResponse.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import no.nav.data.common.auditing.domain.Auditable; import no.nav.data.common.auditing.domain.Auditable.Fields; import java.time.LocalDateTime; @@ -19,4 +20,13 @@ public class ChangeStampResponse { private String lastModifiedBy; private LocalDateTime lastModifiedDate; private LocalDateTime createdDate; + + public static ChangeStampResponse from(Auditable auditable) { + return ChangeStampResponse.builder() + .lastModifiedBy(auditable.getLastModifiedBy()) + .lastModifiedDate(auditable.getLastModifiedDate() == null ? LocalDateTime.now() : auditable.getLastModifiedDate()) + .createdDate(auditable.getCreatedDate()) + .build(); + } + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/security/AppIdMapping.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/security/AppIdMapping.java index ff7935b65..ee1465330 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/security/AppIdMapping.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/security/AppIdMapping.java @@ -33,7 +33,6 @@ public Collection getIds() { @Data static class AuthApps { - private String name; private String clientId; } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/security/azure/AADStatelessAuthenticationFilter.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/security/azure/AADStatelessAuthenticationFilter.java index 47398b642..fb17edd01 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/security/azure/AADStatelessAuthenticationFilter.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/security/azure/AADStatelessAuthenticationFilter.java @@ -191,7 +191,7 @@ private ConfigurableJWTProcessor getAadJwtTokenValidator(JWSAlg new JWSVerificationKeySelector<>(jwsAlgorithm, keySource); jwtProcessor.setJWSKeySelector(keySelector); - jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>() { + jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>(null, null) { @Override public void verify(JWTClaimsSet claimsSet, SecurityContext ctx) throws BadJWTException { super.verify(claimsSet, ctx); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/security/azure/AzureAdService.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/security/azure/AzureAdService.java index 74c8b4c05..547f833fc 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/security/azure/AzureAdService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/security/azure/AzureAdService.java @@ -3,27 +3,26 @@ import com.microsoft.graph.serviceclient.GraphServiceClient; import com.microsoft.graph.users.item.sendmail.SendMailPostRequestBody; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import no.nav.data.common.mail.EmailProvider; import no.nav.data.common.mail.MailTask; import no.nav.data.common.storage.StorageService; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import static no.nav.data.common.security.azure.support.MailMessage.compose; @Slf4j @Service +@RequiredArgsConstructor public class AzureAdService implements EmailProvider { private final AzureTokenProvider azureTokenProvider; private final StorageService storage; - public AzureAdService(AzureTokenProvider azureTokenProvider, StorageService storage) { - this.azureTokenProvider = azureTokenProvider; - this.storage = storage; - } - @Override + @Transactional public void sendMail(MailTask mailTask) { log.info("sending mail {} to {}", mailTask.getSubject(), mailTask.getTo()); SendMailPostRequestBody sendMailPostRequestBody = new SendMailPostRequestBody(); @@ -31,7 +30,6 @@ public void sendMail(MailTask mailTask) { sendMailPostRequestBody.setSaveToSentItems(true); getMailGraphClient().me().sendMail().post(sendMailPostRequestBody); - storage.save(mailTask.toMailLog()); } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/security/dto/UserInfoResponse.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/security/dto/UserInfoResponse.java index d7a7b4fe3..a7008247b 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/security/dto/UserInfoResponse.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/security/dto/UserInfoResponse.java @@ -36,7 +36,6 @@ public static UserInfoResponse noUser(boolean securityEnabled) { return responseBuilder.build(); } - @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") public static class UserInfoResponseBuilder { private List groups = new ArrayList<>(); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/StorageService.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/StorageService.java index 5f8045470..95099fc3a 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/StorageService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/StorageService.java @@ -1,6 +1,7 @@ package no.nav.data.common.storage; import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import lombok.RequiredArgsConstructor; import no.nav.data.common.storage.domain.AppState; import no.nav.data.common.storage.domain.GenericStorage; import no.nav.data.common.storage.domain.GenericStorageData; @@ -13,14 +14,11 @@ import java.util.function.Consumer; @Service +@RequiredArgsConstructor public class StorageService { private final GenericStorageRepository repository; - public StorageService(GenericStorageRepository repository) { - this.repository = repository; - } - public T getSingleton(Class type) { return getSingletonAsStorage(type).getDataObject(type); } @@ -29,6 +27,7 @@ public T get(Class type, UUID id) { return repository.findById(id).orElseThrow().getDataObject(type); } + @Transactional(readOnly = false) // Merk: På tross av navnet på metoden, kan den resultere i save public GenericStorage getSingletonAsStorage(Class type) { return findType(StorageType.fromClass(type)); } @@ -41,17 +40,19 @@ private GenericStorage create(StorageType type) { return repository.save(GenericStorage.builder().generateId().type(type).data(JsonNodeFactory.instance.objectNode()).build()); } + @Transactional public GenericStorage save(GenericStorage storage) { return repository.save(storage); } + @Transactional public void save(GenericStorageData data) { var storage = GenericStorage.builder().generateId().build(); storage.setDataObject(data); repository.save(storage); } - @Transactional + @Transactional public void usingAppState(Consumer consumer) { var appStateStorage = getSingletonAsStorage(AppState.class); var appState = appStateStorage.getDataObject(AppState.class); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/domain/GenericStorage.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/domain/GenericStorage.java index d2c3efa92..4c9bee0f0 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/domain/GenericStorage.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/domain/GenericStorage.java @@ -31,6 +31,8 @@ @Entity @Table(name = "GENERIC_STORAGE") public class GenericStorage extends Auditable { + + // TODO: Klassen bør types @Id @Column(name = "ID") diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/domain/GenericStorageData.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/domain/GenericStorageData.java index d5b866691..e342fc1a4 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/domain/GenericStorageData.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/storage/domain/GenericStorageData.java @@ -7,6 +7,8 @@ public interface GenericStorageData { + // TODO: Dette interfacet bør types (GenericStorageData>) + @JsonIgnore ChangeStamp getChangeStamp(); @@ -17,6 +19,7 @@ default StorageType type() { return StorageType.fromClass(this.getClass()); } + // TODO: Snu avhengigheten innover default ChangeStampResponse convertChangeStampResponse() { if (getChangeStamp() == null) { return null; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/utils/MdcExecutor.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/utils/MdcExecutor.java index 1ddf82a90..9d5c50ab2 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/utils/MdcExecutor.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/utils/MdcExecutor.java @@ -28,6 +28,7 @@ public void execute(Runnable command) { super.execute(wrap(command, parentContext)); } + @SuppressWarnings("deprecation") // TODO: Fjern bruk av deprekert kode public static ListenableFutureCallback wrap(Consumer onSuccessCallback, Consumer onErrorCallback) { var parentContext = MDC.getCopyOfContextMap(); return new ListenableFutureCallback() { diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/common/validator/FieldValidator.java b/apps/backend/polly-app/src/main/java/no/nav/data/common/validator/FieldValidator.java index 5212ffc00..13e41ac36 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/common/validator/FieldValidator.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/common/validator/FieldValidator.java @@ -42,7 +42,6 @@ public class FieldValidator { private final String reference; private final String parentField; - public FieldValidator(String reference) { this.reference = reference; this.parentField = ""; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/alert/AlertService.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/alert/AlertService.java index 9d7cc7088..50f72b73a 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/alert/AlertService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/alert/AlertService.java @@ -38,7 +38,6 @@ @Slf4j @Service -@Transactional public class AlertService { private static final String SENSITIVITY_ART9 = "SAERLIGE"; @@ -72,6 +71,7 @@ public void testMail() { // CALCULATE AND SAVE EVENTS + @Transactional public void calculateEventsForInforamtionType(UUID informationTypeId) { var alerts = checkAlertsForInformationType(informationTypeId); var currentEvents = StreamUtils.convertFlat(alerts.getProcesses(), this::convertAlertsToEvents); @@ -79,6 +79,7 @@ public void calculateEventsForInforamtionType(UUID informationTypeId) { updateEvents(existingEvents, currentEvents); } + @Transactional public void calculateEventsForProcess(UUID processId) { var alerts = checkAlertsForProcess(processId); var currentEvents = convertAlertsToEvents(alerts); @@ -86,6 +87,7 @@ public void calculateEventsForProcess(UUID processId) { updateEvents(existingEvents, currentEvents); } + @Transactional public void calculateEventsForPolicy(Policy policy) { var alerts = checkProcess(policy.getProcess(), policy.getInformationType()); var currentEvents = convertAlertsToEvents(alerts); @@ -95,6 +97,7 @@ public void calculateEventsForPolicy(Policy policy) { updateEvents(existingEvents, currentEvents); } + @Transactional public void calculateEventsForDisclosure(UUID disclosureId) { var alerts = checkAlertsForDisclosure(disclosureId); var currentEvents = convertAlertsToEvents(alerts); @@ -112,21 +115,25 @@ private void updateEvents(List existingEvents, List curr // DELETE + @Transactional public void deleteEventsForInformationType(UUID informationTypeId) { int deleted = alertRepository.deleteByInformationTypeId(informationTypeId); log.info("deleted {} events for informationType {}", deleted, informationTypeId); } + @Transactional public void deleteEventsForProcess(UUID processId) { int deleted = alertRepository.deleteByProcessId(processId); log.info("deleted {} events for process {}", deleted, processId); } + @Transactional public void deleteEventsForPolicy(Policy policy) { int deleted = alertRepository.deleteByProcessIdAndInformationTypeId(policy.getProcess().getId(), policy.getInformationTypeId()); log.info("deleted {} events for process {} informationType {}", deleted, policy.getProcess().getId(), policy.getInformationTypeId()); } + @Transactional public void deleteEventsForDisclosure(UUID disclosureId) { int deleted = alertRepository.deleteByDisclosureId(disclosureId); log.info("deleted {} events for disclosure {}", deleted, disclosureId); @@ -248,6 +255,7 @@ private boolean containsArticle(List legalBases, String articlePrefi return safeStream(legalBases).anyMatch(lb -> lb.getGdpr().startsWith(articlePrefix)); } + @Transactional public Page getEvents(AlertEventRequest request) { return alertRepository.findAlerts(request); } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/alert/domain/AlertRepositoryImpl.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/alert/domain/AlertRepositoryImpl.java index e19cab778..bddb62d0e 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/alert/domain/AlertRepositoryImpl.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/alert/domain/AlertRepositoryImpl.java @@ -30,9 +30,11 @@ public AlertRepositoryImpl(NamedParameterJdbcTemplate jdbcTemplate, @Lazy AlertR @Override public Page findAlerts(AlertEventRequest request) { - var query = "select id , count(*) over () as count " - + "from generic_storage gs " - + "where type = 'ALERT_EVENT' "; + var query = """ + select id , count(*) over () as count + from generic_storage gs + where type = 'ALERT_EVENT' + """; MapSqlParameterSource params = new MapSqlParameterSource() .addValue("page", request.page()) @@ -97,7 +99,6 @@ public record AlertEventRequest( AlertEventType type, AlertEventLevel level, int page, int pageSize, AlertSort sort, SortDir dir ) { - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/AaregAvtaleController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/AaregAvtaleController.java index 6856bae75..532a2ccab 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/AaregAvtaleController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/AaregAvtaleController.java @@ -55,10 +55,8 @@ public ResponseEntity> searchAareg(@PathVa throw new ValidationException("Search parameter must be at least 3 characters"); } List pollyAaregAvtaleList = aaregAvtaleService.searchAaregAvtale(searchParam); - log.info("Returned AAREG avtale"); -// pollyAaregAvtaleList.sort(comparing(paa->paa.getVirksomhet()+paa.getId(),startsWith(searchParam))); log.info("Returned {} Aareg avtale", pollyAaregAvtaleList.size()); - return new ResponseEntity<>(new RestResponsePage<>(convert(pollyAaregAvtaleList,PollyAaregAvtale::toResponse)), HttpStatus.OK); + return new ResponseEntity<>(new RestResponsePage<>(convert(pollyAaregAvtaleList, PollyAaregAvtale::toResponse)), HttpStatus.OK); } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/AaregAvtaleStub.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/AaregAvtaleStub.java index 806f92a57..382646ea4 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/AaregAvtaleStub.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/AaregAvtaleStub.java @@ -9,7 +9,8 @@ @Component @ConditionalOnMissingBean(AaregAvtaleService.class) -public class AaregAvtaleStub implements AaregAvtaleService{ +public class AaregAvtaleStub implements AaregAvtaleService { + @Override public List searchAaregAvtale(String searchString) { return List.of(); @@ -19,4 +20,5 @@ public List searchAaregAvtale(String searchString) { public Optional getAaregAvtale(String avtaleId) { return Optional.empty(); } + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/domain/AaregAvtale.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/domain/AaregAvtale.java index 089832537..7ef10eebe 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/domain/AaregAvtale.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/domain/AaregAvtale.java @@ -11,6 +11,7 @@ @AllArgsConstructor @NoArgsConstructor public class AaregAvtale { + private String avtalenummer; private String organisasjonsnummer; private String virksomhet; @@ -42,6 +43,5 @@ public PollyAaregAvtale toPollyAaregAvtale() { .hjemmel_behandlingsgrunnlag_formal(hjemmel_behandlingsgrunnlag_formal) .hendelser(hendelser) .build(); - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/domain/PollyAaregAvtale.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/domain/PollyAaregAvtale.java index ae496f1cb..a64ec80b5 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/domain/PollyAaregAvtale.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/domain/PollyAaregAvtale.java @@ -13,6 +13,7 @@ @AllArgsConstructor @NoArgsConstructor public class PollyAaregAvtale { + private String id; private String organisasjonsnummer; private String virksomhet; @@ -27,7 +28,7 @@ public class PollyAaregAvtale { private String hjemmel_behandlingsgrunnlag_formal; private Boolean hendelser; - + // TODO: Snu avhengigheten innover public AaregAvtaleResponse toResponse() { return AaregAvtaleResponse.builder() .avtalenummer(id) @@ -44,6 +45,5 @@ public AaregAvtaleResponse toResponse() { .hjemmel_behandlingsgrunnlag_formal(hjemmel_behandlingsgrunnlag_formal) .hendelser(hendelser) .build(); - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/dto/AaregAvtaleResponse.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/dto/AaregAvtaleResponse.java index 1618f76ad..b8c87b3be 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/dto/AaregAvtaleResponse.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/bigquery/dto/AaregAvtaleResponse.java @@ -15,7 +15,6 @@ @AllArgsConstructor @NoArgsConstructor @JsonPropertyOrder({"avtalenummer", "organisasjonsnummer", "virksomhet"}) - public class AaregAvtaleResponse { private String avtalenummer; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistCache.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistCache.java index 06315bf3c..d7186adbb 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistCache.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistCache.java @@ -16,6 +16,8 @@ * Should not be used outside {@link CodelistService} */ final class CodelistCache { + + // TODO: Denne bør skrives om til en self-contained @Component. Det inkluderer å flytte funksjonalitet for periodisk refresh inn til den. private CodelistCache() { } @@ -28,11 +30,11 @@ private CodelistCache() { CodelistCache.init(); } - static void init() { + static synchronized void init() { init(null); } - static void init(Consumer consumer) { + static synchronized void init(Consumer consumer) { var newCache = new CodelistCache(); Stream.of(ListName.values()).forEach(listName -> newCache.codelists.put(listName, new HashMap<>())); if (consumer != null) { @@ -41,27 +43,27 @@ static void init(Consumer consumer) { cache = newCache; } - static List getAll() { + static synchronized List getAll() { return cache.codelists.values().stream().flatMap(e -> e.values().stream()).collect(Collectors.toList()); } - static List getCodelist(ListName name) { + static synchronized List getCodelist(ListName name) { return List.copyOf(cache.codelists.get(name).values()); } - static Codelist getCodelist(ListName listName, String code) { + static synchronized Codelist getCodelist(ListName listName, String code) { return cache.codelists.get(listName).get(code); } - static boolean contains(ListName listName, String code) { + static synchronized boolean contains(ListName listName, String code) { return cache.codelists.get(listName).containsKey(code); } - static void remove(ListName listName, String code) { + static synchronized void remove(ListName listName, String code) { cache.codelists.get(listName).remove(code); } - static void set(Codelist codelist) { + static synchronized void set(Codelist codelist) { cache.setCode(codelist); } @@ -70,6 +72,8 @@ void setCode(Codelist codelist) { Assert.notNull(codelist.getCode(), "code cannot be null"); Assert.notNull(codelist.getShortName(), "shortName cannot be null"); Assert.notNull(codelist.getDescription(), "description cannot be null"); - codelists.get(codelist.getList()).put(codelist.getCode(), codelist); + synchronized(CodelistCache.class) { + codelists.get(codelist.getList()).put(codelist.getCode(), codelist); + } } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistController.java index 76235c789..55d0d80a4 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistController.java @@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import no.nav.data.common.utils.StreamUtils; import no.nav.data.polly.codelist.commoncode.CommonCodeService; @@ -16,6 +17,7 @@ import no.nav.data.polly.codelist.domain.ListName; import no.nav.data.polly.codelist.dto.AllCodelistResponse; import no.nav.data.polly.codelist.dto.CodelistRequest; +import no.nav.data.polly.codelist.dto.CodelistRequestValidator; import no.nav.data.polly.codelist.dto.CodelistResponse; import org.springframework.http.HttpStatus; import org.springframework.transaction.annotation.Transactional; @@ -39,15 +41,12 @@ @RestController @RequestMapping("/codelist") @Tag(name = "Codelist", description = "REST API for common list of values") +@RequiredArgsConstructor public class CodelistController { private final CodelistService service; private final CommonCodeService commonCodeService; - - public CodelistController(CodelistService service, CommonCodeService commonCodeService) { - this.service = service; - this.commonCodeService = commonCodeService; - } + private final CodelistRequestValidator requestValidator; @Operation(summary = "Get the entire Codelist") @ApiResponse(description = "Entire Codelist fetched") @@ -66,7 +65,7 @@ public AllCodelistResponse findAll(@RequestParam(value = "refresh", required = f public List getByListName(@PathVariable String listName) { String listUpper = toUpperCaseAndTrim(listName); log.info("Received a request for all codelists with listName={}", listUpper); - service.validateListName(listUpper); + requestValidator.validateListName(listUpper); return CodelistService.getCodelistResponseList(ListName.valueOf(listUpper)); } @@ -77,7 +76,7 @@ public CodelistResponse getByListNameAndCode(@PathVariable String listName, @Pat String listUpper = toUpperCaseAndTrim(listName); String codeUpper = toUpperCaseAndTrim(code); log.info("Received a request for the codelist with the code={} in listName={}", codeUpper, listUpper); - service.validateListNameAndCode(listUpper, codeUpper); + requestValidator.validateListNameAndCode(listUpper, codeUpper); return CodelistService.getCodelistResponse(ListName.valueOf(listUpper), codeUpper); } @@ -88,7 +87,7 @@ public CodelistResponse getByListNameAndCode(@PathVariable String listName, @Pat public List save(@Valid @RequestBody List requests) { log.info("Received a requests to create codelists"); requests = StreamUtils.nullToEmptyList(requests); - service.validateRequest(requests, false); + requestValidator.validateRequest(requests, false); return service.save(requests).stream().map(Codelist::convertToResponse).collect(Collectors.toList()); } @@ -99,7 +98,7 @@ public List save(@Valid @RequestBody List req public List update(@Valid @RequestBody List requests) { log.info("Received a request to update codelists"); requests = StreamUtils.nullToEmptyList(requests); - service.validateRequest(requests, true); + requestValidator.validateRequest(requests, true); return service.update(requests).stream().map(Codelist::convertToResponse).collect(Collectors.toList()); } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistService.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistService.java index 810db01ac..52923b3b4 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/CodelistService.java @@ -1,23 +1,18 @@ package no.nav.data.polly.codelist; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import no.nav.data.common.exceptions.CodelistNotErasableException; import no.nav.data.common.exceptions.CodelistNotFoundException; -import no.nav.data.common.utils.StreamUtils; -import no.nav.data.common.validator.FieldValidator; -import no.nav.data.common.validator.RequestElement; -import no.nav.data.common.validator.RequestValidator; -import no.nav.data.polly.codelist.codeusage.CodeUsageService; import no.nav.data.polly.codelist.domain.Codelist; import no.nav.data.polly.codelist.domain.ListName; -import no.nav.data.polly.codelist.dto.CodeUsageRequest; -import no.nav.data.polly.codelist.dto.CodeUsageResponse; import no.nav.data.polly.codelist.dto.CodelistRequest; +import no.nav.data.polly.codelist.dto.CodelistRequestValidator; import no.nav.data.polly.codelist.dto.CodelistResponse; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.annotation.Lazy; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; import java.util.Collection; @@ -29,25 +24,20 @@ @Slf4j @Lazy(false) -@Service -public class CodelistService extends RequestValidator implements InitializingBean { - - private static final String FIELD_NAME_LIST = "list"; - private static final String FIELD_NAME_CODE = "code"; - private static final String REFERENCE = "Validate Codelist"; +@Service // Likevel, tjenestene som tilbys her har state (gjennom CodelistCache) +@RequiredArgsConstructor +public class CodelistService implements InitializingBean { + private final CodelistRepository codelistRepository; - private final CodeUsageService codeUsageService; - - // @Lazy to avoid circular dependancy - public CodelistService(CodelistRepository codelistRepository, @Lazy CodeUsageService codeUsageService) { - this.codelistRepository = codelistRepository; - this.codeUsageService = codeUsageService; - } + private final CodelistRequestValidator codelistRequestValidator; // TODO: Avhengighet utover + // TODO: Denne skal ikke være static public static Codelist getCodelist(ListName listName, String code) { return CodelistCache.getCodelist(listName, code); } + // TODO: Snu avhengigheten innover + // TODO: Denne skal ikke være static public static CodelistResponse getCodelistResponse(ListName listName, String code) { if (code == null) { return null; @@ -59,29 +49,38 @@ public static CodelistResponse getCodelistResponse(ListName listName, String cod return codelist.convertToResponse(); } + // TODO: Snu avhengigheten innover + // TODO: Denne skal ikke være static public static List getCodelistResponseList(ListName listName) { return convert(CodelistCache.getCodelist(listName), Codelist::convertToResponse); } + // TODO: Snu avhengigheten innover + // TODO: Denne skal ikke være static public static List getCodelistResponseList(ListName listName, Collection codes) { return convert(codes, code -> getCodelistResponse(listName, code)); } + // TODO: Denne skal ikke være static public static List getCodelist(ListName name) { return CodelistCache.getCodelist(name); } + // TODO: Denne skal ikke være static public static List getAll() { return CodelistCache.getAll(); } @Scheduled(initialDelayString = "PT1M", fixedRateString = "PT1M") + @Transactional public void refreshCache() { log.info("Refreshing codelist cache"); List allCodelists = codelistRepository.findAll(); CodelistCache.init(cache -> allCodelists.forEach(cache::setCode)); } + // TODO: Snu avhengigheten innover + @Transactional public List save(List requests) { List codelists = requests.stream() .map(CodelistRequest::convert) @@ -91,11 +90,11 @@ public List save(List requests) { return saved; } + // TODO: Snu avhengigheten innover public List update(List requests) { List codelists = requests.stream() .map(this::updateDescriptionInRepository) .collect(Collectors.toList()); - List saved = codelistRepository.saveAll(codelists); saved.forEach(CodelistCache::set); return saved; @@ -110,74 +109,19 @@ private Codelist updateDescriptionInRepository(CodelistRequest request) { return codelist; } + @Transactional public void delete(ListName name, String code) { Optional toDelete = codelistRepository.findByListAndCode(name, code); if (toDelete.isEmpty()) { log.warn("Cannot find a codelist to delete with code={} and listName={}", code, name); - throw new CodelistNotFoundException( - String.format("Cannot find a codelist to delete with code=%s and listName=%s", code, name)); + throw new CodelistNotFoundException(String.format("Cannot find a codelist to delete with code=%s and listName=%s", code, name)); } - validateNonImmutableTypeOfCodelist(name); - validateCodelistIsNotInUse(name, code); + codelistRequestValidator.validateNonImmutableTypeOfCodelist(name); + codelistRequestValidator.validateCodelistIsNotInUse(name, code); codelistRepository.delete(toDelete.get()); CodelistCache.remove(name, code); } - private void validateCodelistIsNotInUse(ListName name, String code) { - CodeUsageResponse codeUsage = codeUsageService.findCodeUsage(name, code); - if (codeUsage.isInUse()) { - log.warn("The code {} in list {} cannot be erased. {}", code, name, codeUsage.toString()); - throw new CodelistNotErasableException(String.format("The code %s in list %s cannot be erased. %s", code, name, codeUsage.toString())); - } - } - - public void validateNonImmutableTypeOfCodelist(ListName listName) { - FieldValidator validator = new FieldValidator(REFERENCE); - validator.checkIfCodelistIsOfImmutableType(listName); - ifErrorsThrowCodelistNotFoundException(validator.getErrors()); - } - - public void validateListName(String listName) { - FieldValidator validator = new FieldValidator(REFERENCE); - validator.checkRequiredEnum(FIELD_NAME_LIST, listName, ListName.class); - ifErrorsThrowCodelistNotFoundException(validator.getErrors()); - } - - public void validateListNameAndCode(String listName, String code) { - FieldValidator validator = new FieldValidator(REFERENCE); - checkValidCode(listName, code, validator); - ifErrorsThrowCodelistNotFoundException(validator.getErrors()); - } - - public void validateCodeUsageRequests(List requests) { - FieldValidator validator = new FieldValidator(REFERENCE); - StreamUtils.safeStream(requests).forEach(request -> checkValidCode(request.getListName(), request.getCode(), validator)); - ifErrorsThrowCodelistNotFoundException(validator.getErrors()); - } - - private void checkValidListName(String listName, FieldValidator validator) { - validator.checkRequiredEnum(FIELD_NAME_LIST, listName, ListName.class); - } - - public void checkValidCode(String listName, String code, FieldValidator validator) { - checkValidListName(listName, validator); - validator.checkRequiredCodelist(FIELD_NAME_CODE, code, ListName.valueOf(listName)); - } - - public void validateRequest(List requests, boolean update) { - initialize(requests, update); - - requests.forEach(CodelistRequest::format); - var validationErrors = validateNoDuplicates(requests); - - validationErrors.addAll(StreamUtils.applyAll(requests, - RequestElement::validateFields, - req -> validateRepositoryValues(req, codelistRepository.findByListAndCode(req.getListAsListName(), req.getCode()).isPresent()) - )); - - ifErrorsThrowValidationException(validationErrors); - } - @Override public void afterPropertiesSet() { log.info("init codelist cache"); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/codeusage/CodeUsageService.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/codeusage/CodeUsageService.java index 232a0e8e1..ad59b1eae 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/codeusage/CodeUsageService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/codeusage/CodeUsageService.java @@ -6,6 +6,7 @@ import no.nav.data.polly.codelist.domain.ListName; import no.nav.data.polly.codelist.dto.CodeUsageRequest; import no.nav.data.polly.codelist.dto.CodeUsageResponse; +import no.nav.data.polly.codelist.dto.CodelistRequestValidator; import no.nav.data.polly.codelist.dto.UsedInInstance; import no.nav.data.polly.codelist.dto.UsedInInstancePurpose; import no.nav.data.polly.disclosure.domain.Disclosure; @@ -42,7 +43,6 @@ @Transactional public class CodeUsageService { - private final CodelistService codelistService; private final ProcessRepository processRepository; private final DpProcessRepository dpProcessRepository; private final PolicyRepository policyRepository; @@ -51,12 +51,11 @@ public class CodeUsageService { private final DocumentRepository documentRepository; private final ProcessorRepository processorRepository; private final Summary summary; + private final CodelistRequestValidator requestValidator; - public CodeUsageService(CodelistService codelistService, ProcessRepository processRepository, DpProcessRepository dpProcessRepository, - PolicyRepository policyRepository, + public CodeUsageService(ProcessRepository processRepository, DpProcessRepository dpProcessRepository, PolicyRepository policyRepository, InformationTypeRepository informationTypeRepository, DisclosureRepository disclosureRepository, DocumentRepository documentRepository, - ProcessorRepository processorRepository) { - this.codelistService = codelistService; + ProcessorRepository processorRepository, CodelistRequestValidator requestValidator) { this.processRepository = processRepository; this.dpProcessRepository = dpProcessRepository; this.policyRepository = policyRepository; @@ -74,10 +73,11 @@ public CodeUsageService(CodelistService codelistService, ProcessRepository proce .maxAgeSeconds(Duration.ofHours(6).getSeconds()) .ageBuckets(6) .register(); + this.requestValidator = requestValidator; } public void validateListName(String list) { - codelistService.validateListName(list); + requestValidator.validateListName(list); } void validateRequests(String listName, String code) { @@ -85,7 +85,7 @@ void validateRequests(String listName, String code) { } void validateRequests(List requests) { - codelistService.validateCodeUsageRequests(requests); + requestValidator.validateCodeUsageRequests(requests); } public List findCodeUsageOfList(ListName list) { @@ -156,6 +156,7 @@ public CodeUsageResponse replaceUsage(ListName listName, String oldCode, String case TRANSFER_GROUNDS_OUTSIDE_EU -> { getProcessors(usage).forEach(p -> p.getData().setTransferGroundsOutsideEU(newCode)); } + case DATA_ACCESS_CLASS -> {} // TODO: Er det riktig at dette er en no-op? } } return usage; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/domain/Codelist.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/domain/Codelist.java index 6527e7735..f4faca70d 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/domain/Codelist.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/domain/Codelist.java @@ -60,10 +60,8 @@ public UUID getId() { @Data static class IdClass implements Serializable { - private ListName list; private String code; - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/dto/CodelistRequest.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/dto/CodelistRequest.java index 7a620fa65..938b82de6 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/dto/CodelistRequest.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/dto/CodelistRequest.java @@ -26,6 +26,9 @@ public class CodelistRequest implements RequestElement { private String shortName; private String description; + private boolean update; + private int requestIndex; + public Codelist convert() { return Codelist.builder() .list(ListName.valueOf(list)) @@ -35,9 +38,6 @@ public Codelist convert() { .build(); } - private boolean update; - private int requestIndex; - @JsonIgnore @Override public String getId() { diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/dto/CodelistRequestValidator.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/dto/CodelistRequestValidator.java new file mode 100644 index 000000000..25cea428c --- /dev/null +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/codelist/dto/CodelistRequestValidator.java @@ -0,0 +1,89 @@ +package no.nav.data.polly.codelist.dto; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import no.nav.data.common.exceptions.CodelistNotErasableException; +import no.nav.data.common.utils.StreamUtils; +import no.nav.data.common.validator.FieldValidator; +import no.nav.data.common.validator.RequestElement; +import no.nav.data.common.validator.RequestValidator; +import no.nav.data.polly.codelist.CodelistRepository; +import no.nav.data.polly.codelist.codeusage.CodeUsageService; +import no.nav.data.polly.codelist.domain.ListName; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +public class CodelistRequestValidator extends RequestValidator { + + private static final String FIELD_NAME_LIST = "list"; + private static final String FIELD_NAME_CODE = "code"; + private static final String REFERENCE = "Validate Codelist"; + + private final CodeUsageService codeUsageService; + private final CodelistRepository codelistRepository; + + public CodelistRequestValidator(@Lazy CodeUsageService codeUsageService, CodelistRepository codelistRepository) { + this.codeUsageService = codeUsageService; + this.codelistRepository = codelistRepository; + } + + public void validateRequest(List requests, boolean update) { + initialize(requests, update); + + requests.forEach(CodelistRequest::format); + var validationErrors = validateNoDuplicates(requests); + + validationErrors.addAll(StreamUtils.applyAll(requests, + RequestElement::validateFields, + req -> validateRepositoryValues(req, codelistRepository.findByListAndCode(req.getListAsListName(), req.getCode()).isPresent()) + )); + + ifErrorsThrowValidationException(validationErrors); + } + + public void validateNonImmutableTypeOfCodelist(ListName listName) { + FieldValidator validator = new FieldValidator(REFERENCE); + validator.checkIfCodelistIsOfImmutableType(listName); + ifErrorsThrowCodelistNotFoundException(validator.getErrors()); + } + + public void validateListName(String listName) { + FieldValidator validator = new FieldValidator(REFERENCE); + validator.checkRequiredEnum(FIELD_NAME_LIST, listName, ListName.class); + ifErrorsThrowCodelistNotFoundException(validator.getErrors()); + } + + public void validateListNameAndCode(String listName, String code) { + FieldValidator validator = new FieldValidator(REFERENCE); + checkValidCode(listName, code, validator); + ifErrorsThrowCodelistNotFoundException(validator.getErrors()); + } + + public void validateCodeUsageRequests(List requests) { + FieldValidator validator = new FieldValidator(REFERENCE); + StreamUtils.safeStream(requests).forEach(request -> checkValidCode(request.getListName(), request.getCode(), validator)); + ifErrorsThrowCodelistNotFoundException(validator.getErrors()); + } + + public void validateCodelistIsNotInUse(ListName name, String code) { + CodeUsageResponse codeUsage = codeUsageService.findCodeUsage(name, code); + if (codeUsage.isInUse()) { + log.warn("The code {} in list {} cannot be erased. {}", code, name, codeUsage.toString()); + throw new CodelistNotErasableException(String.format("The code %s in list %s cannot be erased. %s", code, name, codeUsage.toString())); + } + } + + private void checkValidListName(String listName, FieldValidator validator) { + validator.checkRequiredEnum(FIELD_NAME_LIST, listName, ListName.class); + } + + public void checkValidCode(String listName, String code, FieldValidator validator) { + checkValidListName(listName, validator); + validator.checkRequiredCodelist(FIELD_NAME_CODE, code, ListName.valueOf(listName)); + } + +} \ No newline at end of file diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/dashboard/DashboardController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/dashboard/DashboardController.java index 7884beadb..9a391d9f5 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/dashboard/DashboardController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/dashboard/DashboardController.java @@ -56,6 +56,8 @@ @Tag(name = "Dashboard") @RequiredArgsConstructor public class DashboardController { + + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk, *Repository-aksess og @Transactional til tjenestelaget. private final ProcessRepository processRepository; private final DpProcessRepository dpProcessRepository; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/DisclosureController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/DisclosureController.java index 7c7bca4b6..0269b663d 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/DisclosureController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/DisclosureController.java @@ -53,6 +53,8 @@ @RequiredArgsConstructor public class DisclosureController { + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk, *Repository-aksess og @Transactional til tjenestelaget. + private final DisclosureRepository repository; private final DisclosureService service; @@ -173,7 +175,7 @@ public ResponseEntity updatePolicy(@PathVariable UUID id, @V @Operation(summary = "Delete Disclosure") @ApiResponse(description = "Disclosure deleted") @DeleteMapping("/{id}") - @Transactional + @Transactional // TODO: Flytt til tjenestelaget public ResponseEntity deleteDisclosureById(@PathVariable UUID id) { log.info("Received a request to delete Disclosure with id={}", id); Optional fromRepository = repository.findById(id); @@ -203,10 +205,9 @@ private DisclosureResponse convertAndAddObjects(Disclosure disclosure) { } static class DisclosureSummaryPage extends RestResponsePage { - } static class DisclosurePage extends RestResponsePage { - } + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/DisclosureService.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/DisclosureService.java index 6ff982783..7648d2e3c 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/DisclosureService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/DisclosureService.java @@ -19,6 +19,8 @@ @Service public class DisclosureService extends RequestValidator { + // TODO: Denne klassen skal ikke subklasse RequestValidator. Flytt dette ut til en egen komponent (XxxRequestValidator). + private final DisclosureRepository repository; private final DocumentRepository documentRepository; private final InformationTypeRepository informationTypeRepository; @@ -33,7 +35,6 @@ public DisclosureService(DisclosureRepository repository, DocumentRepository doc this.informationTypeRepository = informationTypeRepository; this.processRepository = processRepository; this.alertService = alertService; - } @Transactional diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/domain/Disclosure.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/domain/Disclosure.java index 632643e9f..9c0263363 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/domain/Disclosure.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/domain/Disclosure.java @@ -17,6 +17,7 @@ import no.nav.data.polly.codelist.CodelistService; import no.nav.data.polly.codelist.domain.ListName; import no.nav.data.polly.codelist.dto.UsedInInstance; +import no.nav.data.polly.disclosure.dto.DisclosureAbroadResponse; import no.nav.data.polly.disclosure.dto.DisclosureRequest; import no.nav.data.polly.disclosure.dto.DisclosureResponse; import no.nav.data.polly.disclosure.dto.DisclosureSummaryResponse; @@ -51,6 +52,7 @@ public class Disclosure extends Auditable { @Column(name = "DATA", nullable = false) private DisclosureData data = new DisclosureData(); + // TODO: Snu avhengigheten innover public Disclosure convertFromRequest(DisclosureRequest request) { if (!request.isUpdate()) { setId(UUID.randomUUID()); @@ -75,6 +77,7 @@ public Disclosure convertFromRequest(DisclosureRequest request) { return this; } + // TODO: Snu avhengigheten innover public DisclosureResponse convertToResponse() { return DisclosureResponse.builder() .id(id) @@ -88,7 +91,7 @@ public DisclosureResponse convertToResponse() { .documentId(data.getDocumentId()) .informationTypeIds(copyOf(data.getInformationTypeIds())) .processIds(copyOf(data.getProcessIds())) - .abroad(data.getAbroad().convertToResponse()) + .abroad(DisclosureAbroadResponse.buildFrom(data.getAbroad() != null ? data.getAbroad() : new DisclosureAbroad())) .administrationArchiveCaseNumber(data.getAdministrationArchiveCaseNumber()) .thirdCountryReceiver(data.getThirdCountryReceiver()) .assessedConfidentiality(data.getAssessedConfidentiality()) diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/domain/DisclosureAbroad.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/domain/DisclosureAbroad.java index a65924f24..1ea6cc72c 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/domain/DisclosureAbroad.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/domain/DisclosureAbroad.java @@ -5,7 +5,6 @@ import lombok.Data; import lombok.NoArgsConstructor; import no.nav.data.polly.disclosure.dto.DisclosureAbroadRequest; -import no.nav.data.polly.disclosure.dto.DisclosureAbroadResponse; import java.util.List; @@ -22,15 +21,7 @@ public class DisclosureAbroad { private String refToAgreement; private String businessArea; - public DisclosureAbroadResponse convertToResponse() { - return DisclosureAbroadResponse.builder() - .abroad(abroad) - .countries(copyOf(countries)) - .refToAgreement(refToAgreement) - .businessArea(businessArea) - .build(); - } - + // TODO: Snu avhengigheten innover public static DisclosureAbroad convertAbroad(DisclosureAbroadRequest request) { if (request == null) { return new DisclosureAbroad(); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/dto/DisclosureAbroadResponse.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/dto/DisclosureAbroadResponse.java index 4cbd998df..baeb2a803 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/dto/DisclosureAbroadResponse.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/disclosure/dto/DisclosureAbroadResponse.java @@ -6,9 +6,12 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.Singular; +import no.nav.data.polly.disclosure.domain.DisclosureAbroad; import java.util.List; +import static no.nav.data.common.utils.StreamUtils.copyOf; + @Data @Builder @AllArgsConstructor @@ -16,10 +19,19 @@ @JsonPropertyOrder({"abroad", "countries", "refToAgreement", "businessArea"}) public class DisclosureAbroadResponse { - private Boolean abroad; @Singular private List countries; private String refToAgreement; private String businessArea; + + public static DisclosureAbroadResponse buildFrom(DisclosureAbroad da) { + return DisclosureAbroadResponse.builder() + .abroad(da.getAbroad()) + .countries(copyOf(da.getCountries())) + .refToAgreement(da.getRefToAgreement()) + .businessArea(da.getBusinessArea()) + .build(); + } + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/DocumentController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/DocumentController.java index 3ddf8b40a..dce9fd4db 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/DocumentController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/DocumentController.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import no.nav.data.common.exceptions.ValidationException; import no.nav.data.common.rest.PageParameters; @@ -13,6 +14,7 @@ import no.nav.data.polly.document.domain.Document; import no.nav.data.polly.document.domain.DocumentRepository; import no.nav.data.polly.document.dto.DocumentRequest; +import no.nav.data.polly.document.dto.DocumentRequestValidator; import no.nav.data.polly.document.dto.DocumentResponse; import no.nav.data.polly.informationtype.dto.InformationTypeShortResponse; import org.springframework.http.HttpStatus; @@ -41,15 +43,14 @@ @RestController @RequestMapping("/document") @Tag(name = "Document", description = "REST API for Document") +@RequiredArgsConstructor public class DocumentController { + + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk, *Repository-aksess og @Transactional til tjenestelaget. private final DocumentRepository repository; private final DocumentService service; - - public DocumentController(DocumentRepository repository, DocumentService service) { - this.repository = repository; - this.service = service; - } + private final DocumentRequestValidator requestValidator; @Operation(summary = "Get All Documents") @ApiResponse(description = "All Documents fetched") @@ -62,7 +63,7 @@ public ResponseEntity> getAll(PageParameters var doc = repository.findByInformationTypeId(informationTypeId); return returnResults(new RestResponsePage<>(StreamUtils.convert(doc, this::convertToResponseWithInfoTypes))); } - return returnResults(new RestResponsePage<>(repository.findAll(pageParameters.createIdSortedPage()).map(Document::convertToResponse))); + return returnResults(new RestResponsePage<>(repository.findAll(pageParameters.createIdSortedPage()).map(DocumentResponse::buildFrom))); } @Operation(summary = "Search Documents") @@ -103,6 +104,7 @@ public ResponseEntity findForId(@PathVariable UUID id) { @ResponseStatus(HttpStatus.CREATED) public ResponseEntity createPolicy(@Valid @RequestBody DocumentRequest request) { log.debug("Received request to create Document"); + requestValidator.validateRequest(request, false); var setup = service.save(request); return new ResponseEntity<>(convertToResponseWithInfoTypes(setup), HttpStatus.CREATED); } @@ -113,13 +115,14 @@ public ResponseEntity createPolicy(@Valid @RequestBody Documen public ResponseEntity updatePolicy(@PathVariable UUID id, @Valid @RequestBody DocumentRequest request) { log.debug("Received request to update Document"); Assert.isTrue(id.equals(request.getIdAsUUID()), "id mismatch"); + requestValidator.validateRequest(request, true); return ResponseEntity.ok(convertToResponseWithInfoTypes(service.update(request))); } @Operation(summary = "Delete Document") @ApiResponse(description = "Document deleted") @DeleteMapping("/{id}") - @Transactional + @Transactional // TODO: Flytt til tjenestelaget public ResponseEntity deleteDocumentById(@PathVariable UUID id) { log.info("Received a request to delete Document with id={}", id); var doc = service.delete(id); @@ -128,7 +131,7 @@ public ResponseEntity deleteDocumentById(@PathVariable UUID id } private DocumentResponse convertToResponseWithInfoTypes(Document document) { - var response = document.convertToResponse(); + var response = DocumentResponse.buildFrom(document); Map informationTypes = service.getInformationTypes(document); response.getInformationTypes().forEach(it -> it.setInformationType(informationTypes.get(it.getInformationTypeId()))); return response; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/DocumentService.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/DocumentService.java index 8b4d06e79..d5ccce280 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/DocumentService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/DocumentService.java @@ -1,12 +1,8 @@ package no.nav.data.polly.document; -import lombok.extern.slf4j.Slf4j; +import lombok.RequiredArgsConstructor; import no.nav.data.common.exceptions.NotFoundException; import no.nav.data.common.exceptions.ValidationException; -import no.nav.data.common.utils.StreamUtils; -import no.nav.data.common.validator.RequestElement; -import no.nav.data.common.validator.RequestValidator; -import no.nav.data.common.validator.ValidationError; import no.nav.data.polly.disclosure.domain.DisclosureRepository; import no.nav.data.polly.document.domain.Document; import no.nav.data.polly.document.domain.DocumentData.InformationTypeUse; @@ -20,18 +16,16 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; import java.util.Map; import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; import static no.nav.data.common.utils.StreamUtils.convert; -import static no.nav.data.common.utils.StreamUtils.filter; -@Slf4j @Service -public class DocumentService extends RequestValidator { +@RequiredArgsConstructor +public class DocumentService { private final DocumentRepository repository; private final InformationTypeRepository informationTypeRepository; @@ -39,23 +33,16 @@ public class DocumentService extends RequestValidator { private final DisclosureRepository disclosureRepository; private final SettingsService settingsService; - public DocumentService(DocumentRepository repository, InformationTypeRepository informationTypeRepository, PolicyRepository policyRepository, - DisclosureRepository disclosureRepository, SettingsService settingsService) { - this.repository = repository; - this.informationTypeRepository = informationTypeRepository; - this.policyRepository = policyRepository; - this.disclosureRepository = disclosureRepository; - this.settingsService = settingsService; - } - + // TODO: Snu avhengigheten innover public DocumentResponse getDocumentAsResponse(UUID uuid) { var document = repository.findById(uuid).orElseThrow(() -> new NotFoundException("Document " + uuid + " not found")); - DocumentResponse response = document.convertToResponse(); + DocumentResponse response = DocumentResponse.buildFrom(document); Map informationTypes = getInformationTypes(document); response.getInformationTypes().forEach(it -> it.setInformationType(informationTypes.get(it.getInformationTypeId()))); return response; } + // TODO: Snu avhengigheten innover public Map getInformationTypes(Document document) { return informationTypeRepository.findAllById(convert(document.getData().getInformationTypes(), InformationTypeUse::getInformationTypeId)) .stream() @@ -63,20 +50,18 @@ public Map getInformationTypes(Document docu .collect(Collectors.toMap(InformationTypeShortResponse::getId, Function.identity())); } + // TODO: Snu avhengigheten innover @Transactional public Document save(DocumentRequest request) { - initialize(List.of(request), false); - validateRequest(request); return repository.save(new Document().convertFromRequest(request)); } - @Transactional + // TODO: Fantastisk navn på denne metoden... Metoden gjør IKKE en update, men et oppslag og manipulering (uten å skrive til DB). public Document update(DocumentRequest request) { - initialize(List.of(request), true); - validateRequest(request); return repository.findById(request.getIdAsUUID()).orElseThrow().convertFromRequest(request); } + @Transactional public Document delete(UUID uuid) { var doc = repository.findById(uuid).orElseThrow(() -> new NotFoundException("Couldn't find document " + uuid)); var disclosures = disclosureRepository.findByDocumentId(uuid.toString()); @@ -94,36 +79,4 @@ public Document delete(UUID uuid) { return doc; } - private void validateRequest(DocumentRequest request) { - var validationErrors = StreamUtils.applyAll(request, - RequestElement::validateFields, - r -> validateRepositoryValues(r, r.getIdAsUUID() != null && repository.findById(r.getIdAsUUID()).isPresent()), - this::validateInformationTypes - ); - - ifErrorsThrowValidationException(validationErrors); - } - - private List validateInformationTypes(DocumentRequest request) { - if (request.getInformationTypes().isEmpty()) { - return List.of(); - } - List ids = convert(filter(request.getInformationTypes(), it -> - isValidUUID(it.getInformationTypeId())), infoType -> UUID.fromString(infoType.getInformationTypeId())); - var infoTypes = informationTypeRepository.findAllById(ids); - var missingInfoTypes = ids.stream().filter(id -> filter(infoTypes, infoType -> ids.contains(infoType.getId())).isEmpty()).collect(Collectors.toList()); - return missingInfoTypes.isEmpty() ? List.of() - : List.of(new ValidationError(request.getReference(), "informationTypeDoesNotExist", String.format("The InformationTypes %s doesnt exist", missingInfoTypes))); - } - - private boolean isValidUUID(String uuid) { - try { - //noinspection ResultOfMethodCallIgnored - UUID.fromString(uuid); - return true; - } catch (Exception e) { - log.trace("invalid uuid " + uuid, e); - return false; - } - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/domain/Document.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/domain/Document.java index 5c3aaf8c3..3d5ce0f33 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/domain/Document.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/domain/Document.java @@ -18,7 +18,6 @@ import no.nav.data.polly.codelist.dto.UsedInInstance; import no.nav.data.polly.document.dto.DocumentInfoTypeUseResponse; import no.nav.data.polly.document.dto.DocumentRequest; -import no.nav.data.polly.document.dto.DocumentResponse; import no.nav.data.polly.informationtype.domain.InformationType; import no.nav.data.polly.informationtype.dto.InformationTypeShortResponse; import org.hibernate.annotations.Type; @@ -47,6 +46,8 @@ public class Document extends Auditable { @Column(name = "DATA", nullable = false) private DocumentData data = new DocumentData(); + // TODO: Snu avhengigheten innover + // TODO: Dette er ikke en ren convert... public Document convertFromRequest(DocumentRequest request) { if (!request.isUpdate()) { setId(UUID.randomUUID()); @@ -58,16 +59,7 @@ public Document convertFromRequest(DocumentRequest request) { return this; } - public DocumentResponse convertToResponse() { - return DocumentResponse.builder() - .id(id) - .name(data.getName()) - .description(data.getDescription()) - .informationTypes(convert(data.getInformationTypes(), Document::convertToInfoTypeUseResponse)) - .dataAccessClass(CodelistService.getCodelistResponse(ListName.DATA_ACCESS_CLASS,data.getDataAccessClass())) - .build(); - } - + // TODO: Snu avhengigheten innover public static DocumentInfoTypeUseResponse convertToInfoTypeUseResponse(DocumentData.InformationTypeUse informationTypeUse) { return DocumentInfoTypeUseResponse.builder() .informationTypeId(informationTypeUse.getInformationTypeId()) @@ -75,6 +67,7 @@ public static DocumentInfoTypeUseResponse convertToInfoTypeUseResponse(DocumentD .build(); } + // TODO: Snu avhengigheten innover public static InformationTypeShortResponse convertToInformationTypeResponse(InformationType informationType) { return new InformationTypeShortResponse(informationType.getId(), informationType.getData().getName(), informationType.getData().sensitivityCode()); } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/dto/DocumentRequestValidator.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/dto/DocumentRequestValidator.java new file mode 100644 index 000000000..d9a3ced18 --- /dev/null +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/dto/DocumentRequestValidator.java @@ -0,0 +1,61 @@ +package no.nav.data.polly.document.dto; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import no.nav.data.common.utils.StreamUtils; +import no.nav.data.common.validator.RequestElement; +import no.nav.data.common.validator.RequestValidator; +import no.nav.data.common.validator.ValidationError; +import no.nav.data.polly.document.domain.DocumentRepository; +import no.nav.data.polly.informationtype.InformationTypeRepository; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import static no.nav.data.common.utils.StreamUtils.convert; +import static no.nav.data.common.utils.StreamUtils.filter; + +@Slf4j +@Component +@RequiredArgsConstructor +public class DocumentRequestValidator extends RequestValidator { + + private final DocumentRepository repository; + private final InformationTypeRepository informationTypeRepository; + + public void validateRequest(DocumentRequest request, boolean update) { + initialize(List.of(request), update); + var validationErrors = StreamUtils.applyAll(request, + RequestElement::validateFields, + r -> validateRepositoryValues(r, r.getIdAsUUID() != null && repository.findById(r.getIdAsUUID()).isPresent()), + this::validateInformationTypes + ); + + ifErrorsThrowValidationException(validationErrors); + } + + private List validateInformationTypes(DocumentRequest request) { + if (request.getInformationTypes().isEmpty()) { + return List.of(); + } + List ids = convert(filter(request.getInformationTypes(), it -> + isValidUUID(it.getInformationTypeId())), infoType -> UUID.fromString(infoType.getInformationTypeId())); + var infoTypes = informationTypeRepository.findAllById(ids); + var missingInfoTypes = ids.stream().filter(id -> filter(infoTypes, infoType -> ids.contains(infoType.getId())).isEmpty()).collect(Collectors.toList()); + return missingInfoTypes.isEmpty() ? List.of() + : List.of(new ValidationError(request.getReference(), "informationTypeDoesNotExist", String.format("The InformationTypes %s doesnt exist", missingInfoTypes))); + } + + private boolean isValidUUID(String uuid) { + try { + UUID.fromString(uuid); + return true; + } catch (Exception e) { + log.trace("invalid uuid " + uuid, e); + return false; + } + } + +} diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/dto/DocumentResponse.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/dto/DocumentResponse.java index b74a0a76b..aaae0cb8f 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/dto/DocumentResponse.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/document/dto/DocumentResponse.java @@ -6,11 +6,17 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.Singular; +import no.nav.data.polly.codelist.CodelistService; +import no.nav.data.polly.codelist.domain.ListName; import no.nav.data.polly.codelist.dto.CodelistResponse; +import no.nav.data.polly.document.domain.Document; +import no.nav.data.polly.document.domain.DocumentData; import java.util.List; import java.util.UUID; +import static no.nav.data.common.utils.StreamUtils.convert; + @Data @Builder @AllArgsConstructor @@ -25,4 +31,15 @@ public class DocumentResponse { private List informationTypes; private CodelistResponse dataAccessClass; + public static DocumentResponse buildFrom(Document d) { + DocumentData data = d.getData(); + return DocumentResponse.builder() + .id(d.getId()) + .name(data.getName()) + .description(data.getDescription()) + .informationTypes(convert(data.getInformationTypes(), Document::convertToInfoTypeUseResponse)) + .dataAccessClass(CodelistService.getCodelistResponse(ListName.DATA_ACCESS_CLASS, data.getDataAccessClass())) + .build(); + } + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/export/ExportController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/export/ExportController.java index 5a4d2041d..ff17ac41a 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/export/ExportController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/export/ExportController.java @@ -7,11 +7,13 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import no.nav.data.common.exceptions.NotFoundException; import no.nav.data.common.exceptions.ValidationException; import no.nav.data.polly.codelist.CodelistService; import no.nav.data.polly.codelist.domain.ListName; +import no.nav.data.polly.codelist.dto.CodelistRequestValidator; import no.nav.data.polly.export.domain.DocumentAccess; import no.nav.data.polly.process.domain.Process; import no.nav.data.polly.process.domain.ProcessStatus; @@ -36,25 +38,21 @@ @RestController @RequestMapping("/export") @Tag(name = "Export", description = "REST API for exports") +@RequiredArgsConstructor public class ExportController { + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk, *Repository-aksess og @Transactional til tjenestelaget. + private static final String WORDPROCESSINGML_DOCUMENT = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; private final ProcessRepository processRepository; - private final CodelistService codelistService; + private final CodelistRequestValidator codelistRequestValidator; private final ProcessToDocx processToDocx; private final TeamService teamService; - public ExportController(ProcessRepository processRepository, CodelistService codelistService, ProcessToDocx processToDocx, TeamService teamService) { - this.processRepository = processRepository; - this.codelistService = codelistService; - this.processToDocx = processToDocx; - this.teamService = teamService; - } - @Operation(summary = "Get export for process") @ApiResponse(description = "Doc fetched", content = @Content(schema = @Schema(implementation = byte[].class))) - @Transactional(readOnly = true) + @Transactional(readOnly = true) // TODO: Flytt dette inn til tjenestelaget @SneakyThrows @GetMapping(value = "/process", produces = WORDPROCESSINGML_DOCUMENT) public void getExport( @@ -76,12 +74,13 @@ public void getExport( throw new NotFoundException("Couldn't find process " + processId); } Process p; - if(documentAccess==DocumentAccess.INTERNAL || (documentAccess==DocumentAccess.EXTERNAL && process.get().getData().getStatus()== ProcessStatus.COMPLETED)){ - p=process.get(); - } else{ + if (documentAccess == DocumentAccess.INTERNAL || (documentAccess == DocumentAccess.EXTERNAL + && process.get().getData().getStatus() == ProcessStatus.COMPLETED)) { + p = process.get(); + } else { throw new NotFoundException("The process is not completed therefore it can not be exported"); } - doc = processToDocx.generateDocForProcess(p,documentAccess); + doc = processToDocx.generateDocForProcess(p, documentAccess); filename = "behandling_" + String.join(".", p.getData().getPurposes()) + "-" + p.getData().getName().replaceAll("[^a-zA-Z\\d]", "-") + "_" + p.getId() + ".docx"; } else if (productArea != null) { var teams = teamService.getTeamsForProductArea(productArea); @@ -89,14 +88,14 @@ public void getExport( String productAreaName = productAreaData.isPresent() ? productAreaData.get().getName() : ""; List processes = processRepository.findByProductTeams(convert(teams, Team::getId)); - doc = processToDocx.generateDocForProcessList(processes, "Produktområde: " + StringUtils.capitalize(productAreaName),documentAccess); + doc = processToDocx.generateDocForProcessList(processes, "Produktområde: " + StringUtils.capitalize(productAreaName), documentAccess); filename = "behandling_produktområde_" + productArea + ".docx"; } else if (productTeam != null) { List processes = processRepository.findByProductTeam(productTeam); var productTeamData = teamService.getTeam(productTeam); String productTeamName = productTeamData.isPresent() ? productTeamData.get().getName() : ""; - doc = processToDocx.generateDocForProcessList(processes, "Team: " + StringUtils.capitalize(productTeamName),documentAccess); + doc = processToDocx.generateDocForProcessList(processes, "Team: " + StringUtils.capitalize(productTeamName), documentAccess); filename = "behandling_team_" + productTeam + ".docx"; } else { ListName list; @@ -116,8 +115,8 @@ public void getExport( } else { throw new ValidationException("No paramater given"); } - codelistService.validateListNameAndCode(list.name(), code); - doc = processToDocx.generateDocFor(list, code,documentAccess); + codelistRequestValidator.validateListNameAndCode(list.name(), code); + doc = processToDocx.generateDocFor(list, code, documentAccess); String depNameClean = cleanCodelistName(list, code); filename = "behandling_" + list.name().toLowerCase() + "_" + depNameClean + ".docx"; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/export/ProcessToDocx.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/export/ProcessToDocx.java index e25287e7f..3c5b4cbfd 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/export/ProcessToDocx.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/export/ProcessToDocx.java @@ -5,7 +5,6 @@ import no.nav.data.common.exceptions.ValidationException; import no.nav.data.common.rest.ChangeStampResponse; import no.nav.data.common.utils.StreamUtils; -import no.nav.data.polly.Period; import no.nav.data.polly.alert.AlertService; import no.nav.data.polly.alert.dto.PolicyAlert; import no.nav.data.polly.codelist.CodelistService; @@ -103,7 +102,6 @@ public class ProcessToDocx { private static final String headingProcessList = "Dokumentet inneholder følgende behandlinger (%s)"; private static final String headingExternalProcessList = "Dokumentet inneholder følgende ferdigstilte behandlinger (%s)"; - @SneakyThrows public byte[] generateDocForProcess(Process process, DocumentAccess documentAccess) { var doc = new DocumentBuilder(); Date date = new Date(); @@ -123,9 +121,9 @@ public byte[] generateDocForProcessList(List processes, String title, D doc.addTitle(title); doc.addText("Eksportert " + formatter.format(date)); - if(documentAccess.equals(DocumentAccess.INTERNAL)){ + if (documentAccess.equals(DocumentAccess.INTERNAL)) { doc.addHeading1(String.format(headingProcessList, processList.size())); - }else { + } else { doc.addHeading1(String.format(headingExternalProcessList, processList.size())); } @@ -143,7 +141,7 @@ public byte[] generateDocForProcessList(List processes, String title, D private List getProcesses(List processes, DocumentAccess documentAccess) { List processList; - if(documentAccess == DocumentAccess.EXTERNAL) { + if (documentAccess == DocumentAccess.EXTERNAL) { processList = new ArrayList<>(processes.stream().filter(p -> p.getData().getStatus().equals(ProcessStatus.COMPLETED)).toList()); } else { processList = new ArrayList<>(processes); @@ -190,9 +188,9 @@ public byte[] generateDocFor(ListName list, String code, DocumentAccess document doc.addText("Eksportert " + formatter.format(date)); doc.addText(codelist.getDescription()); - if(documentAccess.equals(DocumentAccess.INTERNAL)){ + if (documentAccess.equals(DocumentAccess.INTERNAL)) { doc.addHeading1(String.format(headingProcessList, processes.size())); - }else { + } else { doc.addHeading1(String.format(headingExternalProcessList, processes.size())); } doc.addToc(processes); @@ -328,16 +326,6 @@ private Text mapLegalBasis(LegalBasis lb) { ); } - private String periodText(Period period) { - var active = period.isActive() ? "Aktiv" : "Inaktiv"; - return period.hasStart() || period.hasEnd() ? - String.format(" (%s, periode %s - %s)", - active, - period.getStart().format(df), - period.getEnd().format(df) - ) : null; - } - private void policies(Process process) { addHeading2("Opplysningstyper"); if (process.getData().isUsesAllInformationTypes()) { @@ -425,12 +413,11 @@ private void dpia(Dpia data, DocumentAccess documentAccess) { if (data == null) { return; } - var riskOwner = Optional.ofNullable(data.getRiskOwner()).flatMap(resourceService::getResource).map(Resource::getFullName).orElse(data.getRiskOwner()); addHeading4("Er det behov for personvernkonsekvensvurdering (PVK)?"); addText(boolToText(data.getNeedForDpia())); if (boolToText(data.getNeedForDpia()).equals("Nei")) { addText("Begrunnelse: "); - if(data.getNoDpiaReasons() != null) { + if (data.getNoDpiaReasons() != null) { data.getNoDpiaReasons().forEach(noDpiaReason -> { addText(noDpiaReasonToString(noDpiaReason)); }); @@ -474,7 +461,7 @@ private void dataProcessing(DataProcessing data, List processors, Doc processors.forEach(processor -> { var pd = processor.getData(); - var transferGrounds = Boolean.TRUE.equals(pd.getOutsideEU()) ? + var transferGrounds = pd.getOutsideEU() ? text("Overføringsgrunnlag for behandling utenfor EU/EØS: ", shortName(ListName.TRANSFER_GROUNDS_OUTSIDE_EU, pd.getTransferGroundsOutsideEU()), Optional.ofNullable(pd.getTransferGroundsOutsideEUOther()).map(s -> ": " + s).orElse("")) @@ -538,11 +525,6 @@ private void addHeading4(String text) { ((R) p.getContent().get(0)).setRPr(createRpr()); } - private void addHeading5(String text) { - P p = main.addStyledParagraphOfText(HEADING_5, text); - ((R) p.getContent().get(0)).setRPr(createRpr()); - } - private Text text(String... values) { List strings = filter(Arrays.asList(values), Objects::nonNull); if (strings.isEmpty()) { @@ -699,7 +681,6 @@ private void addBookmark(P p, String name) { p.getContent().add(0, bmStart); } - @SneakyThrows public byte[] build() { var outStream = new ByteArrayOutputStream(); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/InformationTypeController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/InformationTypeController.java index 00d1a1012..e68fd86ea 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/InformationTypeController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/InformationTypeController.java @@ -46,6 +46,8 @@ @Tag(name = "InformationType", description = "REST API for InformationType") public class InformationTypeController { + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk, *Repository-aksess og @Transactional til tjenestelaget. + private final InformationTypeRepository repository; private final InformationTypeService service; private final TeamService teamService; @@ -186,7 +188,7 @@ public ResponseEntity updateOneInformationTypeById(@Pat @Operation(summary = "Delete InformationType") @ApiResponse(description = "InformationType deleted") @DeleteMapping("/{id}") - @Transactional + @Transactional // TODO: Flytt dette inn til tjenestelaget public ResponseEntity deleteInformationTypeById(@PathVariable UUID id) { log.info("Received a request to delete InformationType with id={}", id); if (id == null) { @@ -197,11 +199,9 @@ public ResponseEntity deleteInformationTypeById(@PathVa } static final class InformationTypePage extends RestResponsePage { - } static final class InformationTypeShortPage extends RestResponsePage { - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/InformationTypeService.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/InformationTypeService.java index 408b6c5b5..acf38e071 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/InformationTypeService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/InformationTypeService.java @@ -36,6 +36,8 @@ @Transactional public class InformationTypeService extends RequestValidator { + // TODO: Denne klassen skal ikke subklasse RequestValidator. Flytt dette ut til en egen komponent (XxxRequestValidator). + private final InformationTypeRepository repository; private final PolicyRepository policyRepository; private final DocumentRepository documentRepository; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/domain/InformationType.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/domain/InformationType.java index 2d902f68c..b2cd7ae7f 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/domain/InformationType.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/informationtype/domain/InformationType.java @@ -68,10 +68,12 @@ public InformationType addPolicy(Policy policy) { return this; } + // TODO: Snu avhengigheten innover public InformationTypeResponse convertToResponse() { return new InformationTypeResponse(this); } + // TODO: Snu avhengigheten innover public InformationTypeShortResponse convertToShortResponse() { return new InformationTypeShortResponse(getId(), getData().getName(), CodelistService.getCodelistResponse(ListName.SENSITIVITY, data.getSensitivity())); } @@ -80,16 +82,19 @@ public UsedInInstance getInstanceIdentification() { return UsedInInstance.builder().id(id.toString()).name(data.getName()).build(); } + // TODO: Snu avhengigheten innover public InformationType convertNewFromRequest(InformationTypeRequest request) { id = UUID.randomUUID(); convertFromRequest(request); return this; } + // TODO: Snu avhengigheten innover public void convertUpdateFromRequest(InformationTypeRequest request) { convertFromRequest(request); } + // TODO: Snu avhengigheten innover private void convertFromRequest(InformationTypeRequest request) { setTermId(request.getTerm()); data.setName(request.getName()); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/PolicyService.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/PolicyService.java index 07c5ff8b5..99daa5ecb 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/PolicyService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/PolicyService.java @@ -31,6 +31,8 @@ @Service public class PolicyService extends RequestValidator { + // TODO: Denne klassen skal ikke subklasse RequestValidator. Flytt dette ut til en egen komponent (XxxRequestValidator). + private final PolicyRepository policyRepository; private final InformationTypeRepository informationTypeRepository; private final ProcessRepository processRepository; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/domain/Policy.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/domain/Policy.java index 12ee0c0a2..4c5b0a5e0 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/domain/Policy.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/domain/Policy.java @@ -85,6 +85,7 @@ public void setInformationType(InformationType informationType) { } } + // TODO: Snu avhengigheten innover public static Policy mapRequestToPolicy(PolicyRequest policyRequest) { Policy policy = policyRequest.getExistingPolicy() != null ? policyRequest.getExistingPolicy() : Policy.builder().generateId().data(new PolicyData()).build(); policyRequest.getInformationType().addPolicy(policy); @@ -97,6 +98,7 @@ public static Policy mapRequestToPolicy(PolicyRequest policyRequest) { return policy; } + // TODO: Snu avhengigheten innover public PolicyResponse convertToResponse(boolean includeProcess) { return PolicyResponse.builder() .id(getId()) @@ -112,10 +114,12 @@ public PolicyResponse convertToResponse(boolean includeProcess) { .build(); } + // TODO: Snu avhengigheten innover public UsedInInstancePurpose getInstanceIdentification() { return UsedInInstancePurpose.builder().id(id.toString()).processId(process.getId().toString()).name(informationTypeName).purposes(getData().getPurposes()).build(); } + // TODO: Snu avhengigheten innover private InformationTypeShortResponse convertInformationTypeShortResponse() { return getInformationType() == null ? null : new InformationTypeShortResponse(getInformationTypeId(), getInformationTypeName(), getInformationType().getData().sensitivityCode()); @@ -124,7 +128,6 @@ private InformationTypeShortResponse convertInformationTypeShortResponse() { @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") public static class PolicyBuilder { - public PolicyBuilder generateId() { id = UUID.randomUUID(); return this; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/domain/PolicyData.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/domain/PolicyData.java index 0c5318678..f472b596e 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/domain/PolicyData.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/domain/PolicyData.java @@ -24,6 +24,7 @@ public class PolicyData { @NotNull @Singular private List subjectCategories; + @Builder.Default private boolean legalBasesInherited = false; @NotNull private LegalBasesUse legalBasesUse; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/rest/PolicyRestController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/rest/PolicyRestController.java index c734ca25a..06596e8eb 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/rest/PolicyRestController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/policy/rest/PolicyRestController.java @@ -42,9 +42,11 @@ @RestController @Tag(name = "Policy", description = "Data Catalog Policies") @RequestMapping("/policy") -@Transactional +@Transactional // TODO: Flytt dette inn til tjenestelaget public class PolicyRestController { + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk, *Repository-aksess og @Transactional til tjenestelaget. + private final PolicyService service; private final PolicyRepository policyRepository; @@ -189,6 +191,6 @@ private RuntimeException notFoundError(UUID id) { } public static final class PolicyPage extends RestResponsePage { - } + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/DomainCache.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/DomainCache.java index ec0ac2aa7..eacbd409b 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/DomainCache.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/DomainCache.java @@ -8,13 +8,13 @@ import no.nav.data.polly.informationtype.domain.InformationType; import no.nav.data.polly.process.domain.Process; import no.nav.data.polly.process.domain.repo.ProcessRepository; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import java.time.Duration; import java.util.Optional; import java.util.UUID; -@Service +@Component public class DomainCache { private final LoadingCache> processCache; diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessReadController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessReadController.java index 4161dbe1f..b399e9f72 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessReadController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessReadController.java @@ -63,6 +63,8 @@ @RequiredArgsConstructor public class ProcessReadController { + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk, *Repository-aksess og @Transactional til tjenestelaget. + private final TeamService teamService; private final ProcessRepository repository; private final ProcessService processService; @@ -71,7 +73,7 @@ public class ProcessReadController { @Operation(summary = "Get Process with InformationTypes for Id or process number") @ApiResponse(description = "Process fetched") @GetMapping("/{id}") - @Transactional + @Transactional // TODO: Flytt til tjenestelaget public ResponseEntity findForId(@PathVariable @Parameter(description = "Treated as process number if numeric") String id) { log.info("Received request for Process with id/number={}", id); Optional process; @@ -143,7 +145,7 @@ public ResponseEntity> getProcessesByInformati @Operation(summary = "Get Processes for Purpose") @ApiResponse(description = "Processes fetched") @GetMapping("/purpose/{purpose}") - @Transactional + @Transactional // TODO: Flytt til tjenestelaget public ResponseEntity> getPurpose(@PathVariable String purpose) { log.info("Get processes for purpose={}", purpose); Codelist codelist = CodelistService.getCodelist(ListName.PURPOSE, purpose); @@ -248,11 +250,9 @@ private boolean isSet(HttpServletRequest request, String param) { } static class ProcessPage extends RestResponsePage { - } static class LastEditedPage extends RestResponsePage { - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessService.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessService.java index aa2c23f61..63f02ddff 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessService.java @@ -15,7 +15,6 @@ import no.nav.data.polly.codelist.dto.CodeUsageResponse; import no.nav.data.polly.disclosure.domain.Disclosure; import no.nav.data.polly.disclosure.domain.DisclosureRepository; -import no.nav.data.polly.informationtype.InformationTypeRepository; import no.nav.data.polly.policy.domain.PolicyRepository; import no.nav.data.polly.process.domain.Process; import no.nav.data.polly.process.domain.ProcessStatus; @@ -52,6 +51,8 @@ @RequiredArgsConstructor public class ProcessService extends RequestValidator { + // TODO: Denne klassen skal ikke subklasse RequestValidator. Flytt dette ut til en egen komponent (XxxRequestValidator). + private final ProcessRepository processRepository; private final ProcessorRepository processorRepository; private final DisclosureRepository disclosureRepository; @@ -64,7 +65,6 @@ public class ProcessService extends RequestValidator { private final PolicyRepository policyRepository; - @Transactional public Process save(Process process) { var saved = processRepository.save(process); @@ -114,6 +114,13 @@ private List fetchAllProcessesAndFilter(CodeUsageResponse gdpr, CodeUsa return processRepository.findAllById(all); } + private List getAllProcessIds(CodeUsageResponse usage) { + return union( + convert(usage.getProcesses(), ProcessShortResponse::getId), + convert(usage.getPolicies(), p -> UUID.fromString(p.getProcessId())) + ).stream().distinct().collect(toList()); + } + public List fetchAllProcessesByInformationTypeSensitivity(String sensitivity) { CodeUsageResponse codeUsageResponse = codeUsageService.findCodeUsage(ListName.SENSITIVITY,sensitivity); List processes = new ArrayList<>(); @@ -126,13 +133,6 @@ public List fetchAllProcessesByInformationTypeSensitivity(String sensit return processes.stream().distinct().collect(Collectors.toList()); } - private List getAllProcessIds(CodeUsageResponse usage) { - return union( - convert(usage.getProcesses(), ProcessShortResponse::getId), - convert(usage.getPolicies(), p -> UUID.fromString(p.getProcessId())) - ).stream().distinct().collect(toList()); - } - public void validateRequest(ProcessRequest request, boolean update) { initialize(List.of(request), update); var validationErrors = StreamUtils.applyAll(request, diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessStateController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessStateController.java index 74b457565..a442cc931 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessStateController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessStateController.java @@ -26,6 +26,8 @@ @RequestMapping("/process/state") public class ProcessStateController { + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk, *Repository-aksess og @Transactional til tjenestelaget. + private final ProcessRepository processRepository; private final TeamService teamService; @@ -52,6 +54,5 @@ public RestResponsePage getProcesses(ProcessStateRequest r } static class ProcessShortPage extends RestResponsePage { - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessWriteController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessWriteController.java index a4d85fec5..3de5b895c 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessWriteController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/ProcessWriteController.java @@ -35,11 +35,13 @@ @Slf4j @RestController -@Transactional +@Transactional // TODO: Flytt dette inn til tjenestelaget @Tag(name = "Process", description = "REST API for Process") @RequestMapping("/process") public class ProcessWriteController { + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk, *Repository-aksess og @Transactional til tjenestelaget. + private final ProcessService service; private final ProcessRepository repository; private final TeamService teamService; @@ -83,7 +85,6 @@ public ResponseEntity updateProcess(@PathVariable UUID id, @Val @Operation(summary = "Delete Process") @ApiResponse(description = "Process deleted") @DeleteMapping("/{id}") - @Transactional public ResponseEntity deleteProcessById(@PathVariable UUID id) { log.info("Received a request to delete Process with id={}", id); Optional fromRepository = repository.findById(id); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/Process.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/Process.java index cce4f5530..898f6f935 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/Process.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/Process.java @@ -11,7 +11,6 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import no.nav.data.common.auditing.domain.Auditable; @@ -26,6 +25,8 @@ import no.nav.data.polly.process.dto.ProcessRequest; import no.nav.data.polly.process.dto.ProcessResponse; import no.nav.data.polly.process.dto.ProcessShortResponse; +import no.nav.data.polly.process.dto.sub.DataProcessingRequest; +import no.nav.data.polly.process.dto.sub.DataProcessingResponse; import org.hibernate.annotations.Type; import java.util.HashSet; @@ -38,14 +39,12 @@ import static no.nav.data.common.utils.StreamUtils.convert; import static no.nav.data.common.utils.StreamUtils.safeStream; import static no.nav.data.polly.process.domain.sub.Affiliation.convertAffiliation; -import static no.nav.data.polly.process.domain.sub.DataProcessing.convertDataProcessing; import static no.nav.data.polly.process.domain.sub.Dpia.convertDpia; import static no.nav.data.polly.process.domain.sub.Retention.convertRetention; @Data @Builder @ToString(exclude = {"policies"}) -@EqualsAndHashCode(callSuper = false, exclude = {"policies"}) @AllArgsConstructor @NoArgsConstructor @Entity @@ -79,6 +78,7 @@ public boolean isActive() { return DateUtil.isNow(data.getStart(), data.getEnd()); } + // TODO: Snu avhengigheten innover public ProcessResponse convertToResponse() { return ProcessResponse.builder() .id(id) @@ -95,16 +95,17 @@ public ProcessResponse convertToResponse() { .usesAllInformationTypes(data.isUsesAllInformationTypes()) .automaticProcessing(data.getAutomaticProcessing()) .profiling(data.getProfiling()) - .dataProcessing(data.getDataProcessing().convertToResponse()) + .dataProcessing(DataProcessingResponse.buildFrom(data.getDataProcessing())) .retention(data.getRetention().convertToResponse()) .dpia(data.getDpia().convertToResponse()) // If we dont include policies avoid loading them all from DB - .changeStamp(super.convertChangeStampResponse()) + .changeStamp(ChangeStampResponse.from(this)) .status(data.getStatus()) .revisionText(data.getRevisionText()) .build(); } + // TODO: Snu avhengigheten innover public ProcessResponse convertToResponseWithPolicies() { var response = convertToResponse(); response.setPolicies(convert(policies, policy -> policy.convertToResponse(false))); @@ -112,6 +113,7 @@ public ProcessResponse convertToResponseWithPolicies() { return response; } + // TODO: Snu avhengigheten innover public Process convertFromRequest(ProcessRequest request) { if (!request.isUpdate()) { id = UUID.randomUUID(); @@ -130,7 +132,7 @@ public Process convertFromRequest(ProcessRequest request) { data.setUsesAllInformationTypes(request.isUsesAllInformationTypes()); data.setAutomaticProcessing(request.getAutomaticProcessing()); data.setProfiling(request.getProfiling()); - data.setDataProcessing(convertDataProcessing(request.getDataProcessing())); + data.setDataProcessing(DataProcessingRequest.convertToDataProcessingNullSafe(request.getDataProcessing())); data.setRetention(convertRetention(request.getRetention())); data.setDpia(convertDpia(request.getDpia())); if (request.getStatus() == ProcessStatus.NEEDS_REVISION && data.getStatus() != ProcessStatus.NEEDS_REVISION) { @@ -144,6 +146,7 @@ public Process convertFromRequest(ProcessRequest request) { return this; } + // TODO: Snu avhengigheten innover public ProcessShortResponse convertToShortResponse() { return ProcessShortResponse.builder() .id(getId()) @@ -179,14 +182,8 @@ public boolean equals(Object obj) { if (this == obj) { return true; } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Process process = (Process) obj; - return id == process.id; + if (!(obj instanceof Process process)) return false; + return id == process.id; // Merk: Er dette riktig i alle tilfeller hvis id == process.id == null? } @Override diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/ProcessData.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/ProcessData.java index 7d012de9a..2cf93d44f 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/ProcessData.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/ProcessData.java @@ -64,7 +64,6 @@ public Period toPeriod() { return new Period(start, end); } - public DataProcessing getDataProcessing() { if (dataProcessing == null) { dataProcessing = new DataProcessing(); @@ -92,4 +91,5 @@ public Affiliation getAffiliation() { } return affiliation; } + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Affiliation.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Affiliation.java index 5b1dc3492..934dc6e38 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Affiliation.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Affiliation.java @@ -27,6 +27,7 @@ public class Affiliation { private List products; private List disclosureDispatchers; + // TODO: Snu avhengigheten innover public static Affiliation convertAffiliation(AffiliationRequest request) { if (request == null) { return new Affiliation(); @@ -40,6 +41,7 @@ public static Affiliation convertAffiliation(AffiliationRequest request) { .build(); } + // TODO: Snu avhengigheten innover public AffiliationResponse convertToResponse() { return AffiliationResponse.builder() .department(getDepartmentCodeResponse()) @@ -50,21 +52,24 @@ public AffiliationResponse convertToResponse() { .build(); } + // TODO: Avhengighet utover public CodelistResponse getDepartmentCodeResponse() { return CodelistService.getCodelistResponse(ListName.DEPARTMENT, getDepartment()); } + // TODO: Avhengighet utover public List getSubDepartmentCodeResponses() { return CodelistService.getCodelistResponseList(ListName.SUB_DEPARTMENT, getSubDepartments()); } + // TODO: Avhengighet utover public List getProductCodeResponses() { return CodelistService.getCodelistResponseList(ListName.SYSTEM, getProducts()); } + // TODO: Avhengighet utover public List getDisclosureDispatcherCodeResponses() { return CodelistService.getCodelistResponseList(ListName.SYSTEM, getDisclosureDispatchers()); } - } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/DataProcessing.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/DataProcessing.java index 5ec6ed1cc..95124db2a 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/DataProcessing.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/DataProcessing.java @@ -4,15 +4,10 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import no.nav.data.polly.process.dto.sub.DataProcessingRequest; -import no.nav.data.polly.process.dto.sub.DataProcessingResponse; import java.util.List; import java.util.UUID; -import static no.nav.data.common.utils.StreamUtils.convert; -import static no.nav.data.common.utils.StreamUtils.copyOf; - @Data @Builder @AllArgsConstructor @@ -22,20 +17,4 @@ public class DataProcessing { private Boolean dataProcessor; private List processors; - public DataProcessingResponse convertToResponse() { - return DataProcessingResponse.builder() - .dataProcessor(getDataProcessor()) - .processors(copyOf(processors)) - .build(); - } - - public static DataProcessing convertDataProcessing(DataProcessingRequest request) { - if (request == null) { - return new DataProcessing(); - } - return DataProcessing.builder() - .dataProcessor(request.getDataProcessor()) - .processors(convert(request.getProcessors(), UUID::fromString)) - .build(); - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Dpia.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Dpia.java index 7407a6828..72cb802d5 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Dpia.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Dpia.java @@ -28,6 +28,7 @@ public class Dpia { private String riskOwner; private String riskOwnerFunction; + // TODO: Snu avhengigheten innover public DpiaResponse convertToResponse() { return DpiaResponse.builder() .needForDpia(getNeedForDpia()) @@ -40,6 +41,7 @@ public DpiaResponse convertToResponse() { .build(); } + // TODO: Snu avhengigheten innover public static Dpia convertDpia(DpiaRequest dpia) { if (dpia == null) { return new Dpia(); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Retention.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Retention.java index 62c432146..b066cee2e 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Retention.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/domain/sub/Retention.java @@ -18,6 +18,7 @@ public class Retention { private String retentionStart; private String retentionDescription; + // TODO: Snu avhengigheten innover public RetentionResponse convertToResponse() { return RetentionResponse.builder() .retentionPlan(getRetentionPlan()) @@ -27,6 +28,7 @@ public RetentionResponse convertToResponse() { .build(); } + // TODO: Snu avhengigheten innover public static Retention convertRetention(RetentionRequest retention) { if (retention == null) { return new Retention(); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/DpProcessController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/DpProcessController.java index 378779667..c3d8aead9 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/DpProcessController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/DpProcessController.java @@ -13,6 +13,7 @@ import no.nav.data.polly.process.dpprocess.domain.DpProcess; import no.nav.data.polly.process.dpprocess.domain.repo.DpProcessRepository; import no.nav.data.polly.process.dpprocess.dto.DpProcessRequest; +import no.nav.data.polly.process.dpprocess.dto.DpProcessRequestValidator; import no.nav.data.polly.process.dpprocess.dto.DpProcessResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; @@ -39,8 +40,11 @@ @RequestMapping("/dpprocess") public class DpProcessController { + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk og *Repository-aksess til tjenestelaget. + private final DpProcessRepository repository; private final DpProcessService service; + private final DpProcessRequestValidator requestValidator; @Operation(summary = "Get DpProcessTypes") @ApiResponse(description = "DpProcess fetched") @@ -89,7 +93,7 @@ public ResponseEntity> search(@PathVariable @PostMapping public ResponseEntity create(@RequestBody DpProcessRequest request) { log.info("Received requests to create DpProcess"); - service.validateRequest(request, false); + requestValidator.validateRequest(request, false); request.setNewDpProcessNmber(repository.nextDpProcessNumber()); @@ -110,7 +114,7 @@ public ResponseEntity update(@PathVariable UUID id, @Valid @R log.info("Cannot find DpProcess with id={}", id); return new ResponseEntity<>(HttpStatus.NOT_FOUND); } - service.validateRequest(request, true); + requestValidator.validateRequest(request, true); return ResponseEntity.ok(service.update(request).convertToResponse()); } @@ -129,7 +133,6 @@ public ResponseEntity delete(@PathVariable UUID id) { } static class DpProcessPage extends RestResponsePage { - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/DpProcessService.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/DpProcessService.java index 6df240bc2..e7efe1988 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/DpProcessService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/DpProcessService.java @@ -1,30 +1,19 @@ package no.nav.data.polly.process.dpprocess; import lombok.RequiredArgsConstructor; -import no.nav.data.common.utils.StreamUtils; -import no.nav.data.common.validator.RequestElement; -import no.nav.data.common.validator.RequestValidator; -import no.nav.data.common.validator.ValidationError; import no.nav.data.polly.process.dpprocess.domain.DpProcess; import no.nav.data.polly.process.dpprocess.domain.repo.DpProcessRepository; import no.nav.data.polly.process.dpprocess.dto.DpProcessRequest; -import no.nav.data.polly.teams.TeamService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; import java.util.UUID; -import static java.lang.String.format; - @Service @RequiredArgsConstructor -public class DpProcessService extends RequestValidator { +public class DpProcessService { private final DpProcessRepository repository; - private final TeamService teamService; @Transactional public DpProcess save(DpProcess process) { @@ -43,43 +32,4 @@ public void deleteById(UUID uuid) { repository.deleteById(uuid); } - public void validateRequest(DpProcessRequest request, boolean update) { - initialize(List.of(request), update); - - var validationErrors = StreamUtils.applyAll(request, - RequestElement::validateFields, - this::validateProcessRepositoryValues - ); - - ifErrorsThrowValidationException(validationErrors); - } - - private List validateProcessRepositoryValues(DpProcessRequest request) { - var validations = new ArrayList(); - if (request.isUpdate()) { - var repoValue = Optional.ofNullable(request.getIdAsUUID()).flatMap(repository::findById); - validations.addAll(validateRepositoryValues(request, repoValue.isPresent())); - repoValue.ifPresent(existing -> validateTeams(request, existing.getData().getAffiliation().getProductTeams(), validations)); - } else { - validations.addAll(validateRepositoryValues(request, false)); - validateTeams(request, List.of(), validations); - } - Optional byName = repository.findByName(request.getName()).filter(p -> !p.getId().equals(request.getIdAsUUID())); - if (byName.isPresent()) { - validations.add(new ValidationError(request.getReference(), "nameAndPurposeExists", - format("DpProcess with name %s already exists", request.getName()))); - } - return validations; - } - - private void validateTeams(DpProcessRequest dpRequest, List existingTeams, ArrayList validations) { - var request = dpRequest.getAffiliation(); - if (!request.getProductTeams().isEmpty() && !existingTeams.equals(request.getProductTeams())) { - request.getProductTeams().forEach(t -> { - if (!teamService.teamExists(t)) { - validations.add(new ValidationError(request.getReference(), "invalidProductTeam", "Product team " + t + " does not exist")); - } - }); - } - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/domain/DpProcess.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/domain/DpProcess.java index 9f4566dc3..c89007c38 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/domain/DpProcess.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/domain/DpProcess.java @@ -20,13 +20,14 @@ import no.nav.data.polly.process.dpprocess.dto.DpProcessRequest; import no.nav.data.polly.process.dpprocess.dto.DpProcessResponse; import no.nav.data.polly.process.dpprocess.dto.DpProcessShortResponse; +import no.nav.data.polly.process.dto.sub.DataProcessingRequest; +import no.nav.data.polly.process.dto.sub.DataProcessingResponse; import org.hibernate.annotations.Type; import java.util.UUID; import static no.nav.data.common.utils.StreamUtils.copyOf; import static no.nav.data.polly.process.domain.sub.Affiliation.convertAffiliation; -import static no.nav.data.polly.process.domain.sub.DataProcessing.convertDataProcessing; import static no.nav.data.polly.process.dpprocess.domain.sub.DpRetention.convertRetention; @Data @@ -53,6 +54,7 @@ public boolean isActive() { return DateUtil.isNow(data.getStart(), data.getEnd()); } + // TODO: Snu avhengigheten innover public DpProcess convertFromRequest(DpProcessRequest request) { if (!request.isUpdate()) { id = UUID.randomUUID(); @@ -64,7 +66,7 @@ public DpProcess convertFromRequest(DpProcessRequest request) { data.setStart(DateUtil.parseStart(request.getStart())); data.setEnd(DateUtil.parseEnd(request.getEnd())); data.setDataProcessingAgreements(copyOf(request.getDataProcessingAgreements())); - data.setSubDataProcessing(convertDataProcessing(request.getSubDataProcessing())); + data.setSubDataProcessing(DataProcessingRequest.convertToDataProcessingNullSafe(request.getSubDataProcessing())); data.setPurposeDescription(request.getPurposeDescription()); data.setDescription(request.getDescription()); data.setArt9(request.getArt9()); @@ -73,6 +75,7 @@ public DpProcess convertFromRequest(DpProcessRequest request) { return this; } + // TODO: Snu avhengigheten innover public DpProcessResponse convertToResponse() { return DpProcessResponse.builder() .id(id) @@ -83,7 +86,7 @@ public DpProcessResponse convertToResponse() { .start(data.getStart()) .end(data.getEnd()) .dataProcessingAgreements(copyOf(data.getDataProcessingAgreements())) - .subDataProcessing(data.getSubDataProcessing().convertToResponse()) + .subDataProcessing(DataProcessingResponse.buildFrom(data.getSubDataProcessing())) .purposeDescription(data.getPurposeDescription()) .description(data.getDescription()) .art9(data.getArt9()) @@ -93,10 +96,13 @@ public DpProcessResponse convertToResponse() { .build(); } + // TODO: Snu avhengigheten innover + // TODO: Skal ikke ha kall til service her. private CodelistResponse getExternalProcessResponsibleCodeResponse() { return CodelistService.getCodelistResponse(ListName.THIRD_PARTY, data.getExternalProcessResponsible()); } + // TODO: Snu avhengigheten innover public DpProcessShortResponse convertToShortResponse() { return new DpProcessShortResponse(id, data.getName(), data.getAffiliation().convertToResponse()); } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/domain/sub/DpRetention.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/domain/sub/DpRetention.java index becf29657..9662b990d 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/domain/sub/DpRetention.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/domain/sub/DpRetention.java @@ -16,6 +16,7 @@ public class DpRetention { private Integer retentionMonths; private String retentionStart; + // TODO: Snu avhengigheten innover public DpRetentionResponse convertToResponse() { return DpRetentionResponse.builder() .retentionMonths(getRetentionMonths()) @@ -23,6 +24,7 @@ public DpRetentionResponse convertToResponse() { .build(); } + // TODO: Snu avhengigheten innover public static DpRetention convertRetention(DpRetentionRequest retention) { if (retention == null) { return new DpRetention(); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/dto/DpProcessRequestValidator.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/dto/DpProcessRequestValidator.java new file mode 100644 index 000000000..ad16b6a33 --- /dev/null +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dpprocess/dto/DpProcessRequestValidator.java @@ -0,0 +1,65 @@ +package no.nav.data.polly.process.dpprocess.dto; + +import lombok.RequiredArgsConstructor; +import no.nav.data.common.utils.StreamUtils; +import no.nav.data.common.validator.RequestElement; +import no.nav.data.common.validator.RequestValidator; +import no.nav.data.common.validator.ValidationError; +import no.nav.data.polly.process.dpprocess.domain.DpProcess; +import no.nav.data.polly.process.dpprocess.domain.repo.DpProcessRepository; +import no.nav.data.polly.teams.TeamService; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static java.lang.String.format; + +@Component +@RequiredArgsConstructor +public class DpProcessRequestValidator extends RequestValidator { + + private final DpProcessRepository repository; + private final TeamService teamService; + + public void validateRequest(DpProcessRequest request, boolean update) { + initialize(List.of(request), update); + + var validationErrors = StreamUtils.applyAll(request, + RequestElement::validateFields, + this::validateProcessRepositoryValues + ); + + ifErrorsThrowValidationException(validationErrors); + } + + private List validateProcessRepositoryValues(DpProcessRequest request) { + var validations = new ArrayList(); + if (request.isUpdate()) { + var repoValue = Optional.ofNullable(request.getIdAsUUID()).flatMap(repository::findById); + validations.addAll(validateRepositoryValues(request, repoValue.isPresent())); + repoValue.ifPresent(existing -> validateTeams(request, existing.getData().getAffiliation().getProductTeams(), validations)); + } else { + validations.addAll(validateRepositoryValues(request, false)); + validateTeams(request, List.of(), validations); + } + Optional byName = repository.findByName(request.getName()).filter(p -> !p.getId().equals(request.getIdAsUUID())); + if (byName.isPresent()) { + validations.add(new ValidationError(request.getReference(), "nameAndPurposeExists", + format("DpProcess with name %s already exists", request.getName()))); + } + return validations; + } + + private void validateTeams(DpProcessRequest dpRequest, List existingTeams, ArrayList validations) { + var request = dpRequest.getAffiliation(); + if (!request.getProductTeams().isEmpty() && !existingTeams.equals(request.getProductTeams())) { + request.getProductTeams().forEach(t -> { + if (!teamService.teamExists(t)) { + validations.add(new ValidationError(request.getReference(), "invalidProductTeam", "Product team " + t + " does not exist")); + } + }); + } + } +} diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dto/sub/DataProcessingRequest.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dto/sub/DataProcessingRequest.java index e78655755..fc5dcb94a 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dto/sub/DataProcessingRequest.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dto/sub/DataProcessingRequest.java @@ -9,9 +9,12 @@ import lombok.experimental.FieldNameConstants; import no.nav.data.common.validator.FieldValidator; import no.nav.data.common.validator.Validated; +import no.nav.data.polly.process.domain.sub.DataProcessing; import java.util.List; +import java.util.UUID; +import static no.nav.data.common.utils.StreamUtils.convert; import static no.nav.data.common.utils.StringUtils.formatList; @Data @@ -37,4 +40,20 @@ public void format() { public void validate(FieldValidator validator) { processors.forEach(processor -> validator.checkUUID(Fields.processors, processor)); } + + public DataProcessing convertToDataProcessing() { + return DataProcessing.builder() + .dataProcessor(getDataProcessor()) + .processors(convert(getProcessors(), UUID::fromString)) + .build(); + } + + public static DataProcessing convertToDataProcessingNullSafe(DataProcessingRequest request) { + if (request == null) { + return new DataProcessing(); + } + return request.convertToDataProcessing(); + } + + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dto/sub/DataProcessingResponse.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dto/sub/DataProcessingResponse.java index 88a99986f..111b504db 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dto/sub/DataProcessingResponse.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/process/dto/sub/DataProcessingResponse.java @@ -6,10 +6,13 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.Singular; +import no.nav.data.polly.process.domain.sub.DataProcessing; import java.util.List; import java.util.UUID; +import static no.nav.data.common.utils.StreamUtils.copyOf; + @Data @Builder @AllArgsConstructor @@ -21,4 +24,11 @@ public class DataProcessingResponse { @Singular private List processors; + public static DataProcessingResponse buildFrom(DataProcessing dp) { + return DataProcessingResponse.builder() + .dataProcessor(dp.getDataProcessor()) + .processors(copyOf(dp.getProcessors())) + .build(); + } + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/ProcessorController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/ProcessorController.java index 694d147ba..50f2faaf8 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/ProcessorController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/ProcessorController.java @@ -38,6 +38,8 @@ @RequestMapping("/processor") public class ProcessorController { + // TODO: Implementerer ikke controller → service → DB. Flytt all forretningslogikk, *Repository-aksess og @Transactional til tjenestelaget. + private final ProcessorRepository repository; private final ProcessorService service; @@ -118,7 +120,6 @@ public ResponseEntity delete(@PathVariable UUID id) { } static class ProcessorPage extends RestResponsePage { - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/ProcessorService.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/ProcessorService.java index 8c896b4d7..cdb414c51 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/ProcessorService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/ProcessorService.java @@ -28,6 +28,8 @@ @RequiredArgsConstructor public class ProcessorService extends RequestValidator { + // TODO: Denne klassen skal ikke subklasse RequestValidator. Flytt dette ut til en egen komponent (XxxRequestValidator). + private final ProcessorRepository repository; private final ProcessRepository processRepository; private final ResourceService resourceService; @@ -53,6 +55,7 @@ public void deleteById(UUID uuid) { repository.deleteById(uuid); } + // TODO: Snu avhengigheten innover Ikke trivielt å flytte ut (men heller ikke så vanskelig) public void validateRequest(ProcessorRequest request, boolean update) { initialize(List.of(request), update); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/domain/Processor.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/domain/Processor.java index 50a3420e0..899ce51cd 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/domain/Processor.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/domain/Processor.java @@ -42,6 +42,7 @@ public class Processor extends Auditable { @Column(name = "DATA", nullable = false) private ProcessorData data = new ProcessorData(); + // TODO: Snu avhengigheten innover public ProcessorResponse convertToResponse() { return ProcessorResponse.builder() .id(id) @@ -58,6 +59,7 @@ public ProcessorResponse convertToResponse() { .build(); } + // TODO: Snu avhengigheten innover public Processor convertFromRequest(ProcessorRequest request) { if (!request.isUpdate()) { id = UUID.randomUUID(); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/domain/ProcessorData.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/domain/ProcessorData.java index 8b910b794..cc8fa2bb1 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/domain/ProcessorData.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/processor/domain/ProcessorData.java @@ -31,6 +31,7 @@ public class ProcessorData { @Singular private List countries; + // TODO: Snu avhengigheten innover public CodelistResponse getTransferGroundsOutsideEUCodeResponse() { return CodelistService.getCodelistResponse(ListName.TRANSFER_GROUNDS_OUTSIDE_EU, getTransferGroundsOutsideEU()); } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/settings/SettingsController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/settings/SettingsController.java index c3c88580f..8d19bc25a 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/settings/SettingsController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/settings/SettingsController.java @@ -41,5 +41,4 @@ public ResponseEntity write(@RequestBody Settings settings) { return ResponseEntity.ok(service.updateSettings(settings)); } - } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/settings/SettingsService.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/settings/SettingsService.java index c93db020c..056f8cf6a 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/settings/SettingsService.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/settings/SettingsService.java @@ -7,6 +7,7 @@ import no.nav.data.polly.document.domain.DocumentRepository; import no.nav.data.polly.settings.dto.Settings; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.UUID; @@ -21,10 +22,12 @@ public SettingsService(StorageService storage, DocumentRepository documentReposi this.documentRepository = documentRepository; } + @Transactional // Merk: Kallet kan resultere i save mot databasen public Settings getSettings() { return storage.getSingleton(Settings.class); } + @Transactional public Settings updateSettings(Settings settings) { validate(settings); GenericStorage settingsStorage = storage.getSingletonAsStorage(Settings.class); diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/TeamController.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/TeamController.java index ae4898114..b10f48d11 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/TeamController.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/TeamController.java @@ -51,7 +51,7 @@ public TeamController(TeamService teamsService, ResourceService resourceService) @GetMapping public RestResponsePage findAllTeams() { log.info("Received a request for all teams"); - return new RestResponsePage<>(convert(teamsService.getAllTeams(), Team::convertToResponse)); + return new RestResponsePage<>(convert(teamsService.getAllTeams(), TeamResponse::buildFrom)); } @Operation(summary = "Get team") @@ -63,7 +63,7 @@ public ResponseEntity getTeamByName(@PathVariable String teamId) { if (team.isEmpty()) { throw new NotFoundException("Couldn't find team " + teamId); } - return new ResponseEntity<>(team.get().convertToResponseWithMembers(), HttpStatus.OK); + return new ResponseEntity<>(TeamResponse.buildFromWithMembers(team.get()), HttpStatus.OK); } @Operation(summary = "Search teams") @@ -77,7 +77,7 @@ public ResponseEntity> searchTeamByName(@PathVari var teams = StreamUtils.filter(teamsService.getAllTeams(), team -> containsIgnoreCase(team.getName(), name)); teams.sort(comparing(Team::getName, startsWith(name))); log.info("Returned {} teams", teams.size()); - return new ResponseEntity<>(new RestResponsePage<>(convert(teams, Team::convertToResponse)), HttpStatus.OK); + return new ResponseEntity<>(new RestResponsePage<>(convert(teams, TeamResponse::buildFrom)), HttpStatus.OK); } // Product Areas @@ -87,7 +87,7 @@ public ResponseEntity> searchTeamByName(@PathVari @GetMapping("/productarea") public RestResponsePage findAllProductAreas() { log.info("Received a request for all product areas"); - return new RestResponsePage<>(convert(teamsService.getAllProductAreas(), ProductArea::convertToResponse)); + return new RestResponsePage<>(convert(teamsService.getAllProductAreas(), ProductAreaResponse::buildFrom)); } @Operation(summary = "Get product area") @@ -99,7 +99,7 @@ public ResponseEntity getProductAreaById(@PathVariable Stri if (pa.isEmpty()) { throw new NotFoundException("Couldn't find product area " + paId); } - return new ResponseEntity<>(pa.get().convertToResponseWithMembers(), HttpStatus.OK); + return new ResponseEntity<>(ProductAreaResponse.buildFromWithMembers(pa.get()), HttpStatus.OK); } @Operation(summary = "Search product areas") @@ -113,7 +113,7 @@ public ResponseEntity> searchProductAreaBy var pas = StreamUtils.filter(teamsService.getAllProductAreas(), pa -> containsIgnoreCase(pa.getName(), name)); pas.sort(comparing(ProductArea::getName, startsWith(name))); log.info("Returned {} pas", pas.size()); - return new ResponseEntity<>(new RestResponsePage<>(convert(pas, ProductArea::convertToResponse)), HttpStatus.OK); + return new ResponseEntity<>(new RestResponsePage<>(convert(pas, ProductAreaResponse::buildFrom)), HttpStatus.OK); } // Resources @@ -144,14 +144,11 @@ public ResponseEntity getById(@PathVariable String id) { } static class ResourcePage extends RestResponsePage { - } static class TeamPage extends RestResponsePage { - } static class ProductAreaPage extends RestResponsePage { - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/Member.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/Member.java index 0e1e13283..af17998fd 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/Member.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/Member.java @@ -4,7 +4,6 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import no.nav.data.polly.teams.dto.MemberResponse; @Data @Builder @@ -15,7 +14,4 @@ public class Member { private String name; private String email; - public MemberResponse convertToResponse() { - return MemberResponse.builder().name(name).email(email).build(); - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/ProductArea.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/ProductArea.java index 12ba679ac..8c887afea 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/ProductArea.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/ProductArea.java @@ -4,12 +4,9 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import no.nav.data.polly.teams.dto.ProductAreaResponse; import java.util.List; -import static no.nav.data.common.utils.StreamUtils.convert; - @Data @Builder @AllArgsConstructor @@ -22,18 +19,4 @@ public class ProductArea { private List tags; private List members; - public ProductAreaResponse convertToResponseWithMembers() { - var resp = convertToResponse(); - resp.setMembers(convert(members, Member::convertToResponse)); - return resp; - } - - public ProductAreaResponse convertToResponse() { - return ProductAreaResponse.builder() - .id(id) - .name(name) - .description(description) - .tags(tags) - .build(); - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/Team.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/Team.java index 8a0eb280b..1e5e23759 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/Team.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/domain/Team.java @@ -4,12 +4,9 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import no.nav.data.polly.teams.dto.TeamResponse; import java.util.List; -import static no.nav.data.common.utils.StreamUtils.convert; - @Data @Builder @AllArgsConstructor @@ -24,20 +21,4 @@ public class Team { private List tags; private List members; - public TeamResponse convertToResponseWithMembers() { - var resp = convertToResponse(); - resp.setMembers(convert(members, Member::convertToResponse)); - return resp; - } - - public TeamResponse convertToResponse() { - return TeamResponse.builder() - .id(id) - .name(name) - .description(description) - .slackChannel(slackChannel) - .productAreaId(productAreaId) - .tags(tags) - .build(); - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/MemberResponse.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/MemberResponse.java index 280ffc5c3..036ec0adc 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/MemberResponse.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/MemberResponse.java @@ -5,6 +5,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import no.nav.data.polly.teams.domain.Member; @Data @Builder @@ -16,4 +17,11 @@ public class MemberResponse { private String name; private String email; + public static MemberResponse buildFrom(Member m) { + return builder() + .name(m.getName()) + .email(m.getEmail()) + .build(); + } + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/ProductAreaResponse.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/ProductAreaResponse.java index 31636eba5..846fa7605 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/ProductAreaResponse.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/ProductAreaResponse.java @@ -5,9 +5,14 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import no.nav.data.polly.teams.domain.Member; +import no.nav.data.polly.teams.domain.ProductArea; import java.util.List; +import static no.nav.data.common.utils.StreamUtils.convert; + + @Data @Builder @AllArgsConstructor @@ -21,5 +26,19 @@ public class ProductAreaResponse { private List tags; private List members; + public static ProductAreaResponse buildFrom(ProductArea pa) { + return ProductAreaResponse.builder() + .id(pa.getId()) + .name(pa.getName()) + .description(pa.getDescription()) + .tags(pa.getTags()) + .build(); + } + + public static ProductAreaResponse buildFromWithMembers(ProductArea pa) { + var resp = buildFrom(pa); + resp.setMembers(convert(pa.getMembers(), MemberResponse::buildFrom)); + return resp; + } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/ResourceType.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/ResourceType.java index d3ecb869d..712f60e46 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/ResourceType.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/ResourceType.java @@ -3,5 +3,4 @@ public enum ResourceType { INTERNAL, EXTERNAL - } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/TeamResponse.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/TeamResponse.java index d449e8555..0a904f7ec 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/TeamResponse.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/dto/TeamResponse.java @@ -1,14 +1,16 @@ package no.nav.data.polly.teams.dto; - import com.fasterxml.jackson.annotation.JsonPropertyOrder; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import no.nav.data.polly.teams.domain.Team; import java.util.List; +import static no.nav.data.common.utils.StreamUtils.convert; + @Data @Builder @AllArgsConstructor @@ -24,4 +26,21 @@ public class TeamResponse { private List tags; private List members; + public static TeamResponse buildFrom(Team t) { + return builder() + .id(t.getId()) + .name(t.getName()) + .description(t.getDescription()) + .slackChannel(t.getSlackChannel()) + .productAreaId(t.getProductAreaId()) + .tags(t.getTags()) + .build(); + } + + public static TeamResponse buildFromWithMembers(Team t) { + var resp = buildFrom(t); + resp.setMembers(convert(t.getMembers(), MemberResponse::buildFrom)); + return resp; + } + } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/teamcat/TeamcatResourceClient.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/teamcat/TeamcatResourceClient.java index 40e009968..d7a549413 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/teamcat/TeamcatResourceClient.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/teamcat/TeamcatResourceClient.java @@ -9,7 +9,7 @@ import no.nav.data.polly.teams.dto.Resource; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; @@ -23,7 +23,7 @@ import static no.nav.data.common.utils.StreamUtils.toMap; @Slf4j -@Service +@Component @ConditionalOnProperty("client.teamcat-resource.enabled") public class TeamcatResourceClient implements ResourceService { @@ -33,8 +33,7 @@ public class TeamcatResourceClient implements ResourceService { private final LoadingCache> searchCache; private final LoadingCache cache; - public TeamcatResourceClient(RestTemplate restTemplate, - TeamcatProperties properties) { + public TeamcatResourceClient(RestTemplate restTemplate, TeamcatProperties properties) { this.restTemplate = restTemplate; this.properties = properties; @@ -92,6 +91,5 @@ private RestResponsePage doSearch(String name) { } static class ResourcePage extends RestResponsePage { - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/teamcat/TeamcatTeamClient.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/teamcat/TeamcatTeamClient.java index 745adb18d..d1dcf4bfb 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/teamcat/TeamcatTeamClient.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/teams/teamcat/TeamcatTeamClient.java @@ -9,7 +9,7 @@ import no.nav.data.polly.teams.domain.ProductArea; import no.nav.data.polly.teams.domain.Team; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Service; +import org.springframework.stereotype.Component; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; @@ -26,7 +26,7 @@ import static no.nav.data.common.utils.StreamUtils.filter; import static no.nav.data.common.utils.StreamUtils.safeStream; -@Service +@Component @ConditionalOnProperty("client.teamcat-team.enabled") @Slf4j public class TeamcatTeamClient implements TeamService { @@ -37,8 +37,7 @@ public class TeamcatTeamClient implements TeamService { private final LoadingCache> allTeamsCache; private final LoadingCache> allPaCache; - public TeamcatTeamClient(RestTemplate restTemplate, - TeamcatProperties properties) { + public TeamcatTeamClient(RestTemplate restTemplate, TeamcatProperties properties) { this.restTemplate = restTemplate; this.properties = properties; @@ -117,11 +116,9 @@ private Map getTeamsResponse() { } static class TeamPage extends RestResponsePage { - } static class ProductAreaPage extends RestResponsePage { - } } diff --git a/apps/backend/polly-app/src/main/java/no/nav/data/polly/term/domain/PollyTerm.java b/apps/backend/polly-app/src/main/java/no/nav/data/polly/term/domain/PollyTerm.java index 2732fdc9d..9c0c12f96 100644 --- a/apps/backend/polly-app/src/main/java/no/nav/data/polly/term/domain/PollyTerm.java +++ b/apps/backend/polly-app/src/main/java/no/nav/data/polly/term/domain/PollyTerm.java @@ -16,6 +16,7 @@ public class PollyTerm { private String name; private String description; + // TODO: Snu avhengigheten innover public TermResponse convertToResponse() { return TermResponse.builder() .id(id) diff --git a/apps/backend/polly-test/polly-test-component/src/test/java/no/nav/data/polly/codelist/CodelistControllerTest.java b/apps/backend/polly-test/polly-test-component/src/test/java/no/nav/data/polly/codelist/CodelistControllerTest.java index 00bb15894..19ffb48ea 100644 --- a/apps/backend/polly-test/polly-test-component/src/test/java/no/nav/data/polly/codelist/CodelistControllerTest.java +++ b/apps/backend/polly-test/polly-test-component/src/test/java/no/nav/data/polly/codelist/CodelistControllerTest.java @@ -8,6 +8,7 @@ import no.nav.data.polly.codelist.domain.ListName; import no.nav.data.polly.codelist.dto.AllCodelistResponse; import no.nav.data.polly.codelist.dto.CodelistRequest; +import no.nav.data.polly.codelist.dto.CodelistRequestValidator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -58,6 +59,8 @@ class CodelistControllerTest { @MockBean private CodelistService service; @MockBean + private CodelistRequestValidator requestValidator; + @MockBean private CommonCodeService commonCodeService; @BeforeEach @@ -106,7 +109,7 @@ void getByListNameAndCode_shouldReturnForARBEIDSGIVER() throws Exception { void getByListName_shouldReturnNotFound_whenUnknownListName() throws Exception { String uri = "/codelist/UNKNOWN_LISTNAME"; doThrow(new CodelistNotFoundException("Codelist with listName=UNKNOWN_LISTNAME does not exist")) - .when(service).validateListName("UNKNOWN_LISTNAME"); + .when(requestValidator).validateListName("UNKNOWN_LISTNAME"); Exception exception = mvc.perform(get(uri)) .andExpect(status().isNotFound()) @@ -120,7 +123,7 @@ void getByListName_shouldReturnNotFound_whenUnknownListName() throws Exception { void getByListNameAndCode_shouldReturnNotFound_whenUnknownCode() throws Exception { String uri = "/codelist/THIRD_PARTY/UNKNOWN_CODE"; doThrow(new CodelistNotFoundException("The code=UNKNOWN_CODE does not exist in the list=THIRD_PARTY.")) - .when(service).validateListNameAndCode("THIRD_PARTY", "UNKNOWN_CODE"); + .when(requestValidator).validateListNameAndCode("THIRD_PARTY", "UNKNOWN_CODE"); Exception exception = mvc.perform(get(uri)) .andExpect(status().isNotFound()) diff --git a/apps/backend/polly-test/polly-test-component/src/test/java/no/nav/data/polly/codelist/CodelistRequestValidatorTest.java b/apps/backend/polly-test/polly-test-component/src/test/java/no/nav/data/polly/codelist/CodelistRequestValidatorTest.java new file mode 100644 index 000000000..e28591a8b --- /dev/null +++ b/apps/backend/polly-test/polly-test-component/src/test/java/no/nav/data/polly/codelist/CodelistRequestValidatorTest.java @@ -0,0 +1,211 @@ +package no.nav.data.polly.codelist; + +import no.nav.data.common.exceptions.CodelistNotFoundException; +import no.nav.data.common.exceptions.ValidationException; +import no.nav.data.polly.codelist.codeusage.CodeUsageService; +import no.nav.data.polly.codelist.domain.Codelist; +import no.nav.data.polly.codelist.domain.ListName; +import no.nav.data.polly.codelist.dto.CodelistRequest; +import no.nav.data.polly.codelist.dto.CodelistRequestValidator; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.AdditionalAnswers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import static no.nav.data.polly.codelist.CodelistUtils.createCodelist; +import static no.nav.data.polly.codelist.CodelistUtils.createCodelistRequest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings("static-access") // TODO: Fjern når dette ikke trengs +class CodelistRequestValidatorTest { + + private static final String VALIDATION_EXCEPTION_ERROR_MESSAGE = "The request was not accepted. The following errors occurred during validation: [%s]"; + @Mock + private CodelistRepository repository; + @Mock + private CodeUsageService codeUsageService; + + @InjectMocks + private CodelistService service; + + @InjectMocks + CodelistRequestValidator requestValidator; + + @Nested + class validateListName { + + @Test + void shouldValidate_whenListNameExist() { + saveCodelist(createCodelist(ListName.PURPOSE, "VALID_CODE")); + requestValidator.validateListName("PURPOSE"); + } + + @ParameterizedTest + @NullAndEmptySource + void shouldThrowCodelistNotFoundException_whenInputIsNullOrMissing(String input) { + try { + requestValidator.validateListName(input); + fail(); + } catch (CodelistNotFoundException e) { + assertThat(e.getLocalizedMessage()).isEqualTo("Validate Codelist -- fieldIsNullOrMissing -- list was null or missing"); + } + } + + @Test + void shouldThrowCodelistNotFoundException_withInvalidListName() { + try { + requestValidator.validateListName("INVALID_LISTNAME"); + fail(); + } catch (CodelistNotFoundException e) { + assertThat(e.getLocalizedMessage()).isEqualTo("Validate Codelist -- fieldIsInvalidEnum -- list: INVALID_LISTNAME was invalid for type ListName"); + } + } + } + + @Nested + class validateListNameAndCode { + + @Test + void shouldValidateWhenListNameAndCodeExist() { + saveCodelist(createCodelist(ListName.PURPOSE, "VALID_CODE")); + requestValidator.validateListNameAndCode("PURPOSE", "VALID_CODE"); + } + + @ParameterizedTest + @NullAndEmptySource + void shouldThrowCodelistNotFoundException_whenInputForCodeIsNullOrMissing(String input) { + try { + requestValidator.validateListNameAndCode("PURPOSE", input); + fail(); + } catch (CodelistNotFoundException e) { + assertThat(e.getLocalizedMessage()).isEqualTo("Validate Codelist -- fieldIsNullOrMissing -- code was null or missing"); + } + } + + @Test + void shouldThrowCodelistNotFoundException_whenCodeNotValidForListName() { + try { + requestValidator.validateListNameAndCode("THIRD_PARTY", "UNKNOWN_CODE"); + fail(); + } catch (CodelistNotFoundException e) { + assertThat(e.getLocalizedMessage()).isEqualTo("Validate Codelist -- fieldIsInvalidCodelist -- code: UNKNOWN_CODE code not found in codelist THIRD_PARTY"); + } + } + } + + @Nested + class validateListNameNotImmutable { + + @Test + void shouldValidate_whenCodelistIsNotOfImmutableType() { + requestValidator.validateNonImmutableTypeOfCodelist(ListName.PURPOSE); + } + + @ParameterizedTest + @ValueSource(strings = {"GDPR_ARTICLE", "SENSITIVITY"}) + void shouldThrowCodelistNotFoundException_whenCodelistIsOfImmutableType(String input) { + try { + requestValidator.validateNonImmutableTypeOfCodelist(ListName.valueOf(input)); + fail(); + } catch (CodelistNotFoundException e) { + assertThat(e.getLocalizedMessage()).isEqualTo( + String.format("Validate Codelist -- codelistIsOfImmutableType -- %s is an immutable type of codelist. For amendments, please contact team #dataplatform", input)); + } + } + } + + @Nested + class validateRequest { + + @Test + void shouldValidateWithoutAnyProcessing_whenRequestIsEmpty() { + requestValidator.validateRequest(Collections.emptyList(), false); + } + + @Test + void shouldValidate_whenCreatingNonExistingItem() { + List requests = List.of( + createCodelistRequest("THIRD_PARTY"), + createCodelistRequest("CATEGORY")); + + when(repository.findByListAndCode(any(ListName.class), anyString())).thenReturn(Optional.empty()); + + requestValidator.validateRequest(requests, false); //false => create new codelist + } + + @Test + void shouldThrowValidationException_whenCreatingExistingItem() { + List requests = List.of(createCodelistRequest("THIRD_PARTY", "BRUKER")); + Codelist expectedCodelist = createCodelist(ListName.THIRD_PARTY, "BRUKER"); + + when(repository.findByListAndCode(ListName.THIRD_PARTY, "BRUKER")).thenReturn(Optional.of(expectedCodelist)); + + try { + requestValidator.validateRequest(requests, false); + fail(); + } catch (ValidationException e) { + assertThat(e.get().size()).isEqualTo(1); + assertThat(e.getLocalizedMessage()).isEqualTo(String.format(VALIDATION_EXCEPTION_ERROR_MESSAGE, + "Request:1 -- creatingExistingCodelist -- The Codelist THIRD_PARTY-BRUKER already exists and therefore cannot be created")); + } + } + + @Test + void shouldValidate_whenUpdatingExistingItem() { + saveCodelist(createCodelist(ListName.THIRD_PARTY, "TEST", "name", "description")); + when(repository.findByListAndCode(ListName.THIRD_PARTY, "TEST")).thenReturn(Optional.of(CodelistCache.getCodelist(ListName.THIRD_PARTY, "TEST"))); + + List requests = List.of(createCodelistRequest("THIRD_PARTY", "TEST", "name", "Informasjon oppgitt av tester")); + + requestValidator.validateRequest(requests, true); + } + + @Test + void shouldThrowValidationException_whenUpdatingNonExistingItem() { + List requests = List.of(createCodelistRequest("THIRD_PARTY", "unknownCode")); + + try { + requestValidator.validateRequest(requests, true); + fail(); + } catch (ValidationException e) { + assertThat(e.get().size()).isEqualTo(1); + assertThat(e.getLocalizedMessage()) + .isEqualTo(String.format(VALIDATION_EXCEPTION_ERROR_MESSAGE, + "Request:1 -- updatingNonExistingCodelist -- The Codelist THIRD_PARTY-UNKNOWNCODE does not exist and therefore cannot be updated")); + } + } + + @Test + void validateThatAllFieldsHaveValidValues_shouldChangeInputInRequestToCorrectFormat() { + List requests = List.of( + createCodelistRequest(" category ", " cOrRecTFormAT ", " name ", " Trim av description ")); + when(repository.saveAll(anyList())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + requestValidator.validateRequest(requests, false); + service.save(requests); + assertTrue(CodelistCache.contains(ListName.CATEGORY, "CORRECTFORMAT")); + assertThat(service.getCodelist(ListName.CATEGORY, "CORRECTFORMAT").getDescription()).isEqualTo("Trim av description"); + } + + } + + private void saveCodelist(Codelist codelist) { + CodelistCache.set(codelist); + } +} diff --git a/apps/backend/polly-test/polly-test-component/src/test/java/no/nav/data/polly/codelist/CodelistServiceTest.java b/apps/backend/polly-test/polly-test-component/src/test/java/no/nav/data/polly/codelist/CodelistServiceTest.java index 81335e0be..795f03bfb 100644 --- a/apps/backend/polly-test/polly-test-component/src/test/java/no/nav/data/polly/codelist/CodelistServiceTest.java +++ b/apps/backend/polly-test/polly-test-component/src/test/java/no/nav/data/polly/codelist/CodelistServiceTest.java @@ -2,25 +2,25 @@ import no.nav.data.common.exceptions.CodelistNotErasableException; import no.nav.data.common.exceptions.CodelistNotFoundException; -import no.nav.data.common.exceptions.ValidationException; import no.nav.data.polly.codelist.codeusage.CodeUsageService; import no.nav.data.polly.codelist.domain.Codelist; import no.nav.data.polly.codelist.domain.ListName; import no.nav.data.polly.codelist.dto.CodeUsageResponse; import no.nav.data.polly.codelist.dto.CodelistRequest; +import no.nav.data.polly.codelist.dto.CodelistRequestValidator; import no.nav.data.polly.process.dto.ProcessShortResponse; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.junit.jupiter.params.provider.ValueSource; import org.mockito.AdditionalAnswers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -29,258 +29,116 @@ import static no.nav.data.polly.codelist.CodelistUtils.createCodelistRequest; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) +@SuppressWarnings("static-access") // TODO: Fjern når dette ikke trengs class CodelistServiceTest { - private static final String VALIDATION_EXCEPTION_ERROR_MESSAGE = "The request was not accepted. The following errors occurred during validation: [%s]"; @Mock private CodelistRepository repository; + @Mock private CodeUsageService codeUsageService; @InjectMocks - private CodelistService service; - - @Nested - class CrudMethods { - - @Test - void save_shouldSaveCodelist_whenRequestIsValid() { - when(repository.saveAll(anyList())).thenAnswer(AdditionalAnswers.returnsFirstArg()); - service.save(List.of(createCodelistRequest("THIRD_PARTY", "TEST_CODE", "Test shortName", "Test description"))); - verify(repository, times(1)).saveAll(anyList()); - assertThat(CodelistService.getCodelist(ListName.THIRD_PARTY, "TEST_CODE").getShortName()).isEqualTo("Test shortName"); - assertThat(CodelistService.getCodelist(ListName.THIRD_PARTY, "TEST_CODE").getDescription()).isEqualTo("Test description"); - } - - @Test - void update_shouldUpdateCodelist_whenRequestIsValid() { - saveCodelist(createCodelist(ListName.THIRD_PARTY, "CODE", "name1", "desc1")); - - CodelistRequest request = createCodelistRequest(); - request.setShortName("name2"); - request.setDescription("desc2"); - - when(repository.findByListAndCode(ListName.THIRD_PARTY, "CODE")).thenReturn(Optional.of(request.convert())); - when(repository.saveAll(List.of(request.convert()))).thenReturn(List.of(request.convert())); - - service.update(List.of(request)); - - verify(repository, times(1)).saveAll(anyList()); - Codelist codelist = CodelistService.getCodelist(ListName.THIRD_PARTY, request.getCode()); - assertThat(codelist.getShortName()).isEqualTo("name2"); - assertThat(codelist.getDescription()).isEqualTo("desc2"); - } + private CodelistRequestValidator crv; - @Test - void delete_shouldDelete_whenListAndCodeExists() { - when(repository.findByListAndCode(ListName.THIRD_PARTY, "DELETE_CODE")).thenReturn(Optional.of(createCodelist(ListName.THIRD_PARTY, "DELETE_CODE"))); - when(codeUsageService.findCodeUsage(ListName.THIRD_PARTY, "DELETE_CODE")).thenReturn(new CodeUsageResponse(ListName.THIRD_PARTY, "DELETE_CODE")); + private CodelistService service; + + @BeforeEach + void init() { + service = new CodelistService(repository, crv); + } - service.delete(ListName.THIRD_PARTY, "DELETE_CODE"); + @Test + void save_shouldSaveCodelist_whenRequestIsValid() { + when(repository.saveAll(anyList())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + service.save(List.of(createCodelistRequest("THIRD_PARTY", "TEST_CODE", "Test shortName", "Test description"))); + verify(repository, times(1)).saveAll(anyList()); + assertThat(service.getCodelist(ListName.THIRD_PARTY, "TEST_CODE").getShortName()).isEqualTo("Test shortName"); + assertThat(service.getCodelist(ListName.THIRD_PARTY, "TEST_CODE").getDescription()).isEqualTo("Test description"); + } - verify(repository, times(1)).delete(any(Codelist.class)); - assertNull(CodelistService.getCodelist(ListName.THIRD_PARTY, "DELETE_CODE")); - } + @Test + void update_shouldUpdateCodelist_whenRequestIsValid() { + saveCodelist(createCodelist(ListName.THIRD_PARTY, "CODE", "name1", "desc1")); - @Test - void delete_shouldThrowCodelistNotFoundException_whenCodeDoesNotExist() { - when(repository.findByListAndCode(ListName.THIRD_PARTY, "UNKNOWN_CODE")).thenReturn(Optional.empty()); + CodelistRequest request = createCodelistRequest(); + request.setShortName("name2"); + request.setDescription("desc2"); - try { - service.delete(ListName.THIRD_PARTY, "UNKNOWN_CODE"); - fail(); - } catch (CodelistNotFoundException e) { - assertThat(e.getLocalizedMessage()).isEqualTo("Cannot find a codelist to delete with code=UNKNOWN_CODE and listName=THIRD_PARTY"); - } - } + when(repository.findByListAndCode(ListName.THIRD_PARTY, "CODE")).thenReturn(Optional.of(request.convert())); + when(repository.saveAll(List.of(request.convert()))).thenReturn(List.of(request.convert())); - @Test - void delete_shouldThrowCodelistNotErasableException_whenCodelistIsInUse() { - CodeUsageResponse codeUsage = new CodeUsageResponse(ListName.THIRD_PARTY, "DELETE_CODE"); - codeUsage.setProcesses(List.of(ProcessShortResponse.builder().id(UUID.randomUUID()).name("name").build())); - when(repository.findByListAndCode(ListName.PURPOSE, "DELETE_CODE")).thenReturn(Optional.of(createCodelist(ListName.THIRD_PARTY, "DELETE_CODE"))); - when(codeUsageService.findCodeUsage(ListName.PURPOSE, "DELETE_CODE")).thenReturn(codeUsage); + service.update(List.of(request)); - try { - service.delete(ListName.PURPOSE, "DELETE_CODE"); - fail(); - } catch (CodelistNotErasableException e) { - assertThat(e.getLocalizedMessage()).contains("The code DELETE_CODE in list PURPOSE cannot be erased"); - } - } + verify(repository, times(1)).saveAll(anyList()); + Codelist codelist = service.getCodelist(ListName.THIRD_PARTY, request.getCode()); + assertThat(codelist.getShortName()).isEqualTo("name2"); + assertThat(codelist.getDescription()).isEqualTo("desc2"); } - @Nested - class validationMethods { - - @Nested - class validateListName { - - @Test - void shouldValidate_whenListNameExist() { - saveCodelist(createCodelist(ListName.PURPOSE, "VALID_CODE")); - service.validateListName("PURPOSE"); - } - - @ParameterizedTest - @NullAndEmptySource - void shouldThrowCodelistNotFoundException_whenInputIsNullOrMissing(String input) { - try { - service.validateListName(input); - fail(); - } catch (CodelistNotFoundException e) { - assertThat(e.getLocalizedMessage()).isEqualTo("Validate Codelist -- fieldIsNullOrMissing -- list was null or missing"); - } - } - - @Test - void shouldThrowCodelistNotFoundException_withInvalidListName() { - try { - service.validateListName("INVALID_LISTNAME"); - fail(); - } catch (CodelistNotFoundException e) { - assertThat(e.getLocalizedMessage()).isEqualTo("Validate Codelist -- fieldIsInvalidEnum -- list: INVALID_LISTNAME was invalid for type ListName"); - } - } - } + @Test + void delete_shouldDelete_whenListAndCodeExists() { + when(repository.findByListAndCode(ListName.THIRD_PARTY, "DELETE_CODE")).thenReturn(Optional.of(createCodelist(ListName.THIRD_PARTY, "DELETE_CODE"))); + when(codeUsageService.findCodeUsage(ListName.THIRD_PARTY, "DELETE_CODE")).thenReturn(new CodeUsageResponse(ListName.THIRD_PARTY, "DELETE_CODE")); - @Nested - class validateListNameAndCode { + service.delete(ListName.THIRD_PARTY, "DELETE_CODE"); - @Test - void shouldValidateWhenListNameAndCodeExist() { - saveCodelist(createCodelist(ListName.PURPOSE, "VALID_CODE")); - service.validateListNameAndCode("PURPOSE", "VALID_CODE"); - } + verify(repository, times(1)).delete(any(Codelist.class)); + assertNull(service.getCodelist(ListName.THIRD_PARTY, "DELETE_CODE")); + } - @ParameterizedTest - @NullAndEmptySource - void shouldThrowCodelistNotFoundException_whenInputForCodeIsNullOrMissing(String input) { - try { - service.validateListNameAndCode("PURPOSE", input); - fail(); - } catch (CodelistNotFoundException e) { - assertThat(e.getLocalizedMessage()).isEqualTo("Validate Codelist -- fieldIsNullOrMissing -- code was null or missing"); - } - } + @Test + void delete_shouldThrowCodelistNotFoundException_whenCodeDoesNotExist() { + when(repository.findByListAndCode(ListName.THIRD_PARTY, "UNKNOWN_CODE")).thenReturn(Optional.empty()); - @Test - void shouldThrowCodelistNotFoundException_whenCodeNotValidForListName() { - try { - service.validateListNameAndCode("THIRD_PARTY", "UNKNOWN_CODE"); - fail(); - } catch (CodelistNotFoundException e) { - assertThat(e.getLocalizedMessage()).isEqualTo("Validate Codelist -- fieldIsInvalidCodelist -- code: UNKNOWN_CODE code not found in codelist THIRD_PARTY"); - } - } + try { + service.delete(ListName.THIRD_PARTY, "UNKNOWN_CODE"); + fail(); + } catch (CodelistNotFoundException e) { + assertThat(e.getLocalizedMessage()).isEqualTo("Cannot find a codelist to delete with code=UNKNOWN_CODE and listName=THIRD_PARTY"); } + } - @Nested - class validateListNameNotImmutable { - - @Test - void shouldValidate_whenCodelistIsNotOfImmutableType() { - service.validateNonImmutableTypeOfCodelist(ListName.PURPOSE); - } - - @ParameterizedTest - @ValueSource(strings = {"GDPR_ARTICLE", "SENSITIVITY"}) - void shouldThrowCodelistNotFoundException_whenCodelistIsOfImmutableType(String input) { - try { - service.validateNonImmutableTypeOfCodelist(ListName.valueOf(input)); - fail(); - } catch (CodelistNotFoundException e) { - assertThat(e.getLocalizedMessage()).isEqualTo( - String.format("Validate Codelist -- codelistIsOfImmutableType -- %s is an immutable type of codelist. For amendments, please contact team #dataplatform", input)); - } - } + @Test + void delete_shouldThrowCodelistNotErasableException_whenCodelistIsInUse() { + CodeUsageResponse codeUsage = new CodeUsageResponse(ListName.THIRD_PARTY, "DELETE_CODE"); + codeUsage.setProcesses(List.of(ProcessShortResponse.builder().id(UUID.randomUUID()).name("name").build())); + when(repository.findByListAndCode(ListName.PURPOSE, "DELETE_CODE")).thenReturn(Optional.of(createCodelist(ListName.THIRD_PARTY, "DELETE_CODE"))); + //when(crv.validateCodelistIsNotInUse(ListName.PURPOSE, "DELETE_CODE")); + when(codeUsageService.findCodeUsage(ListName.PURPOSE, "DELETE_CODE")).thenReturn(codeUsage); + + try { + service.delete(ListName.PURPOSE, "DELETE_CODE"); + fail(); + } catch (CodelistNotErasableException e) { + assertThat(e.getLocalizedMessage()).contains("The code DELETE_CODE in list PURPOSE cannot be erased"); } - - @Nested - class validateRequest { - - @Test - void shouldValidateWithoutAnyProcessing_whenRequestIsEmpty() { - service.validateRequest(Collections.emptyList(), false); - } - - @Test - void shouldValidate_whenCreatingNonExistingItem() { - List requests = List.of( - createCodelistRequest("THIRD_PARTY"), - createCodelistRequest("CATEGORY")); - - when(repository.findByListAndCode(any(ListName.class), anyString())).thenReturn(Optional.empty()); - - service.validateRequest(requests, false); //false => create new codelist - } - - @Test - void shouldThrowValidationException_whenCreatingExistingItem() { - List requests = List.of(createCodelistRequest("THIRD_PARTY", "BRUKER")); - Codelist expectedCodelist = createCodelist(ListName.THIRD_PARTY, "BRUKER"); - - when(repository.findByListAndCode(ListName.THIRD_PARTY, "BRUKER")).thenReturn(Optional.of(expectedCodelist)); - - try { - service.validateRequest(requests, false); - fail(); - } catch (ValidationException e) { - assertThat(e.get().size()).isEqualTo(1); - assertThat(e.getLocalizedMessage()).isEqualTo(String.format(VALIDATION_EXCEPTION_ERROR_MESSAGE, - "Request:1 -- creatingExistingCodelist -- The Codelist THIRD_PARTY-BRUKER already exists and therefore cannot be created")); - } - } - - @Test - void shouldValidate_whenUpdatingExistingItem() { - saveCodelist(createCodelist(ListName.THIRD_PARTY, "TEST", "name", "description")); - when(repository.findByListAndCode(ListName.THIRD_PARTY, "TEST")).thenReturn(Optional.of(CodelistCache.getCodelist(ListName.THIRD_PARTY, "TEST"))); - - List requests = List.of(createCodelistRequest("THIRD_PARTY", "TEST", "name", "Informasjon oppgitt av tester")); - - service.validateRequest(requests, true); - } - - @Test - void shouldThrowValidationException_whenUpdatingNonExistingItem() { - List requests = List.of(createCodelistRequest("THIRD_PARTY", "unknownCode")); - - try { - service.validateRequest(requests, true); - fail(); - } catch (ValidationException e) { - assertThat(e.get().size()).isEqualTo(1); - assertThat(e.getLocalizedMessage()) - .isEqualTo(String.format(VALIDATION_EXCEPTION_ERROR_MESSAGE, - "Request:1 -- updatingNonExistingCodelist -- The Codelist THIRD_PARTY-UNKNOWNCODE does not exist and therefore cannot be updated")); - } - } - - @Test - void validateThatAllFieldsHaveValidValues_shouldChangeInputInRequestToCorrectFormat() { - List requests = List.of( - createCodelistRequest(" category ", " cOrRecTFormAT ", " name ", " Trim av description ")); - when(repository.saveAll(anyList())).thenAnswer(AdditionalAnswers.returnsFirstArg()); - service.validateRequest(requests, false); - service.save(requests); - assertTrue(CodelistCache.contains(ListName.CATEGORY, "CORRECTFORMAT")); - assertThat(CodelistService.getCodelist(ListName.CATEGORY, "CORRECTFORMAT").getDescription()).isEqualTo("Trim av description"); - } + } + + @Test + void delete_shouldThrowCodelistNotErasableException_whenCodelistIsInUse2() { + CodeUsageResponse codeUsage = new CodeUsageResponse(ListName.THIRD_PARTY, "DELETE_CODE"); + codeUsage.setProcesses(List.of(ProcessShortResponse.builder().id(UUID.randomUUID()).name("name").build())); + when(repository.findByListAndCode(ListName.PURPOSE, "DELETE_CODE")).thenReturn(Optional.of(createCodelist(ListName.THIRD_PARTY, "DELETE_CODE"))); + when(codeUsageService.findCodeUsage(ListName.PURPOSE, "DELETE_CODE")).thenReturn(codeUsage); + + try { + service.delete(ListName.PURPOSE, "DELETE_CODE"); + fail(); + } catch (CodelistNotErasableException e) { + assertThat(e.getLocalizedMessage()).contains("The code DELETE_CODE in list PURPOSE cannot be erased"); } - } + private void saveCodelist(Codelist codelist) { CodelistCache.set(codelist); } diff --git a/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/IntegrationTestBase.java b/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/IntegrationTestBase.java index 360820f6f..010d7a7e1 100644 --- a/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/IntegrationTestBase.java +++ b/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/IntegrationTestBase.java @@ -3,6 +3,7 @@ import com.github.tomakehurst.wiremock.client.WireMock; import io.prometheus.client.CollectorRegistry; import no.nav.data.AppStarter; +import no.nav.data.common.auditing.AuditVersionListener; import no.nav.data.common.auditing.domain.AuditVersionRepository; import no.nav.data.common.storage.domain.GenericStorageRepository; import no.nav.data.common.utils.JsonUtils; @@ -58,8 +59,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.transaction.support.TransactionTemplate; @@ -77,7 +81,6 @@ import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.okJson; import static no.nav.data.polly.codelist.CodelistService.getCodelistResponse; -import static no.nav.data.polly.process.domain.sub.DataProcessing.convertDataProcessing; import static no.nav.data.polly.process.domain.sub.Dpia.convertDpia; import static no.nav.data.polly.process.domain.sub.Retention.convertRetention; @@ -284,7 +287,7 @@ protected Process createAndSaveProcess(String purpose) { .usesAllInformationTypes(true) .automaticProcessing(true) .profiling(true) - .dataProcessing(convertDataProcessing(dataProcessingRequest())) + .dataProcessing(DataProcessingRequest.convertToDataProcessingNullSafe(dataProcessingRequest())) .retention(convertRetention(retentionRequest())) .dpia(convertDpia(dpiaRequest())) .status(ProcessStatus.IN_PROGRESS) @@ -458,6 +461,7 @@ public static class Initializer implements ApplicationContextInitializer> CODELIST_LIST_RESP = new ParameterizedTypeReference<>() { @@ -68,7 +69,7 @@ void findAll_shouldReturnAllCodelists() { Arrays.stream(ListName.values()) .forEach(listName -> assertThat(responseEntity.getBody().getCodelist() - .get(listName)).containsAll(CodelistService.getCodelistResponseList(listName))); + .get(listName)).containsAll(service.getCodelistResponseList(listName))); } @Test @@ -78,7 +79,7 @@ void getCodelistByListName() { assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(responseEntity.getBody()).isNotNull(); - assertThat(responseEntity.getBody()).isEqualTo(CodelistService.getCodelistResponseList(ListName.THIRD_PARTY)); + assertThat(responseEntity.getBody()).isEqualTo(service.getCodelistResponseList(ListName.THIRD_PARTY)); } @Test @@ -90,7 +91,7 @@ void getByListNameAndCode() { assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(responseEntity.getBody()).isNotNull(); - assertThat(responseEntity.getBody().getDescription()).isEqualTo(CodelistService.getCodelist(ListName.THIRD_PARTY, "TEST_CODE").getDescription()); + assertThat(responseEntity.getBody().getDescription()).isEqualTo(service.getCodelist(ListName.THIRD_PARTY, "TEST_CODE").getDescription()); } @Test @@ -146,7 +147,7 @@ void shouldSaveNewCodelists() { assertThat(codelist.getDescription()).isEqualTo("SaveDescription"); assertTrue(CodelistCache.contains(ListName.THIRD_PARTY, "SAVECODE")); - Codelist savedCodelist = CodelistService.getCodelist(ListName.THIRD_PARTY, "SAVECODE"); + Codelist savedCodelist = service.getCodelist(ListName.THIRD_PARTY, "SAVECODE"); assertThat(savedCodelist.getCode()).isEqualTo("SAVECODE"); assertThat(savedCodelist.getShortName()).isEqualTo("SaveShortName"); assertThat(savedCodelist.getDescription()).isEqualTo("SaveDescription"); @@ -202,8 +203,8 @@ class Update { @Test void shouldUpdateOneCodelist() { saveCodelist(createCodelist(ListName.THIRD_PARTY, "CODE", "SavedShortName", "SavedDescription")); - assertThat(CodelistService.getCodelist(ListName.THIRD_PARTY, "CODE").getShortName()).isEqualTo("SavedShortName"); - assertThat(CodelistService.getCodelist(ListName.THIRD_PARTY, "CODE").getDescription()).isEqualTo("SavedDescription"); + assertThat(service.getCodelist(ListName.THIRD_PARTY, "CODE").getShortName()).isEqualTo("SavedShortName"); + assertThat(service.getCodelist(ListName.THIRD_PARTY, "CODE").getDescription()).isEqualTo("SavedDescription"); List updatedCodelists = List.of( createCodelistRequest("THIRD_PARTY", "CODE", "UpdatedShortName", "UpdatedDescription")); @@ -212,8 +213,8 @@ void shouldUpdateOneCodelist() { "/codelist", HttpMethod.PUT, new HttpEntity<>(updatedCodelists), String.class); assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(CodelistService.getCodelist(ListName.THIRD_PARTY, "CODE").getShortName()).isEqualTo("UpdatedShortName"); - assertThat(CodelistService.getCodelist(ListName.THIRD_PARTY, "CODE").getDescription()).isEqualTo("UpdatedDescription"); + assertThat(service.getCodelist(ListName.THIRD_PARTY, "CODE").getShortName()).isEqualTo("UpdatedShortName"); + assertThat(service.getCodelist(ListName.THIRD_PARTY, "CODE").getDescription()).isEqualTo("UpdatedDescription"); } @Test diff --git a/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/disclosure/DisclosureControllerIT.java b/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/disclosure/DisclosureControllerIT.java index d9b9aea8d..7d394728d 100644 --- a/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/disclosure/DisclosureControllerIT.java +++ b/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/disclosure/DisclosureControllerIT.java @@ -121,7 +121,7 @@ void getAllDisclosureSummary() { DisclosureRequest request = buildDisclosure(); request.setProcessIds(List.of(process.getId().toString())); var d1 = restTemplate.postForEntity("/disclosure", request, DisclosureResponse.class); - var d2 = restTemplate.postForEntity("/disclosure", buildDisclosure(), DisclosureResponse.class); + restTemplate.postForEntity("/disclosure", buildDisclosure(), DisclosureResponse.class); var resp = restTemplate.getForEntity("/disclosure/summary", DisclosureSummaryPage.class); assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK); diff --git a/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/policy/rest/PolicyControllerIT.java b/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/policy/rest/PolicyControllerIT.java index 3ad5a12d6..5a708d170 100644 --- a/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/policy/rest/PolicyControllerIT.java +++ b/apps/backend/polly-test/polly-test-integration/src/test/java/no/nav/data/polly/policy/rest/PolicyControllerIT.java @@ -39,7 +39,7 @@ class PolicyControllerIT extends IntegrationTestBase { @Autowired protected TestRestTemplate restTemplate; - + @BeforeEach void setUp() { processRepository.save(Process.builder().id(PROCESS_ID_1) diff --git a/apps/backend/pom.xml b/apps/backend/pom.xml index 845ac45e8..eb0dc597e 100644 --- a/apps/backend/pom.xml +++ b/apps/backend/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.5 + 3.2.6 no.nav.data