From 60a08fc78630fbdc9241fdc688aff9131c37826c Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Fri, 10 Jul 2020 00:46:14 +0200 Subject: [PATCH] Return member count and if current user is owner or member --- .../scala/docspell/backend/ops/OSpace.scala | 12 +-- .../src/main/resources/docspell-openapi.yml | 14 +++ .../restserver/conv/Conversions.scala | 2 +- .../restserver/routes/SpaceRoutes.scala | 12 ++- .../scala/docspell/store/queries/QSpace.scala | 85 ++++++++++++++++--- 5 files changed, 101 insertions(+), 24 deletions(-) diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OSpace.scala b/modules/backend/src/main/scala/docspell/backend/ops/OSpace.scala index efec5b46..25ec9e20 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OSpace.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OSpace.scala @@ -10,12 +10,12 @@ import docspell.store.queries.QSpace trait OSpace[F[_]] { def findAll( - collective: Ident, + account: AccountId, ownerLogin: Option[Ident], nameQuery: Option[String] ): F[Vector[OSpace.SpaceItem]] - def findById(id: Ident, collective: Ident): F[Option[OSpace.SpaceDetail]] + def findById(id: Ident, account: AccountId): F[Option[OSpace.SpaceDetail]] /** Adds a new space. If `login` is non-empty, the `space.user` * property is ignored and the user-id is determined by the given @@ -58,14 +58,14 @@ object OSpace { def apply[F[_]: Effect](store: Store[F]): Resource[F, OSpace[F]] = Resource.pure[F, OSpace[F]](new OSpace[F] { def findAll( - collective: Ident, + account: AccountId, ownerLogin: Option[Ident], nameQuery: Option[String] ): F[Vector[SpaceItem]] = - store.transact(QSpace.findAll(collective, None, ownerLogin, nameQuery)) + store.transact(QSpace.findAll(account, None, ownerLogin, nameQuery)) - def findById(id: Ident, collective: Ident): F[Option[SpaceDetail]] = - store.transact(QSpace.findById(id, collective)) + def findById(id: Ident, account: AccountId): F[Option[SpaceDetail]] = + store.transact(QSpace.findById(id, account)) def add(space: RSpace, login: Option[Ident]): F[AddResult] = { val insert = login match { diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index fd352df6..75ed64ad 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -2510,6 +2510,8 @@ components: - name - owner - created + - isMember + - memberCount properties: id: type: string @@ -2521,6 +2523,11 @@ components: created: type: integer format: date-time + isMember: + type: boolean + memberCount: + type: integer + format: int32 NewSpace: description: | Data required to create a new space. @@ -2537,6 +2544,8 @@ components: - name - owner - created + - isMember + - memberCount - members properties: id: @@ -2549,6 +2558,11 @@ components: created: type: integer format: date-time + isMember: + type: boolean + memberCount: + type: integer + format: int32 members: type: array items: diff --git a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala index 19997c74..271fae25 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/conv/Conversions.scala @@ -15,8 +15,8 @@ import docspell.common.syntax.all._ import docspell.ftsclient.FtsResult import docspell.restapi.model._ import docspell.restserver.conv.Conversions._ -import docspell.store.{AddResult, UpdateResult} import docspell.store.records._ +import docspell.store.{AddResult, UpdateResult} import bitpeace.FileMeta import org.http4s.headers.`Content-Type` diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/SpaceRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/SpaceRoutes.scala index b51fefa0..71c0a916 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/SpaceRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/SpaceRoutes.scala @@ -8,10 +8,10 @@ import docspell.backend.BackendApp import docspell.backend.auth.AuthToken import docspell.backend.ops.OSpace import docspell.common._ -import docspell.store.records.RSpace import docspell.restapi.model._ import docspell.restserver.conv.Conversions import docspell.restserver.http4s._ +import docspell.store.records.RSpace import org.http4s.HttpRoutes import org.http4s.circe.CirceEntityDecoder._ @@ -29,7 +29,7 @@ object SpaceRoutes { val login = owning.filter(identity).map(_ => user.account.user) for { - all <- backend.space.findAll(user.account.collective, login, q.map(_.q)) + all <- backend.space.findAll(user.account, login, q.map(_.q)) resp <- Ok(SpaceList(all.map(mkSpace).toList)) } yield resp @@ -43,7 +43,7 @@ object SpaceRoutes { case GET -> Root / Ident(id) => (for { - space <- OptionT(backend.space.findById(id, user.account.collective)) + space <- OptionT(backend.space.findById(id, user.account)) resp <- OptionT.liftF(Ok(mkSpaceDetail(space))) } yield resp).getOrElseF(NotFound()) @@ -82,7 +82,9 @@ object SpaceRoutes { item.id, item.name, Conversions.mkIdName(item.owner), - item.created + item.created, + item.member, + item.memberCount ) private def mkSpaceDetail(item: OSpace.SpaceDetail): SpaceDetail = @@ -91,6 +93,8 @@ object SpaceRoutes { item.name, Conversions.mkIdName(item.owner), item.created, + item.member, + item.memberCount, item.members.map(Conversions.mkIdName) ) diff --git a/modules/store/src/main/scala/docspell/store/queries/QSpace.scala b/modules/store/src/main/scala/docspell/store/queries/QSpace.scala index 696ad9fe..8a840506 100644 --- a/modules/store/src/main/scala/docspell/store/queries/QSpace.scala +++ b/modules/store/src/main/scala/docspell/store/queries/QSpace.scala @@ -16,10 +16,12 @@ object QSpace { id: Ident, name: String, owner: IdRef, - created: Timestamp + created: Timestamp, + member: Boolean, + memberCount: Int ) { def withMembers(members: List[IdRef]): SpaceDetail = - SpaceDetail(id, name, owner, created, members) + SpaceDetail(id, name, owner, created, member, memberCount, members) } final case class SpaceDetail( @@ -27,6 +29,8 @@ object QSpace { name: String, owner: IdRef, created: Timestamp, + member: Boolean, + memberCount: Int, members: List[IdRef] ) @@ -130,7 +134,7 @@ object QSpace { } yield res).getOrElse(SpaceChangeResult.notFound) } - def findById(id: Ident, collective: Ident): ConnectionIO[Option[SpaceDetail]] = { + def findById(id: Ident, account: AccountId): ConnectionIO[Option[SpaceDetail]] = { val mUserId = RSpaceMember.Columns.user.prefix("m") val mSpaceId = RSpaceMember.Columns.space.prefix("m") val uId = RUser.Columns.uid.prefix("u") @@ -145,44 +149,99 @@ object QSpace { val memberQ = selectSimple( Seq(uId, uLogin), from, - and(mSpaceId.is(id), sColl.is(collective)) + and(mSpaceId.is(id), sColl.is(account.collective)) ).query[IdRef].to[Vector] (for { - space <- OptionT(findAll(collective, Some(id), None, None).map(_.headOption)) + space <- OptionT(findAll(account, Some(id), None, None).map(_.headOption)) memb <- OptionT.liftF(memberQ) } yield space.withMembers(memb.toList)).value } def findAll( - collective: Ident, + account: AccountId, idQ: Option[Ident], ownerLogin: Option[Ident], nameQ: Option[String] ): ConnectionIO[Vector[SpaceItem]] = { +// with memberlogin as +// (select m.space_id,u.login +// from space_member m +// inner join user_ u on u.uid = m.user_id +// inner join space s on s.id = m.space_id +// where s.cid = 'eike' +// union all +// select s.id,u.login +// from space s +// inner join user_ u on u.uid = s.owner +// where s.cid = 'eike') +// select s.id +// ,s.name +// ,s.owner +// ,u.login +// ,s.created +// ,(select count(*) > 0 from memberlogin where space_id = s.id and login = 'eike') as member +// ,(select count(*) - 1 from memberlogin where space_id = s.id) as member_count +// from space s +// inner join user_ u on u.uid = s.owner +// where s.cid = 'eike'; + val uId = RUser.Columns.uid.prefix("u") val uLogin = RUser.Columns.login.prefix("u") val sId = RSpace.Columns.id.prefix("s") val sOwner = RSpace.Columns.owner.prefix("s") val sName = RSpace.Columns.name.prefix("s") val sColl = RSpace.Columns.collective.prefix("s") + val mUser = RSpaceMember.Columns.user.prefix("m") + val mSpace = RSpaceMember.Columns.space.prefix("m") + + //CTE + val cte: Fragment = { + val from1 = RSpaceMember.table ++ fr"m INNER JOIN" ++ + RUser.table ++ fr"u ON" ++ uId.is(mUser) ++ fr"INNER JOIN" ++ + RSpace.table ++ fr"s ON" ++ sId.is(mSpace) + + val from2 = RSpace.table ++ fr"s INNER JOIN" ++ + RUser.table ++ fr"u ON" ++ uId.is(sOwner) + + withCTE( + "memberlogin" -> + (selectSimple(Seq(mSpace, uLogin), from1, sColl.is(account.collective)) ++ + fr"UNION ALL" ++ + selectSimple(Seq(sId, uLogin), from2, sColl.is(account.collective))) + ) + } + + val isMember = + fr"SELECT COUNT(*) > 0 FROM memberlogin WHERE" ++ mSpace.prefix("").is(sId) ++ + fr"AND" ++ uLogin.prefix("").is(account.user) + + val memberCount = + fr"SELECT COUNT(*) - 1 FROM memberlogin WHERE" ++ mSpace.prefix("").is(sId) + + //Query val cols = Seq( - sId, - sName, - uId, - RUser.Columns.login.prefix("u"), - RSpace.Columns.created.prefix("s") + sId.f, + sName.f, + sOwner.f, + uLogin.f, + RSpace.Columns.created.prefix("s").f, + fr"(" ++ isMember ++ fr") as mem", + fr"(" ++ memberCount ++ fr") as cnt" ) val from = RSpace.table ++ fr"s INNER JOIN" ++ RUser.table ++ fr"u ON" ++ uId.is(sOwner) val where = - sColl.is(collective) :: idQ.toList.map(id => sId.is(id)) ::: nameQ.toList.map(q => + sColl.is(account.collective) :: idQ.toList + .map(id => sId.is(id)) ::: nameQ.toList.map(q => sName.lowerLike(s"%${q.toLowerCase}%") ) ::: ownerLogin.toList.map(login => uLogin.is(login)) - selectSimple(cols, from, and(where) ++ orderBy(sName.asc)).query[SpaceItem].to[Vector] + (cte ++ selectSimple(commas(cols), from, and(where) ++ orderBy(sName.asc))) + .query[SpaceItem] + .to[Vector] } private def findUserId(account: AccountId): ConnectionIO[Option[Ident]] =