Skip to content

Commit

Permalink
refactor: Move creation Group from responder to services (#3185)
Browse files Browse the repository at this point in the history
  • Loading branch information
mpro7 committed Apr 15, 2024
1 parent a806f39 commit a21a34f
Show file tree
Hide file tree
Showing 11 changed files with 101 additions and 85 deletions.
Expand Up @@ -183,13 +183,13 @@ class GroupsADME2ESpec extends E2ESpec with GroupsADMJsonProtocol {
val response: HttpResponse = singleAwaitingRequest(request)
response.status should be(StatusCodes.OK)

val groupInfo: Group = AkkaHttpUtils.httpResponseToJson(response).fields("group").convertTo[Group]
val group: Group = AkkaHttpUtils.httpResponseToJson(response).fields("group").convertTo[Group]

groupInfo.name should be("NewGroup")
groupInfo.descriptions should be(Seq(StringLiteralV2.from("NewGroupDescription", Some("en"))))
groupInfo.project should be(Some(SharedTestDataADM.imagesProjectExternal))
groupInfo.status should be(true)
groupInfo.selfjoin should be(false)
group.name should be("NewGroup")
group.descriptions should be(Seq(StringLiteralV2.from("NewGroupDescription", Some("en"))))
group.project should be(Some(SharedTestDataADM.imagesProjectExternal))
group.status should be(true)
group.selfjoin should be(false)

clientTestDataCollector.addFile(
TestDataFileContent(
Expand All @@ -202,7 +202,7 @@ class GroupsADME2ESpec extends E2ESpec with GroupsADMJsonProtocol {
),
)

val iri = groupInfo.id
val iri = group.id
newGroupIri.set(iri)
}

Expand Down
Expand Up @@ -65,8 +65,8 @@ class GroupsResponderADMSpec extends CoreSpec {

"CREATE the group and return the group's info if the supplied group name is unique" in {
val response = UnsafeZioRun.runOrThrow(
ZIO.serviceWithZIO[GroupsResponderADM](
_.createGroup(
groupRestService(
_.postGroup(
GroupCreateRequest(
id = None,
name = GroupName.unsafeFrom("NewGroup"),
Expand All @@ -83,29 +83,29 @@ class GroupsResponderADMSpec extends CoreSpec {
status = GroupStatus.active,
selfjoin = GroupSelfJoin.disabled,
),
UUID.randomUUID,
rootUser,
),
),
)

val newGroupInfo = response.group
newGroupInfo.name should equal("NewGroup")
newGroupInfo.descriptions should equal(
val group = response.group
group.name should equal("NewGroup")
group.descriptions should equal(
Seq(StringLiteralV2.from("""NewGroupDescription with "quotes" and <html tag>""", Some("en"))),
)
newGroupInfo.project should equal(Some(imagesProject))
newGroupInfo.status should equal(true)
newGroupInfo.selfjoin should equal(false)
group.project should equal(Some(imagesProjectExternal))
group.status should equal(true)
group.selfjoin should equal(false)

// store for later usage
newGroupIri.set(newGroupInfo.id)
newGroupIri.set(group.id)
}

"return a 'DuplicateValueException' if the supplied group name is not unique" in {
val groupName = GroupName.unsafeFrom("NewGroup")
val exit = UnsafeZioRun.run(
ZIO.serviceWithZIO[GroupsResponderADM](
_.createGroup(
groupRestService(
_.postGroup(
GroupCreateRequest(
id = Some(GroupIri.unsafeFrom(imagesReviewerGroup.id)),
name = groupName,
Expand All @@ -115,13 +115,13 @@ class GroupsResponderADMSpec extends CoreSpec {
status = GroupStatus.active,
selfjoin = GroupSelfJoin.disabled,
),
UUID.randomUUID,
rootUser,
),
),
)
assertFailsWithA[DuplicateValueException](
exit,
s"Group with the name '${groupName.value}' already exists",
s"Group with name: '${groupName.value}' already exists.",
)
}

Expand Down Expand Up @@ -200,7 +200,7 @@ class GroupsResponderADMSpec extends CoreSpec {
)
assertFailsWithA[BadRequestException](
exit,
s"Group with the name '${groupName.value}' already exists.",
s"Group with name: '${groupName.value}' already exists.",
)
}

Expand Down
11 changes: 11 additions & 0 deletions webapi/src/main/scala/org/knora/webapi/responders/IriService.scala
Expand Up @@ -15,6 +15,8 @@ import org.knora.webapi.messages.SmartIri
import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.messages.StringFormatter.MAX_IRI_ATTEMPTS
import org.knora.webapi.messages.twirl.queries.sparql
import org.knora.webapi.slice.admin.domain.model.GroupIri
import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode
import org.knora.webapi.slice.admin.domain.model.UserIri
import org.knora.webapi.slice.resourceinfo.domain.IriConverter
import org.knora.webapi.store.triplestore.api.TriplestoreService
Expand Down Expand Up @@ -71,6 +73,15 @@ final case class IriService(
.orElseFail(BadRequestException(s"Invalid User IRI: $userIriStr"))
} yield userIri

def checkOrCreateNewGroupIri(entityIri: Option[GroupIri], shortcode: Shortcode): Task[GroupIri] =
for {
iriToSmartIri <- ZIO.foreach(entityIri.map(_.value))(iriConverter.asSmartIri)
checkedCustomIriOrNewIri <- checkOrCreateEntityIri(iriToSmartIri, GroupIri.makeNew(shortcode).value)
iri <- ZIO
.fromEither(GroupIri.from(checkedCustomIriOrNewIri))
.orElseFail(BadRequestException(s"Invalid Group IRI: $checkedCustomIriOrNewIri"))
} yield iri

/**
* Checks whether an entity with the provided custom IRI exists in the triplestore. If yes, throws an exception.
* If no custom IRI was given, creates a random unused IRI.
Expand Down
Expand Up @@ -15,10 +15,8 @@ import org.knora.webapi._
import org.knora.webapi.core.MessageHandler
import org.knora.webapi.core.MessageRelay
import org.knora.webapi.messages.IriConversions._
import org.knora.webapi.messages.OntologyConstants
import org.knora.webapi.messages.OntologyConstants.KnoraAdmin._
import org.knora.webapi.messages.ResponderRequest
import org.knora.webapi.messages.SmartIri
import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.messages.admin.responder.groupsmessages._
import org.knora.webapi.messages.admin.responder.projectsmessages.Project
Expand All @@ -30,8 +28,6 @@ import org.knora.webapi.messages.util.KnoraSystemInstances
import org.knora.webapi.responders.IriLocker
import org.knora.webapi.responders.IriService
import org.knora.webapi.responders.Responder
import org.knora.webapi.slice.admin.AdminConstants
import org.knora.webapi.slice.admin.api.GroupsRequests.GroupCreateRequest
import org.knora.webapi.slice.admin.api.GroupsRequests.GroupStatusUpdateRequest
import org.knora.webapi.slice.admin.api.GroupsRequests.GroupUpdateRequest
import org.knora.webapi.slice.admin.domain.model
Expand Down Expand Up @@ -185,58 +181,6 @@ final case class GroupsResponderADM(
def groupMembersGetRequest(iri: GroupIri, user: User): Task[GroupMembersGetResponseADM] =
groupMembersGetADM(iri.value, user).map(GroupMembersGetResponseADM.apply)

/**
* Create a new group.
*
* @param request the create request information.
* @param apiRequestID the unique request ID.
* @return a [[GroupGetResponseADM]]
*/
def createGroup(
request: GroupCreateRequest,
apiRequestID: UUID,
): Task[GroupGetResponseADM] = {
val task = for {
nameExists <- groupByNameAndProjectExists(request.name.value, request.project.value)
_ <- ZIO
.fail(DuplicateValueException(s"Group with the name '${request.name.value}' already exists"))
.when(nameExists)

project <-
findProjectByIriOrFail(
request.project.value,
NotFoundException(s"Cannot create group inside project <${request.project}>. The project was not found."),
)

// check the custom IRI; if not given, create an unused IRI
customGroupIri: Option[SmartIri] = request.id.map(_.value).map(iri => iri.toSmartIri)
groupIri <- iriService.checkOrCreateEntityIri(customGroupIri, GroupIri.makeNew(project.getShortcode).value)

/* create the group */
createNewGroupSparqlString =
sparql.admin.txt
.createNewGroup(
AdminConstants.adminDataNamedGraph.value,
groupIri,
groupClassIri = OntologyConstants.KnoraAdmin.UserGroup,
name = request.name.value,
descriptions = request.descriptions.value,
projectIri = request.project.value,
status = request.status.value,
hasSelfJoinEnabled = request.selfjoin.value,
)

_ <- triplestore.query(Update(createNewGroupSparqlString))

/* Verify that the group was created and updated */
createdGroup <-
groupGetADM(groupIri)
.someOrFail(UpdateNotPerformedException("Group was not created. Please report this as a possible bug."))

} yield GroupGetResponseADM(createdGroup)
IriLocker.runWithIriLock(apiRequestID, "http://rdfh.ch/groups", task)
}

/**
* Change group's basic information.
*
Expand Down Expand Up @@ -324,7 +268,7 @@ final case class GroupsResponderADM(
} yield groupByNameAndProjectExists(name.value, project.id)
).getOrElse(ZIO.succeed(false))
_ <- ZIO
.fail(BadRequestException(s"Group with the name '${request.name.get.value}' already exists."))
.fail(BadRequestException(s"Group with name: '${request.name.get.value}' already exists."))
.when(groupByNameAlreadyExists)

/* Update group */
Expand Down
Expand Up @@ -17,14 +17,16 @@ import org.knora.webapi.slice.admin.api.GroupsRequests.GroupUpdateRequest
import org.knora.webapi.slice.admin.domain.model.GroupIri
import org.knora.webapi.slice.admin.domain.model.User
import org.knora.webapi.slice.admin.domain.service.GroupService
import org.knora.webapi.slice.admin.domain.service.KnoraProjectService
import org.knora.webapi.slice.common.api.AuthorizationRestService
import org.knora.webapi.slice.common.api.KnoraResponseRenderer

final case class GroupRestService(
auth: AuthorizationRestService,
format: KnoraResponseRenderer,
groupService: GroupService,
knoraProjectService: KnoraProjectService,
responder: GroupsResponderADM,
format: KnoraResponseRenderer,
) {

def getGroups: Task[GroupsGetResponseADM] = for {
Expand All @@ -49,9 +51,11 @@ final case class GroupRestService(

def postGroup(request: GroupCreateRequest, user: User): Task[GroupGetResponseADM] =
for {
_ <- auth.ensureSystemAdminOrProjectAdmin(user, request.project)
uuid <- Random.nextUUID
internal <- responder.createGroup(request, uuid)
_ <- auth.ensureSystemAdminOrProjectAdmin(user, request.project)
project <- knoraProjectService
.findById(request.project)
.someOrFail(NotFoundException(s"Project <${request.project}> not found."))
internal <- groupService.createGroup(request, project).map(GroupGetResponseADM.apply)
external <- format.toExternalADM(internal)
} yield external

Expand Down
Expand Up @@ -8,9 +8,11 @@ package org.knora.webapi.slice.admin.domain.service
import zio.ZIO
import zio._

import org.knora.webapi.slice.admin.api.GroupsRequests.GroupCreateRequest
import org.knora.webapi.slice.admin.domain.model.Group
import org.knora.webapi.slice.admin.domain.model.GroupIri
import org.knora.webapi.slice.admin.domain.model.KnoraGroup
import org.knora.webapi.slice.admin.domain.model.KnoraProject

final case class GroupService(
private val knoraGroupService: KnoraGroupService,
Expand All @@ -36,6 +38,9 @@ final case class GroupService(
status = knoraGroup.status.value,
selfjoin = knoraGroup.hasSelfJoinEnabled.value,
)

def createGroup(request: GroupCreateRequest, project: KnoraProject): Task[Group] =
knoraGroupService.createGroup(request, project).flatMap(toGroup)
}

object GroupService {
Expand Down
Expand Up @@ -19,6 +19,10 @@ import org.knora.webapi.slice.common.repo.service.Repository

trait KnoraGroupRepo extends Repository[KnoraGroup, GroupIri] {

def findByName(name: GroupName): Task[Option[KnoraGroup]]

def existsByName(name: GroupName): Task[Boolean] = findByName(name).map(_.isDefined)

/**
* Saves the user group, returns the created data. Updates not supported.
*
Expand Down
Expand Up @@ -7,12 +7,22 @@ package org.knora.webapi.slice.admin.domain.service

import zio.Chunk
import zio.Task
import zio.ZIO
import zio.ZLayer

import dsp.errors.DuplicateValueException
import org.knora.webapi.responders.IriService
import org.knora.webapi.slice.admin.api.GroupsRequests.GroupCreateRequest
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.KnoraGroup
import org.knora.webapi.slice.admin.domain.model.KnoraProject

case class KnoraGroupService(knoraGroupRepo: KnoraGroupRepo) {
case class KnoraGroupService(
knoraGroupRepo: KnoraGroupRepo,
knoraProjectRepo: KnoraProjectRepo,
iriService: IriService,
) {

def findAllRegularGroups(): Task[Chunk[KnoraGroup]] = findAll().map(_.filter(_.id.isRegularGroupIri))

Expand All @@ -21,6 +31,27 @@ case class KnoraGroupService(knoraGroupRepo: KnoraGroupRepo) {
def findById(id: GroupIri): Task[Option[KnoraGroup]] = knoraGroupRepo.findById(id)

def findByIds(ids: Seq[GroupIri]): Task[Chunk[KnoraGroup]] = knoraGroupRepo.findByIds(ids)

def createGroup(request: GroupCreateRequest, project: KnoraProject): Task[KnoraGroup] =
for {
_ <- ensureGroupNameIsUnique(request.name)
groupIri <- iriService.checkOrCreateNewGroupIri(request.id, project.shortcode)
group =
KnoraGroup(
id = groupIri,
groupName = request.name,
groupDescriptions = request.descriptions,
status = request.status,
belongsToProject = Some(project.id),
hasSelfJoinEnabled = request.selfjoin,
)
_ <- knoraGroupRepo.save(group)
} yield group

private def ensureGroupNameIsUnique(name: GroupName) =
ZIO.whenZIO(knoraGroupRepo.existsByName(name)) {
ZIO.fail(DuplicateValueException(s"Group with name: '${name.value}' already exists."))
}
}

object KnoraGroupService {
Expand Down
Expand Up @@ -53,6 +53,10 @@ final case class KnoraGroupRepoLive(triplestore: TriplestoreService, mapper: Rdf
.die(new IllegalArgumentException("Update not supported for built-in groups"))
.when(KnoraGroupRepo.builtIn.findOneBy(_.id == group.id).isDefined) *>
super.save(group)

override def findByName(name: GroupName): Task[Option[KnoraGroup]] =
findOneByTriplePattern(_.has(groupName, Rdf.literalOf(name.value)))
.map(_.orElse(KnoraGroupRepo.builtIn.findOneBy(_.groupName == name)))
}

object KnoraGroupRepoLive {
Expand Down
Expand Up @@ -12,7 +12,10 @@ import zio.test._

import dsp.errors.ForbiddenException
import org.knora.webapi.TestDataFactory
import org.knora.webapi.config.AppConfig
import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.messages.admin.responder.permissionsmessages.PermissionsDataADM
import org.knora.webapi.responders.IriService
import org.knora.webapi.slice.admin.domain.model.User
import org.knora.webapi.slice.admin.domain.repo.KnoraProjectRepoInMemory
import org.knora.webapi.slice.admin.domain.service.KnoraGroupRepo
Expand All @@ -21,7 +24,9 @@ import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo.builtIn.Syst
import org.knora.webapi.slice.admin.domain.service.KnoraProjectService
import org.knora.webapi.slice.admin.repo.service.KnoraGroupRepoInMemory
import org.knora.webapi.slice.common.api.AuthorizationRestService
import org.knora.webapi.slice.resourceinfo.domain.IriConverter
import org.knora.webapi.store.cache.CacheService
import org.knora.webapi.store.triplestore.impl.TriplestoreServiceLive

object AuthorizationRestServiceSpec extends ZIOSpecDefault {

Expand Down Expand Up @@ -132,5 +137,10 @@ object AuthorizationRestServiceSpec extends ZIOSpecDefault {
KnoraProjectRepoInMemory.layer,
KnoraGroupService.layer,
KnoraGroupRepoInMemory.layer,
IriService.layer,
IriConverter.layer,
TriplestoreServiceLive.layer,
StringFormatter.live,
AppConfig.layer,
)
}
Expand Up @@ -7,6 +7,7 @@ package org.knora.webapi.slice.admin.repo.service

import zio.Chunk
import zio.Ref
import zio.Task
import zio.ZIO
import zio.ZLayer
import zio.test.Gen
Expand All @@ -26,7 +27,9 @@ import org.knora.webapi.store.triplestore.api.TriplestoreServiceInMemory

final case class KnoraGroupRepoInMemory(groups: Ref[Chunk[KnoraGroup]])
extends AbstractInMemoryCrudRepository[KnoraGroup, GroupIri](groups, _.id)
with KnoraGroupRepo {}
with KnoraGroupRepo {
override def findByName(name: GroupName): Task[Option[KnoraGroup]] = ???
}

object KnoraGroupRepoInMemory {
val layer = ZLayer.fromZIO(Ref.make(Chunk.empty[KnoraGroup])) >>>
Expand Down

0 comments on commit a21a34f

Please sign in to comment.