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: The restricted view must be either restricted with a watermark or by a particular size (DEV-3356) #3080

Merged
merged 14 commits into from
Mar 4, 2024
Merged
Expand Up @@ -787,7 +787,7 @@ class ProjectsADME2ESpec extends E2ESpec with ProjectsADMJsonProtocol {
)
val response = singleAwaitingRequest(request)
assert(response.status === StatusCodes.OK)
assert(responseToString(response) === """{"size":"pct:1","watermark":false}""")
assert(responseToString(response) === """{"size":"pct:1"}""")
}

"return the `BadRequest` if the size value is invalid" in {
Expand Down Expand Up @@ -822,12 +822,11 @@ class ProjectsADME2ESpec extends E2ESpec with ProjectsADMJsonProtocol {
}

"used to set RestrictedViewSize by project Shortcode" should {
"return requested value to be set with 200 Response Status" in {
"when setting watermark to false return default size with 200 Response Status" in {
val shortcode = SharedTestDataADM.imagesProject.shortcode
val payload = """
|{
| "size":"!512,512",
| "watermark":true
| "watermark": false
|}""".stripMargin
val request =
Post(
Expand All @@ -838,7 +837,7 @@ class ProjectsADME2ESpec extends E2ESpec with ProjectsADMJsonProtocol {
)
val response: HttpResponse = singleAwaitingRequest(request)
assert(response.status === StatusCodes.OK)
assert(responseToString(response) === """{"size":"!512,512","watermark":true}""")
assert(responseToString(response) === """{"size":"!128,128"}""")
}

"return the `BadRequest` if the size value is invalid" in {
Expand Down
Expand Up @@ -31,7 +31,6 @@
import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequestsAndResponses.ProjectUpdateRequest
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.admin.domain.model.RestrictedView
import org.knora.webapi.slice.admin.domain.model.RestrictedViewSize
import org.knora.webapi.slice.admin.domain.model.User
import org.knora.webapi.slice.admin.domain.model.UserIri
import org.knora.webapi.slice.admin.domain.service.ProjectADMService
Expand Down Expand Up @@ -745,10 +744,7 @@
)
// create permissions for admins and members of the new group
_ <- createPermissionsForAdminsAndMembersOfNewProject(newProjectIRI)
_ <- projectService.setProjectRestrictedView(
newProjectADM,
RestrictedView(RestrictedViewSize.default, watermark = false)
)
_ <- projectService.setProjectRestrictedView(newProjectADM, RestrictedView.default)

Check warning on line 747 in webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala#L747

Added line #L747 was not covered by tests

} yield ProjectOperationResponseADM(project = newProjectADM.unescape)

Expand Down
Expand Up @@ -17,6 +17,12 @@
import org.knora.webapi.slice.admin.domain.model.ListProperties.ListIri
import org.knora.webapi.slice.admin.domain.model.ListProperties.ListName
import org.knora.webapi.slice.admin.domain.model.ListProperties.Position
import org.knora.webapi.slice.admin.domain.model.Password
import org.knora.webapi.slice.admin.domain.model.RestrictedView
import org.knora.webapi.slice.admin.domain.model.SystemAdmin
import org.knora.webapi.slice.admin.domain.model.UserIri
import org.knora.webapi.slice.admin.domain.model.UserStatus
import org.knora.webapi.slice.admin.domain.model.Username
import org.knora.webapi.slice.admin.domain.model.*
import org.knora.webapi.slice.common.Value.BooleanValue
import org.knora.webapi.slice.common.Value.IntValue
Expand All @@ -42,7 +48,6 @@
implicit val logo: StringCodec[Logo] = stringCodec(Logo.from)
implicit val longname: StringCodec[Longname] = stringCodec(Longname.from)
implicit val projectIri: StringCodec[ProjectIri] = stringCodec(ProjectIri.from)
implicit val restrictedViewSize: StringCodec[RestrictedViewSize] = stringCodec(RestrictedViewSize.from)
implicit val selfJoin: StringCodec[SelfJoin] = booleanCodec(SelfJoin.from)
implicit val shortcode: StringCodec[Shortcode] = stringCodec(Shortcode.from)
implicit val shortname: StringCodec[Shortname] = stringCodec(Shortname.from)
Expand Down Expand Up @@ -89,11 +94,14 @@
implicit val assetId: StringCodec[AssetId] = stringCodec(AssetId.from, _.value)

// project
implicit val keyword: StringCodec[Keyword] = stringCodec(Keyword.from)
implicit val logo: StringCodec[Logo] = stringCodec(Logo.from)
implicit val longname: StringCodec[Longname] = stringCodec(Longname.from)
implicit val projectIri: StringCodec[ProjectIri] = stringCodec(ProjectIri.from)
implicit val restrictedViewSize: StringCodec[RestrictedViewSize] = stringCodec(RestrictedViewSize.from)
implicit val keyword: StringCodec[Keyword] = stringCodec(Keyword.from)
implicit val logo: StringCodec[Logo] = stringCodec(Logo.from)
implicit val longname: StringCodec[Longname] = stringCodec(Longname.from)
implicit val projectIri: StringCodec[ProjectIri] = stringCodec(ProjectIri.from)
implicit val restrictedViewSize: StringCodec[RestrictedView.Size] = stringCodec(RestrictedView.Size.from)
implicit val restrictedViewWatermark: StringCodec[RestrictedView.Watermark] = booleanCodec(
RestrictedView.Watermark.from

Check warning on line 103 in webapi/src/main/scala/org/knora/webapi/slice/admin/api/Codecs.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/api/Codecs.scala#L97-L103

Added lines #L97 - L103 were not covered by tests
)
implicit val selfJoin: StringCodec[SelfJoin] = booleanCodec(SelfJoin.from)
implicit val shortcode: StringCodec[Shortcode] = stringCodec(Shortcode.from)
implicit val shortname: StringCodec[Shortname] = stringCodec(Shortname.from)
Expand Down
Expand Up @@ -24,7 +24,7 @@
import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequestsAndResponses.ProjectUpdateRequest
import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequestsAndResponses.RestrictedViewResponse
import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequestsAndResponses.SetRestrictedViewRequest
import org.knora.webapi.slice.admin.domain.model.RestrictedViewSize
import org.knora.webapi.slice.admin.domain.model.RestrictedView
import org.knora.webapi.slice.common.api.BaseEndpoints

final case class ProjectsEndpoints(
Expand Down Expand Up @@ -94,17 +94,19 @@
object Secured {
private val bodyProjectSetRestrictedViewSizeRequest =
zioJsonBody[SetRestrictedViewRequest]
.default(SetRestrictedViewRequest(RestrictedViewSize.default, watermark = Some(false)))
.description(
"The restricted view settings to be set.\n" +
"The image restrictions support two of the (IIIF size)[https://iiif.io/api/image/3.0/#42-size] forms:\n" +
"Set how all still image resources of a projects should be displayed when viewed as restricted.\n" +
"This can be either a size restriction or a watermark.\n" +
"For that, we support two of the (IIIF size)[https://iiif.io/api/image/3.0/#42-size] forms:\n" +
"* `!d,d` The returned image is scaled so that the width and height of the returned image are not " +
"greater than d, while maintaining the aspect ratio.\n" +
"* `pct:n` The width and height of the returned image is scaled to n percent of the width and height " +
"of the extracted region. 1<= n <= 100.\n\n" +
"If the watermark is set to `true`, the returned image will be watermarked."
"If the watermark is set to `true`, the returned image will be watermarked, " +
"otherwise the default size " + RestrictedView.Size.default.value + " is set.\n\n" +

Check warning on line 106 in webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpoints.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpoints.scala#L106

Added line #L106 was not covered by tests
"It is only possible to set either the size or the watermark, not both at the same time."
)
.example(SetRestrictedViewRequest(RestrictedViewSize.default, watermark = Some(false)))
.example(SetRestrictedViewRequest(Some(RestrictedView.Size.default), None))

Check warning on line 109 in webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpoints.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/api/ProjectsEndpoints.scala#L109

Added line #L109 was not covered by tests

val postAdminProjectsByProjectIriRestrictedViewSettings = baseEndpoints.securedEndpoint.post
.in(projectsByIri / restrictedViewSettings)
Expand Down
Expand Up @@ -5,12 +5,15 @@

package org.knora.webapi.slice.admin.api.model

import zio.IO
import zio.ZIO
import zio.json.DeriveJsonCodec
import zio.json.JsonCodec

import dsp.errors.BadRequestException
import org.knora.webapi.slice.admin.api.Codecs.ZioJsonCodec.*
import org.knora.webapi.slice.admin.domain.model.KnoraProject.*
import org.knora.webapi.slice.admin.domain.model.RestrictedViewSize
import org.knora.webapi.slice.admin.domain.model.RestrictedView

object ProjectsEndpointsRequestsAndResponses {

Expand Down Expand Up @@ -42,13 +45,34 @@
implicit val codec: JsonCodec[ProjectUpdateRequest] = DeriveJsonCodec.gen[ProjectUpdateRequest]
}

final case class SetRestrictedViewRequest(size: RestrictedViewSize, watermark: Option[Boolean])
final case class SetRestrictedViewRequest(
size: Option[RestrictedView.Size],
watermark: Option[RestrictedView.Watermark]
) {
def toRestrictedView: IO[BadRequestException, RestrictedView] =
(size, watermark) match {
case (Some(size), None) => ZIO.succeed(size)
case (None, Some(watermark)) => ZIO.succeed(RestrictedView.Watermark.from(watermark.value))

Check warning on line 55 in webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/ProjectsEndpointsRequestsAndResponses.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/ProjectsEndpointsRequestsAndResponses.scala#L54-L55

Added lines #L54 - L55 were not covered by tests
case _ =>
ZIO.fail(BadRequestException("Specify either the size or the watermark; if none was provided, include one."))

Check warning on line 57 in webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/ProjectsEndpointsRequestsAndResponses.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/ProjectsEndpointsRequestsAndResponses.scala#L57

Added line #L57 was not covered by tests
}
}

object SetRestrictedViewRequest {
implicit val codec: JsonCodec[SetRestrictedViewRequest] = DeriveJsonCodec.gen[SetRestrictedViewRequest]
}

final case class RestrictedViewResponse(size: RestrictedViewSize, watermark: Boolean)
final case class RestrictedViewResponse(
size: Option[RestrictedView.Size],
watermark: Option[RestrictedView.Watermark]
)
object RestrictedViewResponse {
implicit val codec: JsonCodec[RestrictedViewResponse] = DeriveJsonCodec.gen[RestrictedViewResponse]

def from(restrictedView: RestrictedView): RestrictedViewResponse =
restrictedView match {
case size: RestrictedView.Size => RestrictedViewResponse(Some(size), None)
case watermark: RestrictedView.Watermark => RestrictedViewResponse(None, Some(watermark))

Check warning on line 75 in webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/ProjectsEndpointsRequestsAndResponses.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/ProjectsEndpointsRequestsAndResponses.scala#L74-L75

Added lines #L74 - L75 were not covered by tests
}
}
}
Expand Up @@ -23,7 +23,6 @@
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Status
import org.knora.webapi.slice.admin.domain.model.RestrictedView
import org.knora.webapi.slice.admin.domain.model.User
import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo
import org.knora.webapi.slice.admin.domain.service.ProjectADMService
Expand Down Expand Up @@ -274,19 +273,19 @@
* @param id The project's id represented by iri, shortcode or shortname.
* @param user Requesting user.
* @param req Contains the values to be set.
* @return [[ProjectRestrictedViewSizeResponseADM]].
* @return [[RestrictedViewResponse]].
*/
override def updateProjectRestrictedViewSettings(
id: ProjectIdentifierADM,
user: User,
req: SetRestrictedViewRequest
): Task[RestrictedViewResponse] =
for {
project <- projectRepo.findById(id).someOrFail(NotFoundException(s"Project '${getId(id)}' not found."))
_ <- permissionService.ensureSystemAdminOrProjectAdmin(user, project)
watermarkBool = req.watermark.getOrElse(false)
_ <- projectService.setProjectRestrictedView(project, RestrictedView(req.size, watermarkBool))
} yield RestrictedViewResponse(req.size, watermarkBool)
project <- projectRepo.findById(id).someOrFail(NotFoundException(s"Project '${getId(id)}' not found."))
_ <- permissionService.ensureSystemAdminOrProjectAdmin(user, project)
restrictedView <- req.toRestrictedView
newSettings <- projectService.setProjectRestrictedView(project, restrictedView)
} yield RestrictedViewResponse.from(newSettings)

Check warning on line 288 in webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala#L284-L288

Added lines #L284 - L288 were not covered by tests

override def exportProject(shortcodeStr: String, user: User): Task[Unit] =
convertStringToShortcodeId(shortcodeStr).flatMap(exportProject(_, user))
Expand Down
Expand Up @@ -8,27 +8,42 @@
import scala.util.matching.Regex

import org.knora.webapi.slice.common.StringValueCompanion
import org.knora.webapi.slice.common.Value.BooleanValue
import org.knora.webapi.slice.common.Value.StringValue

final case class RestrictedView(size: RestrictedViewSize, watermark: Boolean)
sealed trait RestrictedView

final case class RestrictedViewSize private (value: String) extends AnyVal with StringValue
object RestrictedView {

object RestrictedViewSize extends StringValueCompanion[RestrictedViewSize] {
val default: RestrictedView = Size.default

Check warning on line 18 in webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/RestrictedView.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/RestrictedView.scala#L18

Added line #L18 was not covered by tests

// matches strings "pct:n" with n between 1 and 100
private val percentagePattern: Regex = "pct:(?:100|[1-9][0-9]?)$".r
final case class Watermark private (value: Boolean) extends RestrictedView with BooleanValue
object Watermark {

// matches strings "!x,x" where x is a positive integer and represents the dimensions of the restricted view
private val dimensionsPattern: Regex = "!(\\d+),(\\1)$".r
val On: Watermark = Watermark(true)
val Off: Watermark = Watermark(false)

Check warning on line 24 in webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/RestrictedView.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/RestrictedView.scala#L23-L24

Added lines #L23 - L24 were not covered by tests

val default: RestrictedViewSize = RestrictedViewSize.unsafeFrom("!512,512")
def from(value: Boolean): Watermark = if (value) On else Off

Check warning on line 26 in webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/RestrictedView.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/RestrictedView.scala#L26

Added line #L26 was not covered by tests
}

def from(value: String): Either[String, RestrictedViewSize] =
value match {
case _ if value.isEmpty => Left("RestrictedViewSize cannot be empty.")
case _ if percentagePattern.matches(value) => Right(RestrictedViewSize(value))
case _ if dimensionsPattern.matches(value) => Right(RestrictedViewSize(value))
case _ => Left(s"Invalid RestrictedViewSize: $value")
}
final case class Size private (value: String) extends RestrictedView with StringValue

object Size extends StringValueCompanion[Size] {

// matches strings "pct:n" with n between 1 and 100
private val percentagePattern: Regex = "pct:(?:100|[1-9][0-9]?)$".r

// matches strings "!x,x" where x is a positive integer and represents the dimensions of the restricted view
private val dimensionsPattern: Regex = "!(\\d+),(\\1)$".r

val default: Size = Size.unsafeFrom("!128,128")

def from(value: String): Either[String, Size] =
value match {
case _ if value.isEmpty => Left("RestrictedViewSize cannot be empty.")
case _ if percentagePattern.matches(value) => Right(Size(value))
case _ if dimensionsPattern.matches(value) => Right(Size(value))
case _ => Left(s"Invalid RestrictedViewSize: $value")
}
}
}
Expand Up @@ -96,10 +96,15 @@
.map(_ :+ projectGraph)
}

def setProjectRestrictedView(project: KnoraProject, settings: RestrictedView): Task[Unit] =
projectRepo.setProjectRestrictedView(project, settings)
def setProjectRestrictedView(project: KnoraProject, settings: RestrictedView): Task[RestrictedView] = {
val newSettings = settings match {
case RestrictedView.Watermark(false) => RestrictedView.default
case s => s

Check warning on line 102 in webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala#L101-L102

Added lines #L101 - L102 were not covered by tests
}
projectRepo.setProjectRestrictedView(project, newSettings).as(newSettings)

Check warning on line 104 in webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala

View check run for this annotation

Codecov / codecov/patch

webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala#L104

Added line #L104 was not covered by tests
}

def setProjectRestrictedView(project: ProjectADM, settings: RestrictedView): Task[Unit] =
def setProjectRestrictedView(project: ProjectADM, settings: RestrictedView): Task[RestrictedView] =
setProjectRestrictedView(toKnoraProject(project), settings)
}

Expand Down
Expand Up @@ -41,6 +41,10 @@ object Vocabulary {
val belongsToProject: Iri = Rdf.iri(KnoraAdminPrefixExpansion, "belongsToProject")
val groupName: Iri = Rdf.iri(KnoraAdminPrefixExpansion, "groupName")
val groupDescriptions: Iri = Rdf.iri(KnoraAdminPrefixExpansion, "groupDescriptions")

// project properties
val projectRestrictedViewSize: Iri = Rdf.iri(KnoraAdminPrefixExpansion, "projectRestrictedViewSize")
val projectRestrictedViewWatermark: Iri = Rdf.iri(KnoraAdminPrefixExpansion, "projectRestrictedViewWatermark")
}

object KnoraBase {
Expand Down