mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-07 06:39:32 +00:00
Implement space operations
This commit is contained in:
parent
0e8c9b1819
commit
752a94a9e2
modules
backend/src/main/scala/docspell/backend/ops
joex/src/main/scala/docspell/joex/process
restapi/src/main/resources
restserver/src/main/scala/docspell/restserver
store/src/main/scala/docspell/store
webapp/src/main/elm/Comp
@ -4,81 +4,102 @@ import cats.effect._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.{AddResult, Store}
|
||||
import docspell.store.records.RSpace
|
||||
import docspell.store.records.{RSpace, RUser}
|
||||
import docspell.store.queries.QSpace
|
||||
|
||||
trait OSpace[F[_]] {
|
||||
|
||||
def findAll(account: AccountId, nameQuery: Option[String]): F[Vector[OSpace.SpaceItem]]
|
||||
def findAll(collective: Ident, nameQuery: Option[String]): F[Vector[OSpace.SpaceItem]]
|
||||
|
||||
def findById(id: Ident, collective: Ident): F[Option[OSpace.SpaceDetail]]
|
||||
|
||||
def delete(id: Ident, collective: Ident): F[Int]
|
||||
/** Adds a new space. If `login` is non-empty, the `space.user`
|
||||
* property is ignored and the user-id is determined by the given
|
||||
* login name.
|
||||
*/
|
||||
def add(space: RSpace, login: Option[Ident]): F[AddResult]
|
||||
|
||||
def add(space: RSpace): F[AddResult]
|
||||
|
||||
def changeName(space: Ident, account: AccountId, name: String): F[AddResult]
|
||||
def changeName(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
name: String
|
||||
): F[OSpace.SpaceChangeResult]
|
||||
|
||||
def addMember(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[OSpace.MemberChangeResult]
|
||||
): F[OSpace.SpaceChangeResult]
|
||||
|
||||
def removeMember(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[OSpace.MemberChangeResult]
|
||||
): F[OSpace.SpaceChangeResult]
|
||||
|
||||
def delete(id: Ident, account: AccountId): F[OSpace.SpaceChangeResult]
|
||||
}
|
||||
|
||||
object OSpace {
|
||||
|
||||
sealed trait MemberChangeResult
|
||||
object MemberChangeResult {
|
||||
case object Success extends MemberChangeResult
|
||||
case object NotFound extends MemberChangeResult
|
||||
case object Forbidden extends MemberChangeResult
|
||||
}
|
||||
type SpaceChangeResult = QSpace.SpaceChangeResult
|
||||
val SpaceChangeResult = QSpace.SpaceChangeResult
|
||||
|
||||
final case class SpaceItem(
|
||||
id: Ident,
|
||||
name: String,
|
||||
owner: IdRef,
|
||||
created: Timestamp,
|
||||
members: Int
|
||||
)
|
||||
type SpaceItem = QSpace.SpaceItem
|
||||
val SpaceItem = QSpace.SpaceItem
|
||||
|
||||
final case class SpaceDetail(
|
||||
id: Ident,
|
||||
name: String,
|
||||
owner: IdRef,
|
||||
created: Timestamp,
|
||||
members: List[IdRef]
|
||||
)
|
||||
type SpaceDetail = QSpace.SpaceDetail
|
||||
val SpaceDetail = QSpace.SpaceDetail
|
||||
|
||||
def apply[F[_]: Effect](store: Store[F]): Resource[F, OSpace[F]] =
|
||||
Resource.pure[F, OSpace[F]](new OSpace[F] {
|
||||
println(s"$store")
|
||||
def findAll(
|
||||
account: AccountId,
|
||||
collective: Ident,
|
||||
nameQuery: Option[String]
|
||||
): F[Vector[OSpace.SpaceItem]] = ???
|
||||
): F[Vector[SpaceItem]] =
|
||||
store.transact(QSpace.findAll(collective, None, nameQuery))
|
||||
|
||||
def findById(id: Ident, collective: Ident): F[Option[SpaceDetail]] =
|
||||
store.transact(QSpace.findById(id, collective))
|
||||
|
||||
def add(space: RSpace, login: Option[Ident]): F[AddResult] = {
|
||||
val insert = login match {
|
||||
case Some(n) =>
|
||||
for {
|
||||
user <- RUser.findByAccount(AccountId(space.collectiveId, n))
|
||||
s = user.map(u => space.copy(owner = u.uid)).getOrElse(space)
|
||||
n <- RSpace.insert(s)
|
||||
} yield n
|
||||
|
||||
case None =>
|
||||
RSpace.insert(space)
|
||||
}
|
||||
val exists = RSpace.existsByName(space.collectiveId, space.name)
|
||||
store.add(insert, exists)
|
||||
}
|
||||
|
||||
def changeName(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
name: String
|
||||
): F[SpaceChangeResult] =
|
||||
store.transact(QSpace.changeName(space, account, name))
|
||||
|
||||
def findById(id: Ident, collective: Ident): F[Option[OSpace.SpaceDetail]] = ???
|
||||
def add(space: RSpace): F[AddResult] = ???
|
||||
def changeName(space: Ident, account: AccountId, name: String): F[AddResult] = ???
|
||||
def delete(id: Ident, collective: Ident): F[Int] = ???
|
||||
def addMember(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[MemberChangeResult] =
|
||||
???
|
||||
): F[SpaceChangeResult] =
|
||||
store.transact(QSpace.addMember(space, account, member))
|
||||
|
||||
def removeMember(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[MemberChangeResult] =
|
||||
???
|
||||
): F[SpaceChangeResult] =
|
||||
store.transact(QSpace.removeMember(space, account, member))
|
||||
|
||||
def delete(id: Ident, account: AccountId): F[SpaceChangeResult] =
|
||||
store.transact(QSpace.delete(id, account))
|
||||
})
|
||||
}
|
||||
|
@ -251,7 +251,6 @@ object ExtractArchive {
|
||||
this
|
||||
case Some(nel) =>
|
||||
val sorted = nel.sorted
|
||||
println(s"---------------------------- $sorted ")
|
||||
val offset = sorted.head.first
|
||||
val pos =
|
||||
sorted.zipWithIndex.map({ case (p, i) => p.id -> (i + offset) }).toList.toMap
|
||||
|
@ -837,7 +837,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
$ref: "#/components/schemas/IdResult"
|
||||
/sec/space/{id}:
|
||||
get:
|
||||
tags: [ Space ]
|
||||
@ -2509,7 +2509,6 @@ components:
|
||||
- name
|
||||
- owner
|
||||
- created
|
||||
- members
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
@ -2521,9 +2520,6 @@ components:
|
||||
created:
|
||||
type: integer
|
||||
format: date-time
|
||||
members:
|
||||
type: integer
|
||||
format: int32
|
||||
NewSpace:
|
||||
description: |
|
||||
Data required to create a new space.
|
||||
@ -3844,6 +3840,22 @@ components:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
IdResult:
|
||||
description: |
|
||||
Some basic result of an operation with an ID as payload. If
|
||||
success if `false` the id is not usable.
|
||||
required:
|
||||
- success
|
||||
- message
|
||||
- id
|
||||
properties:
|
||||
success:
|
||||
type: boolean
|
||||
message:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
format: ident
|
||||
Tag:
|
||||
description: |
|
||||
A tag used to annotate items. A tag may have a category which
|
||||
|
@ -15,7 +15,7 @@ import docspell.common.syntax.all._
|
||||
import docspell.ftsclient.FtsResult
|
||||
import docspell.restapi.model._
|
||||
import docspell.restserver.conv.Conversions._
|
||||
import docspell.store.AddResult
|
||||
import docspell.store.{AddResult, UpdateResult}
|
||||
import docspell.store.records._
|
||||
|
||||
import bitpeace.FileMeta
|
||||
@ -537,6 +537,14 @@ trait Conversions {
|
||||
BasicResult(true, "The job has been removed from the queue.")
|
||||
}
|
||||
|
||||
def idResult(ar: AddResult, id: Ident, successMsg: String): IdResult =
|
||||
ar match {
|
||||
case AddResult.Success => IdResult(true, successMsg, id)
|
||||
case AddResult.EntityExists(msg) => IdResult(false, msg, Ident.unsafe(""))
|
||||
case AddResult.Failure(ex) =>
|
||||
IdResult(false, s"Internal error: ${ex.getMessage}", Ident.unsafe(""))
|
||||
}
|
||||
|
||||
def basicResult(ar: AddResult, successMsg: String): BasicResult =
|
||||
ar match {
|
||||
case AddResult.Success => BasicResult(true, successMsg)
|
||||
@ -545,6 +553,14 @@ trait Conversions {
|
||||
BasicResult(false, s"Internal error: ${ex.getMessage}")
|
||||
}
|
||||
|
||||
def basicResult(ar: UpdateResult, successMsg: String): BasicResult =
|
||||
ar match {
|
||||
case UpdateResult.Success => BasicResult(true, successMsg)
|
||||
case UpdateResult.NotFound => BasicResult(false, "Not found")
|
||||
case UpdateResult.Failure(ex) =>
|
||||
BasicResult(false, s"Internal error: ${ex.getMessage}")
|
||||
}
|
||||
|
||||
def basicResult(ur: OUpload.UploadResult): BasicResult =
|
||||
ur match {
|
||||
case UploadResult.Success => BasicResult(true, "Files submitted.")
|
||||
|
@ -27,16 +27,16 @@ object SpaceRoutes {
|
||||
HttpRoutes.of {
|
||||
case GET -> Root :? QueryParam.QueryOpt(q) =>
|
||||
for {
|
||||
all <- backend.space.findAll(user.account, q.map(_.q))
|
||||
all <- backend.space.findAll(user.account.collective, q.map(_.q))
|
||||
resp <- Ok(SpaceList(all.map(mkSpace).toList))
|
||||
} yield resp
|
||||
|
||||
case req @ POST -> Root =>
|
||||
for {
|
||||
data <- req.as[NewSpace]
|
||||
tag <- newSpace(data, user.account)
|
||||
res <- backend.space.add(tag)
|
||||
resp <- Ok(Conversions.basicResult(res, "Space successfully created."))
|
||||
data <- req.as[NewSpace]
|
||||
nspace <- newSpace(data, user.account)
|
||||
res <- backend.space.add(nspace, Some(user.account.user))
|
||||
resp <- Ok(Conversions.idResult(res, nspace.id, "Space successfully created."))
|
||||
} yield resp
|
||||
|
||||
case GET -> Root / Ident(id) =>
|
||||
@ -49,28 +49,25 @@ object SpaceRoutes {
|
||||
for {
|
||||
data <- req.as[NewSpace]
|
||||
res <- backend.space.changeName(id, user.account, data.name)
|
||||
resp <- Ok(Conversions.basicResult(res, "Space successfully updated."))
|
||||
resp <- Ok(mkSpaceChangeResult(res))
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
for {
|
||||
del <- backend.space.delete(id, user.account.collective)
|
||||
resp <- Ok(
|
||||
if (del > 0) BasicResult(true, "Successfully deleted space")
|
||||
else BasicResult(false, "Could not delete space")
|
||||
)
|
||||
res <- backend.space.delete(id, user.account)
|
||||
resp <- Ok(mkSpaceChangeResult(res))
|
||||
} yield resp
|
||||
|
||||
case PUT -> Root / Ident(id) / "member" / Ident(userId) =>
|
||||
for {
|
||||
res <- backend.space.addMember(id, user.account, userId)
|
||||
resp <- Ok(mkMemberResult(res))
|
||||
resp <- Ok(mkSpaceChangeResult(res))
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) / "member" / Ident(userId) =>
|
||||
for {
|
||||
res <- backend.space.removeMember(id, user.account, userId)
|
||||
resp <- Ok(mkMemberResult(res))
|
||||
resp <- Ok(mkSpaceChangeResult(res))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
@ -83,8 +80,7 @@ object SpaceRoutes {
|
||||
item.id,
|
||||
item.name,
|
||||
Conversions.mkIdName(item.owner),
|
||||
item.created,
|
||||
item.members
|
||||
item.created
|
||||
)
|
||||
|
||||
private def mkSpaceDetail(item: OSpace.SpaceDetail): SpaceDetail =
|
||||
@ -96,13 +92,15 @@ object SpaceRoutes {
|
||||
item.members.map(Conversions.mkIdName)
|
||||
)
|
||||
|
||||
private def mkMemberResult(r: OSpace.MemberChangeResult): BasicResult =
|
||||
private def mkSpaceChangeResult(r: OSpace.SpaceChangeResult): BasicResult =
|
||||
r match {
|
||||
case OSpace.MemberChangeResult.Success =>
|
||||
BasicResult(true, "Successfully changed space")
|
||||
case OSpace.MemberChangeResult.NotFound =>
|
||||
BasicResult(false, "Space or user not found")
|
||||
case OSpace.MemberChangeResult.Forbidden =>
|
||||
BasicResult(false, "Not allowed to edit space")
|
||||
case OSpace.SpaceChangeResult.Success =>
|
||||
BasicResult(true, "Successfully changed space.")
|
||||
case OSpace.SpaceChangeResult.NotFound =>
|
||||
BasicResult(false, "Space or user not found.")
|
||||
case OSpace.SpaceChangeResult.Forbidden =>
|
||||
BasicResult(false, "Not allowed to edit space.")
|
||||
case OSpace.SpaceChangeResult.Exists =>
|
||||
BasicResult(false, "The member already exists.")
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package docspell.store
|
||||
|
||||
import cats.implicits._
|
||||
import cats.ApplicativeError
|
||||
|
||||
sealed trait UpdateResult
|
||||
|
||||
object UpdateResult {
|
||||
|
||||
case object Success extends UpdateResult
|
||||
case object NotFound extends UpdateResult
|
||||
final case class Failure(ex: Throwable) extends UpdateResult
|
||||
|
||||
def success: UpdateResult = Success
|
||||
def notFound: UpdateResult = NotFound
|
||||
def failure(ex: Throwable): UpdateResult = Failure(ex)
|
||||
|
||||
def fromUpdateRows(n: Int): UpdateResult =
|
||||
if (n > 0) success
|
||||
else notFound
|
||||
|
||||
def fromUpdate[F[_]](
|
||||
fn: F[Int]
|
||||
)(implicit ev: ApplicativeError[F, Throwable]): F[UpdateResult] =
|
||||
fn.attempt.map {
|
||||
case Right(n) => fromUpdateRows(n)
|
||||
case Left(ex) => failure(ex)
|
||||
}
|
||||
}
|
188
modules/store/src/main/scala/docspell/store/queries/QSpace.scala
Normal file
188
modules/store/src/main/scala/docspell/store/queries/QSpace.scala
Normal file
@ -0,0 +1,188 @@
|
||||
package docspell.store.queries
|
||||
|
||||
import cats.data.OptionT
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.impl.Implicits._
|
||||
import docspell.store.records._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
object QSpace {
|
||||
|
||||
final case class SpaceItem(
|
||||
id: Ident,
|
||||
name: String,
|
||||
owner: IdRef,
|
||||
created: Timestamp
|
||||
) {
|
||||
def withMembers(members: List[IdRef]): SpaceDetail =
|
||||
SpaceDetail(id, name, owner, created, members)
|
||||
}
|
||||
|
||||
final case class SpaceDetail(
|
||||
id: Ident,
|
||||
name: String,
|
||||
owner: IdRef,
|
||||
created: Timestamp,
|
||||
members: List[IdRef]
|
||||
)
|
||||
|
||||
sealed trait SpaceChangeResult
|
||||
object SpaceChangeResult {
|
||||
case object Success extends SpaceChangeResult
|
||||
def success: SpaceChangeResult = Success
|
||||
case object NotFound extends SpaceChangeResult
|
||||
def notFound: SpaceChangeResult = NotFound
|
||||
case object Forbidden extends SpaceChangeResult
|
||||
def forbidden: SpaceChangeResult = Forbidden
|
||||
case object Exists extends SpaceChangeResult
|
||||
def exists: SpaceChangeResult = Exists
|
||||
}
|
||||
|
||||
def delete(id: Ident, account: AccountId): ConnectionIO[SpaceChangeResult] = {
|
||||
def tryDelete =
|
||||
for {
|
||||
_ <- RItem.removeSpace(id)
|
||||
_ <- RSpaceMember.deleteAll(id)
|
||||
_ <- RSpace.delete(id)
|
||||
} yield SpaceChangeResult.success
|
||||
|
||||
(for {
|
||||
uid <- OptionT(findUserId(account))
|
||||
space <- OptionT(RSpace.findById(id))
|
||||
res <- OptionT.liftF(
|
||||
if (space.owner == uid) tryDelete
|
||||
else SpaceChangeResult.forbidden.pure[ConnectionIO]
|
||||
)
|
||||
} yield res).getOrElse(SpaceChangeResult.notFound)
|
||||
}
|
||||
|
||||
def changeName(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
name: String
|
||||
): ConnectionIO[SpaceChangeResult] = {
|
||||
def tryUpdate(ns: RSpace): ConnectionIO[SpaceChangeResult] =
|
||||
for {
|
||||
n <- RSpace.update(ns)
|
||||
res =
|
||||
if (n == 0) SpaceChangeResult.notFound
|
||||
else SpaceChangeResult.Success
|
||||
} yield res
|
||||
|
||||
(for {
|
||||
uid <- OptionT(findUserId(account))
|
||||
space <- OptionT(RSpace.findById(space))
|
||||
res <- OptionT.liftF(
|
||||
if (space.owner == uid) tryUpdate(space.copy(name = name))
|
||||
else SpaceChangeResult.forbidden.pure[ConnectionIO]
|
||||
)
|
||||
} yield res).getOrElse(SpaceChangeResult.notFound)
|
||||
}
|
||||
|
||||
def removeMember(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): ConnectionIO[SpaceChangeResult] = {
|
||||
def tryRemove: ConnectionIO[SpaceChangeResult] =
|
||||
for {
|
||||
n <- RSpaceMember.delete(member, space)
|
||||
res =
|
||||
if (n == 0) SpaceChangeResult.notFound
|
||||
else SpaceChangeResult.Success
|
||||
} yield res
|
||||
|
||||
(for {
|
||||
uid <- OptionT(findUserId(account))
|
||||
space <- OptionT(RSpace.findById(space))
|
||||
res <- OptionT.liftF(
|
||||
if (space.owner == uid) tryRemove
|
||||
else SpaceChangeResult.forbidden.pure[ConnectionIO]
|
||||
)
|
||||
} yield res).getOrElse(SpaceChangeResult.notFound)
|
||||
}
|
||||
|
||||
def addMember(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): ConnectionIO[SpaceChangeResult] = {
|
||||
def tryAdd: ConnectionIO[SpaceChangeResult] =
|
||||
for {
|
||||
spm <- RSpaceMember.findByUserId(member, space)
|
||||
mem <- RSpaceMember.newMember[ConnectionIO](space, member)
|
||||
res <-
|
||||
if (spm.isDefined) SpaceChangeResult.exists.pure[ConnectionIO]
|
||||
else RSpaceMember.insert(mem).map(_ => SpaceChangeResult.Success)
|
||||
} yield res
|
||||
|
||||
(for {
|
||||
uid <- OptionT(findUserId(account))
|
||||
space <- OptionT(RSpace.findById(space))
|
||||
res <- OptionT.liftF(
|
||||
if (space.owner == uid) tryAdd
|
||||
else SpaceChangeResult.forbidden.pure[ConnectionIO]
|
||||
)
|
||||
} yield res).getOrElse(SpaceChangeResult.notFound)
|
||||
}
|
||||
|
||||
def findById(id: Ident, collective: Ident): ConnectionIO[Option[SpaceDetail]] = {
|
||||
val mUserId = RSpaceMember.Columns.user.prefix("m")
|
||||
val mSpaceId = RSpaceMember.Columns.space.prefix("m")
|
||||
val uId = RUser.Columns.uid.prefix("u")
|
||||
val uLogin = RUser.Columns.login.prefix("u")
|
||||
val sColl = RSpace.Columns.collective.prefix("s")
|
||||
val sId = RSpace.Columns.id.prefix("s")
|
||||
|
||||
val from = RSpaceMember.table ++ fr"m INNER JOIN" ++
|
||||
RUser.table ++ fr"u ON" ++ mUserId.is(uId) ++ fr"INNER JOIN" ++
|
||||
RSpace.table ++ fr"s ON" ++ mSpaceId.is(sId)
|
||||
|
||||
val memberQ = selectSimple(
|
||||
Seq(uId, uLogin),
|
||||
from,
|
||||
and(mSpaceId.is(id), sColl.is(collective))
|
||||
).query[IdRef].to[Vector]
|
||||
|
||||
(for {
|
||||
space <- OptionT(findAll(collective, Some(id), None).map(_.headOption))
|
||||
memb <- OptionT.liftF(memberQ)
|
||||
} yield space.withMembers(memb.toList)).value
|
||||
}
|
||||
|
||||
def findAll(
|
||||
collective: Ident,
|
||||
idQ: Option[Ident],
|
||||
nameQ: Option[String]
|
||||
): ConnectionIO[Vector[SpaceItem]] = {
|
||||
val uId = RUser.Columns.uid.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 cols = Seq(
|
||||
sId,
|
||||
sName,
|
||||
uId,
|
||||
RUser.Columns.login.prefix("u"),
|
||||
RSpace.Columns.created.prefix("s")
|
||||
)
|
||||
|
||||
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 =>
|
||||
sName.lowerLike(s"%${q.toLowerCase}%")
|
||||
)
|
||||
|
||||
selectSimple(cols, from, and(where) ++ orderBy(sName.asc)).query[SpaceItem].to[Vector]
|
||||
}
|
||||
|
||||
private def findUserId(account: AccountId): ConnectionIO[Option[Ident]] =
|
||||
RUser.findByAccount(account).map(_.map(_.uid))
|
||||
}
|
@ -299,4 +299,9 @@ object RItem {
|
||||
|
||||
def findByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Option[RItem]] =
|
||||
selectSimple(all, table, and(id.is(itemId), cid.is(coll))).query[RItem].option
|
||||
|
||||
def removeSpace(spaceId: Ident): ConnectionIO[Int] = {
|
||||
val empty: Option[Ident] = None
|
||||
updateRow(table, space.is(spaceId), space.setTo(empty)).update.run
|
||||
}
|
||||
}
|
||||
|
@ -80,4 +80,6 @@ object RSpace {
|
||||
sql.query[RSpace].to[Vector]
|
||||
}
|
||||
|
||||
def delete(spaceId: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, id.is(spaceId)).update.run
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
package docspell.store.records
|
||||
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.impl.Column
|
||||
import docspell.store.impl.Implicits._
|
||||
@ -16,7 +19,13 @@ case class RSpaceMember(
|
||||
|
||||
object RSpaceMember {
|
||||
|
||||
val table = fr"space"
|
||||
def newMember[F[_]: Sync](space: Ident, user: Ident): F[RSpaceMember] =
|
||||
for {
|
||||
nId <- Ident.randomId[F]
|
||||
now <- Timestamp.current[F]
|
||||
} yield RSpaceMember(nId, space, user, now)
|
||||
|
||||
val table = fr"space_member"
|
||||
|
||||
object Columns {
|
||||
|
||||
@ -39,4 +48,14 @@ object RSpaceMember {
|
||||
sql.update.run
|
||||
}
|
||||
|
||||
def findByUserId(userId: Ident, spaceId: Ident): ConnectionIO[Option[RSpaceMember]] =
|
||||
selectSimple(all, table, and(space.is(spaceId), user.is(userId)))
|
||||
.query[RSpaceMember]
|
||||
.option
|
||||
|
||||
def delete(userId: Ident, spaceId: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, and(space.is(spaceId), user.is(userId))).update.run
|
||||
|
||||
def deleteAll(spaceId: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, space.is(spaceId)).update.run
|
||||
}
|
||||
|
@ -48,7 +48,6 @@ view _ items =
|
||||
[ th [ class "collapsing" ] []
|
||||
, th [] [ text "Name" ]
|
||||
, th [] [ text "Owner" ]
|
||||
, th [] [ text "Members" ]
|
||||
, th [] [ text "Created" ]
|
||||
]
|
||||
, tbody []
|
||||
@ -78,10 +77,6 @@ viewItem item =
|
||||
, td []
|
||||
[ text item.owner.name
|
||||
]
|
||||
, td []
|
||||
[ String.fromInt item.members
|
||||
|> text
|
||||
]
|
||||
, td []
|
||||
[ Util.Time.formatDateShort item.created
|
||||
|> text
|
||||
|
Loading…
x
Reference in New Issue
Block a user