Skip to content

Commit

Permalink
feat: Add license and copyright attribution to project (DEV-4347) (#3429
Browse files Browse the repository at this point in the history
)

Co-authored-by: Marcin Procyk <[email protected]>
  • Loading branch information
seakayone and mpro7 authored Nov 18, 2024
1 parent 2d62dcb commit e51af72
Show file tree
Hide file tree
Showing 19 changed files with 201 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ object ProjectEraseIT extends E2EZSpec {
None,
KnoraProject.Status.Active,
KnoraProject.SelfJoin.CanJoin,
None,
None,
),
),
).orDie
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ import org.knora.webapi.slice.admin.api.model.ProjectAdminMembersGetResponseADM
import org.knora.webapi.slice.admin.api.model.ProjectMembersGetResponseADM
import org.knora.webapi.slice.admin.api.model.ProjectOperationResponseADM
import org.knora.webapi.slice.admin.domain.model.Group
import org.knora.webapi.slice.admin.domain.model.KnoraProject.CopyrightAttribution
import org.knora.webapi.slice.admin.domain.model.KnoraProject.License
import org.knora.webapi.slice.admin.domain.model.User
import org.knora.webapi.slice.common.Value.StringValue

/**
* A spray-json protocol for generating Knora API JSON providing data about projects.
Expand Down Expand Up @@ -189,9 +192,27 @@ object IntegrationTestAdminJsonProtocol extends TriplestoreJsonProtocol {
"ontologies",
"status",
"selfjoin",
"copyrightAttribution",
"license",
),
)

trait StringValueFormat[T <: StringValue] extends JsonFormat[T] { self =>
def from: String => Either[String, T]
override def write(v: T): JsValue = JsString(v.value)
override def read(json: JsValue): T = json match
case JsString(str) => self.from(str).fold(err => throw DeserializationException(err), identity)
case _ => throw DeserializationException("Value must be a JSON string.")
}

implicit object CopyrightAttributionFormat extends StringValueFormat[CopyrightAttribution] {
override val from: String => Either[String, CopyrightAttribution] = CopyrightAttribution.from
}

implicit object LicenseFormat extends StringValueFormat[License] {
override val from: String => Either[String, License] = License.from
}

implicit val groupFormat: JsonFormat[Group] = jsonFormat6(Group.apply)

implicit val projectAdminMembersGetResponseADMFormat: RootJsonFormat[ProjectAdminMembersGetResponseADM] = rootFormat(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ class ProjectRestServiceSpec extends CoreSpec with ImplicitSender {
logo = Some(Logo.unsafeFrom("/fu/bar/baz.jpg")),
status = Status.Active,
selfjoin = SelfJoin.CannotJoin,
copyrightAttribution = Some(CopyrightAttribution.unsafeFrom("2024, Example Project")),
license = Some(License.unsafeFrom("CC-BY-4.0")),
),
SharedTestDataADM.rootUser,
),
Expand All @@ -174,6 +176,8 @@ class ProjectRestServiceSpec extends CoreSpec with ImplicitSender {
received.project.description should be(
Seq(StringLiteralV2.from(value = "project description", language = Some("en"))),
)
received.project.copyrightAttribution should be(Some(CopyrightAttribution.unsafeFrom("2024, Example Project")))
received.project.license should be(Some(License.unsafeFrom("CC-BY-4.0")))

newProjectIri.set(received.project.id)

Expand Down Expand Up @@ -257,6 +261,8 @@ class ProjectRestServiceSpec extends CoreSpec with ImplicitSender {
logo = Some(Logo.unsafeFrom("/fu/bar/baz.jpg")),
status = Status.Active,
selfjoin = SelfJoin.CannotJoin,
None,
None,
),
SharedTestDataADM.rootUser,
),
Expand All @@ -269,7 +275,8 @@ class ProjectRestServiceSpec extends CoreSpec with ImplicitSender {
received.project.description should be(
Seq(StringLiteralV2.from(value = "project description", language = Some("en"))),
)

received.project.copyrightAttribution should be(None)
received.project.license should be(None)
}

"CREATE a project that its info has special characters" in {
Expand All @@ -292,6 +299,8 @@ class ProjectRestServiceSpec extends CoreSpec with ImplicitSender {
logo = Some(Logo.unsafeFrom("/fu/bar/baz.jpg")),
status = Status.Active,
selfjoin = SelfJoin.CannotJoin,
None,
None,
),
SharedTestDataADM.rootUser,
),
Expand Down Expand Up @@ -324,6 +333,8 @@ class ProjectRestServiceSpec extends CoreSpec with ImplicitSender {
logo = Some(Logo.unsafeFrom("/fu/bar/baz.jpg")),
status = Status.Active,
selfjoin = SelfJoin.CannotJoin,
copyrightAttribution = None,
license = None,
),
SharedTestDataADM.rootUser,
),
Expand All @@ -346,6 +357,8 @@ class ProjectRestServiceSpec extends CoreSpec with ImplicitSender {
logo = Some(Logo.unsafeFrom("/fu/bar/baz.jpg")),
status = Status.Active,
selfjoin = SelfJoin.CannotJoin,
copyrightAttribution = None,
license = None,
),
SharedTestDataADM.rootUser,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ object SharedTestDataADM {
),
status = true,
selfjoin = false,
copyrightAttribution = None,
license = None,
)

/* represents the full ProjectADM of the default shared ontologies project */
Expand All @@ -183,6 +185,8 @@ object SharedTestDataADM {
ontologies = Seq.empty[IRI],
status = true,
selfjoin = false,
copyrightAttribution = None,
license = None,
)

/**
Expand Down Expand Up @@ -295,6 +299,8 @@ object SharedTestDataADM {
ontologies = Seq(SharedOntologyTestDataADM.IMAGES_ONTOLOGY_IRI),
status = true,
selfjoin = false,
copyrightAttribution = None,
license = None,
)

/* represents the full ProjectADM of the images project in the external format */
Expand All @@ -309,6 +315,8 @@ object SharedTestDataADM {
ontologies = Seq(SharedOntologyTestDataADM.IMAGES_ONTOLOGY_IRI_LocalHost),
status = true,
selfjoin = false,
copyrightAttribution = None,
license = None,
)

/* represents the full GroupADM of the images ProjectAdmin group */
Expand Down Expand Up @@ -472,6 +480,8 @@ object SharedTestDataADM {
ontologies = Seq(SharedOntologyTestDataADM.INCUNABULA_ONTOLOGY_IRI),
status = true,
selfjoin = false,
copyrightAttribution = None,
license = None,
)

/* represents the ProjectADM of the incunabula project in the external format*/
Expand Down Expand Up @@ -507,6 +517,8 @@ object SharedTestDataADM {
ontologies = Seq(SharedOntologyTestDataADM.INCUNABULA_ONTOLOGY_IRI_LocalHost),
status = true,
selfjoin = false,
copyrightAttribution = None,
license = None,
)

/**
Expand Down Expand Up @@ -619,6 +631,8 @@ object SharedTestDataADM {
ontologies = Seq(SharedOntologyTestDataADM.ANYTHING_ONTOLOGY_IRI, SharedOntologyTestDataADM.SomethingOntologyIri),
status = true,
selfjoin = false,
copyrightAttribution = None,
license = None,
)

def anythingProjectExternal: Project = Project(
Expand All @@ -635,6 +649,8 @@ object SharedTestDataADM {
),
status = true,
selfjoin = false,
copyrightAttribution = None,
license = None,
)

/* represents the full GroupADM of the Thing searcher group */
Expand Down Expand Up @@ -672,6 +688,8 @@ object SharedTestDataADM {
),
status = true,
selfjoin = false,
copyrightAttribution = None,
license = None,
)

/* represents the user profile of 'superuser' as found in admin-data.ttl */
Expand Down Expand Up @@ -721,5 +739,7 @@ object SharedTestDataADM {
ontologies = Seq("http://www.knora.org/ontology/0804/dokubib"),
status = false,
selfjoin = false,
copyrightAttribution = None,
license = None,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,13 @@ object Codecs {
implicit val restrictedViewWatermark: StringCodec[RestrictedView.Watermark] = booleanCodec(
RestrictedView.Watermark.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)
implicit val sparqlEncodedString: StringCodec[SparqlEncodedString] = stringCodec(SparqlEncodedString.from)
implicit val status: StringCodec[Status] = booleanCodec(Status.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)
implicit val sparqlEncodedString: StringCodec[SparqlEncodedString] = stringCodec(SparqlEncodedString.from)
implicit val status: StringCodec[Status] = booleanCodec(Status.from)
implicit val copyrightAttribution: StringCodec[CopyrightAttribution] = stringCodec(CopyrightAttribution.from)
implicit val license: StringCodec[License] = stringCodec(License.from)

// user
implicit val userIri: StringCodec[UserIri] = stringCodec(UserIri.from)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import org.knora.webapi.slice.admin.domain.model.GroupIri
import org.knora.webapi.slice.admin.domain.model.GroupName
import org.knora.webapi.slice.admin.domain.model.GroupSelfJoin
import org.knora.webapi.slice.admin.domain.model.GroupStatus
import org.knora.webapi.slice.admin.domain.model.KnoraProject.CopyrightAttribution
import org.knora.webapi.slice.admin.domain.model.KnoraProject.License
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.admin.domain.model.User
import org.knora.webapi.slice.admin.domain.model.UserIri
Expand Down Expand Up @@ -93,6 +95,8 @@ object Examples {
status = true,
ontologies = Seq.empty,
selfjoin = false,
copyrightAttribution = Some(CopyrightAttribution.unsafeFrom("2024, Example Project")),
license = Some(License.unsafeFrom("CC-BY-4.0")),
)

private val group = Group(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ object ProjectsEndpointsRequestsAndResponses {
logo: Option[Logo] = None,
status: Status,
selfjoin: SelfJoin,
copyrightAttribution: Option[CopyrightAttribution],
license: Option[License],
)
object ProjectCreateRequest {
implicit val codec: JsonCodec[ProjectCreateRequest] = DeriveJsonCodec.gen[ProjectCreateRequest]
Expand All @@ -40,6 +42,8 @@ object ProjectsEndpointsRequestsAndResponses {
logo: Option[Logo] = None,
status: Option[Status] = None,
selfjoin: Option[SelfJoin] = None,
copyrightAttribution: Option[CopyrightAttribution] = None,
license: Option[License] = None,
)
object ProjectUpdateRequest {
implicit val codec: JsonCodec[ProjectUpdateRequest] = DeriveJsonCodec.gen[ProjectUpdateRequest]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import zio.json.JsonCodec
import org.knora.webapi.IRI
import org.knora.webapi.messages.admin.responder.AdminKnoraResponseADM
import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2
import org.knora.webapi.slice.admin.api.Codecs.ZioJsonCodec.copyrightAttribution
import org.knora.webapi.slice.admin.api.Codecs.ZioJsonCodec.license
import org.knora.webapi.slice.admin.domain.model.KnoraProject.*
import org.knora.webapi.slice.admin.domain.model.RestrictedView
import org.knora.webapi.slice.admin.domain.model.User
Expand Down Expand Up @@ -40,6 +42,8 @@ case class Project(
ontologies: Seq[IRI],
status: Boolean,
selfjoin: Boolean,
copyrightAttribution: Option[CopyrightAttribution],
license: Option[License],
) extends Ordered[Project] {

def projectIri: ProjectIri = ProjectIri.unsafeFrom(id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ case class KnoraProject(
status: Status,
selfjoin: SelfJoin,
restrictedView: RestrictedView,
copyrightAttribution: Option[CopyrightAttribution],
license: Option[License],
) extends EntityWithId[ProjectIri]

object KnoraProject {
Expand Down Expand Up @@ -173,4 +175,23 @@ object KnoraProject {

def from(value: Boolean): SelfJoin = if (value) CanJoin else CannotJoin
}

final case class CopyrightAttribution private (override val value: String) extends StringValue
object CopyrightAttribution extends StringValueCompanion[CopyrightAttribution] {
private val maxLength = 1_000
def from(str: String): Either[String, CopyrightAttribution] =
if (str.isEmpty) Left("Copyright attribution cannot be empty.")
else if (str.contains("\n")) Left("Copyright attribution may not contain line breaks.")
else if (str.length >= maxLength) Left(s"Copyright attribution may only be ${maxLength} characters long.")
else Right(CopyrightAttribution(str))
}

final case class License private (override val value: String) extends StringValue
object License extends StringValueCompanion[License] {
private val maxLength = 10_000
def from(str: String): Either[String, License] =
if (str.isEmpty) Left("License cannot be empty.")
else if (str.length >= maxLength) Left(s"License may only be ${maxLength} characters long.")
else Right(License(str))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ object KnoraProjectRepo {
KnoraProject.Status.Active,
KnoraProject.SelfJoin.CannotJoin,
RestrictedView.default,
None,
None,
)

val SystemProject: KnoraProject = makeBuiltIn(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ final case class KnoraProjectService(knoraProjectRepo: KnoraProjectRepo, ontolog
req.status,
req.selfjoin,
RestrictedView.default,
req.copyrightAttribution,
req.license,
)
project <- knoraProjectRepo.save(project)
} yield project
Expand Down Expand Up @@ -97,6 +99,8 @@ final case class KnoraProjectService(knoraProjectRepo: KnoraProjectRepo, ontolog
logo = updateReq.logo.orElse(project.logo),
status = updateReq.status.getOrElse(project.status),
selfjoin = updateReq.selfjoin.getOrElse(project.selfjoin),
copyrightAttribution = updateReq.copyrightAttribution.orElse(project.copyrightAttribution),
license = updateReq.license.orElse(project.license),
),
)
} yield updated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ final case class ProjectService(
ontologies,
knoraProject.status.value,
knoraProject.selfjoin.value,
knoraProject.copyrightAttribution,
knoraProject.license,
),
)

Expand All @@ -73,6 +75,8 @@ final case class ProjectService(
status = Status.from(project.status),
selfjoin = SelfJoin.from(project.selfjoin),
restrictedView,
project.copyrightAttribution,
project.license,
)

def setProjectRestrictedView(project: Project, settings: RestrictedView): Task[RestrictedView] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ object RdfConversions {
implicit val selfjoinConverter: Boolean => Either[String, SelfJoin] = value => Right(SelfJoin.from(value))
implicit val descriptionConverter: LangString => Either[String, Description] = langString =>
Description.from(StringLiteralV2.from(langString.value, langString.lang))
implicit val copyrightAttributionConverter: String => Either[String, CopyrightAttribution] = CopyrightAttribution.from
implicit val licenseConverter: String => Either[String, License] = License.from

// User properties
implicit val usernameConverter: String => Either[String, Username] = Username.from
Expand Down
Loading

0 comments on commit e51af72

Please sign in to comment.