diff --git a/docs/03-endpoints/api-admin/projects.md b/docs/03-endpoints/api-admin/projects.md index 2bf24196df..3f325794dc 100644 --- a/docs/03-endpoints/api-admin/projects.md +++ b/docs/03-endpoints/api-admin/projects.md @@ -6,7 +6,7 @@ # Projects Endpoint | Scope | Route | Operations | Explanation | -| --------------- | -------------------------------------------------------------- | ---------- | ----------------------------------------------------------------------- | +| --------------- | -------------------------------------------------------------- |------------|-------------------------------------------------------------------------| | projects | `/admin/projects` | `GET` | [get all projects](#get-all-projects) | | projects | `/admin/projects` | `POST` | [create a project](#create-a-new-project) | | projects | `/admin/projects/shortname/{shortname}` | `GET` | [get a single project](#get-project-by-id) | @@ -26,7 +26,8 @@ | view settings | `/admin/projects/shortname/{shortname}/RestrictedViewSettings` | `GET` | [get restricted view settings for a project](#restricted-view-settings) | | view settings | `/admin/projects/shortcode/{shortcode}/RestrictedViewSettings` | `GET` | [get restricted view settings for a project](#restricted-view-settings) | | view settings | `/admin/projects/iri/{iri}/RestrictedViewSettings` | `GET` | [get restricted view settings for a project](#restricted-view-settings) | - +| view settings | `/admin/projects/iri/{iri}/RestrictedViewSettings` | `POST` | [set restricted view settings for a project](#restricted-view-settings) | +| view settings | `/admin/projects/shortcode/{shortcode}/RestrictedViewSettings` | `POST` | [set restricted view settings for a project](#restricted-view-settings) | ## Project Operations @@ -775,7 +776,7 @@ Example response: ``` -### Restricted View Settings +### Get Restricted View Settings Permissions: ProjectAdmin @@ -799,7 +800,7 @@ curl --request GET 'http://0.0.0.0:3333/admin/projects/shortname/anything/Restri ``` ```bash -curl --request GET 'http://0.0.0.0:3333/admin/projects/iri/http%3A%2F%2Frdfh.ch%2Fprojects%2F0001/RestrictedViewSettings' +curl --request GET 'http://0.0.0.0:3333/admin/projects/iri/http%3A%2F%2Frdfh.ch%2Fprojects%2F0001/RestrictedViewSettings' \ --header 'Authorization: Basic cm9vdEBleGFtcGxlLmNvbTp0ZXN0' ``` @@ -814,6 +815,49 @@ Example response: } ``` +### Set Restricted View Settings + +Both routes take String parameter which sets restricted view size in one of two formats: as an image dimensions or a +percentage. The dimensions pattern looks like: `!X,X`, where X is the number representing scaled image dimensions in +a square, so that the width and height of the returned image are not greater than the requested value. +Example: `!512,512` means the image's bigger side will be set to 512 pixels, setting the other side respectively to +image aspect ratio. The percentage pattern looks like: `pct:X`, where X is the number between 1-100 representing the +percentage the image will be scaled to. Example: `pct:1` means the image will be scaled to 1% of the original image +size. + +Permissions: ProjectAdmin/SystemAdmin + +Request definition: +- `POST /admin/projects/iri/{iri}/RestrictedViewSettings` +- `POST /admin/projects/shortcode/{shortcode}/RestrictedViewSettings` + +Description: Set the project's restricted view + +Required payload: +- `size` + +Example request: + +```bash +curl --request POST 'http://0.0.0.0:5555/admin/projects/iri/http%3A%2F%2Frdfh.ch%2Fprojects%2F0001/RestrictedViewSettings' \ +--header 'Authorization: Basic cm9vdEBleGFtcGxlLmNvbTp0ZXN0' \ +--data '{"size": "!512,512"} +``` + +```bash +curl --request POST 'http://0.0.0.0:5555/admin/projects/shortcode/0001/RestrictedViewSettings' \ +--header 'Authorization: Basic cm9vdEBleGFtcGxlLmNvbTp0ZXN0' \ +--data '{"size": "!512,512"} +``` + +Example response: + +```json +{ + "size": "!512,512" +} +``` + Operates on the following properties: - `knora-admin:projectRestrictedViewSize`: the IIIF size value - `knora-admin:projectRestrictedViewWatermark`: the path to the watermark image. **Currently not used!** diff --git a/integration/src/test/scala/org/knora/webapi/e2e/admin/ProjectsADME2EZioHttpSpec.scala b/integration/src/test/scala/org/knora/webapi/e2e/admin/ProjectsADME2EZioHttpSpec.scala index ad0487bf4f..632557ac84 100644 --- a/integration/src/test/scala/org/knora/webapi/e2e/admin/ProjectsADME2EZioHttpSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/e2e/admin/ProjectsADME2EZioHttpSpec.scala @@ -22,7 +22,6 @@ import org.knora.webapi.messages.admin.responder.projectsmessages._ import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.admin.responder.usersmessages.UsersADMJsonProtocol._ import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject -import org.knora.webapi.messages.store.triplestoremessages.TriplestoreJsonProtocol import org.knora.webapi.messages.util.rdf.RdfModel import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.util.AkkaHttpUtils @@ -39,7 +38,7 @@ import pekko.util.Timeout /** * End-to-End (E2E) test specification for testing groups endpoint. */ -class ProjectsADME2EZioHttpSpec extends E2ESpec with ProjectsADMJsonProtocol with TriplestoreJsonProtocol { +class ProjectsADME2EZioHttpSpec extends E2ESpec with ProjectsADMJsonProtocol { private val rootEmail = SharedTestDataADM.rootUser.email private val testPass = SharedTestDataADM.testPass @@ -776,5 +775,103 @@ class ProjectsADME2EZioHttpSpec extends E2ESpec with ProjectsADMJsonProtocol wit ) } } + + if (baseApiUrl.contains("5555")) "used to set RestrictedViewSize by project IRI" should { + "return requested value to be set with 200 Response Status" in { + val encodedIri = URLEncoder.encode(SharedTestDataADM.imagesProject.id, "utf-8") + val payload = """{"size":"pct:1"}""" + val request = + Post( + baseApiUrl + s"/admin/projects/iri/$encodedIri/RestrictedViewSettings", + HttpEntity(ContentTypes.`application/json`, payload) + ) ~> addCredentials( + BasicHttpCredentials(rootEmail, testPass) + ) + val response: HttpResponse = singleAwaitingRequest(request) + val result: String = responseToString(response) + assert(response.status === StatusCodes.OK) + assert(payload === result) + } + + "return the `BadRequest` if the size value is invalid" in { + val encodedIri = URLEncoder.encode(SharedTestDataADM.imagesProject.id, "utf-8") + val payload = """{"size":"pct:0"}""" + val request = + Post( + baseApiUrl + s"/admin/projects/iri/$encodedIri/RestrictedViewSettings", + HttpEntity(ContentTypes.`application/json`, payload) + ) ~> addCredentials( + BasicHttpCredentials(rootEmail, testPass) + ) + val response: HttpResponse = singleAwaitingRequest(request) + val result: String = responseToString(response) + assert(response.status === StatusCodes.BadRequest) + assert(result.contains("Invalid RestrictedViewSize: pct:0")) + } + + "return `Forbidden` for the user who is not a system nor project admin" in { + val encodedIri = URLEncoder.encode(SharedTestDataADM.imagesProject.id, "utf-8") + val payload = """{"size":"pct:1"}""" + val request = + Post( + baseApiUrl + s"/admin/projects/iri/$encodedIri/RestrictedViewSettings", + HttpEntity(ContentTypes.`application/json`, payload) + ) ~> addCredentials( + BasicHttpCredentials(SharedTestDataADM.imagesUser02.email, testPass) + ) + val response: HttpResponse = singleAwaitingRequest(request) + assert(response.status === StatusCodes.Forbidden) + } + } + else "used to set RestrictedViewSize by project IRI" ignore () + + if (baseApiUrl.contains("5555")) "used to set RestrictedViewSize by project Shortcode" should { + "return requested value to be set with 200 Response Status" in { + val shortcode = SharedTestDataADM.imagesProject.shortcode + val payload = """{"size":"pct:1"}""" + val request = + Post( + baseApiUrl + s"/admin/projects/shortcode/$shortcode/RestrictedViewSettings", + HttpEntity(ContentTypes.`application/json`, payload) + ) ~> addCredentials( + BasicHttpCredentials(rootEmail, testPass) + ) + val response: HttpResponse = singleAwaitingRequest(request) + val result: String = responseToString(response) + assert(response.status === StatusCodes.OK) + assert(payload === result) + } + + "return the `BadRequest` if the size value is invalid" in { + val shortcode = SharedTestDataADM.imagesProject.shortcode + val payload = """{"size":"pct:0"}""" + val request = + Post( + baseApiUrl + s"/admin/projects/shortcode/$shortcode/RestrictedViewSettings", + HttpEntity(ContentTypes.`application/json`, payload) + ) ~> addCredentials( + BasicHttpCredentials(rootEmail, testPass) + ) + val response: HttpResponse = singleAwaitingRequest(request) + val result: String = responseToString(response) + assert(response.status === StatusCodes.BadRequest) + assert(result.contains("Invalid RestrictedViewSize: pct:0")) + } + + "return `Forbidden` for the user who is not a system nor project admin" in { + val shortcode = SharedTestDataADM.imagesProject.shortcode + val payload = """{"size":"pct:1"}""" + val request = + Post( + baseApiUrl + s"/admin/projects/shortcode/$shortcode/RestrictedViewSettings", + HttpEntity(ContentTypes.`application/json`, payload) + ) ~> addCredentials( + BasicHttpCredentials(SharedTestDataADM.imagesUser02.email, testPass) + ) + val response: HttpResponse = singleAwaitingRequest(request) + assert(response.status === StatusCodes.Forbidden) + } + } + else "used to set RestrictedViewSize by project Shortcode" ignore () } } diff --git a/webapi/src/main/scala/dsp/valueobjects/RestrictedViewSize.scala b/webapi/src/main/scala/dsp/valueobjects/RestrictedViewSize.scala new file mode 100644 index 0000000000..2fb747171b --- /dev/null +++ b/webapi/src/main/scala/dsp/valueobjects/RestrictedViewSize.scala @@ -0,0 +1,45 @@ +/* + * Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package dsp.valueobjects + +import zio.json.JsonCodec + +import scala.util.matching.Regex + +/** + * RestrictedViewSize value object. + */ +sealed abstract case class RestrictedViewSize private (value: String) + +object RestrictedViewSize { + def make(value: String): Either[String, RestrictedViewSize] = { + val trimmed: String = value.trim + // matches strings "pct:1-100" + val percentagePattern: Regex = "pct:[1-9][0-9]?0?$".r + // matches strings "!x,x" where x is a number of pixels + val dimensionsPattern: Regex = "!\\d+,\\d+$".r + def isSquare: Boolean = { + val substr = trimmed.substring(1).split(",").toSeq + substr.head == substr.last + } + + if (value.isEmpty) Left(ErrorMessages.RestrictedViewSizeMissing) + else if (percentagePattern.matches(trimmed)) Right(new RestrictedViewSize(trimmed) {}) + else if (dimensionsPattern.matches(trimmed) && isSquare) Right(new RestrictedViewSize(trimmed) {}) + else Left(ErrorMessages.RestrictedViewSizeInvalid(value)) + } + + def unsafeFrom(value: String): RestrictedViewSize = + make(value).fold(s => throw new IllegalArgumentException(s), identity) + + implicit val codec: JsonCodec[RestrictedViewSize] = + JsonCodec[String].transformOrFail(RestrictedViewSize.make, _.value) +} + +object ErrorMessages { + val RestrictedViewSizeMissing = "RestrictedViewSize cannot be empty." + val RestrictedViewSizeInvalid = (v: String) => s"Invalid RestrictedViewSize: $v" +} diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsMessagesADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsMessagesADM.scala index b81203ee17..ff00a67498 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsMessagesADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsMessagesADM.scala @@ -11,6 +11,8 @@ import spray.json.DefaultJsonProtocol import spray.json.JsValue import spray.json.JsonFormat import spray.json.RootJsonFormat +import zio.json.DeriveJsonCodec +import zio.json.JsonCodec import zio.prelude.Validation import java.util.UUID @@ -21,6 +23,7 @@ import dsp.errors.ValidationException import dsp.valueobjects.Iri import dsp.valueobjects.Iri.ProjectIri import dsp.valueobjects.Project._ +import dsp.valueobjects.RestrictedViewSize import dsp.valueobjects.V2 import org.knora.webapi.IRI import org.knora.webapi.core.RelayedMessage @@ -346,6 +349,12 @@ case class ProjectRestrictedViewSettingsGetResponseADM(settings: ProjectRestrict def toJsValue: JsValue = projectRestrictedViewGetResponseADMFormat.write(this) } +case class ProjectRestrictedViewSizeResponseADM(size: RestrictedViewSize) +object ProjectRestrictedViewSizeResponseADM { + implicit val codec: JsonCodec[ProjectRestrictedViewSizeResponseADM] = + DeriveJsonCodec.gen[ProjectRestrictedViewSizeResponseADM] +} + /** * Represents an answer to a project creating/modifying operation. * @@ -606,9 +615,7 @@ trait ProjectsADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol jsonFormat(ProjectKeywordsGetResponseADM, "keywords") implicit val projectRestrictedViewGetResponseADMFormat: RootJsonFormat[ProjectRestrictedViewSettingsGetResponseADM] = jsonFormat(ProjectRestrictedViewSettingsGetResponseADM, "settings") - implicit val projectOperationResponseADMFormat: RootJsonFormat[ProjectOperationResponseADM] = rootFormat( lazyFormat(jsonFormat(ProjectOperationResponseADM, "project")) ) - } diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsPayloadsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsPayloadsADM.scala index 84f33c9c6c..132026f947 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsPayloadsADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsPayloadsADM.scala @@ -79,3 +79,10 @@ object ProjectUpdatePayloadADM { ) } } + +final case class ProjectSetRestrictedViewSizePayload(size: String) + +object ProjectSetRestrictedViewSizePayload { + implicit val codec: JsonCodec[ProjectSetRestrictedViewSizePayload] = + DeriveJsonCodec.gen[ProjectSetRestrictedViewSizePayload] +} diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala index 53b2812527..0abfaf1121 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala @@ -114,7 +114,7 @@ trait ProjectsResponderADM { /** * Get project's restricted view settings. * - * @param id the project's identifier (IRI / shortcode / shortname / UUID) + * @param id the project's identifier (IRI / shortcode / shortname) * @return [[ProjectRestrictedViewSettingsADM]] */ def projectRestrictedViewSettingsGetADM(id: ProjectIdentifierADM): Task[Option[ProjectRestrictedViewSettingsADM]] @@ -122,7 +122,7 @@ trait ProjectsResponderADM { /** * Get project's restricted view settings. * - * @param id the project's identifier (IRI / shortcode / shortname / UUID) + * @param id the project's identifier (IRI / shortcode / shortname) * @return [[ProjectRestrictedViewSettingsGetResponseADM]] */ def projectRestrictedViewSettingsGetRequestADM( @@ -646,7 +646,6 @@ final case class ProjectsResponderADMLive( ) ) } - } /** @@ -816,6 +815,9 @@ final case class ProjectsResponderADMLive( ) // create permissions for admins and members of the new group _ <- createPermissionsForAdminsAndMembersOfNewProject(newProjectIRI) +// TODO: DEV-2626 add default value here +// defaultSize = "" +// _ <- setProjectRestrictedViewSettings(id.value, requestingUser, defaultSize) } yield ProjectOperationResponseADM(project = newProjectADM.unescape) diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/ProjectsRouteZ.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/ProjectsRouteZ.scala index e154b4cbf0..eca18d3243 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/ProjectsRouteZ.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/ProjectsRouteZ.scala @@ -15,11 +15,14 @@ import java.nio.file.Files import dsp.errors.BadRequestException import dsp.valueobjects.Iri._ +import dsp.valueobjects.RestrictedViewSize import org.knora.webapi.config.AppConfig import org.knora.webapi.http.handler.ExceptionHandlerZ import org.knora.webapi.http.middleware.AuthenticationMiddleware import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectCreatePayloadADM +import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM._ +import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectSetRestrictedViewSizePayload import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectUpdatePayloadADM import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.routing.RouteUtilZ @@ -84,6 +87,16 @@ final case class ProjectsRouteZ( getRestrictedViewSettingsByShortname(shortname) case (Method.GET -> !! / "admin" / "projects" / "shortcode" / shortcode / "RestrictedViewSettings", _) => getRestrictedViewSettingsByShortcode(shortcode) + case ( + request @ Method.POST -> !! / "admin" / "projects" / "iri" / iri / "RestrictedViewSettings", + requestingUser + ) => + setProjectRestrictedViewSizeByIri(iri, request.body, requestingUser) + case ( + request @ Method.POST -> !! / "admin" / "projects" / "shortcode" / shortcode / "RestrictedViewSettings", + requstingUser + ) => + setProjectRestrictedViewSizeByShortcode(shortcode, request.body, requstingUser) } .catchAll(ExceptionHandlerZ.exceptionToJsonHttpResponseZ(_, appConfig)) @@ -234,6 +247,26 @@ final case class ProjectsRouteZ( r <- projectsService.getProjectRestrictedViewSettings(shortcodeIdentifier) } yield Response.json(r.toJsValue.toString) + private def setProjectRestrictedViewSizeByIri(iri: IRI, body: Body, user: UserADM): Task[Response] = + for { + iriDecoded <- RouteUtilZ.urlDecode(iri, s"Failed to URL decode IRI parameter $iri.") + id <- IriIdentifier.fromString(iriDecoded).toZIO.mapError(e => BadRequestException(e.msg)) + result <- handleRestrictedViewSizeRequest(id, body, user) + } yield result + + private def setProjectRestrictedViewSizeByShortcode(shortcode: String, body: Body, user: UserADM): Task[Response] = + for { + id <- ShortcodeIdentifier.fromString(shortcode).toZIO.mapError(e => BadRequestException(e.msg)) + result <- handleRestrictedViewSizeRequest(id, body, user) + } yield result + + private def handleRestrictedViewSizeRequest(id: ProjectIdentifierADM, body: Body, user: UserADM) = + for { + body <- body.asString + payload <- ZIO.fromEither(body.fromJson[ProjectSetRestrictedViewSizePayload]).mapError(BadRequestException(_)) + size <- ZIO.fromEither(RestrictedViewSize.make(payload.size)).mapError(BadRequestException(_)) + response <- projectsService.setProjectRestrictedViewSettings(id, user, size) + } yield Response.json(response.toJson) } object ProjectsRouteZ { diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala index ec048d3d5c..d19769cf5d 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala @@ -13,6 +13,7 @@ import dsp.errors.NotFoundException import dsp.valueobjects.Iri.ProjectIri import dsp.valueobjects.Project import dsp.valueobjects.Project.Shortcode +import dsp.valueobjects.RestrictedViewSize import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM._ import org.knora.webapi.messages.admin.responder.projectsmessages._ import org.knora.webapi.messages.admin.responder.usersmessages.UserADM @@ -62,6 +63,11 @@ trait ProjectADMRestService { def getProjectRestrictedViewSettings( identifier: ProjectIdentifierADM ): Task[ProjectRestrictedViewSettingsGetResponseADM] + def setProjectRestrictedViewSettings( + id: ProjectIdentifierADM, + user: UserADM, + size: RestrictedViewSize + ): Task[ProjectRestrictedViewSizeResponseADM] } final case class ProjectsADMRestServiceLive( @@ -239,6 +245,25 @@ final case class ProjectsADMRestServiceLive( def getProjectRestrictedViewSettings(id: ProjectIdentifierADM): Task[ProjectRestrictedViewSettingsGetResponseADM] = responder.projectRestrictedViewSettingsGetRequestADM(id) + /** + * Sets project's restricted view settings. + * + * @param id the project's id represented by iri, shortocde or shortname, + * @param user requesting user, + * @param size value to be set, + * @return [[ProjectRestrictedViewSizeResponseADM]]. + */ + override def setProjectRestrictedViewSettings( + id: ProjectIdentifierADM, + user: UserADM, + size: RestrictedViewSize + ): Task[ProjectRestrictedViewSizeResponseADM] = + for { + project <- projectRepo.findById(id).someOrFail(NotFoundException(s"Project '${getId(id)}' not found.")) + _ <- permissionService.ensureSystemOrProjectAdmin(user, project) + _ <- projectRepo.setProjectRestrictedViewSize(project, size) + } yield ProjectRestrictedViewSizeResponseADM(size) + override def exportProject(shortcodeStr: String, requestingUser: UserADM): Task[Unit] = for { _ <- permissionService.ensureSystemAdmin(requestingUser) shortcode <- convertStringToShortcode(shortcodeStr) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepo.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepo.scala index 6e93af9483..499f465a5b 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepo.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepo.scala @@ -7,6 +7,7 @@ package org.knora.webapi.slice.admin.domain.service import zio.Task import dsp.valueobjects.Project.Shortcode +import dsp.valueobjects.RestrictedViewSize import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.ShortcodeIdentifier import org.knora.webapi.slice.admin.domain.model.KnoraProject @@ -17,4 +18,5 @@ trait KnoraProjectRepo extends Repository[KnoraProject, InternalIri] { def findById(id: ProjectIdentifierADM): Task[Option[KnoraProject]] def findByShortcode(shortcode: Shortcode): Task[Option[KnoraProject]] = findById(ShortcodeIdentifier(shortcode)) def findOntologies(project: KnoraProject): Task[List[InternalIri]] + def setProjectRestrictedViewSize(project: KnoraProject, size: RestrictedViewSize): Task[Unit] } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala index fb8f5746d5..187c055085 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala @@ -9,6 +9,7 @@ import play.twirl.api.TxtFormat import zio._ import dsp.valueobjects.Project +import dsp.valueobjects.RestrictedViewSize import dsp.valueobjects.V2 import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.HasSelfJoinEnabled import org.knora.webapi.messages.OntologyConstants.KnoraAdmin.ProjectDescription @@ -33,6 +34,7 @@ import org.knora.webapi.slice.resourceinfo.domain.InternalIri import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Construct import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Select +import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Update final case class KnoraProjectRepoLive( private val triplestore: TriplestoreService, @@ -104,6 +106,15 @@ final case class KnoraProjectRepoLive( .query(Select(query)) .map(_.results.bindings.flatMap(_.rowMap.get("ontologyIri")).map(InternalIri).toList) } + + override def setProjectRestrictedViewSize( + project: KnoraProject, + size: RestrictedViewSize + ): Task[Unit] = { + val query = sparql.admin.txt + .setProjectRestrictedViewSettings(project.id.value, size.value) + triplestore.query(Update(query.toString)) + } } object KnoraProjectRepoLive { diff --git a/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/admin/setProjectRestrictedViewSettings.scala.txt b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/admin/setProjectRestrictedViewSettings.scala.txt new file mode 100644 index 0000000000..ec499c6a93 --- /dev/null +++ b/webapi/src/main/twirl/org/knora/webapi/messages/twirl/queries/sparql/admin/setProjectRestrictedViewSettings.scala.txt @@ -0,0 +1,35 @@ +@* + * Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + *@ + +@import org.knora.webapi.IRI +@import dsp.valueobjects.RestrictedViewSize + +@* + * Sets project restricted view settings. + * + * @param iri project's IRI. + * @param size project's restricted view size. + * + *@ +@(iri: IRI, + size: String) + +PREFIX rdf: +PREFIX xsd: +PREFIX knora-admin: + +WITH +DELETE { + <@iri> knora-admin:projectRestrictedViewSize ?prevSize . +} +INSERT { + <@iri> knora-admin:projectRestrictedViewSize "@size"^^xsd:string . +} +WHERE { + <@iri> a knora-admin:knoraProject . + OPTIONAL { + <@iri> knora-admin:projectRestrictedViewSize ?prevSize . + } +} diff --git a/webapi/src/test/scala/dsp/valueobjects/RestrictedViewSizeSpec.scala b/webapi/src/test/scala/dsp/valueobjects/RestrictedViewSizeSpec.scala new file mode 100644 index 0000000000..3a84a27064 --- /dev/null +++ b/webapi/src/test/scala/dsp/valueobjects/RestrictedViewSizeSpec.scala @@ -0,0 +1,47 @@ +/* + * Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package dsp.valueobjects + +import zio.test._ + +/** + * This spec is used to test the [[dsp.valueobjects.RestrictedViewSize]] value object creation. + */ +object RestrictedViewSizeSpec extends ZIOSpecDefault { + + def spec = suite("Size")( + test("should succeed on passing percentage values") { + val gen = Gen.int(1, 100) + check(gen) { int => + val param = s"pct:$int" + assertTrue(RestrictedViewSize.make(param).map(_.value) == Right(param)) + } + }, + test("should succeed on passing the same x y dimensions") { + val gen = Gen.int(1, 1000) + check(gen) { integer => + val param = s"!$integer,$integer" + assertTrue(RestrictedViewSize.make(param).map(_.value) == Right(param)) + } + }, + test("should fail on passing negative x y dimensions") { + val gen = Gen.int(-1000, -1) + check(gen) { integer => + val param = s"!$integer,$integer" + assertTrue(RestrictedViewSize.make(param).map(_.value) == Left(ErrorMessages.RestrictedViewSizeInvalid(param))) + } + }, + test("should fail on passing incorrect values") { + val gen = Gen.fromIterable(Seq("!512,100", "pct:-1", "pct:0", "pct:101")) + check(gen) { param => + assertTrue(RestrictedViewSize.make(param) == Left(ErrorMessages.RestrictedViewSizeInvalid(param))) + } + }, + test("should fail on passing empty value") { + assertTrue(RestrictedViewSize.make("") == Left(ErrorMessages.RestrictedViewSizeMissing)) + } + ) +} diff --git a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectADMRestServiceMock.scala b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectADMRestServiceMock.scala index 6ba1e27760..2a202abc02 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectADMRestServiceMock.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectADMRestServiceMock.scala @@ -10,6 +10,7 @@ import zio._ import zio.mock._ import dsp.valueobjects.Iri._ +import dsp.valueobjects.RestrictedViewSize import org.knora.webapi.messages.admin.responder.projectsmessages._ import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.slice.admin.api.model.ProjectDataGetResponseADM @@ -97,6 +98,12 @@ object ProjectADMRestServiceMock extends Mock[ProjectADMRestService] { override def importProject(projectIri: IRI, requestingUser: UserADM): Task[ProjectImportResponse] = ??? override def listExports(requestingUser: UserADM): Task[Chunk[ProjectExportInfoResponse]] = ??? + + override def setProjectRestrictedViewSettings( + id: ProjectIdentifierADM, + user: UserADM, + size: RestrictedViewSize + ): Task[ProjectRestrictedViewSizeResponseADM] = ??? } } } diff --git a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMMock.scala b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMMock.scala index 818c909a4f..b425279898 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMMock.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMMock.scala @@ -16,19 +16,7 @@ import zio.mock.Proxy import java.util.UUID import dsp.valueobjects.Iri -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectAdminMembersGetResponseADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectCreatePayloadADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetResponseADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectKeywordsGetResponseADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectMembersGetResponseADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectOperationResponseADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectRestrictedViewSettingsADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectRestrictedViewSettingsGetResponseADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectUpdatePayloadADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectsGetResponseADM -import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectsKeywordsGetResponseADM +import org.knora.webapi.messages.admin.responder.projectsmessages._ import org.knora.webapi.messages.admin.responder.usersmessages.UserADM object ProjectsResponderADMMock extends Mock[ProjectsResponderADM] { diff --git a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala index 45a40aa1c3..c2ae32e209 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala @@ -22,6 +22,7 @@ import org.knora.webapi.slice.admin.domain.service.DspIngestClientMock import org.knora.webapi.slice.admin.domain.service.ProjectExportServiceStub import org.knora.webapi.slice.admin.domain.service.ProjectExportStorageServiceLive import org.knora.webapi.slice.admin.domain.service.ProjectImportServiceLive +import org.knora.webapi.store.triplestore.impl.TriplestoreServiceLive object ProjectsServiceLiveSpec extends ZIOSpecDefault { @@ -58,6 +59,8 @@ object ProjectsServiceLiveSpec extends ZIOSpecDefault { private def projectServiceLayer(exp: Expectation[ProjectsResponderADM]): ULayer[ProjectADMRestService] = ZLayer.make[ProjectADMRestService]( + TriplestoreServiceLive.layer, + StringFormatter.test, ProjectsADMRestServiceLive.layer, exp.toLayer, org.knora.webapi.slice.common.api.RestPermissionServiceLive.layer, diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/repo/service/KnoraProjectRepoInMemory.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/repo/service/KnoraProjectRepoInMemory.scala index e234aefed1..d9a9de8455 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/repo/service/KnoraProjectRepoInMemory.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/repo/service/KnoraProjectRepoInMemory.scala @@ -10,6 +10,7 @@ import zio.Task import zio.ULayer import zio.ZLayer +import dsp.valueobjects.RestrictedViewSize import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo @@ -32,6 +33,8 @@ final case class KnoraProjectRepoInMemory(projects: Ref[List[KnoraProject]]) override def findOntologies(project: KnoraProject): Task[List[InternalIri]] = throw new UnsupportedOperationException("not implemented (yet)") + + override def setProjectRestrictedViewSize(project: KnoraProject, size: RestrictedViewSize): Task[Unit] = ??? } object KnoraProjectRepoInMemory { diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepoInMemory.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepoInMemory.scala index 479a6c4abc..8e7e45cfc7 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepoInMemory.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepoInMemory.scala @@ -10,6 +10,7 @@ import zio.Task import zio.ULayer import zio.ZLayer +import dsp.valueobjects.RestrictedViewSize import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.IriIdentifier import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.ShortcodeIdentifier @@ -32,6 +33,8 @@ final case class KnoraProjectRepoInMemory(projects: Ref[List[KnoraProject]]) override def findOntologies(project: KnoraProject): Task[List[InternalIri]] = throw new UnsupportedOperationException("not yet implemented") + + override def setProjectRestrictedViewSize(project: KnoraProject, size: RestrictedViewSize): Task[Unit] = ??? } object KnoraProjectRepoInMemory {