mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-04 18:39:33 +00:00
Rename space -> folder
This commit is contained in:
parent
0365c1980a
commit
2ab0b5e222
modules
backend/src/main/scala/docspell/backend
restapi/src/main/resources
restserver/src/main/scala/docspell/restserver
store/src/main
resources/db/migration/postgresql
scala/docspell/store
webapp/src/main/elm
@ -35,7 +35,7 @@ trait BackendApp[F[_]] {
|
||||
def mail: OMail[F]
|
||||
def joex: OJoex[F]
|
||||
def userTask: OUserTask[F]
|
||||
def space: OSpace[F]
|
||||
def folder: OFolder[F]
|
||||
}
|
||||
|
||||
object BackendApp {
|
||||
@ -68,7 +68,7 @@ object BackendApp {
|
||||
JavaMailEmil(blocker, Settings.defaultSettings.copy(debug = cfg.mailDebug))
|
||||
mailImpl <- OMail(store, javaEmil)
|
||||
userTaskImpl <- OUserTask(utStore, queue, joexImpl)
|
||||
spaceImpl <- OSpace(store)
|
||||
folderImpl <- OFolder(store)
|
||||
} yield new BackendApp[F] {
|
||||
val login: Login[F] = loginImpl
|
||||
val signup: OSignup[F] = signupImpl
|
||||
@ -86,7 +86,7 @@ object BackendApp {
|
||||
val mail = mailImpl
|
||||
val joex = joexImpl
|
||||
val userTask = userTaskImpl
|
||||
val space = spaceImpl
|
||||
val folder = folderImpl
|
||||
}
|
||||
|
||||
def apply[F[_]: ConcurrentEffect: ContextShift](
|
||||
|
@ -0,0 +1,110 @@
|
||||
package docspell.backend.ops
|
||||
|
||||
import cats.effect._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.{AddResult, Store}
|
||||
import docspell.store.records.{RFolder, RUser}
|
||||
import docspell.store.queries.QFolder
|
||||
|
||||
trait OFolder[F[_]] {
|
||||
|
||||
def findAll(
|
||||
account: AccountId,
|
||||
ownerLogin: Option[Ident],
|
||||
nameQuery: Option[String]
|
||||
): F[Vector[OFolder.FolderItem]]
|
||||
|
||||
def findById(id: Ident, account: AccountId): F[Option[OFolder.FolderDetail]]
|
||||
|
||||
/** Adds a new folder. If `login` is non-empty, the `folder.user`
|
||||
* property is ignored and the user-id is determined by the given
|
||||
* login name.
|
||||
*/
|
||||
def add(folder: RFolder, login: Option[Ident]): F[AddResult]
|
||||
|
||||
def changeName(
|
||||
folder: Ident,
|
||||
account: AccountId,
|
||||
name: String
|
||||
): F[OFolder.FolderChangeResult]
|
||||
|
||||
def addMember(
|
||||
folder: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[OFolder.FolderChangeResult]
|
||||
|
||||
def removeMember(
|
||||
folder: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[OFolder.FolderChangeResult]
|
||||
|
||||
def delete(id: Ident, account: AccountId): F[OFolder.FolderChangeResult]
|
||||
}
|
||||
|
||||
object OFolder {
|
||||
|
||||
type FolderChangeResult = QFolder.FolderChangeResult
|
||||
val FolderChangeResult = QFolder.FolderChangeResult
|
||||
|
||||
type FolderItem = QFolder.FolderItem
|
||||
val FolderItem = QFolder.FolderItem
|
||||
|
||||
type FolderDetail = QFolder.FolderDetail
|
||||
val FolderDetail = QFolder.FolderDetail
|
||||
|
||||
def apply[F[_]: Effect](store: Store[F]): Resource[F, OFolder[F]] =
|
||||
Resource.pure[F, OFolder[F]](new OFolder[F] {
|
||||
def findAll(
|
||||
account: AccountId,
|
||||
ownerLogin: Option[Ident],
|
||||
nameQuery: Option[String]
|
||||
): F[Vector[FolderItem]] =
|
||||
store.transact(QFolder.findAll(account, None, ownerLogin, nameQuery))
|
||||
|
||||
def findById(id: Ident, account: AccountId): F[Option[FolderDetail]] =
|
||||
store.transact(QFolder.findById(id, account))
|
||||
|
||||
def add(folder: RFolder, login: Option[Ident]): F[AddResult] = {
|
||||
val insert = login match {
|
||||
case Some(n) =>
|
||||
for {
|
||||
user <- RUser.findByAccount(AccountId(folder.collectiveId, n))
|
||||
s = user.map(u => folder.copy(owner = u.uid)).getOrElse(folder)
|
||||
n <- RFolder.insert(s)
|
||||
} yield n
|
||||
|
||||
case None =>
|
||||
RFolder.insert(folder)
|
||||
}
|
||||
val exists = RFolder.existsByName(folder.collectiveId, folder.name)
|
||||
store.add(insert, exists)
|
||||
}
|
||||
|
||||
def changeName(
|
||||
folder: Ident,
|
||||
account: AccountId,
|
||||
name: String
|
||||
): F[FolderChangeResult] =
|
||||
store.transact(QFolder.changeName(folder, account, name))
|
||||
|
||||
def addMember(
|
||||
folder: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[FolderChangeResult] =
|
||||
store.transact(QFolder.addMember(folder, account, member))
|
||||
|
||||
def removeMember(
|
||||
folder: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[FolderChangeResult] =
|
||||
store.transact(QFolder.removeMember(folder, account, member))
|
||||
|
||||
def delete(id: Ident, account: AccountId): F[FolderChangeResult] =
|
||||
store.transact(QFolder.delete(id, account))
|
||||
})
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
package docspell.backend.ops
|
||||
|
||||
import cats.effect._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.{AddResult, Store}
|
||||
import docspell.store.records.{RSpace, RUser}
|
||||
import docspell.store.queries.QSpace
|
||||
|
||||
trait OSpace[F[_]] {
|
||||
|
||||
def findAll(
|
||||
account: AccountId,
|
||||
ownerLogin: Option[Ident],
|
||||
nameQuery: Option[String]
|
||||
): F[Vector[OSpace.SpaceItem]]
|
||||
|
||||
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
|
||||
* login name.
|
||||
*/
|
||||
def add(space: RSpace, login: Option[Ident]): F[AddResult]
|
||||
|
||||
def changeName(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
name: String
|
||||
): F[OSpace.SpaceChangeResult]
|
||||
|
||||
def addMember(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[OSpace.SpaceChangeResult]
|
||||
|
||||
def removeMember(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[OSpace.SpaceChangeResult]
|
||||
|
||||
def delete(id: Ident, account: AccountId): F[OSpace.SpaceChangeResult]
|
||||
}
|
||||
|
||||
object OSpace {
|
||||
|
||||
type SpaceChangeResult = QSpace.SpaceChangeResult
|
||||
val SpaceChangeResult = QSpace.SpaceChangeResult
|
||||
|
||||
type SpaceItem = QSpace.SpaceItem
|
||||
val SpaceItem = QSpace.SpaceItem
|
||||
|
||||
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] {
|
||||
def findAll(
|
||||
account: AccountId,
|
||||
ownerLogin: Option[Ident],
|
||||
nameQuery: Option[String]
|
||||
): F[Vector[SpaceItem]] =
|
||||
store.transact(QSpace.findAll(account, None, ownerLogin, nameQuery))
|
||||
|
||||
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 {
|
||||
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 addMember(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[SpaceChangeResult] =
|
||||
store.transact(QSpace.addMember(space, account, member))
|
||||
|
||||
def removeMember(
|
||||
space: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): F[SpaceChangeResult] =
|
||||
store.transact(QSpace.removeMember(space, account, member))
|
||||
|
||||
def delete(id: Ident, account: AccountId): F[SpaceChangeResult] =
|
||||
store.transact(QSpace.delete(id, account))
|
||||
})
|
||||
}
|
@ -795,14 +795,14 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
/sec/space:
|
||||
/sec/folder:
|
||||
get:
|
||||
tags: [ Space ]
|
||||
summary: Get a list of spaces.
|
||||
tags: [ Folder ]
|
||||
summary: Get a list of folders.
|
||||
description: |
|
||||
Return a list of spaces for the current collective.
|
||||
Return a list of folders for the current collective.
|
||||
|
||||
All spaces are returned, including those not owned by the
|
||||
All folders are returned, including those not owned by the
|
||||
current user.
|
||||
|
||||
It is possible to restrict the results by a substring match of
|
||||
@ -818,12 +818,12 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SpaceList"
|
||||
$ref: "#/components/schemas/FolderList"
|
||||
post:
|
||||
tags: [ Space ]
|
||||
summary: Create a new space
|
||||
tags: [ Folder ]
|
||||
summary: Create a new folder
|
||||
description: |
|
||||
Create a new space owned by the current user. If a space with
|
||||
Create a new folder owned by the current user. If a folder with
|
||||
the same name already exists, an error is thrown.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
@ -831,7 +831,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/NewSpace"
|
||||
$ref: "#/components/schemas/NewFolder"
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
@ -839,12 +839,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/IdResult"
|
||||
/sec/space/{id}:
|
||||
/sec/folder/{id}:
|
||||
get:
|
||||
tags: [ Space ]
|
||||
summary: Get space details.
|
||||
tags: [ Folder ]
|
||||
summary: Get folder details.
|
||||
description: |
|
||||
Return details about a space.
|
||||
Return details about a folder.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
parameters:
|
||||
@ -855,12 +855,12 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SpaceDetail"
|
||||
$ref: "#/components/schemas/FolderDetail"
|
||||
put:
|
||||
tags: [ Space ]
|
||||
summary: Change the name of a space
|
||||
tags: [ Folder ]
|
||||
summary: Change the name of a folder
|
||||
description: |
|
||||
Changes the name of a space. The new name must not exists.
|
||||
Changes the name of a folder. The new name must not exists.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
parameters:
|
||||
@ -869,7 +869,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/NewSpace"
|
||||
$ref: "#/components/schemas/NewFolder"
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
@ -878,10 +878,10 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
delete:
|
||||
tags: [ Space ]
|
||||
summary: Delete a space by its id.
|
||||
tags: [ Folder ]
|
||||
summary: Delete a folder by its id.
|
||||
description: |
|
||||
Deletes a space.
|
||||
Deletes a folder.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
parameters:
|
||||
@ -893,12 +893,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
/sec/space/{id}/member/{userId}:
|
||||
/sec/folder/{id}/member/{userId}:
|
||||
put:
|
||||
tags: [ Space ]
|
||||
summary: Add a member to this space
|
||||
tags: [ Folder ]
|
||||
summary: Add a member to this folder
|
||||
description: |
|
||||
Adds a member to this space (identified by `id`).
|
||||
Adds a member to this folder (identified by `id`).
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
parameters:
|
||||
@ -912,10 +912,10 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
delete:
|
||||
tags: [ Space ]
|
||||
summary: Removes a member from this space.
|
||||
tags: [ Folder ]
|
||||
summary: Removes a member from this folder.
|
||||
description: |
|
||||
Removes a member from this space.
|
||||
Removes a member from this folder.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
parameters:
|
||||
@ -984,7 +984,7 @@ paths:
|
||||
summary: Get some insights regarding your items.
|
||||
description: |
|
||||
Returns some information about how many items there are, how
|
||||
much space they occupy etc.
|
||||
much folder they occupy etc.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
responses:
|
||||
@ -2492,19 +2492,19 @@ paths:
|
||||
|
||||
components:
|
||||
schemas:
|
||||
SpaceList:
|
||||
FolderList:
|
||||
description: |
|
||||
A list of spaces with their member counts.
|
||||
A list of folders with their member counts.
|
||||
required:
|
||||
- items
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/SpaceItem"
|
||||
SpaceItem:
|
||||
$ref: "#/components/schemas/FolderItem"
|
||||
FolderItem:
|
||||
description: |
|
||||
An item in a space list.
|
||||
An item in a folder list.
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
@ -2528,17 +2528,17 @@ components:
|
||||
memberCount:
|
||||
type: integer
|
||||
format: int32
|
||||
NewSpace:
|
||||
NewFolder:
|
||||
description: |
|
||||
Data required to create a new space.
|
||||
Data required to create a new folder.
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
SpaceDetail:
|
||||
FolderDetail:
|
||||
description: |
|
||||
Details about a space.
|
||||
Details about a folder.
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
@ -2567,9 +2567,9 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/IdName"
|
||||
SpaceMember:
|
||||
FolderMember:
|
||||
description: |
|
||||
Information to add or remove a space member.
|
||||
Information to add or remove a folder member.
|
||||
required:
|
||||
- userId
|
||||
properties:
|
||||
@ -4001,7 +4001,7 @@ components:
|
||||
owning:
|
||||
name: full
|
||||
in: query
|
||||
description: Whether to get owning spaces
|
||||
description: Whether to get owning folders
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
|
@ -82,7 +82,7 @@ object RestServer {
|
||||
"usertask/scanmailbox" -> ScanMailboxRoutes(restApp.backend, token),
|
||||
"calevent/check" -> CalEventCheckRoutes(),
|
||||
"fts" -> FullTextIndexRoutes.secured(cfg, restApp.backend, token),
|
||||
"space" -> SpaceRoutes(restApp.backend, token)
|
||||
"folder" -> FolderRoutes(restApp.backend, token)
|
||||
)
|
||||
|
||||
def openRoutes[F[_]: Effect](cfg: Config, restApp: RestApp[F]): HttpRoutes[F] =
|
||||
|
@ -0,0 +1,113 @@
|
||||
package docspell.restserver.routes
|
||||
|
||||
import cats.data.OptionT
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.backend.BackendApp
|
||||
import docspell.backend.auth.AuthToken
|
||||
import docspell.backend.ops.OFolder
|
||||
import docspell.common._
|
||||
import docspell.restapi.model._
|
||||
import docspell.restserver.conv.Conversions
|
||||
import docspell.restserver.http4s._
|
||||
import docspell.store.records.RFolder
|
||||
|
||||
import org.http4s.HttpRoutes
|
||||
import org.http4s.circe.CirceEntityDecoder._
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
|
||||
object FolderRoutes {
|
||||
|
||||
def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case GET -> Root :? QueryParam.QueryOpt(q) :? QueryParam.OwningOpt(owning) =>
|
||||
val login =
|
||||
owning.filter(identity).map(_ => user.account.user)
|
||||
for {
|
||||
all <- backend.folder.findAll(user.account, login, q.map(_.q))
|
||||
resp <- Ok(FolderList(all.map(mkFolder).toList))
|
||||
} yield resp
|
||||
|
||||
case req @ POST -> Root =>
|
||||
for {
|
||||
data <- req.as[NewFolder]
|
||||
nfolder <- newFolder(data, user.account)
|
||||
res <- backend.folder.add(nfolder, Some(user.account.user))
|
||||
resp <-
|
||||
Ok(Conversions.idResult(res, nfolder.id, "Folder successfully created."))
|
||||
} yield resp
|
||||
|
||||
case GET -> Root / Ident(id) =>
|
||||
(for {
|
||||
folder <- OptionT(backend.folder.findById(id, user.account))
|
||||
resp <- OptionT.liftF(Ok(mkFolderDetail(folder)))
|
||||
} yield resp).getOrElseF(NotFound())
|
||||
|
||||
case req @ PUT -> Root / Ident(id) =>
|
||||
for {
|
||||
data <- req.as[NewFolder]
|
||||
res <- backend.folder.changeName(id, user.account, data.name)
|
||||
resp <- Ok(mkFolderChangeResult(res))
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
for {
|
||||
res <- backend.folder.delete(id, user.account)
|
||||
resp <- Ok(mkFolderChangeResult(res))
|
||||
} yield resp
|
||||
|
||||
case PUT -> Root / Ident(id) / "member" / Ident(userId) =>
|
||||
for {
|
||||
res <- backend.folder.addMember(id, user.account, userId)
|
||||
resp <- Ok(mkFolderChangeResult(res))
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) / "member" / Ident(userId) =>
|
||||
for {
|
||||
res <- backend.folder.removeMember(id, user.account, userId)
|
||||
resp <- Ok(mkFolderChangeResult(res))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
||||
private def newFolder[F[_]: Sync](ns: NewFolder, account: AccountId): F[RFolder] =
|
||||
RFolder.newFolder(ns.name, account)
|
||||
|
||||
private def mkFolder(item: OFolder.FolderItem): FolderItem =
|
||||
FolderItem(
|
||||
item.id,
|
||||
item.name,
|
||||
Conversions.mkIdName(item.owner),
|
||||
item.created,
|
||||
item.member,
|
||||
item.memberCount
|
||||
)
|
||||
|
||||
private def mkFolderDetail(item: OFolder.FolderDetail): FolderDetail =
|
||||
FolderDetail(
|
||||
item.id,
|
||||
item.name,
|
||||
Conversions.mkIdName(item.owner),
|
||||
item.created,
|
||||
item.member,
|
||||
item.memberCount,
|
||||
item.members.map(Conversions.mkIdName)
|
||||
)
|
||||
|
||||
private def mkFolderChangeResult(r: OFolder.FolderChangeResult): BasicResult =
|
||||
r match {
|
||||
case OFolder.FolderChangeResult.Success =>
|
||||
BasicResult(true, "Successfully changed folder.")
|
||||
case OFolder.FolderChangeResult.NotFound =>
|
||||
BasicResult(false, "Folder or user not found.")
|
||||
case OFolder.FolderChangeResult.Forbidden =>
|
||||
BasicResult(false, "Not allowed to edit folder.")
|
||||
case OFolder.FolderChangeResult.Exists =>
|
||||
BasicResult(false, "The member already exists.")
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
package docspell.restserver.routes
|
||||
|
||||
import cats.data.OptionT
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.backend.BackendApp
|
||||
import docspell.backend.auth.AuthToken
|
||||
import docspell.backend.ops.OSpace
|
||||
import docspell.common._
|
||||
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._
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
|
||||
object SpaceRoutes {
|
||||
|
||||
def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case GET -> Root :? QueryParam.QueryOpt(q) :? QueryParam.OwningOpt(owning) =>
|
||||
val login =
|
||||
owning.filter(identity).map(_ => user.account.user)
|
||||
for {
|
||||
all <- backend.space.findAll(user.account, login, q.map(_.q))
|
||||
resp <- Ok(SpaceList(all.map(mkSpace).toList))
|
||||
} yield resp
|
||||
|
||||
case req @ POST -> Root =>
|
||||
for {
|
||||
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) =>
|
||||
(for {
|
||||
space <- OptionT(backend.space.findById(id, user.account))
|
||||
resp <- OptionT.liftF(Ok(mkSpaceDetail(space)))
|
||||
} yield resp).getOrElseF(NotFound())
|
||||
|
||||
case req @ PUT -> Root / Ident(id) =>
|
||||
for {
|
||||
data <- req.as[NewSpace]
|
||||
res <- backend.space.changeName(id, user.account, data.name)
|
||||
resp <- Ok(mkSpaceChangeResult(res))
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
for {
|
||||
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(mkSpaceChangeResult(res))
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) / "member" / Ident(userId) =>
|
||||
for {
|
||||
res <- backend.space.removeMember(id, user.account, userId)
|
||||
resp <- Ok(mkSpaceChangeResult(res))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
||||
private def newSpace[F[_]: Sync](ns: NewSpace, account: AccountId): F[RSpace] =
|
||||
RSpace.newSpace(ns.name, account)
|
||||
|
||||
private def mkSpace(item: OSpace.SpaceItem): SpaceItem =
|
||||
SpaceItem(
|
||||
item.id,
|
||||
item.name,
|
||||
Conversions.mkIdName(item.owner),
|
||||
item.created,
|
||||
item.member,
|
||||
item.memberCount
|
||||
)
|
||||
|
||||
private def mkSpaceDetail(item: OSpace.SpaceDetail): SpaceDetail =
|
||||
SpaceDetail(
|
||||
item.id,
|
||||
item.name,
|
||||
Conversions.mkIdName(item.owner),
|
||||
item.created,
|
||||
item.member,
|
||||
item.memberCount,
|
||||
item.members.map(Conversions.mkIdName)
|
||||
)
|
||||
|
||||
private def mkSpaceChangeResult(r: OSpace.SpaceChangeResult): BasicResult =
|
||||
r match {
|
||||
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.")
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
CREATE TABLE "space" (
|
||||
CREATE TABLE "folder" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"name" varchar(254) not null,
|
||||
"cid" varchar(254) not null,
|
||||
@ -9,15 +9,15 @@ CREATE TABLE "space" (
|
||||
foreign key ("owner") references "user_"("uid")
|
||||
);
|
||||
|
||||
CREATE TABLE "space_member" (
|
||||
CREATE TABLE "folder_member" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"space_id" varchar(254) not null,
|
||||
"folder_id" varchar(254) not null,
|
||||
"user_id" varchar(254) not null,
|
||||
"created" timestamp not null,
|
||||
unique ("space_id", "user_id"),
|
||||
foreign key ("space_id") references "space"("id"),
|
||||
unique ("folder_id", "user_id"),
|
||||
foreign key ("folder_id") references "folder"("id"),
|
||||
foreign key ("user_id") references "user_"("uid")
|
||||
);
|
||||
|
||||
ALTER TABLE "item"
|
||||
ADD COLUMN "space_id" varchar(254) NULL;
|
||||
ADD COLUMN "folder_id" varchar(254) NULL;
|
@ -0,0 +1,249 @@
|
||||
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 QFolder {
|
||||
|
||||
final case class FolderItem(
|
||||
id: Ident,
|
||||
name: String,
|
||||
owner: IdRef,
|
||||
created: Timestamp,
|
||||
member: Boolean,
|
||||
memberCount: Int
|
||||
) {
|
||||
def withMembers(members: List[IdRef]): FolderDetail =
|
||||
FolderDetail(id, name, owner, created, member, memberCount, members)
|
||||
}
|
||||
|
||||
final case class FolderDetail(
|
||||
id: Ident,
|
||||
name: String,
|
||||
owner: IdRef,
|
||||
created: Timestamp,
|
||||
member: Boolean,
|
||||
memberCount: Int,
|
||||
members: List[IdRef]
|
||||
)
|
||||
|
||||
sealed trait FolderChangeResult
|
||||
object FolderChangeResult {
|
||||
case object Success extends FolderChangeResult
|
||||
def success: FolderChangeResult = Success
|
||||
case object NotFound extends FolderChangeResult
|
||||
def notFound: FolderChangeResult = NotFound
|
||||
case object Forbidden extends FolderChangeResult
|
||||
def forbidden: FolderChangeResult = Forbidden
|
||||
case object Exists extends FolderChangeResult
|
||||
def exists: FolderChangeResult = Exists
|
||||
}
|
||||
|
||||
def delete(id: Ident, account: AccountId): ConnectionIO[FolderChangeResult] = {
|
||||
def tryDelete =
|
||||
for {
|
||||
_ <- RItem.removeFolder(id)
|
||||
_ <- RFolderMember.deleteAll(id)
|
||||
_ <- RFolder.delete(id)
|
||||
} yield FolderChangeResult.success
|
||||
|
||||
(for {
|
||||
uid <- OptionT(findUserId(account))
|
||||
folder <- OptionT(RFolder.findById(id))
|
||||
res <- OptionT.liftF(
|
||||
if (folder.owner == uid) tryDelete
|
||||
else FolderChangeResult.forbidden.pure[ConnectionIO]
|
||||
)
|
||||
} yield res).getOrElse(FolderChangeResult.notFound)
|
||||
}
|
||||
|
||||
def changeName(
|
||||
folder: Ident,
|
||||
account: AccountId,
|
||||
name: String
|
||||
): ConnectionIO[FolderChangeResult] = {
|
||||
def tryUpdate(ns: RFolder): ConnectionIO[FolderChangeResult] =
|
||||
for {
|
||||
n <- RFolder.update(ns)
|
||||
res =
|
||||
if (n == 0) FolderChangeResult.notFound
|
||||
else FolderChangeResult.Success
|
||||
} yield res
|
||||
|
||||
(for {
|
||||
uid <- OptionT(findUserId(account))
|
||||
folder <- OptionT(RFolder.findById(folder))
|
||||
res <- OptionT.liftF(
|
||||
if (folder.owner == uid) tryUpdate(folder.copy(name = name))
|
||||
else FolderChangeResult.forbidden.pure[ConnectionIO]
|
||||
)
|
||||
} yield res).getOrElse(FolderChangeResult.notFound)
|
||||
}
|
||||
|
||||
def removeMember(
|
||||
folder: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): ConnectionIO[FolderChangeResult] = {
|
||||
def tryRemove: ConnectionIO[FolderChangeResult] =
|
||||
for {
|
||||
n <- RFolderMember.delete(member, folder)
|
||||
res =
|
||||
if (n == 0) FolderChangeResult.notFound
|
||||
else FolderChangeResult.Success
|
||||
} yield res
|
||||
|
||||
(for {
|
||||
uid <- OptionT(findUserId(account))
|
||||
folder <- OptionT(RFolder.findById(folder))
|
||||
res <- OptionT.liftF(
|
||||
if (folder.owner == uid) tryRemove
|
||||
else FolderChangeResult.forbidden.pure[ConnectionIO]
|
||||
)
|
||||
} yield res).getOrElse(FolderChangeResult.notFound)
|
||||
}
|
||||
|
||||
def addMember(
|
||||
folder: Ident,
|
||||
account: AccountId,
|
||||
member: Ident
|
||||
): ConnectionIO[FolderChangeResult] = {
|
||||
def tryAdd: ConnectionIO[FolderChangeResult] =
|
||||
for {
|
||||
spm <- RFolderMember.findByUserId(member, folder)
|
||||
mem <- RFolderMember.newMember[ConnectionIO](folder, member)
|
||||
res <-
|
||||
if (spm.isDefined) FolderChangeResult.exists.pure[ConnectionIO]
|
||||
else RFolderMember.insert(mem).map(_ => FolderChangeResult.Success)
|
||||
} yield res
|
||||
|
||||
(for {
|
||||
uid <- OptionT(findUserId(account))
|
||||
folder <- OptionT(RFolder.findById(folder))
|
||||
res <- OptionT.liftF(
|
||||
if (folder.owner == uid) tryAdd
|
||||
else FolderChangeResult.forbidden.pure[ConnectionIO]
|
||||
)
|
||||
} yield res).getOrElse(FolderChangeResult.notFound)
|
||||
}
|
||||
|
||||
def findById(id: Ident, account: AccountId): ConnectionIO[Option[FolderDetail]] = {
|
||||
val mUserId = RFolderMember.Columns.user.prefix("m")
|
||||
val mFolderId = RFolderMember.Columns.folder.prefix("m")
|
||||
val uId = RUser.Columns.uid.prefix("u")
|
||||
val uLogin = RUser.Columns.login.prefix("u")
|
||||
val sColl = RFolder.Columns.collective.prefix("s")
|
||||
val sId = RFolder.Columns.id.prefix("s")
|
||||
|
||||
val from = RFolderMember.table ++ fr"m INNER JOIN" ++
|
||||
RUser.table ++ fr"u ON" ++ mUserId.is(uId) ++ fr"INNER JOIN" ++
|
||||
RFolder.table ++ fr"s ON" ++ mFolderId.is(sId)
|
||||
|
||||
val memberQ = selectSimple(
|
||||
Seq(uId, uLogin),
|
||||
from,
|
||||
and(mFolderId.is(id), sColl.is(account.collective))
|
||||
).query[IdRef].to[Vector]
|
||||
|
||||
(for {
|
||||
folder <- OptionT(findAll(account, Some(id), None, None).map(_.headOption))
|
||||
memb <- OptionT.liftF(memberQ)
|
||||
} yield folder.withMembers(memb.toList)).value
|
||||
}
|
||||
|
||||
def findAll(
|
||||
account: AccountId,
|
||||
idQ: Option[Ident],
|
||||
ownerLogin: Option[Ident],
|
||||
nameQ: Option[String]
|
||||
): ConnectionIO[Vector[FolderItem]] = {
|
||||
// with memberlogin as
|
||||
// (select m.folder_id,u.login
|
||||
// from folder_member m
|
||||
// inner join user_ u on u.uid = m.user_id
|
||||
// inner join folder s on s.id = m.folder_id
|
||||
// where s.cid = 'eike'
|
||||
// union all
|
||||
// select s.id,u.login
|
||||
// from folder 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 folder_id = s.id and login = 'eike') as member
|
||||
// ,(select count(*) - 1 from memberlogin where folder_id = s.id) as member_count
|
||||
// from folder 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 = RFolder.Columns.id.prefix("s")
|
||||
val sOwner = RFolder.Columns.owner.prefix("s")
|
||||
val sName = RFolder.Columns.name.prefix("s")
|
||||
val sColl = RFolder.Columns.collective.prefix("s")
|
||||
val mUser = RFolderMember.Columns.user.prefix("m")
|
||||
val mFolder = RFolderMember.Columns.folder.prefix("m")
|
||||
|
||||
//CTE
|
||||
val cte: Fragment = {
|
||||
val from1 = RFolderMember.table ++ fr"m INNER JOIN" ++
|
||||
RUser.table ++ fr"u ON" ++ uId.is(mUser) ++ fr"INNER JOIN" ++
|
||||
RFolder.table ++ fr"s ON" ++ sId.is(mFolder)
|
||||
|
||||
val from2 = RFolder.table ++ fr"s INNER JOIN" ++
|
||||
RUser.table ++ fr"u ON" ++ uId.is(sOwner)
|
||||
|
||||
withCTE(
|
||||
"memberlogin" ->
|
||||
(selectSimple(Seq(mFolder, 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" ++ mFolder.prefix("").is(sId) ++
|
||||
fr"AND" ++ uLogin.prefix("").is(account.user)
|
||||
|
||||
val memberCount =
|
||||
fr"SELECT COUNT(*) - 1 FROM memberlogin WHERE" ++ mFolder.prefix("").is(sId)
|
||||
|
||||
//Query
|
||||
val cols = Seq(
|
||||
sId.f,
|
||||
sName.f,
|
||||
sOwner.f,
|
||||
uLogin.f,
|
||||
RFolder.Columns.created.prefix("s").f,
|
||||
fr"(" ++ isMember ++ fr") as mem",
|
||||
fr"(" ++ memberCount ++ fr") as cnt"
|
||||
)
|
||||
|
||||
val from = RFolder.table ++ fr"s INNER JOIN" ++
|
||||
RUser.table ++ fr"u ON" ++ uId.is(sOwner)
|
||||
|
||||
val where =
|
||||
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))
|
||||
|
||||
(cte ++ selectSimple(commas(cols), from, and(where) ++ orderBy(sName.asc)))
|
||||
.query[FolderItem]
|
||||
.to[Vector]
|
||||
}
|
||||
|
||||
private def findUserId(account: AccountId): ConnectionIO[Option[Ident]] =
|
||||
RUser.findByAccount(account).map(_.map(_.uid))
|
||||
}
|
@ -1,249 +0,0 @@
|
||||
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,
|
||||
member: Boolean,
|
||||
memberCount: Int
|
||||
) {
|
||||
def withMembers(members: List[IdRef]): SpaceDetail =
|
||||
SpaceDetail(id, name, owner, created, member, memberCount, members)
|
||||
}
|
||||
|
||||
final case class SpaceDetail(
|
||||
id: Ident,
|
||||
name: String,
|
||||
owner: IdRef,
|
||||
created: Timestamp,
|
||||
member: Boolean,
|
||||
memberCount: Int,
|
||||
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, 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")
|
||||
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(account.collective))
|
||||
).query[IdRef].to[Vector]
|
||||
|
||||
(for {
|
||||
space <- OptionT(findAll(account, Some(id), None, None).map(_.headOption))
|
||||
memb <- OptionT.liftF(memberQ)
|
||||
} yield space.withMembers(memb.toList)).value
|
||||
}
|
||||
|
||||
def findAll(
|
||||
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.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(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))
|
||||
|
||||
(cte ++ selectSimple(commas(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))
|
||||
}
|
@ -9,7 +9,7 @@ import docspell.store.impl.Implicits._
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
case class RSpace(
|
||||
case class RFolder(
|
||||
id: Ident,
|
||||
name: String,
|
||||
collectiveId: Ident,
|
||||
@ -17,15 +17,15 @@ case class RSpace(
|
||||
created: Timestamp
|
||||
)
|
||||
|
||||
object RSpace {
|
||||
object RFolder {
|
||||
|
||||
def newSpace[F[_]: Sync](name: String, account: AccountId): F[RSpace] =
|
||||
def newFolder[F[_]: Sync](name: String, account: AccountId): F[RFolder] =
|
||||
for {
|
||||
nId <- Ident.randomId[F]
|
||||
now <- Timestamp.current[F]
|
||||
} yield RSpace(nId, name, account.collective, account.user, now)
|
||||
} yield RFolder(nId, name, account.collective, account.user, now)
|
||||
|
||||
val table = fr"space"
|
||||
val table = fr"folder"
|
||||
|
||||
object Columns {
|
||||
|
||||
@ -40,7 +40,7 @@ object RSpace {
|
||||
|
||||
import Columns._
|
||||
|
||||
def insert(value: RSpace): ConnectionIO[Int] = {
|
||||
def insert(value: RFolder): ConnectionIO[Int] = {
|
||||
val sql = insertRow(
|
||||
table,
|
||||
all,
|
||||
@ -49,37 +49,37 @@ object RSpace {
|
||||
sql.update.run
|
||||
}
|
||||
|
||||
def update(v: RSpace): ConnectionIO[Int] =
|
||||
def update(v: RFolder): ConnectionIO[Int] =
|
||||
updateRow(
|
||||
table,
|
||||
and(id.is(v.id), collective.is(v.collectiveId), owner.is(v.owner)),
|
||||
name.setTo(v.name)
|
||||
).update.run
|
||||
|
||||
def existsByName(coll: Ident, spaceName: String): ConnectionIO[Boolean] =
|
||||
selectCount(id, table, and(collective.is(coll), name.is(spaceName)))
|
||||
def existsByName(coll: Ident, folderName: String): ConnectionIO[Boolean] =
|
||||
selectCount(id, table, and(collective.is(coll), name.is(folderName)))
|
||||
.query[Int]
|
||||
.unique
|
||||
.map(_ > 0)
|
||||
|
||||
def findById(spaceId: Ident): ConnectionIO[Option[RSpace]] = {
|
||||
val sql = selectSimple(all, table, id.is(spaceId))
|
||||
sql.query[RSpace].option
|
||||
def findById(folderId: Ident): ConnectionIO[Option[RFolder]] = {
|
||||
val sql = selectSimple(all, table, id.is(folderId))
|
||||
sql.query[RFolder].option
|
||||
}
|
||||
|
||||
def findAll(
|
||||
coll: Ident,
|
||||
nameQ: Option[String],
|
||||
order: Columns.type => Column
|
||||
): ConnectionIO[Vector[RSpace]] = {
|
||||
): ConnectionIO[Vector[RFolder]] = {
|
||||
val q = Seq(collective.is(coll)) ++ (nameQ match {
|
||||
case Some(str) => Seq(name.lowerLike(s"%${str.toLowerCase}%"))
|
||||
case None => Seq.empty
|
||||
})
|
||||
val sql = selectSimple(all, table, and(q)) ++ orderBy(order(Columns).f)
|
||||
sql.query[RSpace].to[Vector]
|
||||
sql.query[RFolder].to[Vector]
|
||||
}
|
||||
|
||||
def delete(spaceId: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, id.is(spaceId)).update.run
|
||||
def delete(folderId: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, id.is(folderId)).update.run
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package docspell.store.records
|
||||
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.impl.Column
|
||||
import docspell.store.impl.Implicits._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
case class RFolderMember(
|
||||
id: Ident,
|
||||
folderId: Ident,
|
||||
userId: Ident,
|
||||
created: Timestamp
|
||||
)
|
||||
|
||||
object RFolderMember {
|
||||
|
||||
def newMember[F[_]: Sync](folder: Ident, user: Ident): F[RFolderMember] =
|
||||
for {
|
||||
nId <- Ident.randomId[F]
|
||||
now <- Timestamp.current[F]
|
||||
} yield RFolderMember(nId, folder, user, now)
|
||||
|
||||
val table = fr"folder_member"
|
||||
|
||||
object Columns {
|
||||
|
||||
val id = Column("id")
|
||||
val folder = Column("folder_id")
|
||||
val user = Column("user_id")
|
||||
val created = Column("created")
|
||||
|
||||
val all = List(id, folder, user, created)
|
||||
}
|
||||
|
||||
import Columns._
|
||||
|
||||
def insert(value: RFolderMember): ConnectionIO[Int] = {
|
||||
val sql = insertRow(
|
||||
table,
|
||||
all,
|
||||
fr"${value.id},${value.folderId},${value.userId},${value.created}"
|
||||
)
|
||||
sql.update.run
|
||||
}
|
||||
|
||||
def findByUserId(userId: Ident, folderId: Ident): ConnectionIO[Option[RFolderMember]] =
|
||||
selectSimple(all, table, and(folder.is(folderId), user.is(userId)))
|
||||
.query[RFolderMember]
|
||||
.option
|
||||
|
||||
def delete(userId: Ident, folderId: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, and(folder.is(folderId), user.is(userId))).update.run
|
||||
|
||||
def deleteAll(folderId: Ident): ConnectionIO[Int] =
|
||||
deleteFrom(table, folder.is(folderId)).update.run
|
||||
}
|
@ -28,7 +28,7 @@ case class RItem(
|
||||
created: Timestamp,
|
||||
updated: Timestamp,
|
||||
notes: Option[String],
|
||||
spaceId: Option[Ident]
|
||||
folderId: Option[Ident]
|
||||
) {}
|
||||
|
||||
object RItem {
|
||||
@ -82,7 +82,7 @@ object RItem {
|
||||
val created = Column("created")
|
||||
val updated = Column("updated")
|
||||
val notes = Column("notes")
|
||||
val space = Column("space_id")
|
||||
val folder = Column("folder_id")
|
||||
val all = List(
|
||||
id,
|
||||
cid,
|
||||
@ -100,7 +100,7 @@ object RItem {
|
||||
created,
|
||||
updated,
|
||||
notes,
|
||||
space
|
||||
folder
|
||||
)
|
||||
}
|
||||
import Columns._
|
||||
@ -111,7 +111,7 @@ object RItem {
|
||||
all,
|
||||
fr"${v.id},${v.cid},${v.name},${v.itemDate},${v.source},${v.direction},${v.state}," ++
|
||||
fr"${v.corrOrg},${v.corrPerson},${v.concPerson},${v.concEquipment},${v.inReplyTo},${v.dueDate}," ++
|
||||
fr"${v.created},${v.updated},${v.notes},${v.spaceId}"
|
||||
fr"${v.created},${v.updated},${v.notes},${v.folderId}"
|
||||
).update.run
|
||||
|
||||
def getCollective(itemId: Ident): ConnectionIO[Option[Ident]] =
|
||||
@ -300,8 +300,8 @@ 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] = {
|
||||
def removeFolder(folderId: Ident): ConnectionIO[Int] = {
|
||||
val empty: Option[Ident] = None
|
||||
updateRow(table, space.is(spaceId), space.setTo(empty)).update.run
|
||||
updateRow(table, folder.is(folderId), folder.setTo(empty)).update.run
|
||||
}
|
||||
}
|
||||
|
@ -1,61 +0,0 @@
|
||||
package docspell.store.records
|
||||
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.impl.Column
|
||||
import docspell.store.impl.Implicits._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
|
||||
case class RSpaceMember(
|
||||
id: Ident,
|
||||
spaceId: Ident,
|
||||
userId: Ident,
|
||||
created: Timestamp
|
||||
)
|
||||
|
||||
object RSpaceMember {
|
||||
|
||||
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 {
|
||||
|
||||
val id = Column("id")
|
||||
val space = Column("space_id")
|
||||
val user = Column("user_id")
|
||||
val created = Column("created")
|
||||
|
||||
val all = List(id, space, user, created)
|
||||
}
|
||||
|
||||
import Columns._
|
||||
|
||||
def insert(value: RSpaceMember): ConnectionIO[Int] = {
|
||||
val sql = insertRow(
|
||||
table,
|
||||
all,
|
||||
fr"${value.id},${value.spaceId},${value.userId},${value.created}"
|
||||
)
|
||||
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
|
||||
}
|
@ -6,16 +6,17 @@ module Api exposing
|
||||
, addMember
|
||||
, addTag
|
||||
, cancelJob
|
||||
, changeFolderName
|
||||
, changePassword
|
||||
, changeSpaceName
|
||||
, checkCalEvent
|
||||
, createImapSettings
|
||||
, createMailSettings
|
||||
, createNewSpace
|
||||
, createNewFolder
|
||||
, createNotifyDueItems
|
||||
, createScanMailbox
|
||||
, deleteAttachment
|
||||
, deleteEquip
|
||||
, deleteFolder
|
||||
, deleteImapSettings
|
||||
, deleteItem
|
||||
, deleteMailSettings
|
||||
@ -24,7 +25,6 @@ module Api exposing
|
||||
, deletePerson
|
||||
, deleteScanMailbox
|
||||
, deleteSource
|
||||
, deleteSpace
|
||||
, deleteTag
|
||||
, deleteUser
|
||||
, getAttachmentMeta
|
||||
@ -32,6 +32,8 @@ module Api exposing
|
||||
, getCollectiveSettings
|
||||
, getContacts
|
||||
, getEquipments
|
||||
, getFolderDetail
|
||||
, getFolders
|
||||
, getImapSettings
|
||||
, getInsights
|
||||
, getItemProposals
|
||||
@ -46,8 +48,6 @@ module Api exposing
|
||||
, getScanMailbox
|
||||
, getSentMails
|
||||
, getSources
|
||||
, getSpaceDetail
|
||||
, getSpaces
|
||||
, getTags
|
||||
, getUsers
|
||||
, itemDetail
|
||||
@ -108,6 +108,8 @@ import Api.Model.EmailSettings exposing (EmailSettings)
|
||||
import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
||||
import Api.Model.Equipment exposing (Equipment)
|
||||
import Api.Model.EquipmentList exposing (EquipmentList)
|
||||
import Api.Model.FolderDetail exposing (FolderDetail)
|
||||
import Api.Model.FolderList exposing (FolderList)
|
||||
import Api.Model.GenInvite exposing (GenInvite)
|
||||
import Api.Model.IdResult exposing (IdResult)
|
||||
import Api.Model.ImapSettings exposing (ImapSettings)
|
||||
@ -122,7 +124,7 @@ import Api.Model.ItemSearch exposing (ItemSearch)
|
||||
import Api.Model.ItemUploadMeta exposing (ItemUploadMeta)
|
||||
import Api.Model.JobQueueState exposing (JobQueueState)
|
||||
import Api.Model.MoveAttachment exposing (MoveAttachment)
|
||||
import Api.Model.NewSpace exposing (NewSpace)
|
||||
import Api.Model.NewFolder exposing (NewFolder)
|
||||
import Api.Model.NotificationSettings exposing (NotificationSettings)
|
||||
import Api.Model.NotificationSettingsList exposing (NotificationSettingsList)
|
||||
import Api.Model.OptionalDate exposing (OptionalDate)
|
||||
@ -141,8 +143,6 @@ import Api.Model.SentMails exposing (SentMails)
|
||||
import Api.Model.SimpleMail exposing (SimpleMail)
|
||||
import Api.Model.Source exposing (Source)
|
||||
import Api.Model.SourceList exposing (SourceList)
|
||||
import Api.Model.SpaceDetail exposing (SpaceDetail)
|
||||
import Api.Model.SpaceList exposing (SpaceList)
|
||||
import Api.Model.Tag exposing (Tag)
|
||||
import Api.Model.TagList exposing (TagList)
|
||||
import Api.Model.User exposing (User)
|
||||
@ -161,13 +161,13 @@ import Util.Http as Http2
|
||||
|
||||
|
||||
|
||||
--- Spaces
|
||||
--- Folders
|
||||
|
||||
|
||||
deleteSpace : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
deleteSpace flags id receive =
|
||||
deleteFolder : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
deleteFolder flags id receive =
|
||||
Http2.authDelete
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/space/" ++ id
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/folder/" ++ id
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
@ -176,7 +176,7 @@ deleteSpace flags id receive =
|
||||
removeMember : Flags -> String -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
removeMember flags id user receive =
|
||||
Http2.authDelete
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/space/" ++ id ++ "/member/" ++ user
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/folder/" ++ id ++ "/member/" ++ user
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
@ -185,48 +185,48 @@ removeMember flags id user receive =
|
||||
addMember : Flags -> String -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
addMember flags id user receive =
|
||||
Http2.authPut
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/space/" ++ id ++ "/member/" ++ user
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/folder/" ++ id ++ "/member/" ++ user
|
||||
, account = getAccount flags
|
||||
, body = Http.emptyBody
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
||||
changeSpaceName : Flags -> String -> NewSpace -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
changeSpaceName flags id ns receive =
|
||||
changeFolderName : Flags -> String -> NewFolder -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
changeFolderName flags id ns receive =
|
||||
Http2.authPut
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/space/" ++ id
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/folder/" ++ id
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Api.Model.NewSpace.encode ns)
|
||||
, body = Http.jsonBody (Api.Model.NewFolder.encode ns)
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
||||
createNewSpace : Flags -> NewSpace -> (Result Http.Error IdResult -> msg) -> Cmd msg
|
||||
createNewSpace flags ns receive =
|
||||
createNewFolder : Flags -> NewFolder -> (Result Http.Error IdResult -> msg) -> Cmd msg
|
||||
createNewFolder flags ns receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/space"
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/folder"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody (Api.Model.NewSpace.encode ns)
|
||||
, body = Http.jsonBody (Api.Model.NewFolder.encode ns)
|
||||
, expect = Http.expectJson receive Api.Model.IdResult.decoder
|
||||
}
|
||||
|
||||
|
||||
getSpaceDetail : Flags -> String -> (Result Http.Error SpaceDetail -> msg) -> Cmd msg
|
||||
getSpaceDetail flags id receive =
|
||||
getFolderDetail : Flags -> String -> (Result Http.Error FolderDetail -> msg) -> Cmd msg
|
||||
getFolderDetail flags id receive =
|
||||
Http2.authGet
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/space/" ++ id
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/folder/" ++ id
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive Api.Model.SpaceDetail.decoder
|
||||
, expect = Http.expectJson receive Api.Model.FolderDetail.decoder
|
||||
}
|
||||
|
||||
|
||||
getSpaces : Flags -> String -> Bool -> (Result Http.Error SpaceList -> msg) -> Cmd msg
|
||||
getSpaces flags query owningOnly receive =
|
||||
getFolders : Flags -> String -> Bool -> (Result Http.Error FolderList -> msg) -> Cmd msg
|
||||
getFolders flags query owningOnly receive =
|
||||
Http2.authGet
|
||||
{ url =
|
||||
flags.config.baseUrl
|
||||
++ "/api/v1/sec/space?q="
|
||||
++ "/api/v1/sec/folder?q="
|
||||
++ Url.percentEncode query
|
||||
++ (if owningOnly then
|
||||
"&owning=true"
|
||||
@ -235,7 +235,7 @@ getSpaces flags query owningOnly receive =
|
||||
""
|
||||
)
|
||||
, account = getAccount flags
|
||||
, expect = Http.expectJson receive Api.Model.SpaceList.decoder
|
||||
, expect = Http.expectJson receive Api.Model.FolderList.decoder
|
||||
}
|
||||
|
||||
|
||||
|
82
modules/webapp/src/main/elm/Comp/SpaceDetail.elm → modules/webapp/src/main/elm/Comp/FolderDetail.elm
82
modules/webapp/src/main/elm/Comp/SpaceDetail.elm → modules/webapp/src/main/elm/Comp/FolderDetail.elm
@ -1,4 +1,4 @@
|
||||
module Comp.SpaceDetail exposing
|
||||
module Comp.FolderDetail exposing
|
||||
( Model
|
||||
, Msg
|
||||
, init
|
||||
@ -9,10 +9,10 @@ module Comp.SpaceDetail exposing
|
||||
|
||||
import Api
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Api.Model.FolderDetail exposing (FolderDetail)
|
||||
import Api.Model.IdName exposing (IdName)
|
||||
import Api.Model.IdResult exposing (IdResult)
|
||||
import Api.Model.NewSpace exposing (NewSpace)
|
||||
import Api.Model.SpaceDetail exposing (SpaceDetail)
|
||||
import Api.Model.NewFolder exposing (NewFolder)
|
||||
import Api.Model.User exposing (User)
|
||||
import Api.Model.UserList exposing (UserList)
|
||||
import Comp.FixedDropdown
|
||||
@ -28,7 +28,7 @@ import Util.Maybe
|
||||
|
||||
type alias Model =
|
||||
{ result : Maybe BasicResult
|
||||
, space : SpaceDetail
|
||||
, folder : FolderDetail
|
||||
, name : Maybe String
|
||||
, members : List IdName
|
||||
, users : List User
|
||||
@ -43,10 +43,10 @@ type Msg
|
||||
= SetName String
|
||||
| MemberDropdownMsg (Comp.FixedDropdown.Msg IdName)
|
||||
| SaveName
|
||||
| NewSpaceResp (Result Http.Error IdResult)
|
||||
| ChangeSpaceResp (Result Http.Error BasicResult)
|
||||
| NewFolderResp (Result Http.Error IdResult)
|
||||
| ChangeFolderResp (Result Http.Error BasicResult)
|
||||
| ChangeNameResp (Result Http.Error BasicResult)
|
||||
| SpaceDetailResp (Result Http.Error SpaceDetail)
|
||||
| FolderDetailResp (Result Http.Error FolderDetail)
|
||||
| AddMember
|
||||
| RemoveMember IdName
|
||||
| RequestDelete
|
||||
@ -55,16 +55,16 @@ type Msg
|
||||
| GoBack
|
||||
|
||||
|
||||
init : List User -> SpaceDetail -> Model
|
||||
init users space =
|
||||
init : List User -> FolderDetail -> Model
|
||||
init users folder =
|
||||
{ result = Nothing
|
||||
, space = space
|
||||
, name = Util.Maybe.fromString space.name
|
||||
, members = space.members
|
||||
, folder = folder
|
||||
, name = Util.Maybe.fromString folder.name
|
||||
, members = folder.members
|
||||
, users = users
|
||||
, memberDropdown =
|
||||
Comp.FixedDropdown.initMap .name
|
||||
(makeOptions users space)
|
||||
(makeOptions users folder)
|
||||
, selectedMember = Nothing
|
||||
, loading = False
|
||||
, deleteDimmer = Comp.YesNoDimmer.emptyModel
|
||||
@ -73,17 +73,17 @@ init users space =
|
||||
|
||||
initEmpty : List User -> Model
|
||||
initEmpty users =
|
||||
init users Api.Model.SpaceDetail.empty
|
||||
init users Api.Model.FolderDetail.empty
|
||||
|
||||
|
||||
makeOptions : List User -> SpaceDetail -> List IdName
|
||||
makeOptions users space =
|
||||
makeOptions : List User -> FolderDetail -> List IdName
|
||||
makeOptions users folder =
|
||||
let
|
||||
toIdName u =
|
||||
IdName u.id u.login
|
||||
|
||||
notMember idn =
|
||||
List.member idn (space.owner :: space.members) |> not
|
||||
List.member idn (folder.owner :: folder.members) |> not
|
||||
in
|
||||
List.map toIdName users
|
||||
|> List.filter notMember
|
||||
@ -129,13 +129,13 @@ update flags msg model =
|
||||
Just name ->
|
||||
let
|
||||
cmd =
|
||||
if model.space.id == "" then
|
||||
Api.createNewSpace flags (NewSpace name) NewSpaceResp
|
||||
if model.folder.id == "" then
|
||||
Api.createNewFolder flags (NewFolder name) NewFolderResp
|
||||
|
||||
else
|
||||
Api.changeSpaceName flags
|
||||
model.space.id
|
||||
(NewSpace name)
|
||||
Api.changeFolderName flags
|
||||
model.folder.id
|
||||
(NewFolder name)
|
||||
ChangeNameResp
|
||||
in
|
||||
( { model
|
||||
@ -149,9 +149,9 @@ update flags msg model =
|
||||
Nothing ->
|
||||
( model, Cmd.none, False )
|
||||
|
||||
NewSpaceResp (Ok ir) ->
|
||||
NewFolderResp (Ok ir) ->
|
||||
if ir.success then
|
||||
( model, Api.getSpaceDetail flags ir.id SpaceDetailResp, False )
|
||||
( model, Api.getFolderDetail flags ir.id FolderDetailResp, False )
|
||||
|
||||
else
|
||||
( { model
|
||||
@ -162,7 +162,7 @@ update flags msg model =
|
||||
, False
|
||||
)
|
||||
|
||||
NewSpaceResp (Err err) ->
|
||||
NewFolderResp (Err err) ->
|
||||
( { model
|
||||
| loading = False
|
||||
, result = Just (BasicResult False (Util.Http.errorToString err))
|
||||
@ -171,10 +171,10 @@ update flags msg model =
|
||||
, False
|
||||
)
|
||||
|
||||
ChangeSpaceResp (Ok r) ->
|
||||
ChangeFolderResp (Ok r) ->
|
||||
if r.success then
|
||||
( model
|
||||
, Api.getSpaceDetail flags model.space.id SpaceDetailResp
|
||||
, Api.getFolderDetail flags model.folder.id FolderDetailResp
|
||||
, False
|
||||
)
|
||||
|
||||
@ -184,7 +184,7 @@ update flags msg model =
|
||||
, False
|
||||
)
|
||||
|
||||
ChangeSpaceResp (Err err) ->
|
||||
ChangeFolderResp (Err err) ->
|
||||
( { model
|
||||
| loading = False
|
||||
, result = Just (BasicResult False (Util.Http.errorToString err))
|
||||
@ -209,10 +209,10 @@ update flags msg model =
|
||||
, False
|
||||
)
|
||||
|
||||
SpaceDetailResp (Ok sd) ->
|
||||
FolderDetailResp (Ok sd) ->
|
||||
( init model.users sd, Cmd.none, False )
|
||||
|
||||
SpaceDetailResp (Err err) ->
|
||||
FolderDetailResp (Err err) ->
|
||||
( { model
|
||||
| loading = False
|
||||
, result = Just (BasicResult False (Util.Http.errorToString err))
|
||||
@ -225,7 +225,7 @@ update flags msg model =
|
||||
case model.selectedMember of
|
||||
Just mem ->
|
||||
( { model | loading = True }
|
||||
, Api.addMember flags model.space.id mem.id ChangeSpaceResp
|
||||
, Api.addMember flags model.folder.id mem.id ChangeFolderResp
|
||||
, False
|
||||
)
|
||||
|
||||
@ -234,7 +234,7 @@ update flags msg model =
|
||||
|
||||
RemoveMember idname ->
|
||||
( { model | loading = True }
|
||||
, Api.removeMember flags model.space.id idname.id ChangeSpaceResp
|
||||
, Api.removeMember flags model.folder.id idname.id ChangeFolderResp
|
||||
, False
|
||||
)
|
||||
|
||||
@ -252,7 +252,7 @@ update flags msg model =
|
||||
|
||||
cmd =
|
||||
if flag then
|
||||
Api.deleteSpace flags model.space.id DeleteResp
|
||||
Api.deleteFolder flags model.folder.id DeleteResp
|
||||
|
||||
else
|
||||
Cmd.none
|
||||
@ -278,23 +278,23 @@ view flags model =
|
||||
let
|
||||
isOwner =
|
||||
Maybe.map .user flags.account
|
||||
|> Maybe.map ((==) model.space.owner.name)
|
||||
|> Maybe.map ((==) model.folder.owner.name)
|
||||
|> Maybe.withDefault False
|
||||
in
|
||||
div []
|
||||
([ Html.map DeleteMsg (Comp.YesNoDimmer.view model.deleteDimmer)
|
||||
, if model.space.id == "" then
|
||||
, if model.folder.id == "" then
|
||||
div []
|
||||
[ text "Create a new space. You are automatically set as owner of this new space."
|
||||
[ text "Create a new folder. You are automatically set as owner of this new folder."
|
||||
]
|
||||
|
||||
else
|
||||
div []
|
||||
[ text "Modify this space by changing the name or add/remove members."
|
||||
[ text "Modify this folder by changing the name or add/remove members."
|
||||
]
|
||||
, if model.space.id /= "" && not isOwner then
|
||||
, if model.folder.id /= "" && not isOwner then
|
||||
div [ class "ui info message" ]
|
||||
[ text "You are not the owner of this space and therefore are not allowed to edit it."
|
||||
[ text "You are not the owner of this folder and therefore are not allowed to edit it."
|
||||
]
|
||||
|
||||
else
|
||||
@ -315,7 +315,7 @@ view flags model =
|
||||
[ text "Owner"
|
||||
]
|
||||
, div [ class "" ]
|
||||
[ text model.space.owner.name
|
||||
[ text model.folder.owner.name
|
||||
]
|
||||
, div [ class "ui header" ]
|
||||
[ text "Name"
|
||||
@ -361,7 +361,7 @@ viewButtons _ =
|
||||
|
||||
viewMembers : Model -> List (Html Msg)
|
||||
viewMembers model =
|
||||
if model.space.id == "" then
|
||||
if model.folder.id == "" then
|
||||
[]
|
||||
|
||||
else
|
78
modules/webapp/src/main/elm/Comp/SpaceManage.elm → modules/webapp/src/main/elm/Comp/FolderManage.elm
78
modules/webapp/src/main/elm/Comp/SpaceManage.elm → modules/webapp/src/main/elm/Comp/FolderManage.elm
@ -1,4 +1,4 @@
|
||||
module Comp.SpaceManage exposing
|
||||
module Comp.FolderManage exposing
|
||||
( Model
|
||||
, Msg
|
||||
, empty
|
||||
@ -8,13 +8,13 @@ module Comp.SpaceManage exposing
|
||||
)
|
||||
|
||||
import Api
|
||||
import Api.Model.SpaceDetail exposing (SpaceDetail)
|
||||
import Api.Model.SpaceItem exposing (SpaceItem)
|
||||
import Api.Model.SpaceList exposing (SpaceList)
|
||||
import Api.Model.FolderDetail exposing (FolderDetail)
|
||||
import Api.Model.FolderItem exposing (FolderItem)
|
||||
import Api.Model.FolderList exposing (FolderList)
|
||||
import Api.Model.User exposing (User)
|
||||
import Api.Model.UserList exposing (UserList)
|
||||
import Comp.SpaceDetail
|
||||
import Comp.SpaceTable
|
||||
import Comp.FolderDetail
|
||||
import Comp.FolderTable
|
||||
import Data.Flags exposing (Flags)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
@ -23,9 +23,9 @@ import Http
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ tableModel : Comp.SpaceTable.Model
|
||||
, detailModel : Maybe Comp.SpaceDetail.Model
|
||||
, spaces : List SpaceItem
|
||||
{ tableModel : Comp.FolderTable.Model
|
||||
, detailModel : Maybe Comp.FolderDetail.Model
|
||||
, folders : List FolderItem
|
||||
, users : List User
|
||||
, query : String
|
||||
, owningOnly : Bool
|
||||
@ -34,21 +34,21 @@ type alias Model =
|
||||
|
||||
|
||||
type Msg
|
||||
= TableMsg Comp.SpaceTable.Msg
|
||||
| DetailMsg Comp.SpaceDetail.Msg
|
||||
= TableMsg Comp.FolderTable.Msg
|
||||
| DetailMsg Comp.FolderDetail.Msg
|
||||
| UserListResp (Result Http.Error UserList)
|
||||
| SpaceListResp (Result Http.Error SpaceList)
|
||||
| SpaceDetailResp (Result Http.Error SpaceDetail)
|
||||
| FolderListResp (Result Http.Error FolderList)
|
||||
| FolderDetailResp (Result Http.Error FolderDetail)
|
||||
| SetQuery String
|
||||
| InitNewSpace
|
||||
| InitNewFolder
|
||||
| ToggleOwningOnly
|
||||
|
||||
|
||||
empty : Model
|
||||
empty =
|
||||
{ tableModel = Comp.SpaceTable.init
|
||||
{ tableModel = Comp.FolderTable.init
|
||||
, detailModel = Nothing
|
||||
, spaces = []
|
||||
, folders = []
|
||||
, users = []
|
||||
, query = ""
|
||||
, owningOnly = True
|
||||
@ -61,7 +61,7 @@ init flags =
|
||||
( empty
|
||||
, Cmd.batch
|
||||
[ Api.getUsers flags UserListResp
|
||||
, Api.getSpaces flags empty.query empty.owningOnly SpaceListResp
|
||||
, Api.getFolders flags empty.query empty.owningOnly FolderListResp
|
||||
]
|
||||
)
|
||||
|
||||
@ -76,14 +76,14 @@ update flags msg model =
|
||||
TableMsg lm ->
|
||||
let
|
||||
( tm, action ) =
|
||||
Comp.SpaceTable.update lm model.tableModel
|
||||
Comp.FolderTable.update lm model.tableModel
|
||||
|
||||
cmd =
|
||||
case action of
|
||||
Comp.SpaceTable.EditAction item ->
|
||||
Api.getSpaceDetail flags item.id SpaceDetailResp
|
||||
Comp.FolderTable.EditAction item ->
|
||||
Api.getFolderDetail flags item.id FolderDetailResp
|
||||
|
||||
Comp.SpaceTable.NoAction ->
|
||||
Comp.FolderTable.NoAction ->
|
||||
Cmd.none
|
||||
in
|
||||
( { model | tableModel = tm }, cmd )
|
||||
@ -93,11 +93,11 @@ update flags msg model =
|
||||
Just detail ->
|
||||
let
|
||||
( dm, dc, back ) =
|
||||
Comp.SpaceDetail.update flags lm detail
|
||||
Comp.FolderDetail.update flags lm detail
|
||||
|
||||
cmd =
|
||||
if back then
|
||||
Api.getSpaces flags model.query model.owningOnly SpaceListResp
|
||||
Api.getFolders flags model.query model.owningOnly FolderListResp
|
||||
|
||||
else
|
||||
Cmd.none
|
||||
@ -121,7 +121,7 @@ update flags msg model =
|
||||
|
||||
SetQuery str ->
|
||||
( { model | query = str }
|
||||
, Api.getSpaces flags str model.owningOnly SpaceListResp
|
||||
, Api.getFolders flags str model.owningOnly FolderListResp
|
||||
)
|
||||
|
||||
ToggleOwningOnly ->
|
||||
@ -130,7 +130,7 @@ update flags msg model =
|
||||
not model.owningOnly
|
||||
in
|
||||
( { model | owningOnly = newOwning }
|
||||
, Api.getSpaces flags model.query newOwning SpaceListResp
|
||||
, Api.getFolders flags model.query newOwning FolderListResp
|
||||
)
|
||||
|
||||
UserListResp (Ok ul) ->
|
||||
@ -139,24 +139,24 @@ update flags msg model =
|
||||
UserListResp (Err err) ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SpaceListResp (Ok sl) ->
|
||||
( { model | spaces = sl.items }, Cmd.none )
|
||||
FolderListResp (Ok sl) ->
|
||||
( { model | folders = sl.items }, Cmd.none )
|
||||
|
||||
SpaceListResp (Err err) ->
|
||||
FolderListResp (Err err) ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SpaceDetailResp (Ok sd) ->
|
||||
( { model | detailModel = Comp.SpaceDetail.init model.users sd |> Just }
|
||||
FolderDetailResp (Ok sd) ->
|
||||
( { model | detailModel = Comp.FolderDetail.init model.users sd |> Just }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
SpaceDetailResp (Err err) ->
|
||||
FolderDetailResp (Err err) ->
|
||||
( model, Cmd.none )
|
||||
|
||||
InitNewSpace ->
|
||||
InitNewFolder ->
|
||||
let
|
||||
sd =
|
||||
Comp.SpaceDetail.initEmpty model.users
|
||||
Comp.FolderDetail.initEmpty model.users
|
||||
in
|
||||
( { model | detailModel = Just sd }
|
||||
, Cmd.none
|
||||
@ -177,10 +177,10 @@ view flags model =
|
||||
viewTable model
|
||||
|
||||
|
||||
viewDetail : Flags -> Comp.SpaceDetail.Model -> Html Msg
|
||||
viewDetail : Flags -> Comp.FolderDetail.Model -> Html Msg
|
||||
viewDetail flags detailModel =
|
||||
div []
|
||||
[ Html.map DetailMsg (Comp.SpaceDetail.view flags detailModel)
|
||||
[ Html.map DetailMsg (Comp.FolderDetail.view flags detailModel)
|
||||
]
|
||||
|
||||
|
||||
@ -209,7 +209,7 @@ viewTable model =
|
||||
, checked model.owningOnly
|
||||
]
|
||||
[]
|
||||
, label [] [ text "Show owning spaces only" ]
|
||||
, label [] [ text "Show owning folders only" ]
|
||||
]
|
||||
]
|
||||
, div [ class "right menu" ]
|
||||
@ -217,15 +217,15 @@ viewTable model =
|
||||
[ a
|
||||
[ class "ui primary button"
|
||||
, href "#"
|
||||
, onClick InitNewSpace
|
||||
, onClick InitNewFolder
|
||||
]
|
||||
[ i [ class "plus icon" ] []
|
||||
, text "New Space"
|
||||
, text "New Folder"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
, Html.map TableMsg (Comp.SpaceTable.view model.tableModel model.spaces)
|
||||
, Html.map TableMsg (Comp.FolderTable.view model.tableModel model.folders)
|
||||
, div
|
||||
[ classList
|
||||
[ ( "ui dimmer", True )
|
12
modules/webapp/src/main/elm/Comp/SpaceTable.elm → modules/webapp/src/main/elm/Comp/FolderTable.elm
12
modules/webapp/src/main/elm/Comp/SpaceTable.elm → modules/webapp/src/main/elm/Comp/FolderTable.elm
@ -1,4 +1,4 @@
|
||||
module Comp.SpaceTable exposing
|
||||
module Comp.FolderTable exposing
|
||||
( Action(..)
|
||||
, Model
|
||||
, Msg
|
||||
@ -7,7 +7,7 @@ module Comp.SpaceTable exposing
|
||||
, view
|
||||
)
|
||||
|
||||
import Api.Model.SpaceItem exposing (SpaceItem)
|
||||
import Api.Model.FolderItem exposing (FolderItem)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
@ -20,12 +20,12 @@ type alias Model =
|
||||
|
||||
|
||||
type Msg
|
||||
= EditItem SpaceItem
|
||||
= EditItem FolderItem
|
||||
|
||||
|
||||
type Action
|
||||
= NoAction
|
||||
| EditAction SpaceItem
|
||||
| EditAction FolderItem
|
||||
|
||||
|
||||
init : Model
|
||||
@ -40,7 +40,7 @@ update msg model =
|
||||
( model, EditAction item )
|
||||
|
||||
|
||||
view : Model -> List SpaceItem -> Html Msg
|
||||
view : Model -> List FolderItem -> Html Msg
|
||||
view _ items =
|
||||
div []
|
||||
[ table [ class "ui very basic center aligned table" ]
|
||||
@ -58,7 +58,7 @@ view _ items =
|
||||
]
|
||||
|
||||
|
||||
viewItem : SpaceItem -> Html Msg
|
||||
viewItem : FolderItem -> Html Msg
|
||||
viewItem item =
|
||||
tr []
|
||||
[ td [ class "collapsing" ]
|
@ -15,12 +15,12 @@ module Data.Icons exposing
|
||||
, editNotesIcon
|
||||
, equipment
|
||||
, equipmentIcon
|
||||
, folder
|
||||
, folderIcon
|
||||
, organization
|
||||
, organizationIcon
|
||||
, person
|
||||
, personIcon
|
||||
, space
|
||||
, spaceIcon
|
||||
, tag
|
||||
, tagIcon
|
||||
, tags
|
||||
@ -31,14 +31,14 @@ import Html exposing (Html, i)
|
||||
import Html.Attributes exposing (class)
|
||||
|
||||
|
||||
space : String
|
||||
space =
|
||||
folder : String
|
||||
folder =
|
||||
"folder outline icon"
|
||||
|
||||
|
||||
spaceIcon : String -> Html msg
|
||||
spaceIcon classes =
|
||||
i [ class (space ++ " " ++ classes) ] []
|
||||
folderIcon : String -> Html msg
|
||||
folderIcon classes =
|
||||
i [ class (folder ++ " " ++ classes) ] []
|
||||
|
||||
|
||||
concerned : String
|
||||
|
@ -6,9 +6,9 @@ module Page.ManageData.Data exposing
|
||||
)
|
||||
|
||||
import Comp.EquipmentManage
|
||||
import Comp.FolderManage
|
||||
import Comp.OrgManage
|
||||
import Comp.PersonManage
|
||||
import Comp.SpaceManage
|
||||
import Comp.TagManage
|
||||
import Data.Flags exposing (Flags)
|
||||
|
||||
@ -19,7 +19,7 @@ type alias Model =
|
||||
, equipManageModel : Comp.EquipmentManage.Model
|
||||
, orgManageModel : Comp.OrgManage.Model
|
||||
, personManageModel : Comp.PersonManage.Model
|
||||
, spaceManageModel : Comp.SpaceManage.Model
|
||||
, folderManageModel : Comp.FolderManage.Model
|
||||
}
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ init _ =
|
||||
, equipManageModel = Comp.EquipmentManage.emptyModel
|
||||
, orgManageModel = Comp.OrgManage.emptyModel
|
||||
, personManageModel = Comp.PersonManage.emptyModel
|
||||
, spaceManageModel = Comp.SpaceManage.empty
|
||||
, folderManageModel = Comp.FolderManage.empty
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
@ -41,7 +41,7 @@ type Tab
|
||||
| EquipTab
|
||||
| OrgTab
|
||||
| PersonTab
|
||||
| SpaceTab
|
||||
| FolderTab
|
||||
|
||||
|
||||
type Msg
|
||||
@ -50,4 +50,4 @@ type Msg
|
||||
| EquipManageMsg Comp.EquipmentManage.Msg
|
||||
| OrgManageMsg Comp.OrgManage.Msg
|
||||
| PersonManageMsg Comp.PersonManage.Msg
|
||||
| SpaceMsg Comp.SpaceManage.Msg
|
||||
| FolderMsg Comp.FolderManage.Msg
|
||||
|
@ -1,9 +1,9 @@
|
||||
module Page.ManageData.Update exposing (update)
|
||||
|
||||
import Comp.EquipmentManage
|
||||
import Comp.FolderManage
|
||||
import Comp.OrgManage
|
||||
import Comp.PersonManage
|
||||
import Comp.SpaceManage
|
||||
import Comp.TagManage
|
||||
import Data.Flags exposing (Flags)
|
||||
import Page.ManageData.Data exposing (..)
|
||||
@ -30,12 +30,12 @@ update flags msg model =
|
||||
PersonTab ->
|
||||
update flags (PersonManageMsg Comp.PersonManage.LoadPersons) m
|
||||
|
||||
SpaceTab ->
|
||||
FolderTab ->
|
||||
let
|
||||
( sm, sc ) =
|
||||
Comp.SpaceManage.init flags
|
||||
Comp.FolderManage.init flags
|
||||
in
|
||||
( { m | spaceManageModel = sm }, Cmd.map SpaceMsg sc )
|
||||
( { m | folderManageModel = sm }, Cmd.map FolderMsg sc )
|
||||
|
||||
TagManageMsg m ->
|
||||
let
|
||||
@ -65,11 +65,11 @@ update flags msg model =
|
||||
in
|
||||
( { model | personManageModel = m2 }, Cmd.map PersonManageMsg c2 )
|
||||
|
||||
SpaceMsg lm ->
|
||||
FolderMsg lm ->
|
||||
let
|
||||
( m2, c2 ) =
|
||||
Comp.SpaceManage.update flags lm model.spaceManageModel
|
||||
Comp.FolderManage.update flags lm model.folderManageModel
|
||||
in
|
||||
( { model | spaceManageModel = m2 }
|
||||
, Cmd.map SpaceMsg c2
|
||||
( { model | folderManageModel = m2 }
|
||||
, Cmd.map FolderMsg c2
|
||||
)
|
||||
|
@ -1,9 +1,9 @@
|
||||
module Page.ManageData.View exposing (view)
|
||||
|
||||
import Comp.EquipmentManage
|
||||
import Comp.FolderManage
|
||||
import Comp.OrgManage
|
||||
import Comp.PersonManage
|
||||
import Comp.SpaceManage
|
||||
import Comp.TagManage
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.Icons as Icons
|
||||
@ -53,11 +53,11 @@ view flags settings model =
|
||||
, text "Person"
|
||||
]
|
||||
, div
|
||||
[ classActive (model.currentTab == Just SpaceTab) "link icon item"
|
||||
, onClick (SetTab SpaceTab)
|
||||
[ classActive (model.currentTab == Just FolderTab) "link icon item"
|
||||
, onClick (SetTab FolderTab)
|
||||
]
|
||||
[ Icons.spaceIcon ""
|
||||
, text "Space"
|
||||
[ Icons.folderIcon ""
|
||||
, text "Folder"
|
||||
]
|
||||
]
|
||||
]
|
||||
@ -77,8 +77,8 @@ view flags settings model =
|
||||
Just PersonTab ->
|
||||
viewPerson settings model
|
||||
|
||||
Just SpaceTab ->
|
||||
viewSpace flags settings model
|
||||
Just FolderTab ->
|
||||
viewFolder flags settings model
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
@ -87,19 +87,19 @@ view flags settings model =
|
||||
]
|
||||
|
||||
|
||||
viewSpace : Flags -> UiSettings -> Model -> List (Html Msg)
|
||||
viewSpace flags _ model =
|
||||
viewFolder : Flags -> UiSettings -> Model -> List (Html Msg)
|
||||
viewFolder flags _ model =
|
||||
[ h2
|
||||
[ class "ui header"
|
||||
]
|
||||
[ Icons.spaceIcon ""
|
||||
[ Icons.folderIcon ""
|
||||
, div
|
||||
[ class "content"
|
||||
]
|
||||
[ text "Spaces"
|
||||
[ text "Folders"
|
||||
]
|
||||
]
|
||||
, Html.map SpaceMsg (Comp.SpaceManage.view flags model.spaceManageModel)
|
||||
, Html.map FolderMsg (Comp.FolderManage.view flags model.folderManageModel)
|
||||
]
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user