Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Add route that sets projectRestrictedViewSetting size (DEV-2304) #2794

Merged
merged 55 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
552664e
add SPARQL query
mpro7 Aug 15, 2023
0092974
add view settings functionality
mpro7 Aug 15, 2023
1c7fb7a
add route
mpro7 Aug 15, 2023
4e83dba
improve query
mpro7 Aug 15, 2023
e95d8d8
remove watermark field
mpro7 Aug 28, 2023
fc17ec3
add checking if project exists
mpro7 Aug 28, 2023
e153722
add response
mpro7 Aug 28, 2023
4df4c17
reuse respone for get and set
mpro7 Aug 28, 2023
d4ae04b
add mocks and service test
mpro7 Aug 29, 2023
85d266c
move user check to responder
mpro7 Aug 30, 2023
2b71493
add code documentation
mpro7 Aug 30, 2023
b19ce9f
tests cleanup
mpro7 Aug 31, 2023
fa73a55
fix docs
mpro7 Aug 31, 2023
6b0b2ac
add route documentation + fix user permission logic
mpro7 Aug 31, 2023
4971df0
make size required and temp. remove default
mpro7 Sep 5, 2023
6487d6c
fix test
mpro7 Sep 5, 2023
2c8f16e
introduce RestrictedViewSize value object
mpro7 Sep 11, 2023
c2be8a4
fix value object
mpro7 Sep 12, 2023
f1fee51
clean mocks and remove test
mpro7 Sep 12, 2023
ca5bbc7
fix'jsonFormat overload
mpro7 Sep 13, 2023
8c3664b
add improvements and tests
mpro7 Sep 14, 2023
2c52c2c
add custom JsonFormat + value object fixes
mpro7 Sep 14, 2023
80bc159
cleanup
mpro7 Sep 18, 2023
20dec4d
update docs
mpro7 Sep 18, 2023
b2bf6b4
Merge branch 'main' into dev-2269-fix-restricted-view-default-settings
mpro7 Sep 18, 2023
a4eba54
update query handling
mpro7 Sep 18, 2023
95cdeac
Remove spray json and replace with zio-json
seakayone Sep 18, 2023
ad85c7c
Replace Validation with Either and add tests
seakayone Sep 18, 2023
c53c279
fix Update usage and tests
mpro7 Sep 18, 2023
b34c791
fmt
mpro7 Sep 18, 2023
e56b999
remove ProjectRestrictedViewSettingsSetRequestADM
mpro7 Sep 19, 2023
a0a1b57
replace ProjectIri with ProjectIdentifierADM
mpro7 Sep 19, 2023
d68efa1
move all setProjectRestrictedViewSettings functionality to rest service
mpro7 Sep 19, 2023
fd48160
names refactoring
mpro7 Sep 20, 2023
a25a0b1
add ByShortcode route version
mpro7 Sep 20, 2023
5bc2c69
update docs
mpro7 Sep 21, 2023
43b5a0e
reorder implementation
mpro7 Sep 21, 2023
8fcaed2
fmt
mpro7 Sep 21, 2023
2bdcaa1
add e2e tests
mpro7 Sep 26, 2023
c5243ad
fmt
mpro7 Sep 26, 2023
b00ada2
place e2e test to separate file
mpro7 Sep 27, 2023
66c2915
extend with ProjectsADMJsonProtocol
mpro7 Sep 27, 2023
362c9e5
add HttpEntity to the requests
mpro7 Sep 27, 2023
d976806
add condition on tests
mpro7 Sep 27, 2023
5551113
Merge branch 'main' into dev-2269-fix-restricted-view-default-settings
mpro7 Sep 27, 2023
c2de3fb
jsonc tpo json
mpro7 Sep 28, 2023
b95d3c7
bring back ProjectRestrictedViewSettingsGetResponseADM name
mpro7 Sep 28, 2023
f373059
remove unused dependency
mpro7 Sep 28, 2023
79bb031
extract route method
mpro7 Sep 28, 2023
b57e6e8
fix extracted method
mpro7 Sep 28, 2023
69b5b46
remove watermark field from twirl template
mpro7 Sep 28, 2023
98d2c57
update docs
mpro7 Sep 29, 2023
0baac59
Merge branch 'main' into dev-2269-fix-restricted-view-default-settings
mpro7 Sep 29, 2023
14ed466
Merge branch 'main' into dev-2269-fix-restricted-view-default-settings
mpro7 Sep 29, 2023
2832866
fmt
mpro7 Sep 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions docs/03-endpoints/api-admin/projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
Expand All @@ -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

Expand Down Expand Up @@ -775,7 +776,7 @@ Example response:

```

### Restricted View Settings
### Get Restricted View Settings

Permissions: ProjectAdmin

Expand All @@ -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'
```

Expand All @@ -814,6 +815,49 @@ Example response:
}
```

### Set Restricted View Settings
mpro7 marked this conversation as resolved.
Show resolved Hide resolved

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`
mpro7 marked this conversation as resolved.
Show resolved Hide resolved
- `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!**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 ()
}
}
45 changes: 45 additions & 0 deletions webapi/src/main/scala/dsp/valueobjects/RestrictedViewSize.scala
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -346,6 +349,12 @@ case class ProjectRestrictedViewSettingsGetResponseADM(settings: ProjectRestrict
def toJsValue: JsValue = projectRestrictedViewGetResponseADMFormat.write(this)
mpro7 marked this conversation as resolved.
Show resolved Hide resolved
}

case class ProjectRestrictedViewSizeResponseADM(size: RestrictedViewSize)
object ProjectRestrictedViewSizeResponseADM {
implicit val codec: JsonCodec[ProjectRestrictedViewSizeResponseADM] =
DeriveJsonCodec.gen[ProjectRestrictedViewSizeResponseADM]
}

/**
* Represents an answer to a project creating/modifying operation.
*
Expand Down Expand Up @@ -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"))
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,10 @@ object ProjectUpdatePayloadADM {
)
}
}

final case class ProjectSetRestrictedViewSizePayload(size: String)

object ProjectSetRestrictedViewSizePayload {
implicit val codec: JsonCodec[ProjectSetRestrictedViewSizePayload] =
DeriveJsonCodec.gen[ProjectSetRestrictedViewSizePayload]
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,15 @@ 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]]

/**
* 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(
Expand Down Expand Up @@ -646,7 +646,6 @@ final case class ProjectsResponderADMLive(
)
)
}

}

/**
Expand Down Expand Up @@ -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)
seakayone marked this conversation as resolved.
Show resolved Hide resolved

} yield ProjectOperationResponseADM(project = newProjectADM.unescape)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
package org.knora.webapi.routing

import org.apache.pekko
import org.apache.pekko.http.scaladsl.model.StatusCodes.{MethodNotAllowed, NotFound}
import org.apache.pekko.http.scaladsl.model.{HttpResponse, StatusCodes}
import org.apache.pekko.http.scaladsl.model.HttpResponse
import org.apache.pekko.http.scaladsl.model.StatusCodes.MethodNotAllowed
import org.apache.pekko.http.scaladsl.model.StatusCodes.NotFound

import org.knora.webapi.instrumentation.InstrumentationSupport

import pekko.http.scaladsl.server.Directive0
import pekko.http.scaladsl.server.Directives._

Expand Down
Loading