mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-05 10:59:33 +00:00
Rename space -> folder
This commit is contained in:
parent
0365c1980a
commit
2ab0b5e222
@ -35,7 +35,7 @@ trait BackendApp[F[_]] {
|
|||||||
def mail: OMail[F]
|
def mail: OMail[F]
|
||||||
def joex: OJoex[F]
|
def joex: OJoex[F]
|
||||||
def userTask: OUserTask[F]
|
def userTask: OUserTask[F]
|
||||||
def space: OSpace[F]
|
def folder: OFolder[F]
|
||||||
}
|
}
|
||||||
|
|
||||||
object BackendApp {
|
object BackendApp {
|
||||||
@ -68,7 +68,7 @@ object BackendApp {
|
|||||||
JavaMailEmil(blocker, Settings.defaultSettings.copy(debug = cfg.mailDebug))
|
JavaMailEmil(blocker, Settings.defaultSettings.copy(debug = cfg.mailDebug))
|
||||||
mailImpl <- OMail(store, javaEmil)
|
mailImpl <- OMail(store, javaEmil)
|
||||||
userTaskImpl <- OUserTask(utStore, queue, joexImpl)
|
userTaskImpl <- OUserTask(utStore, queue, joexImpl)
|
||||||
spaceImpl <- OSpace(store)
|
folderImpl <- OFolder(store)
|
||||||
} yield new BackendApp[F] {
|
} yield new BackendApp[F] {
|
||||||
val login: Login[F] = loginImpl
|
val login: Login[F] = loginImpl
|
||||||
val signup: OSignup[F] = signupImpl
|
val signup: OSignup[F] = signupImpl
|
||||||
@ -86,7 +86,7 @@ object BackendApp {
|
|||||||
val mail = mailImpl
|
val mail = mailImpl
|
||||||
val joex = joexImpl
|
val joex = joexImpl
|
||||||
val userTask = userTaskImpl
|
val userTask = userTaskImpl
|
||||||
val space = spaceImpl
|
val folder = folderImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
def apply[F[_]: ConcurrentEffect: ContextShift](
|
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:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/BasicResult"
|
$ref: "#/components/schemas/BasicResult"
|
||||||
/sec/space:
|
/sec/folder:
|
||||||
get:
|
get:
|
||||||
tags: [ Space ]
|
tags: [ Folder ]
|
||||||
summary: Get a list of spaces.
|
summary: Get a list of folders.
|
||||||
description: |
|
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.
|
current user.
|
||||||
|
|
||||||
It is possible to restrict the results by a substring match of
|
It is possible to restrict the results by a substring match of
|
||||||
@ -818,12 +818,12 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/SpaceList"
|
$ref: "#/components/schemas/FolderList"
|
||||||
post:
|
post:
|
||||||
tags: [ Space ]
|
tags: [ Folder ]
|
||||||
summary: Create a new space
|
summary: Create a new folder
|
||||||
description: |
|
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.
|
the same name already exists, an error is thrown.
|
||||||
security:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
@ -831,7 +831,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/NewSpace"
|
$ref: "#/components/schemas/NewFolder"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Ok
|
description: Ok
|
||||||
@ -839,12 +839,12 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/IdResult"
|
$ref: "#/components/schemas/IdResult"
|
||||||
/sec/space/{id}:
|
/sec/folder/{id}:
|
||||||
get:
|
get:
|
||||||
tags: [ Space ]
|
tags: [ Folder ]
|
||||||
summary: Get space details.
|
summary: Get folder details.
|
||||||
description: |
|
description: |
|
||||||
Return details about a space.
|
Return details about a folder.
|
||||||
security:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
parameters:
|
parameters:
|
||||||
@ -855,12 +855,12 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/SpaceDetail"
|
$ref: "#/components/schemas/FolderDetail"
|
||||||
put:
|
put:
|
||||||
tags: [ Space ]
|
tags: [ Folder ]
|
||||||
summary: Change the name of a space
|
summary: Change the name of a folder
|
||||||
description: |
|
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:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
parameters:
|
parameters:
|
||||||
@ -869,7 +869,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/NewSpace"
|
$ref: "#/components/schemas/NewFolder"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Ok
|
description: Ok
|
||||||
@ -878,10 +878,10 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/BasicResult"
|
$ref: "#/components/schemas/BasicResult"
|
||||||
delete:
|
delete:
|
||||||
tags: [ Space ]
|
tags: [ Folder ]
|
||||||
summary: Delete a space by its id.
|
summary: Delete a folder by its id.
|
||||||
description: |
|
description: |
|
||||||
Deletes a space.
|
Deletes a folder.
|
||||||
security:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
parameters:
|
parameters:
|
||||||
@ -893,12 +893,12 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/BasicResult"
|
$ref: "#/components/schemas/BasicResult"
|
||||||
/sec/space/{id}/member/{userId}:
|
/sec/folder/{id}/member/{userId}:
|
||||||
put:
|
put:
|
||||||
tags: [ Space ]
|
tags: [ Folder ]
|
||||||
summary: Add a member to this space
|
summary: Add a member to this folder
|
||||||
description: |
|
description: |
|
||||||
Adds a member to this space (identified by `id`).
|
Adds a member to this folder (identified by `id`).
|
||||||
security:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
parameters:
|
parameters:
|
||||||
@ -912,10 +912,10 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/BasicResult"
|
$ref: "#/components/schemas/BasicResult"
|
||||||
delete:
|
delete:
|
||||||
tags: [ Space ]
|
tags: [ Folder ]
|
||||||
summary: Removes a member from this space.
|
summary: Removes a member from this folder.
|
||||||
description: |
|
description: |
|
||||||
Removes a member from this space.
|
Removes a member from this folder.
|
||||||
security:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
parameters:
|
parameters:
|
||||||
@ -984,7 +984,7 @@ paths:
|
|||||||
summary: Get some insights regarding your items.
|
summary: Get some insights regarding your items.
|
||||||
description: |
|
description: |
|
||||||
Returns some information about how many items there are, how
|
Returns some information about how many items there are, how
|
||||||
much space they occupy etc.
|
much folder they occupy etc.
|
||||||
security:
|
security:
|
||||||
- authTokenHeader: []
|
- authTokenHeader: []
|
||||||
responses:
|
responses:
|
||||||
@ -2492,19 +2492,19 @@ paths:
|
|||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
SpaceList:
|
FolderList:
|
||||||
description: |
|
description: |
|
||||||
A list of spaces with their member counts.
|
A list of folders with their member counts.
|
||||||
required:
|
required:
|
||||||
- items
|
- items
|
||||||
properties:
|
properties:
|
||||||
items:
|
items:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/SpaceItem"
|
$ref: "#/components/schemas/FolderItem"
|
||||||
SpaceItem:
|
FolderItem:
|
||||||
description: |
|
description: |
|
||||||
An item in a space list.
|
An item in a folder list.
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- name
|
- name
|
||||||
@ -2528,17 +2528,17 @@ components:
|
|||||||
memberCount:
|
memberCount:
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int32
|
||||||
NewSpace:
|
NewFolder:
|
||||||
description: |
|
description: |
|
||||||
Data required to create a new space.
|
Data required to create a new folder.
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
properties:
|
properties:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
SpaceDetail:
|
FolderDetail:
|
||||||
description: |
|
description: |
|
||||||
Details about a space.
|
Details about a folder.
|
||||||
required:
|
required:
|
||||||
- id
|
- id
|
||||||
- name
|
- name
|
||||||
@ -2567,9 +2567,9 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/IdName"
|
$ref: "#/components/schemas/IdName"
|
||||||
SpaceMember:
|
FolderMember:
|
||||||
description: |
|
description: |
|
||||||
Information to add or remove a space member.
|
Information to add or remove a folder member.
|
||||||
required:
|
required:
|
||||||
- userId
|
- userId
|
||||||
properties:
|
properties:
|
||||||
@ -4001,7 +4001,7 @@ components:
|
|||||||
owning:
|
owning:
|
||||||
name: full
|
name: full
|
||||||
in: query
|
in: query
|
||||||
description: Whether to get owning spaces
|
description: Whether to get owning folders
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
@ -82,7 +82,7 @@ object RestServer {
|
|||||||
"usertask/scanmailbox" -> ScanMailboxRoutes(restApp.backend, token),
|
"usertask/scanmailbox" -> ScanMailboxRoutes(restApp.backend, token),
|
||||||
"calevent/check" -> CalEventCheckRoutes(),
|
"calevent/check" -> CalEventCheckRoutes(),
|
||||||
"fts" -> FullTextIndexRoutes.secured(cfg, restApp.backend, token),
|
"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] =
|
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,
|
"id" varchar(254) not null primary key,
|
||||||
"name" varchar(254) not null,
|
"name" varchar(254) not null,
|
||||||
"cid" varchar(254) not null,
|
"cid" varchar(254) not null,
|
||||||
@ -9,15 +9,15 @@ CREATE TABLE "space" (
|
|||||||
foreign key ("owner") references "user_"("uid")
|
foreign key ("owner") references "user_"("uid")
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE "space_member" (
|
CREATE TABLE "folder_member" (
|
||||||
"id" varchar(254) not null primary key,
|
"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,
|
"user_id" varchar(254) not null,
|
||||||
"created" timestamp not null,
|
"created" timestamp not null,
|
||||||
unique ("space_id", "user_id"),
|
unique ("folder_id", "user_id"),
|
||||||
foreign key ("space_id") references "space"("id"),
|
foreign key ("folder_id") references "folder"("id"),
|
||||||
foreign key ("user_id") references "user_"("uid")
|
foreign key ("user_id") references "user_"("uid")
|
||||||
);
|
);
|
||||||
|
|
||||||
ALTER TABLE "item"
|
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._
|
||||||
import doobie.implicits._
|
import doobie.implicits._
|
||||||
|
|
||||||
case class RSpace(
|
case class RFolder(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
name: String,
|
name: String,
|
||||||
collectiveId: Ident,
|
collectiveId: Ident,
|
||||||
@ -17,15 +17,15 @@ case class RSpace(
|
|||||||
created: Timestamp
|
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 {
|
for {
|
||||||
nId <- Ident.randomId[F]
|
nId <- Ident.randomId[F]
|
||||||
now <- Timestamp.current[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 {
|
object Columns {
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ object RSpace {
|
|||||||
|
|
||||||
import Columns._
|
import Columns._
|
||||||
|
|
||||||
def insert(value: RSpace): ConnectionIO[Int] = {
|
def insert(value: RFolder): ConnectionIO[Int] = {
|
||||||
val sql = insertRow(
|
val sql = insertRow(
|
||||||
table,
|
table,
|
||||||
all,
|
all,
|
||||||
@ -49,37 +49,37 @@ object RSpace {
|
|||||||
sql.update.run
|
sql.update.run
|
||||||
}
|
}
|
||||||
|
|
||||||
def update(v: RSpace): ConnectionIO[Int] =
|
def update(v: RFolder): ConnectionIO[Int] =
|
||||||
updateRow(
|
updateRow(
|
||||||
table,
|
table,
|
||||||
and(id.is(v.id), collective.is(v.collectiveId), owner.is(v.owner)),
|
and(id.is(v.id), collective.is(v.collectiveId), owner.is(v.owner)),
|
||||||
name.setTo(v.name)
|
name.setTo(v.name)
|
||||||
).update.run
|
).update.run
|
||||||
|
|
||||||
def existsByName(coll: Ident, spaceName: String): ConnectionIO[Boolean] =
|
def existsByName(coll: Ident, folderName: String): ConnectionIO[Boolean] =
|
||||||
selectCount(id, table, and(collective.is(coll), name.is(spaceName)))
|
selectCount(id, table, and(collective.is(coll), name.is(folderName)))
|
||||||
.query[Int]
|
.query[Int]
|
||||||
.unique
|
.unique
|
||||||
.map(_ > 0)
|
.map(_ > 0)
|
||||||
|
|
||||||
def findById(spaceId: Ident): ConnectionIO[Option[RSpace]] = {
|
def findById(folderId: Ident): ConnectionIO[Option[RFolder]] = {
|
||||||
val sql = selectSimple(all, table, id.is(spaceId))
|
val sql = selectSimple(all, table, id.is(folderId))
|
||||||
sql.query[RSpace].option
|
sql.query[RFolder].option
|
||||||
}
|
}
|
||||||
|
|
||||||
def findAll(
|
def findAll(
|
||||||
coll: Ident,
|
coll: Ident,
|
||||||
nameQ: Option[String],
|
nameQ: Option[String],
|
||||||
order: Columns.type => Column
|
order: Columns.type => Column
|
||||||
): ConnectionIO[Vector[RSpace]] = {
|
): ConnectionIO[Vector[RFolder]] = {
|
||||||
val q = Seq(collective.is(coll)) ++ (nameQ match {
|
val q = Seq(collective.is(coll)) ++ (nameQ match {
|
||||||
case Some(str) => Seq(name.lowerLike(s"%${str.toLowerCase}%"))
|
case Some(str) => Seq(name.lowerLike(s"%${str.toLowerCase}%"))
|
||||||
case None => Seq.empty
|
case None => Seq.empty
|
||||||
})
|
})
|
||||||
val sql = selectSimple(all, table, and(q)) ++ orderBy(order(Columns).f)
|
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] =
|
def delete(folderId: Ident): ConnectionIO[Int] =
|
||||||
deleteFrom(table, id.is(spaceId)).update.run
|
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,
|
created: Timestamp,
|
||||||
updated: Timestamp,
|
updated: Timestamp,
|
||||||
notes: Option[String],
|
notes: Option[String],
|
||||||
spaceId: Option[Ident]
|
folderId: Option[Ident]
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
object RItem {
|
object RItem {
|
||||||
@ -82,7 +82,7 @@ object RItem {
|
|||||||
val created = Column("created")
|
val created = Column("created")
|
||||||
val updated = Column("updated")
|
val updated = Column("updated")
|
||||||
val notes = Column("notes")
|
val notes = Column("notes")
|
||||||
val space = Column("space_id")
|
val folder = Column("folder_id")
|
||||||
val all = List(
|
val all = List(
|
||||||
id,
|
id,
|
||||||
cid,
|
cid,
|
||||||
@ -100,7 +100,7 @@ object RItem {
|
|||||||
created,
|
created,
|
||||||
updated,
|
updated,
|
||||||
notes,
|
notes,
|
||||||
space
|
folder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
import Columns._
|
import Columns._
|
||||||
@ -111,7 +111,7 @@ object RItem {
|
|||||||
all,
|
all,
|
||||||
fr"${v.id},${v.cid},${v.name},${v.itemDate},${v.source},${v.direction},${v.state}," ++
|
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.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
|
).update.run
|
||||||
|
|
||||||
def getCollective(itemId: Ident): ConnectionIO[Option[Ident]] =
|
def getCollective(itemId: Ident): ConnectionIO[Option[Ident]] =
|
||||||
@ -300,8 +300,8 @@ object RItem {
|
|||||||
def findByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Option[RItem]] =
|
def findByIdAndCollective(itemId: Ident, coll: Ident): ConnectionIO[Option[RItem]] =
|
||||||
selectSimple(all, table, and(id.is(itemId), cid.is(coll))).query[RItem].option
|
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
|
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
|
, addMember
|
||||||
, addTag
|
, addTag
|
||||||
, cancelJob
|
, cancelJob
|
||||||
|
, changeFolderName
|
||||||
, changePassword
|
, changePassword
|
||||||
, changeSpaceName
|
|
||||||
, checkCalEvent
|
, checkCalEvent
|
||||||
, createImapSettings
|
, createImapSettings
|
||||||
, createMailSettings
|
, createMailSettings
|
||||||
, createNewSpace
|
, createNewFolder
|
||||||
, createNotifyDueItems
|
, createNotifyDueItems
|
||||||
, createScanMailbox
|
, createScanMailbox
|
||||||
, deleteAttachment
|
, deleteAttachment
|
||||||
, deleteEquip
|
, deleteEquip
|
||||||
|
, deleteFolder
|
||||||
, deleteImapSettings
|
, deleteImapSettings
|
||||||
, deleteItem
|
, deleteItem
|
||||||
, deleteMailSettings
|
, deleteMailSettings
|
||||||
@ -24,7 +25,6 @@ module Api exposing
|
|||||||
, deletePerson
|
, deletePerson
|
||||||
, deleteScanMailbox
|
, deleteScanMailbox
|
||||||
, deleteSource
|
, deleteSource
|
||||||
, deleteSpace
|
|
||||||
, deleteTag
|
, deleteTag
|
||||||
, deleteUser
|
, deleteUser
|
||||||
, getAttachmentMeta
|
, getAttachmentMeta
|
||||||
@ -32,6 +32,8 @@ module Api exposing
|
|||||||
, getCollectiveSettings
|
, getCollectiveSettings
|
||||||
, getContacts
|
, getContacts
|
||||||
, getEquipments
|
, getEquipments
|
||||||
|
, getFolderDetail
|
||||||
|
, getFolders
|
||||||
, getImapSettings
|
, getImapSettings
|
||||||
, getInsights
|
, getInsights
|
||||||
, getItemProposals
|
, getItemProposals
|
||||||
@ -46,8 +48,6 @@ module Api exposing
|
|||||||
, getScanMailbox
|
, getScanMailbox
|
||||||
, getSentMails
|
, getSentMails
|
||||||
, getSources
|
, getSources
|
||||||
, getSpaceDetail
|
|
||||||
, getSpaces
|
|
||||||
, getTags
|
, getTags
|
||||||
, getUsers
|
, getUsers
|
||||||
, itemDetail
|
, itemDetail
|
||||||
@ -108,6 +108,8 @@ import Api.Model.EmailSettings exposing (EmailSettings)
|
|||||||
import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
import Api.Model.EmailSettingsList exposing (EmailSettingsList)
|
||||||
import Api.Model.Equipment exposing (Equipment)
|
import Api.Model.Equipment exposing (Equipment)
|
||||||
import Api.Model.EquipmentList exposing (EquipmentList)
|
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.GenInvite exposing (GenInvite)
|
||||||
import Api.Model.IdResult exposing (IdResult)
|
import Api.Model.IdResult exposing (IdResult)
|
||||||
import Api.Model.ImapSettings exposing (ImapSettings)
|
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.ItemUploadMeta exposing (ItemUploadMeta)
|
||||||
import Api.Model.JobQueueState exposing (JobQueueState)
|
import Api.Model.JobQueueState exposing (JobQueueState)
|
||||||
import Api.Model.MoveAttachment exposing (MoveAttachment)
|
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.NotificationSettings exposing (NotificationSettings)
|
||||||
import Api.Model.NotificationSettingsList exposing (NotificationSettingsList)
|
import Api.Model.NotificationSettingsList exposing (NotificationSettingsList)
|
||||||
import Api.Model.OptionalDate exposing (OptionalDate)
|
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.SimpleMail exposing (SimpleMail)
|
||||||
import Api.Model.Source exposing (Source)
|
import Api.Model.Source exposing (Source)
|
||||||
import Api.Model.SourceList exposing (SourceList)
|
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.Tag exposing (Tag)
|
||||||
import Api.Model.TagList exposing (TagList)
|
import Api.Model.TagList exposing (TagList)
|
||||||
import Api.Model.User exposing (User)
|
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
|
deleteFolder : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||||
deleteSpace flags id receive =
|
deleteFolder flags id receive =
|
||||||
Http2.authDelete
|
Http2.authDelete
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/space/" ++ id
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/folder/" ++ id
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, 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 -> String -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||||
removeMember flags id user receive =
|
removeMember flags id user receive =
|
||||||
Http2.authDelete
|
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
|
, account = getAccount flags
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, 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 -> String -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||||
addMember flags id user receive =
|
addMember flags id user receive =
|
||||||
Http2.authPut
|
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
|
, account = getAccount flags
|
||||||
, body = Http.emptyBody
|
, body = Http.emptyBody
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
changeSpaceName : Flags -> String -> NewSpace -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
changeFolderName : Flags -> String -> NewFolder -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||||
changeSpaceName flags id ns receive =
|
changeFolderName flags id ns receive =
|
||||||
Http2.authPut
|
Http2.authPut
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/space/" ++ id
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/folder/" ++ id
|
||||||
, account = getAccount flags
|
, 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
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
createNewSpace : Flags -> NewSpace -> (Result Http.Error IdResult -> msg) -> Cmd msg
|
createNewFolder : Flags -> NewFolder -> (Result Http.Error IdResult -> msg) -> Cmd msg
|
||||||
createNewSpace flags ns receive =
|
createNewFolder flags ns receive =
|
||||||
Http2.authPost
|
Http2.authPost
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/space"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/folder"
|
||||||
, account = getAccount flags
|
, 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
|
, expect = Http.expectJson receive Api.Model.IdResult.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
getSpaceDetail : Flags -> String -> (Result Http.Error SpaceDetail -> msg) -> Cmd msg
|
getFolderDetail : Flags -> String -> (Result Http.Error FolderDetail -> msg) -> Cmd msg
|
||||||
getSpaceDetail flags id receive =
|
getFolderDetail flags id receive =
|
||||||
Http2.authGet
|
Http2.authGet
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/space/" ++ id
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/folder/" ++ id
|
||||||
, account = getAccount flags
|
, 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
|
getFolders : Flags -> String -> Bool -> (Result Http.Error FolderList -> msg) -> Cmd msg
|
||||||
getSpaces flags query owningOnly receive =
|
getFolders flags query owningOnly receive =
|
||||||
Http2.authGet
|
Http2.authGet
|
||||||
{ url =
|
{ url =
|
||||||
flags.config.baseUrl
|
flags.config.baseUrl
|
||||||
++ "/api/v1/sec/space?q="
|
++ "/api/v1/sec/folder?q="
|
||||||
++ Url.percentEncode query
|
++ Url.percentEncode query
|
||||||
++ (if owningOnly then
|
++ (if owningOnly then
|
||||||
"&owning=true"
|
"&owning=true"
|
||||||
@ -235,7 +235,7 @@ getSpaces flags query owningOnly receive =
|
|||||||
""
|
""
|
||||||
)
|
)
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, expect = Http.expectJson receive Api.Model.SpaceList.decoder
|
, expect = Http.expectJson receive Api.Model.FolderList.decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module Comp.SpaceDetail exposing
|
module Comp.FolderDetail exposing
|
||||||
( Model
|
( Model
|
||||||
, Msg
|
, Msg
|
||||||
, init
|
, init
|
||||||
@ -9,10 +9,10 @@ module Comp.SpaceDetail exposing
|
|||||||
|
|
||||||
import Api
|
import Api
|
||||||
import Api.Model.BasicResult exposing (BasicResult)
|
import Api.Model.BasicResult exposing (BasicResult)
|
||||||
|
import Api.Model.FolderDetail exposing (FolderDetail)
|
||||||
import Api.Model.IdName exposing (IdName)
|
import Api.Model.IdName exposing (IdName)
|
||||||
import Api.Model.IdResult exposing (IdResult)
|
import Api.Model.IdResult exposing (IdResult)
|
||||||
import Api.Model.NewSpace exposing (NewSpace)
|
import Api.Model.NewFolder exposing (NewFolder)
|
||||||
import Api.Model.SpaceDetail exposing (SpaceDetail)
|
|
||||||
import Api.Model.User exposing (User)
|
import Api.Model.User exposing (User)
|
||||||
import Api.Model.UserList exposing (UserList)
|
import Api.Model.UserList exposing (UserList)
|
||||||
import Comp.FixedDropdown
|
import Comp.FixedDropdown
|
||||||
@ -28,7 +28,7 @@ import Util.Maybe
|
|||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ result : Maybe BasicResult
|
{ result : Maybe BasicResult
|
||||||
, space : SpaceDetail
|
, folder : FolderDetail
|
||||||
, name : Maybe String
|
, name : Maybe String
|
||||||
, members : List IdName
|
, members : List IdName
|
||||||
, users : List User
|
, users : List User
|
||||||
@ -43,10 +43,10 @@ type Msg
|
|||||||
= SetName String
|
= SetName String
|
||||||
| MemberDropdownMsg (Comp.FixedDropdown.Msg IdName)
|
| MemberDropdownMsg (Comp.FixedDropdown.Msg IdName)
|
||||||
| SaveName
|
| SaveName
|
||||||
| NewSpaceResp (Result Http.Error IdResult)
|
| NewFolderResp (Result Http.Error IdResult)
|
||||||
| ChangeSpaceResp (Result Http.Error BasicResult)
|
| ChangeFolderResp (Result Http.Error BasicResult)
|
||||||
| ChangeNameResp (Result Http.Error BasicResult)
|
| ChangeNameResp (Result Http.Error BasicResult)
|
||||||
| SpaceDetailResp (Result Http.Error SpaceDetail)
|
| FolderDetailResp (Result Http.Error FolderDetail)
|
||||||
| AddMember
|
| AddMember
|
||||||
| RemoveMember IdName
|
| RemoveMember IdName
|
||||||
| RequestDelete
|
| RequestDelete
|
||||||
@ -55,16 +55,16 @@ type Msg
|
|||||||
| GoBack
|
| GoBack
|
||||||
|
|
||||||
|
|
||||||
init : List User -> SpaceDetail -> Model
|
init : List User -> FolderDetail -> Model
|
||||||
init users space =
|
init users folder =
|
||||||
{ result = Nothing
|
{ result = Nothing
|
||||||
, space = space
|
, folder = folder
|
||||||
, name = Util.Maybe.fromString space.name
|
, name = Util.Maybe.fromString folder.name
|
||||||
, members = space.members
|
, members = folder.members
|
||||||
, users = users
|
, users = users
|
||||||
, memberDropdown =
|
, memberDropdown =
|
||||||
Comp.FixedDropdown.initMap .name
|
Comp.FixedDropdown.initMap .name
|
||||||
(makeOptions users space)
|
(makeOptions users folder)
|
||||||
, selectedMember = Nothing
|
, selectedMember = Nothing
|
||||||
, loading = False
|
, loading = False
|
||||||
, deleteDimmer = Comp.YesNoDimmer.emptyModel
|
, deleteDimmer = Comp.YesNoDimmer.emptyModel
|
||||||
@ -73,17 +73,17 @@ init users space =
|
|||||||
|
|
||||||
initEmpty : List User -> Model
|
initEmpty : List User -> Model
|
||||||
initEmpty users =
|
initEmpty users =
|
||||||
init users Api.Model.SpaceDetail.empty
|
init users Api.Model.FolderDetail.empty
|
||||||
|
|
||||||
|
|
||||||
makeOptions : List User -> SpaceDetail -> List IdName
|
makeOptions : List User -> FolderDetail -> List IdName
|
||||||
makeOptions users space =
|
makeOptions users folder =
|
||||||
let
|
let
|
||||||
toIdName u =
|
toIdName u =
|
||||||
IdName u.id u.login
|
IdName u.id u.login
|
||||||
|
|
||||||
notMember idn =
|
notMember idn =
|
||||||
List.member idn (space.owner :: space.members) |> not
|
List.member idn (folder.owner :: folder.members) |> not
|
||||||
in
|
in
|
||||||
List.map toIdName users
|
List.map toIdName users
|
||||||
|> List.filter notMember
|
|> List.filter notMember
|
||||||
@ -129,13 +129,13 @@ update flags msg model =
|
|||||||
Just name ->
|
Just name ->
|
||||||
let
|
let
|
||||||
cmd =
|
cmd =
|
||||||
if model.space.id == "" then
|
if model.folder.id == "" then
|
||||||
Api.createNewSpace flags (NewSpace name) NewSpaceResp
|
Api.createNewFolder flags (NewFolder name) NewFolderResp
|
||||||
|
|
||||||
else
|
else
|
||||||
Api.changeSpaceName flags
|
Api.changeFolderName flags
|
||||||
model.space.id
|
model.folder.id
|
||||||
(NewSpace name)
|
(NewFolder name)
|
||||||
ChangeNameResp
|
ChangeNameResp
|
||||||
in
|
in
|
||||||
( { model
|
( { model
|
||||||
@ -149,9 +149,9 @@ update flags msg model =
|
|||||||
Nothing ->
|
Nothing ->
|
||||||
( model, Cmd.none, False )
|
( model, Cmd.none, False )
|
||||||
|
|
||||||
NewSpaceResp (Ok ir) ->
|
NewFolderResp (Ok ir) ->
|
||||||
if ir.success then
|
if ir.success then
|
||||||
( model, Api.getSpaceDetail flags ir.id SpaceDetailResp, False )
|
( model, Api.getFolderDetail flags ir.id FolderDetailResp, False )
|
||||||
|
|
||||||
else
|
else
|
||||||
( { model
|
( { model
|
||||||
@ -162,7 +162,7 @@ update flags msg model =
|
|||||||
, False
|
, False
|
||||||
)
|
)
|
||||||
|
|
||||||
NewSpaceResp (Err err) ->
|
NewFolderResp (Err err) ->
|
||||||
( { model
|
( { model
|
||||||
| loading = False
|
| loading = False
|
||||||
, result = Just (BasicResult False (Util.Http.errorToString err))
|
, result = Just (BasicResult False (Util.Http.errorToString err))
|
||||||
@ -171,10 +171,10 @@ update flags msg model =
|
|||||||
, False
|
, False
|
||||||
)
|
)
|
||||||
|
|
||||||
ChangeSpaceResp (Ok r) ->
|
ChangeFolderResp (Ok r) ->
|
||||||
if r.success then
|
if r.success then
|
||||||
( model
|
( model
|
||||||
, Api.getSpaceDetail flags model.space.id SpaceDetailResp
|
, Api.getFolderDetail flags model.folder.id FolderDetailResp
|
||||||
, False
|
, False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -184,7 +184,7 @@ update flags msg model =
|
|||||||
, False
|
, False
|
||||||
)
|
)
|
||||||
|
|
||||||
ChangeSpaceResp (Err err) ->
|
ChangeFolderResp (Err err) ->
|
||||||
( { model
|
( { model
|
||||||
| loading = False
|
| loading = False
|
||||||
, result = Just (BasicResult False (Util.Http.errorToString err))
|
, result = Just (BasicResult False (Util.Http.errorToString err))
|
||||||
@ -209,10 +209,10 @@ update flags msg model =
|
|||||||
, False
|
, False
|
||||||
)
|
)
|
||||||
|
|
||||||
SpaceDetailResp (Ok sd) ->
|
FolderDetailResp (Ok sd) ->
|
||||||
( init model.users sd, Cmd.none, False )
|
( init model.users sd, Cmd.none, False )
|
||||||
|
|
||||||
SpaceDetailResp (Err err) ->
|
FolderDetailResp (Err err) ->
|
||||||
( { model
|
( { model
|
||||||
| loading = False
|
| loading = False
|
||||||
, result = Just (BasicResult False (Util.Http.errorToString err))
|
, result = Just (BasicResult False (Util.Http.errorToString err))
|
||||||
@ -225,7 +225,7 @@ update flags msg model =
|
|||||||
case model.selectedMember of
|
case model.selectedMember of
|
||||||
Just mem ->
|
Just mem ->
|
||||||
( { model | loading = True }
|
( { model | loading = True }
|
||||||
, Api.addMember flags model.space.id mem.id ChangeSpaceResp
|
, Api.addMember flags model.folder.id mem.id ChangeFolderResp
|
||||||
, False
|
, False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -234,7 +234,7 @@ update flags msg model =
|
|||||||
|
|
||||||
RemoveMember idname ->
|
RemoveMember idname ->
|
||||||
( { model | loading = True }
|
( { model | loading = True }
|
||||||
, Api.removeMember flags model.space.id idname.id ChangeSpaceResp
|
, Api.removeMember flags model.folder.id idname.id ChangeFolderResp
|
||||||
, False
|
, False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -252,7 +252,7 @@ update flags msg model =
|
|||||||
|
|
||||||
cmd =
|
cmd =
|
||||||
if flag then
|
if flag then
|
||||||
Api.deleteSpace flags model.space.id DeleteResp
|
Api.deleteFolder flags model.folder.id DeleteResp
|
||||||
|
|
||||||
else
|
else
|
||||||
Cmd.none
|
Cmd.none
|
||||||
@ -278,23 +278,23 @@ view flags model =
|
|||||||
let
|
let
|
||||||
isOwner =
|
isOwner =
|
||||||
Maybe.map .user flags.account
|
Maybe.map .user flags.account
|
||||||
|> Maybe.map ((==) model.space.owner.name)
|
|> Maybe.map ((==) model.folder.owner.name)
|
||||||
|> Maybe.withDefault False
|
|> Maybe.withDefault False
|
||||||
in
|
in
|
||||||
div []
|
div []
|
||||||
([ Html.map DeleteMsg (Comp.YesNoDimmer.view model.deleteDimmer)
|
([ Html.map DeleteMsg (Comp.YesNoDimmer.view model.deleteDimmer)
|
||||||
, if model.space.id == "" then
|
, if model.folder.id == "" then
|
||||||
div []
|
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
|
else
|
||||||
div []
|
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" ]
|
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
|
else
|
||||||
@ -315,7 +315,7 @@ view flags model =
|
|||||||
[ text "Owner"
|
[ text "Owner"
|
||||||
]
|
]
|
||||||
, div [ class "" ]
|
, div [ class "" ]
|
||||||
[ text model.space.owner.name
|
[ text model.folder.owner.name
|
||||||
]
|
]
|
||||||
, div [ class "ui header" ]
|
, div [ class "ui header" ]
|
||||||
[ text "Name"
|
[ text "Name"
|
||||||
@ -361,7 +361,7 @@ viewButtons _ =
|
|||||||
|
|
||||||
viewMembers : Model -> List (Html Msg)
|
viewMembers : Model -> List (Html Msg)
|
||||||
viewMembers model =
|
viewMembers model =
|
||||||
if model.space.id == "" then
|
if model.folder.id == "" then
|
||||||
[]
|
[]
|
||||||
|
|
||||||
else
|
else
|
@ -1,4 +1,4 @@
|
|||||||
module Comp.SpaceManage exposing
|
module Comp.FolderManage exposing
|
||||||
( Model
|
( Model
|
||||||
, Msg
|
, Msg
|
||||||
, empty
|
, empty
|
||||||
@ -8,13 +8,13 @@ module Comp.SpaceManage exposing
|
|||||||
)
|
)
|
||||||
|
|
||||||
import Api
|
import Api
|
||||||
import Api.Model.SpaceDetail exposing (SpaceDetail)
|
import Api.Model.FolderDetail exposing (FolderDetail)
|
||||||
import Api.Model.SpaceItem exposing (SpaceItem)
|
import Api.Model.FolderItem exposing (FolderItem)
|
||||||
import Api.Model.SpaceList exposing (SpaceList)
|
import Api.Model.FolderList exposing (FolderList)
|
||||||
import Api.Model.User exposing (User)
|
import Api.Model.User exposing (User)
|
||||||
import Api.Model.UserList exposing (UserList)
|
import Api.Model.UserList exposing (UserList)
|
||||||
import Comp.SpaceDetail
|
import Comp.FolderDetail
|
||||||
import Comp.SpaceTable
|
import Comp.FolderTable
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
@ -23,9 +23,9 @@ import Http
|
|||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ tableModel : Comp.SpaceTable.Model
|
{ tableModel : Comp.FolderTable.Model
|
||||||
, detailModel : Maybe Comp.SpaceDetail.Model
|
, detailModel : Maybe Comp.FolderDetail.Model
|
||||||
, spaces : List SpaceItem
|
, folders : List FolderItem
|
||||||
, users : List User
|
, users : List User
|
||||||
, query : String
|
, query : String
|
||||||
, owningOnly : Bool
|
, owningOnly : Bool
|
||||||
@ -34,21 +34,21 @@ type alias Model =
|
|||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= TableMsg Comp.SpaceTable.Msg
|
= TableMsg Comp.FolderTable.Msg
|
||||||
| DetailMsg Comp.SpaceDetail.Msg
|
| DetailMsg Comp.FolderDetail.Msg
|
||||||
| UserListResp (Result Http.Error UserList)
|
| UserListResp (Result Http.Error UserList)
|
||||||
| SpaceListResp (Result Http.Error SpaceList)
|
| FolderListResp (Result Http.Error FolderList)
|
||||||
| SpaceDetailResp (Result Http.Error SpaceDetail)
|
| FolderDetailResp (Result Http.Error FolderDetail)
|
||||||
| SetQuery String
|
| SetQuery String
|
||||||
| InitNewSpace
|
| InitNewFolder
|
||||||
| ToggleOwningOnly
|
| ToggleOwningOnly
|
||||||
|
|
||||||
|
|
||||||
empty : Model
|
empty : Model
|
||||||
empty =
|
empty =
|
||||||
{ tableModel = Comp.SpaceTable.init
|
{ tableModel = Comp.FolderTable.init
|
||||||
, detailModel = Nothing
|
, detailModel = Nothing
|
||||||
, spaces = []
|
, folders = []
|
||||||
, users = []
|
, users = []
|
||||||
, query = ""
|
, query = ""
|
||||||
, owningOnly = True
|
, owningOnly = True
|
||||||
@ -61,7 +61,7 @@ init flags =
|
|||||||
( empty
|
( empty
|
||||||
, Cmd.batch
|
, Cmd.batch
|
||||||
[ Api.getUsers flags UserListResp
|
[ 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 ->
|
TableMsg lm ->
|
||||||
let
|
let
|
||||||
( tm, action ) =
|
( tm, action ) =
|
||||||
Comp.SpaceTable.update lm model.tableModel
|
Comp.FolderTable.update lm model.tableModel
|
||||||
|
|
||||||
cmd =
|
cmd =
|
||||||
case action of
|
case action of
|
||||||
Comp.SpaceTable.EditAction item ->
|
Comp.FolderTable.EditAction item ->
|
||||||
Api.getSpaceDetail flags item.id SpaceDetailResp
|
Api.getFolderDetail flags item.id FolderDetailResp
|
||||||
|
|
||||||
Comp.SpaceTable.NoAction ->
|
Comp.FolderTable.NoAction ->
|
||||||
Cmd.none
|
Cmd.none
|
||||||
in
|
in
|
||||||
( { model | tableModel = tm }, cmd )
|
( { model | tableModel = tm }, cmd )
|
||||||
@ -93,11 +93,11 @@ update flags msg model =
|
|||||||
Just detail ->
|
Just detail ->
|
||||||
let
|
let
|
||||||
( dm, dc, back ) =
|
( dm, dc, back ) =
|
||||||
Comp.SpaceDetail.update flags lm detail
|
Comp.FolderDetail.update flags lm detail
|
||||||
|
|
||||||
cmd =
|
cmd =
|
||||||
if back then
|
if back then
|
||||||
Api.getSpaces flags model.query model.owningOnly SpaceListResp
|
Api.getFolders flags model.query model.owningOnly FolderListResp
|
||||||
|
|
||||||
else
|
else
|
||||||
Cmd.none
|
Cmd.none
|
||||||
@ -121,7 +121,7 @@ update flags msg model =
|
|||||||
|
|
||||||
SetQuery str ->
|
SetQuery str ->
|
||||||
( { model | query = str }
|
( { model | query = str }
|
||||||
, Api.getSpaces flags str model.owningOnly SpaceListResp
|
, Api.getFolders flags str model.owningOnly FolderListResp
|
||||||
)
|
)
|
||||||
|
|
||||||
ToggleOwningOnly ->
|
ToggleOwningOnly ->
|
||||||
@ -130,7 +130,7 @@ update flags msg model =
|
|||||||
not model.owningOnly
|
not model.owningOnly
|
||||||
in
|
in
|
||||||
( { model | owningOnly = newOwning }
|
( { model | owningOnly = newOwning }
|
||||||
, Api.getSpaces flags model.query newOwning SpaceListResp
|
, Api.getFolders flags model.query newOwning FolderListResp
|
||||||
)
|
)
|
||||||
|
|
||||||
UserListResp (Ok ul) ->
|
UserListResp (Ok ul) ->
|
||||||
@ -139,24 +139,24 @@ update flags msg model =
|
|||||||
UserListResp (Err err) ->
|
UserListResp (Err err) ->
|
||||||
( model, Cmd.none )
|
( model, Cmd.none )
|
||||||
|
|
||||||
SpaceListResp (Ok sl) ->
|
FolderListResp (Ok sl) ->
|
||||||
( { model | spaces = sl.items }, Cmd.none )
|
( { model | folders = sl.items }, Cmd.none )
|
||||||
|
|
||||||
SpaceListResp (Err err) ->
|
FolderListResp (Err err) ->
|
||||||
( model, Cmd.none )
|
( model, Cmd.none )
|
||||||
|
|
||||||
SpaceDetailResp (Ok sd) ->
|
FolderDetailResp (Ok sd) ->
|
||||||
( { model | detailModel = Comp.SpaceDetail.init model.users sd |> Just }
|
( { model | detailModel = Comp.FolderDetail.init model.users sd |> Just }
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
|
|
||||||
SpaceDetailResp (Err err) ->
|
FolderDetailResp (Err err) ->
|
||||||
( model, Cmd.none )
|
( model, Cmd.none )
|
||||||
|
|
||||||
InitNewSpace ->
|
InitNewFolder ->
|
||||||
let
|
let
|
||||||
sd =
|
sd =
|
||||||
Comp.SpaceDetail.initEmpty model.users
|
Comp.FolderDetail.initEmpty model.users
|
||||||
in
|
in
|
||||||
( { model | detailModel = Just sd }
|
( { model | detailModel = Just sd }
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
@ -177,10 +177,10 @@ view flags model =
|
|||||||
viewTable model
|
viewTable model
|
||||||
|
|
||||||
|
|
||||||
viewDetail : Flags -> Comp.SpaceDetail.Model -> Html Msg
|
viewDetail : Flags -> Comp.FolderDetail.Model -> Html Msg
|
||||||
viewDetail flags detailModel =
|
viewDetail flags detailModel =
|
||||||
div []
|
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
|
, checked model.owningOnly
|
||||||
]
|
]
|
||||||
[]
|
[]
|
||||||
, label [] [ text "Show owning spaces only" ]
|
, label [] [ text "Show owning folders only" ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
, div [ class "right menu" ]
|
, div [ class "right menu" ]
|
||||||
@ -217,15 +217,15 @@ viewTable model =
|
|||||||
[ a
|
[ a
|
||||||
[ class "ui primary button"
|
[ class "ui primary button"
|
||||||
, href "#"
|
, href "#"
|
||||||
, onClick InitNewSpace
|
, onClick InitNewFolder
|
||||||
]
|
]
|
||||||
[ i [ class "plus icon" ] []
|
[ 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
|
, div
|
||||||
[ classList
|
[ classList
|
||||||
[ ( "ui dimmer", True )
|
[ ( "ui dimmer", True )
|
@ -1,4 +1,4 @@
|
|||||||
module Comp.SpaceTable exposing
|
module Comp.FolderTable exposing
|
||||||
( Action(..)
|
( Action(..)
|
||||||
, Model
|
, Model
|
||||||
, Msg
|
, Msg
|
||||||
@ -7,7 +7,7 @@ module Comp.SpaceTable exposing
|
|||||||
, view
|
, view
|
||||||
)
|
)
|
||||||
|
|
||||||
import Api.Model.SpaceItem exposing (SpaceItem)
|
import Api.Model.FolderItem exposing (FolderItem)
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
import Html.Attributes exposing (..)
|
import Html.Attributes exposing (..)
|
||||||
import Html.Events exposing (onClick)
|
import Html.Events exposing (onClick)
|
||||||
@ -20,12 +20,12 @@ type alias Model =
|
|||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= EditItem SpaceItem
|
= EditItem FolderItem
|
||||||
|
|
||||||
|
|
||||||
type Action
|
type Action
|
||||||
= NoAction
|
= NoAction
|
||||||
| EditAction SpaceItem
|
| EditAction FolderItem
|
||||||
|
|
||||||
|
|
||||||
init : Model
|
init : Model
|
||||||
@ -40,7 +40,7 @@ update msg model =
|
|||||||
( model, EditAction item )
|
( model, EditAction item )
|
||||||
|
|
||||||
|
|
||||||
view : Model -> List SpaceItem -> Html Msg
|
view : Model -> List FolderItem -> Html Msg
|
||||||
view _ items =
|
view _ items =
|
||||||
div []
|
div []
|
||||||
[ table [ class "ui very basic center aligned table" ]
|
[ table [ class "ui very basic center aligned table" ]
|
||||||
@ -58,7 +58,7 @@ view _ items =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
viewItem : SpaceItem -> Html Msg
|
viewItem : FolderItem -> Html Msg
|
||||||
viewItem item =
|
viewItem item =
|
||||||
tr []
|
tr []
|
||||||
[ td [ class "collapsing" ]
|
[ td [ class "collapsing" ]
|
@ -15,12 +15,12 @@ module Data.Icons exposing
|
|||||||
, editNotesIcon
|
, editNotesIcon
|
||||||
, equipment
|
, equipment
|
||||||
, equipmentIcon
|
, equipmentIcon
|
||||||
|
, folder
|
||||||
|
, folderIcon
|
||||||
, organization
|
, organization
|
||||||
, organizationIcon
|
, organizationIcon
|
||||||
, person
|
, person
|
||||||
, personIcon
|
, personIcon
|
||||||
, space
|
|
||||||
, spaceIcon
|
|
||||||
, tag
|
, tag
|
||||||
, tagIcon
|
, tagIcon
|
||||||
, tags
|
, tags
|
||||||
@ -31,14 +31,14 @@ import Html exposing (Html, i)
|
|||||||
import Html.Attributes exposing (class)
|
import Html.Attributes exposing (class)
|
||||||
|
|
||||||
|
|
||||||
space : String
|
folder : String
|
||||||
space =
|
folder =
|
||||||
"folder outline icon"
|
"folder outline icon"
|
||||||
|
|
||||||
|
|
||||||
spaceIcon : String -> Html msg
|
folderIcon : String -> Html msg
|
||||||
spaceIcon classes =
|
folderIcon classes =
|
||||||
i [ class (space ++ " " ++ classes) ] []
|
i [ class (folder ++ " " ++ classes) ] []
|
||||||
|
|
||||||
|
|
||||||
concerned : String
|
concerned : String
|
||||||
|
@ -6,9 +6,9 @@ module Page.ManageData.Data exposing
|
|||||||
)
|
)
|
||||||
|
|
||||||
import Comp.EquipmentManage
|
import Comp.EquipmentManage
|
||||||
|
import Comp.FolderManage
|
||||||
import Comp.OrgManage
|
import Comp.OrgManage
|
||||||
import Comp.PersonManage
|
import Comp.PersonManage
|
||||||
import Comp.SpaceManage
|
|
||||||
import Comp.TagManage
|
import Comp.TagManage
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ type alias Model =
|
|||||||
, equipManageModel : Comp.EquipmentManage.Model
|
, equipManageModel : Comp.EquipmentManage.Model
|
||||||
, orgManageModel : Comp.OrgManage.Model
|
, orgManageModel : Comp.OrgManage.Model
|
||||||
, personManageModel : Comp.PersonManage.Model
|
, personManageModel : Comp.PersonManage.Model
|
||||||
, spaceManageModel : Comp.SpaceManage.Model
|
, folderManageModel : Comp.FolderManage.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ init _ =
|
|||||||
, equipManageModel = Comp.EquipmentManage.emptyModel
|
, equipManageModel = Comp.EquipmentManage.emptyModel
|
||||||
, orgManageModel = Comp.OrgManage.emptyModel
|
, orgManageModel = Comp.OrgManage.emptyModel
|
||||||
, personManageModel = Comp.PersonManage.emptyModel
|
, personManageModel = Comp.PersonManage.emptyModel
|
||||||
, spaceManageModel = Comp.SpaceManage.empty
|
, folderManageModel = Comp.FolderManage.empty
|
||||||
}
|
}
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
@ -41,7 +41,7 @@ type Tab
|
|||||||
| EquipTab
|
| EquipTab
|
||||||
| OrgTab
|
| OrgTab
|
||||||
| PersonTab
|
| PersonTab
|
||||||
| SpaceTab
|
| FolderTab
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
@ -50,4 +50,4 @@ type Msg
|
|||||||
| EquipManageMsg Comp.EquipmentManage.Msg
|
| EquipManageMsg Comp.EquipmentManage.Msg
|
||||||
| OrgManageMsg Comp.OrgManage.Msg
|
| OrgManageMsg Comp.OrgManage.Msg
|
||||||
| PersonManageMsg Comp.PersonManage.Msg
|
| PersonManageMsg Comp.PersonManage.Msg
|
||||||
| SpaceMsg Comp.SpaceManage.Msg
|
| FolderMsg Comp.FolderManage.Msg
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
module Page.ManageData.Update exposing (update)
|
module Page.ManageData.Update exposing (update)
|
||||||
|
|
||||||
import Comp.EquipmentManage
|
import Comp.EquipmentManage
|
||||||
|
import Comp.FolderManage
|
||||||
import Comp.OrgManage
|
import Comp.OrgManage
|
||||||
import Comp.PersonManage
|
import Comp.PersonManage
|
||||||
import Comp.SpaceManage
|
|
||||||
import Comp.TagManage
|
import Comp.TagManage
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Page.ManageData.Data exposing (..)
|
import Page.ManageData.Data exposing (..)
|
||||||
@ -30,12 +30,12 @@ update flags msg model =
|
|||||||
PersonTab ->
|
PersonTab ->
|
||||||
update flags (PersonManageMsg Comp.PersonManage.LoadPersons) m
|
update flags (PersonManageMsg Comp.PersonManage.LoadPersons) m
|
||||||
|
|
||||||
SpaceTab ->
|
FolderTab ->
|
||||||
let
|
let
|
||||||
( sm, sc ) =
|
( sm, sc ) =
|
||||||
Comp.SpaceManage.init flags
|
Comp.FolderManage.init flags
|
||||||
in
|
in
|
||||||
( { m | spaceManageModel = sm }, Cmd.map SpaceMsg sc )
|
( { m | folderManageModel = sm }, Cmd.map FolderMsg sc )
|
||||||
|
|
||||||
TagManageMsg m ->
|
TagManageMsg m ->
|
||||||
let
|
let
|
||||||
@ -65,11 +65,11 @@ update flags msg model =
|
|||||||
in
|
in
|
||||||
( { model | personManageModel = m2 }, Cmd.map PersonManageMsg c2 )
|
( { model | personManageModel = m2 }, Cmd.map PersonManageMsg c2 )
|
||||||
|
|
||||||
SpaceMsg lm ->
|
FolderMsg lm ->
|
||||||
let
|
let
|
||||||
( m2, c2 ) =
|
( m2, c2 ) =
|
||||||
Comp.SpaceManage.update flags lm model.spaceManageModel
|
Comp.FolderManage.update flags lm model.folderManageModel
|
||||||
in
|
in
|
||||||
( { model | spaceManageModel = m2 }
|
( { model | folderManageModel = m2 }
|
||||||
, Cmd.map SpaceMsg c2
|
, Cmd.map FolderMsg c2
|
||||||
)
|
)
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
module Page.ManageData.View exposing (view)
|
module Page.ManageData.View exposing (view)
|
||||||
|
|
||||||
import Comp.EquipmentManage
|
import Comp.EquipmentManage
|
||||||
|
import Comp.FolderManage
|
||||||
import Comp.OrgManage
|
import Comp.OrgManage
|
||||||
import Comp.PersonManage
|
import Comp.PersonManage
|
||||||
import Comp.SpaceManage
|
|
||||||
import Comp.TagManage
|
import Comp.TagManage
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.Icons as Icons
|
import Data.Icons as Icons
|
||||||
@ -53,11 +53,11 @@ view flags settings model =
|
|||||||
, text "Person"
|
, text "Person"
|
||||||
]
|
]
|
||||||
, div
|
, div
|
||||||
[ classActive (model.currentTab == Just SpaceTab) "link icon item"
|
[ classActive (model.currentTab == Just FolderTab) "link icon item"
|
||||||
, onClick (SetTab SpaceTab)
|
, onClick (SetTab FolderTab)
|
||||||
]
|
]
|
||||||
[ Icons.spaceIcon ""
|
[ Icons.folderIcon ""
|
||||||
, text "Space"
|
, text "Folder"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@ -77,8 +77,8 @@ view flags settings model =
|
|||||||
Just PersonTab ->
|
Just PersonTab ->
|
||||||
viewPerson settings model
|
viewPerson settings model
|
||||||
|
|
||||||
Just SpaceTab ->
|
Just FolderTab ->
|
||||||
viewSpace flags settings model
|
viewFolder flags settings model
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
[]
|
[]
|
||||||
@ -87,19 +87,19 @@ view flags settings model =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
viewSpace : Flags -> UiSettings -> Model -> List (Html Msg)
|
viewFolder : Flags -> UiSettings -> Model -> List (Html Msg)
|
||||||
viewSpace flags _ model =
|
viewFolder flags _ model =
|
||||||
[ h2
|
[ h2
|
||||||
[ class "ui header"
|
[ class "ui header"
|
||||||
]
|
]
|
||||||
[ Icons.spaceIcon ""
|
[ Icons.folderIcon ""
|
||||||
, div
|
, div
|
||||||
[ class "content"
|
[ 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