diff --git a/src/main/kotlin/no/fdk/fdk_event_harvester/controller/EventController.kt b/src/main/kotlin/no/fdk/fdk_event_harvester/controller/EventController.kt index e7271bf..ad34cd7 100644 --- a/src/main/kotlin/no/fdk/fdk_event_harvester/controller/EventController.kt +++ b/src/main/kotlin/no/fdk/fdk_event_harvester/controller/EventController.kt @@ -52,17 +52,27 @@ open class EventsController( else ResponseEntity(eventService.getAllEvents(returnType ?: Lang.TURTLE, catalogRecords), HttpStatus.OK) } - @DeleteMapping("/{id}") + @PostMapping("/{id}/remove") fun removeInformationModelById( @AuthenticationPrincipal jwt: Jwt, @PathVariable id: String ): ResponseEntity = if (endpointPermissions.hasAdminPermission(jwt)) { eventService.removeEvent(id) + ResponseEntity(HttpStatus.OK) + } else ResponseEntity(HttpStatus.FORBIDDEN) + + @DeleteMapping("/{id}") + fun purgeEventById( + @AuthenticationPrincipal jwt: Jwt, + @PathVariable id: String + ): ResponseEntity = + if (endpointPermissions.hasAdminPermission(jwt)) { + eventService.purgeByFdkId(id) ResponseEntity(HttpStatus.NO_CONTENT) } else ResponseEntity(HttpStatus.FORBIDDEN) - @PostMapping("/duplicates") + @PostMapping("/remove-duplicates") fun removeDuplicates( @AuthenticationPrincipal jwt: Jwt, @RequestBody duplicates: List diff --git a/src/main/kotlin/no/fdk/fdk_event_harvester/service/EventService.kt b/src/main/kotlin/no/fdk/fdk_event_harvester/service/EventService.kt index d37615c..2d8e6b9 100644 --- a/src/main/kotlin/no/fdk/fdk_event_harvester/service/EventService.kt +++ b/src/main/kotlin/no/fdk/fdk_event_harvester/service/EventService.kt @@ -117,4 +117,13 @@ class EventService( } } + // Purges everything associated with a removed fdkID + fun purgeByFdkId(fdkId: String) { + eventMetaRepository.findAllByFdkId(fdkId) + .also { events -> if (events.any { !it.removed }) throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Unable to purge files, event with id $fdkId has not been removed") } + .run { eventMetaRepository.deleteAll(this) } + + turtleService.deleteEventFiles(fdkId) + } + } diff --git a/src/main/kotlin/no/fdk/fdk_event_harvester/service/TurtleService.kt b/src/main/kotlin/no/fdk/fdk_event_harvester/service/TurtleService.kt index d6e569d..fde4c85 100644 --- a/src/main/kotlin/no/fdk/fdk_event_harvester/service/TurtleService.kt +++ b/src/main/kotlin/no/fdk/fdk_event_harvester/service/TurtleService.kt @@ -98,6 +98,11 @@ class TurtleService( ?.turtle ?.let { ungzip(it) } + fun deleteEventFiles(fdkId: String) { + eventRepository.deleteById(fdkId) + fdkEventRepository.deleteById(fdkId) + } + } private fun Model.createEventTurtleDBO(id: String): EventTurtle = diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3a1059f..7fe0e9c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,7 +6,6 @@ logging: level.org.apache.jena: ERROR server: port: 8080 - error.include-message: always spring: security.oauth2.resourceserver.jwt: jwk-set-uri: ${SSO_HOST:https://sso.staging.fellesdatakatalog.digdir.no}/auth/realms/fdk/protocol/openid-connect/certs diff --git a/src/test/kotlin/no/fdk/fdk_event_harvester/contract/EventServicesTest.kt b/src/test/kotlin/no/fdk/fdk_event_harvester/contract/EventServicesTest.kt index 0464aba..f60393c 100644 --- a/src/test/kotlin/no/fdk/fdk_event_harvester/contract/EventServicesTest.kt +++ b/src/test/kotlin/no/fdk/fdk_event_harvester/contract/EventServicesTest.kt @@ -111,9 +111,9 @@ class EventServicesTest : ApiTestContext() { fun unauthorizedForNoToken() { val response = authorizedRequest( port, - "/events/$EVENT_ID_0", + "/events/$EVENT_ID_0/remove", null, - HttpMethod.DELETE + HttpMethod.POST ) assertEquals(HttpStatus.UNAUTHORIZED.value(), response["status"]) } @@ -122,9 +122,9 @@ class EventServicesTest : ApiTestContext() { fun forbiddenWithNonSysAdminRole() { val response = authorizedRequest( port, - "/events/$EVENT_ID_0", + "/events/$EVENT_ID_0/remove", JwtToken(Access.ORG_WRITE).toString(), - HttpMethod.DELETE + HttpMethod.POST ) assertEquals(HttpStatus.FORBIDDEN.value(), response["status"]) } @@ -133,9 +133,9 @@ class EventServicesTest : ApiTestContext() { fun notFoundWhenIdNotInDB() { val response = authorizedRequest( port, - "/events/123", + "/events/123/remove", JwtToken(Access.ROOT).toString(), - HttpMethod.DELETE + HttpMethod.POST ) assertEquals(HttpStatus.NOT_FOUND.value(), response["status"]) } @@ -144,11 +144,11 @@ class EventServicesTest : ApiTestContext() { fun okWithSysAdminRole() { val response = authorizedRequest( port, - "/events/$EVENT_ID_0", + "/events/$EVENT_ID_0/remove", JwtToken(Access.ROOT).toString(), - HttpMethod.DELETE + HttpMethod.POST ) - assertEquals(HttpStatus.NO_CONTENT.value(), response["status"]) + assertEquals(HttpStatus.OK.value(), response["status"]) } } @@ -160,7 +160,7 @@ class EventServicesTest : ApiTestContext() { val body = listOf(DuplicateIRI(iriToRemove = EVENT_META_0.uri, iriToRetain = EVENT_META_1.uri)) val response = authorizedRequest( port, - "/events/duplicates", + "/events/remove-duplicates", null, HttpMethod.POST, mapper.writeValueAsString(body) @@ -173,7 +173,7 @@ class EventServicesTest : ApiTestContext() { val body = listOf(DuplicateIRI(iriToRemove = EVENT_META_0.uri, iriToRetain = EVENT_META_0.uri)) val response = authorizedRequest( port, - "/events/duplicates", + "/events/remove-duplicates", JwtToken(Access.ORG_WRITE).toString(), HttpMethod.POST, mapper.writeValueAsString(body) @@ -187,7 +187,7 @@ class EventServicesTest : ApiTestContext() { val response = authorizedRequest( port, - "/events/duplicates", + "/events/remove-duplicates", JwtToken(Access.ROOT).toString(), HttpMethod.POST, mapper.writeValueAsString(body) @@ -200,7 +200,7 @@ class EventServicesTest : ApiTestContext() { val body = listOf(DuplicateIRI(iriToRemove = EVENT_META_0.uri, iriToRetain = EVENT_META_0.uri)) val response = authorizedRequest( port, - "/events/duplicates", + "/events/remove-duplicates", JwtToken(Access.ROOT).toString(), HttpMethod.POST, mapper.writeValueAsString(body) @@ -209,4 +209,54 @@ class EventServicesTest : ApiTestContext() { } } + @Nested + internal inner class PurgeById { + + @Test + fun unauthorizedForNoToken() { + val response = authorizedRequest(port, "/events/removed", null, HttpMethod.DELETE) + assertEquals(HttpStatus.UNAUTHORIZED.value(), response["status"]) + } + + @Test + fun forbiddenWithNonSysAdminRole() { + val response = authorizedRequest( + port, + "/events/removed", + JwtToken(Access.ORG_WRITE).toString(), + HttpMethod.DELETE + ) + assertEquals(HttpStatus.FORBIDDEN.value(), response["status"]) + } + + @Test + fun badRequestWhenNotAlreadyRemoved() { + val response = authorizedRequest( + port, + "/events/$EVENT_ID_1", + JwtToken(Access.ROOT).toString(), + HttpMethod.DELETE + ) + assertEquals(HttpStatus.BAD_REQUEST.value(), response["status"]) + } + + @Test + fun purgingStopsDeepLinking() { + val pre = apiGet(port, "/events/removed", "text/turtle") + assertEquals(HttpStatus.OK.value(), pre["status"]) + + val response = authorizedRequest( + port, + "/events/removed", + JwtToken(Access.ROOT).toString(), + HttpMethod.DELETE + ) + assertEquals(HttpStatus.NO_CONTENT.value(), response["status"]) + + val post = apiGet(port, "/events/removed", "text/turtle") + assertEquals(HttpStatus.NOT_FOUND.value(), post["status"]) + } + + } + } diff --git a/src/test/kotlin/no/fdk/fdk_event_harvester/utils/DatabaseData.kt b/src/test/kotlin/no/fdk/fdk_event_harvester/utils/DatabaseData.kt index 6d7e205..6c6a729 100644 --- a/src/test/kotlin/no/fdk/fdk_event_harvester/utils/DatabaseData.kt +++ b/src/test/kotlin/no/fdk/fdk_event_harvester/utils/DatabaseData.kt @@ -90,6 +90,25 @@ val EVENT_TURTLE_1_NO_RECORDS = EventTurtle( turtle = gzip(responseReader.readFile("no_records_event_1.ttl")) ) +val REMOVED_EVENT_META = EventMeta( + uri = "http://testdirektoratet.no/events/removed", + fdkId = "removed", + isPartOf = "http://localhost:5050/events/catalogs/$CATALOG_ID_0", + removed = true, + issued = TEST_HARVEST_DATE.timeInMillis, + modified = TEST_HARVEST_DATE.timeInMillis +) + +val REMOVED_EVENT_TURTLE = FDKEventTurtle( + id = "removed", + turtle = gzip(responseReader.readFile("event_1.ttl")) +) + +val REMOVED_EVENT_TURTLE_NO_RECORDS = EventTurtle( + id = "removed", + turtle = gzip(responseReader.readFile("no_records_event_1.ttl")) +) + val EVENT_META_2 = EventMeta( uri = "http://testdirektoratet.no/events/2", fdkId = EVENT_ID_2, @@ -179,16 +198,16 @@ fun harvestSourceTurtlePopulation(): List = listOf(HARVESTED_DBO).map { it.mapDBO() } fun fdkEventTurtlePopulation(): List = - listOf(EVENT_UNION_DATA, EVENT_TURTLE_0, EVENT_TURTLE_1, EVENT_TURTLE_2, EVENT_TURTLE_3, EVENT_TURTLE_4) + listOf(EVENT_UNION_DATA, EVENT_TURTLE_0, EVENT_TURTLE_1, EVENT_TURTLE_2, EVENT_TURTLE_3, EVENT_TURTLE_4, REMOVED_EVENT_TURTLE) .map { it.mapDBO() } fun eventTurtlePopulation(): List = listOf(EVENT_TURTLE_0_NO_RECORDS, EVENT_TURTLE_1_NO_RECORDS, EVENT_UNION_DATA_NO_RECORDS, - EVENT_TURTLE_2_NO_RECORDS, EVENT_TURTLE_3_NO_RECORDS, EVENT_TURTLE_4_NO_RECORDS) + EVENT_TURTLE_2_NO_RECORDS, EVENT_TURTLE_3_NO_RECORDS, EVENT_TURTLE_4_NO_RECORDS, REMOVED_EVENT_TURTLE_NO_RECORDS) .map { it.mapDBO() } fun eventMetaPopulation(): List = - listOf(EVENT_META_0, EVENT_META_1, EVENT_META_2, EVENT_META_3, EVENT_META_4) + listOf(EVENT_META_0, EVENT_META_1, EVENT_META_2, EVENT_META_3, EVENT_META_4, REMOVED_EVENT_META) .map { it.mapDBO() } fun fdkCatalogTurtlePopulation(): List = @@ -216,6 +235,7 @@ private fun EventMeta.mapDBO(): Document = .append("_id", uri) .append("fdkId", fdkId) .append("isPartOf", isPartOf) + .append("removed", removed) .append("issued", issued) .append("modified", modified)