Skip to content

Commit

Permalink
feat: patch public service
Browse files Browse the repository at this point in the history
  • Loading branch information
pooriamehregan committed Nov 2, 2023
1 parent 04e1b0e commit 4efedd9
Show file tree
Hide file tree
Showing 9 changed files with 439 additions and 101 deletions.
11 changes: 11 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<!-- JSON Patch dependencies -->
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.eclipse.parsson</groupId>
<artifactId>jakarta.json</artifactId>
<version>1.1.5</version>
</dependency>

<!-- Test -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package no.digdir.service_catalog.controller

import no.digdir.service_catalog.model.JsonPatchOperation
import no.digdir.service_catalog.model.PublicService
import no.digdir.service_catalog.security.EndpointPermissions
import no.digdir.service_catalog.service.PublicServiceService
Expand All @@ -11,7 +12,9 @@ import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PatchMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping

@Controller
Expand Down Expand Up @@ -39,4 +42,19 @@ class PublicServiceController(private val publicServiceService: PublicServiceSer
} else {
ResponseEntity(HttpStatus.FORBIDDEN)
}

@PatchMapping(value = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
fun patchCodeList(
@AuthenticationPrincipal jwt: Jwt,
@PathVariable catalogId: String,
@PathVariable id: String,
@RequestBody patchOperations: List<JsonPatchOperation>
): ResponseEntity<PublicService> =
if (endpointPermissions.hasOrgWritePermission(jwt, catalogId)) {
publicServiceService.patchPublicService(id, catalogId, patchOperations)
?.let { ResponseEntity(it, HttpStatus.OK) }
?: ResponseEntity(HttpStatus.NOT_FOUND)
} else {
ResponseEntity(HttpStatus.FORBIDDEN)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package no.digdir.service_catalog.model

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonValue

@JsonIgnoreProperties(ignoreUnknown = true)
data class JsonPatchOperation (
val op: OpEnum,
val path: String,
val value: Any? = null,
val from: String? = null
)

enum class OpEnum(val value: String) {
ADD("add"),
REMOVE("remove"),
REPLACE("replace"),
MOVE("move"),
COPY("copy");

@JsonValue
fun jsonValue(): String = value
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package no.digdir.service_catalog.service

import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import jakarta.json.Json
import jakarta.json.JsonException
import no.digdir.service_catalog.model.JsonPatchOperation
import org.springframework.http.HttpStatus
import org.springframework.web.server.ResponseStatusException
import java.io.StringReader

inline fun <reified T> patchOriginal(original: T, operations: List<JsonPatchOperation>): T {
validateOperations(operations)
try {
return applyPatch(original, operations)
} catch (ex: Exception) {
when (ex) {
is JsonException -> throw ResponseStatusException(HttpStatus.BAD_REQUEST, ex.message)
is JsonProcessingException -> throw ResponseStatusException(HttpStatus.BAD_REQUEST, ex.message)
is IllegalArgumentException -> throw ResponseStatusException(HttpStatus.BAD_REQUEST, ex.message)
else -> throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, ex.message)
}
}
}

inline fun <reified T> applyPatch(originalObject: T, operations: List<JsonPatchOperation>): T {
if (operations.isNotEmpty()) {
with(jacksonObjectMapper()) {
val changes = Json.createReader(StringReader(writeValueAsString(operations))).readArray()
val original = Json.createReader(StringReader(writeValueAsString(originalObject))).readObject()

return Json.createPatch(changes).apply(original)
.let { readValue(it.toString()) }
}
}
return originalObject
}

fun validateOperations(operations: List<JsonPatchOperation>) {
val invalidPaths = listOf("/id", "/catalogId")
if (operations.any { it.path in invalidPaths }) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Patch of paths $invalidPaths is not permitted")
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
package no.digdir.service_catalog.service

import no.digdir.service_catalog.model.JsonPatchOperation
import no.digdir.service_catalog.model.PublicService
import no.digdir.service_catalog.mongodb.PublicServiceRepository
import org.slf4j.LoggerFactory
import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service

@Service
class PublicServiceService(private val publicServiceRepository: PublicServiceRepository) {
private val logger = LoggerFactory.getLogger(PublicServiceService::class.java)

fun findPublicServicesByCatalogId(catalogId: String) =
publicServiceRepository.getByCatalogId(catalogId)

fun findPublicServiceById(id: String, catalogId: String) =
publicServiceRepository
.findByIdOrNull(id)
?.takeIf { it.catalogId == catalogId }

fun patchPublicService(id: String, catalogId: String, operations: List<JsonPatchOperation>): PublicService? =
try {
findPublicServiceById(id, catalogId)
?.let { patchOriginal(it, operations)}
?.let { publicServiceRepository.save(it) }
} catch (ex: Exception) {
logger.error("Failed to update code-list with id $id in catalog $catalogId", ex)
throw ex
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import no.digdir.service_catalog.model.Service
import no.digdir.service_catalog.utils.ApiTestContext
import no.digdir.service_catalog.utils.SERVICES
import no.digdir.service_catalog.utils.apiAuthorizedRequest
import no.digdir.service_catalog.utils.apiGet
import no.digdir.service_catalog.utils.jwt.Access
import no.digdir.service_catalog.utils.jwt.JwtToken
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.HttpMethod
import org.springframework.http.HttpStatus
import org.springframework.test.context.ContextConfiguration

Expand All @@ -28,7 +28,7 @@ class GetServices: ApiTestContext() {

@Test
fun `able to get all services`() {
val response = apiAuthorizedRequest("/catalogs/910244132/services", port, null, JwtToken(Access.ORG_READ).toString(), "GET")
val response = apiAuthorizedRequest("/catalogs/910244132/services", port, null, JwtToken(Access.ORG_READ).toString(), HttpMethod.GET)
Assertions.assertEquals(HttpStatus.OK.value(), response["status"])

val result: List<Service> = mapper.readValue(response["body"] as String)
Expand All @@ -37,13 +37,13 @@ class GetServices: ApiTestContext() {

@Test
fun `unauthorized when missing token`() {
val response = apiAuthorizedRequest("/catalogs/910244132/services", port, null, null, "GET")
val response = apiAuthorizedRequest("/catalogs/910244132/services", port, null, null, HttpMethod.GET)
Assertions.assertEquals(HttpStatus.UNAUTHORIZED.value(), response["status"])
}

@Test
fun `forbidden when authorized for other catalog`() {
val response = apiAuthorizedRequest("/catalogs/910244132/services", port, null, JwtToken(Access.WRONG_ORG_READ).toString(), "GET")
val response = apiAuthorizedRequest("/catalogs/910244132/services", port, null, JwtToken(Access.WRONG_ORG_READ).toString(), HttpMethod.GET)
Assertions.assertEquals(HttpStatus.FORBIDDEN.value(), response["status"])
}
}
Loading

0 comments on commit 4efedd9

Please sign in to comment.