mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-05 22:55:58 +00:00
Basic management of shares
This commit is contained in:
parent
de1baf725f
commit
c7d587bea4
15
build.sbt
15
build.sbt
@ -260,6 +260,18 @@ val openapiScalaSettings = Seq(
|
|||||||
.copy(typeDef =
|
.copy(typeDef =
|
||||||
TypeDef("AccountSource", Imports("docspell.common.AccountSource"))
|
TypeDef("AccountSource", Imports("docspell.common.AccountSource"))
|
||||||
)
|
)
|
||||||
|
case "itemquery" =>
|
||||||
|
field =>
|
||||||
|
field
|
||||||
|
.copy(typeDef =
|
||||||
|
TypeDef(
|
||||||
|
"ItemQuery",
|
||||||
|
Imports(
|
||||||
|
"docspell.query.ItemQuery",
|
||||||
|
"docspell.restapi.codec.ItemQueryJson._"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -367,6 +379,7 @@ val store = project
|
|||||||
.settings(testSettingsMUnit)
|
.settings(testSettingsMUnit)
|
||||||
.settings(
|
.settings(
|
||||||
name := "docspell-store",
|
name := "docspell-store",
|
||||||
|
addCompilerPlugin(Dependencies.kindProjectorPlugin),
|
||||||
libraryDependencies ++=
|
libraryDependencies ++=
|
||||||
Dependencies.doobie ++
|
Dependencies.doobie ++
|
||||||
Dependencies.binny ++
|
Dependencies.binny ++
|
||||||
@ -472,7 +485,7 @@ val restapi = project
|
|||||||
openapiSpec := (Compile / resourceDirectory).value / "docspell-openapi.yml",
|
openapiSpec := (Compile / resourceDirectory).value / "docspell-openapi.yml",
|
||||||
openapiStaticGen := OpenApiDocGenerator.Redoc
|
openapiStaticGen := OpenApiDocGenerator.Redoc
|
||||||
)
|
)
|
||||||
.dependsOn(common)
|
.dependsOn(common, query.jvm)
|
||||||
|
|
||||||
val joexapi = project
|
val joexapi = project
|
||||||
.in(file("modules/joexapi"))
|
.in(file("modules/joexapi"))
|
||||||
|
@ -48,6 +48,7 @@ trait BackendApp[F[_]] {
|
|||||||
def simpleSearch: OSimpleSearch[F]
|
def simpleSearch: OSimpleSearch[F]
|
||||||
def clientSettings: OClientSettings[F]
|
def clientSettings: OClientSettings[F]
|
||||||
def totp: OTotp[F]
|
def totp: OTotp[F]
|
||||||
|
def share: OShare[F]
|
||||||
}
|
}
|
||||||
|
|
||||||
object BackendApp {
|
object BackendApp {
|
||||||
@ -85,6 +86,7 @@ object BackendApp {
|
|||||||
customFieldsImpl <- OCustomFields(store)
|
customFieldsImpl <- OCustomFields(store)
|
||||||
simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl)
|
simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl)
|
||||||
clientSettingsImpl <- OClientSettings(store)
|
clientSettingsImpl <- OClientSettings(store)
|
||||||
|
shareImpl <- Resource.pure(OShare(store))
|
||||||
} yield new BackendApp[F] {
|
} yield new BackendApp[F] {
|
||||||
val login = loginImpl
|
val login = loginImpl
|
||||||
val signup = signupImpl
|
val signup = signupImpl
|
||||||
@ -107,6 +109,7 @@ object BackendApp {
|
|||||||
val simpleSearch = simpleSearchImpl
|
val simpleSearch = simpleSearchImpl
|
||||||
val clientSettings = clientSettingsImpl
|
val clientSettings = clientSettingsImpl
|
||||||
val totp = totpImpl
|
val totp = totpImpl
|
||||||
|
val share = shareImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
def apply[F[_]: Async](
|
def apply[F[_]: Async](
|
||||||
|
116
modules/backend/src/main/scala/docspell/backend/ops/OShare.scala
Normal file
116
modules/backend/src/main/scala/docspell/backend/ops/OShare.scala
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Eike K. & Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package docspell.backend.ops
|
||||||
|
|
||||||
|
import cats.data.OptionT
|
||||||
|
import cats.effect._
|
||||||
|
import cats.implicits._
|
||||||
|
|
||||||
|
import docspell.backend.PasswordCrypt
|
||||||
|
import docspell.common._
|
||||||
|
import docspell.query.ItemQuery
|
||||||
|
import docspell.store.Store
|
||||||
|
import docspell.store.records.RShare
|
||||||
|
|
||||||
|
trait OShare[F[_]] {
|
||||||
|
|
||||||
|
def findAll(collective: Ident): F[List[RShare]]
|
||||||
|
|
||||||
|
def delete(id: Ident, collective: Ident): F[Boolean]
|
||||||
|
|
||||||
|
def addNew(share: OShare.NewShare): F[OShare.ChangeResult]
|
||||||
|
|
||||||
|
def findOne(id: Ident, collective: Ident): OptionT[F, RShare]
|
||||||
|
|
||||||
|
def update(
|
||||||
|
id: Ident,
|
||||||
|
share: OShare.NewShare,
|
||||||
|
removePassword: Boolean
|
||||||
|
): F[OShare.ChangeResult]
|
||||||
|
}
|
||||||
|
|
||||||
|
object OShare {
|
||||||
|
|
||||||
|
final case class NewShare(
|
||||||
|
cid: Ident,
|
||||||
|
name: Option[String],
|
||||||
|
query: ItemQuery,
|
||||||
|
enabled: Boolean,
|
||||||
|
password: Option[Password],
|
||||||
|
publishUntil: Timestamp
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed trait ChangeResult
|
||||||
|
object ChangeResult {
|
||||||
|
final case class Success(id: Ident) extends ChangeResult
|
||||||
|
case object PublishUntilInPast extends ChangeResult
|
||||||
|
|
||||||
|
def success(id: Ident): ChangeResult = Success(id)
|
||||||
|
def publishUntilInPast: ChangeResult = PublishUntilInPast
|
||||||
|
}
|
||||||
|
|
||||||
|
def apply[F[_]: Async](store: Store[F]): OShare[F] =
|
||||||
|
new OShare[F] {
|
||||||
|
def findAll(collective: Ident): F[List[RShare]] =
|
||||||
|
store.transact(RShare.findAllByCollective(collective))
|
||||||
|
|
||||||
|
def delete(id: Ident, collective: Ident): F[Boolean] =
|
||||||
|
store.transact(RShare.deleteByIdAndCid(id, collective)).map(_ > 0)
|
||||||
|
|
||||||
|
def addNew(share: NewShare): F[ChangeResult] =
|
||||||
|
for {
|
||||||
|
curTime <- Timestamp.current[F]
|
||||||
|
id <- Ident.randomId[F]
|
||||||
|
pass = share.password.map(PasswordCrypt.crypt)
|
||||||
|
record = RShare(
|
||||||
|
id,
|
||||||
|
share.cid,
|
||||||
|
share.name,
|
||||||
|
share.query,
|
||||||
|
share.enabled,
|
||||||
|
pass,
|
||||||
|
curTime,
|
||||||
|
share.publishUntil,
|
||||||
|
0,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
res <-
|
||||||
|
if (share.publishUntil < curTime) ChangeResult.publishUntilInPast.pure[F]
|
||||||
|
else store.transact(RShare.insert(record)).map(_ => ChangeResult.success(id))
|
||||||
|
} yield res
|
||||||
|
|
||||||
|
def update(
|
||||||
|
id: Ident,
|
||||||
|
share: OShare.NewShare,
|
||||||
|
removePassword: Boolean
|
||||||
|
): F[ChangeResult] =
|
||||||
|
for {
|
||||||
|
curTime <- Timestamp.current[F]
|
||||||
|
record = RShare(
|
||||||
|
id,
|
||||||
|
share.cid,
|
||||||
|
share.name,
|
||||||
|
share.query,
|
||||||
|
share.enabled,
|
||||||
|
share.password.map(PasswordCrypt.crypt),
|
||||||
|
Timestamp.Epoch,
|
||||||
|
share.publishUntil,
|
||||||
|
0,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
res <-
|
||||||
|
if (share.publishUntil < curTime) ChangeResult.publishUntilInPast.pure[F]
|
||||||
|
else
|
||||||
|
store
|
||||||
|
.transact(RShare.updateData(record, removePassword))
|
||||||
|
.map(_ => ChangeResult.success(id))
|
||||||
|
} yield res
|
||||||
|
|
||||||
|
def findOne(id: Ident, collective: Ident): OptionT[F, RShare] =
|
||||||
|
RShare.findOne(id, collective).mapK(store.transform)
|
||||||
|
}
|
||||||
|
}
|
@ -51,6 +51,9 @@ case class Timestamp(value: Instant) {
|
|||||||
|
|
||||||
def <(other: Timestamp): Boolean =
|
def <(other: Timestamp): Boolean =
|
||||||
this.value.isBefore(other.value)
|
this.value.isBefore(other.value)
|
||||||
|
|
||||||
|
def >(other: Timestamp): Boolean =
|
||||||
|
this.value.isAfter(other.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Timestamp {
|
object Timestamp {
|
||||||
|
@ -1711,6 +1711,96 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/BasicResult"
|
$ref: "#/components/schemas/BasicResult"
|
||||||
|
|
||||||
|
/sec/share:
|
||||||
|
get:
|
||||||
|
operationId: "sec-share-get-all"
|
||||||
|
tags: [ Share ]
|
||||||
|
summary: Get a list of shares
|
||||||
|
description: |
|
||||||
|
Return a list of all shares for this collective.
|
||||||
|
security:
|
||||||
|
- authTokenHeader: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ShareList"
|
||||||
|
post:
|
||||||
|
operationId: "sec-share-new"
|
||||||
|
tags: [ Share ]
|
||||||
|
summary: Create a new share.
|
||||||
|
description: |
|
||||||
|
Create a new share.
|
||||||
|
security:
|
||||||
|
- authTokenHeader: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ShareData"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/IdResult"
|
||||||
|
/sec/share/{shareId}:
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/shareId"
|
||||||
|
get:
|
||||||
|
operationId: "sec-share-get"
|
||||||
|
tags: [Share]
|
||||||
|
summary: Get details to a single share.
|
||||||
|
description: |
|
||||||
|
Given the id of a share, returns some details about it.
|
||||||
|
security:
|
||||||
|
- authTokenHeader: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ShareDetail"
|
||||||
|
put:
|
||||||
|
operationId: "sec-share-update"
|
||||||
|
tags: [ Share ]
|
||||||
|
summary: Update an existing share.
|
||||||
|
description: |
|
||||||
|
Updates an existing share.
|
||||||
|
security:
|
||||||
|
- authTokenHeader: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ShareData"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicResult"
|
||||||
|
delete:
|
||||||
|
operationId: "sec-share-delete-by-id"
|
||||||
|
tags: [ Share ]
|
||||||
|
summary: Delete a share.
|
||||||
|
description: |
|
||||||
|
Deletes a share
|
||||||
|
security:
|
||||||
|
- authTokenHeader: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/BasicResult"
|
||||||
|
|
||||||
/sec/item/search:
|
/sec/item/search:
|
||||||
get:
|
get:
|
||||||
operationId: "sec-item-search-by-get"
|
operationId: "sec-item-search-by-get"
|
||||||
@ -4096,6 +4186,83 @@ paths:
|
|||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
|
ShareData:
|
||||||
|
description: |
|
||||||
|
Editable data for a share.
|
||||||
|
required:
|
||||||
|
- query
|
||||||
|
- enabled
|
||||||
|
- publishUntil
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
query:
|
||||||
|
type: string
|
||||||
|
format: itemquery
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
format: password
|
||||||
|
publishUntil:
|
||||||
|
type: integer
|
||||||
|
format: date-time
|
||||||
|
removePassword:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
For an update request, this can control whether to delete
|
||||||
|
the password. Otherwise if the password is not set, it
|
||||||
|
will not be changed. When adding a new share, this has no
|
||||||
|
effect.
|
||||||
|
|
||||||
|
ShareDetail:
|
||||||
|
description: |
|
||||||
|
Details for an existing share.
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- query
|
||||||
|
- enabled
|
||||||
|
- publishAt
|
||||||
|
- publishUntil
|
||||||
|
- password
|
||||||
|
- views
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: ident
|
||||||
|
query:
|
||||||
|
type: string
|
||||||
|
format: itemquery
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
enabled:
|
||||||
|
type: boolean
|
||||||
|
publishAt:
|
||||||
|
type: integer
|
||||||
|
format: date-time
|
||||||
|
publishUntil:
|
||||||
|
type: integer
|
||||||
|
format: date-time
|
||||||
|
password:
|
||||||
|
type: boolean
|
||||||
|
views:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
lastAccess:
|
||||||
|
type: integer
|
||||||
|
format: date-time
|
||||||
|
|
||||||
|
ShareList:
|
||||||
|
description: |
|
||||||
|
A list of shares.
|
||||||
|
required:
|
||||||
|
- items
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ShareDetail"
|
||||||
|
|
||||||
DeleteUserData:
|
DeleteUserData:
|
||||||
description: |
|
description: |
|
||||||
An excerpt of data that would be deleted when deleting the
|
An excerpt of data that would be deleted when deleting the
|
||||||
@ -6121,8 +6288,8 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
IdResult:
|
IdResult:
|
||||||
description: |
|
description: |
|
||||||
Some basic result of an operation with an ID as payload. If
|
Some basic result of an operation with an ID as payload, if
|
||||||
success if `false` the id is not usable.
|
success is true. If success is `false` the id is not usable.
|
||||||
required:
|
required:
|
||||||
- success
|
- success
|
||||||
- message
|
- message
|
||||||
@ -6257,6 +6424,13 @@ components:
|
|||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
shareId:
|
||||||
|
name: shareId
|
||||||
|
in: path
|
||||||
|
description: An identifier for a share
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
username:
|
username:
|
||||||
name: username
|
name: username
|
||||||
in: path
|
in: path
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Eike K. & Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
package docspell.restapi.codec
|
||||||
|
|
||||||
|
import docspell.query.{ItemQuery, ItemQueryParser}
|
||||||
|
|
||||||
|
import io.circe.{Decoder, Encoder}
|
||||||
|
|
||||||
|
trait ItemQueryJson {
|
||||||
|
|
||||||
|
implicit val itemQueryDecoder: Decoder[ItemQuery] =
|
||||||
|
Decoder.decodeString.emap(str => ItemQueryParser.parse(str).left.map(_.render))
|
||||||
|
|
||||||
|
implicit val itemQueryEncoder: Encoder[ItemQuery] =
|
||||||
|
Encoder.encodeString.contramap(q =>
|
||||||
|
q.raw.getOrElse(ItemQueryParser.unsafeAsString(q.expr))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ItemQueryJson extends ItemQueryJson
|
@ -94,6 +94,7 @@ object RestServer {
|
|||||||
"email/send" -> MailSendRoutes(restApp.backend, token),
|
"email/send" -> MailSendRoutes(restApp.backend, token),
|
||||||
"email/settings" -> MailSettingsRoutes(restApp.backend, token),
|
"email/settings" -> MailSettingsRoutes(restApp.backend, token),
|
||||||
"email/sent" -> SentMailRoutes(restApp.backend, token),
|
"email/sent" -> SentMailRoutes(restApp.backend, token),
|
||||||
|
"share" -> ShareRoutes.manage(restApp.backend, token),
|
||||||
"usertask/notifydueitems" -> NotifyDueItemsRoutes(cfg, restApp.backend, token),
|
"usertask/notifydueitems" -> NotifyDueItemsRoutes(cfg, restApp.backend, token),
|
||||||
"usertask/scanmailbox" -> ScanMailboxRoutes(restApp.backend, token),
|
"usertask/scanmailbox" -> ScanMailboxRoutes(restApp.backend, token),
|
||||||
"calevent/check" -> CalEventCheckRoutes(),
|
"calevent/check" -> CalEventCheckRoutes(),
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Eike K. & Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.OShare
|
||||||
|
import docspell.common.Ident
|
||||||
|
import docspell.restapi.model._
|
||||||
|
import docspell.restserver.http4s.ResponseGenerator
|
||||||
|
import docspell.store.records.RShare
|
||||||
|
|
||||||
|
import org.http4s.HttpRoutes
|
||||||
|
import org.http4s.circe.CirceEntityDecoder._
|
||||||
|
import org.http4s.circe.CirceEntityEncoder._
|
||||||
|
import org.http4s.dsl.Http4sDsl
|
||||||
|
|
||||||
|
object ShareRoutes {
|
||||||
|
|
||||||
|
def manage[F[_]: Async](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
|
||||||
|
val dsl = new Http4sDsl[F] with ResponseGenerator[F] {}
|
||||||
|
import dsl._
|
||||||
|
|
||||||
|
HttpRoutes.of {
|
||||||
|
case GET -> Root =>
|
||||||
|
for {
|
||||||
|
all <- backend.share.findAll(user.account.collective)
|
||||||
|
res <- Ok(ShareList(all.map(mkShareDetail)))
|
||||||
|
} yield res
|
||||||
|
|
||||||
|
case req @ POST -> Root =>
|
||||||
|
for {
|
||||||
|
data <- req.as[ShareData]
|
||||||
|
share = mkNewShare(data, user)
|
||||||
|
res <- backend.share.addNew(share)
|
||||||
|
resp <- Ok(mkIdResult(res, "New share created."))
|
||||||
|
} yield resp
|
||||||
|
|
||||||
|
case GET -> Root / Ident(id) =>
|
||||||
|
(for {
|
||||||
|
share <- backend.share.findOne(id, user.account.collective)
|
||||||
|
resp <- OptionT.liftF(Ok(mkShareDetail(share)))
|
||||||
|
} yield resp).getOrElseF(NotFound())
|
||||||
|
|
||||||
|
case req @ PUT -> Root / Ident(id) =>
|
||||||
|
for {
|
||||||
|
data <- req.as[ShareData]
|
||||||
|
share = mkNewShare(data, user)
|
||||||
|
updated <- backend.share.update(id, share, data.removePassword.getOrElse(false))
|
||||||
|
resp <- Ok(mkBasicResult(updated, "Share updated."))
|
||||||
|
} yield resp
|
||||||
|
|
||||||
|
case DELETE -> Root / Ident(id) =>
|
||||||
|
for {
|
||||||
|
del <- backend.share.delete(id, user.account.collective)
|
||||||
|
resp <- Ok(BasicResult(del, if (del) "Share deleted." else "Deleting failed."))
|
||||||
|
} yield resp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def mkNewShare(data: ShareData, user: AuthToken): OShare.NewShare =
|
||||||
|
OShare.NewShare(
|
||||||
|
user.account.collective,
|
||||||
|
data.name,
|
||||||
|
data.query,
|
||||||
|
data.enabled,
|
||||||
|
data.password,
|
||||||
|
data.publishUntil
|
||||||
|
)
|
||||||
|
|
||||||
|
def mkIdResult(r: OShare.ChangeResult, msg: => String): IdResult =
|
||||||
|
r match {
|
||||||
|
case OShare.ChangeResult.Success(id) => IdResult(true, msg, id)
|
||||||
|
case OShare.ChangeResult.PublishUntilInPast =>
|
||||||
|
IdResult(false, "Until date must not be in the past", Ident.unsafe(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
def mkBasicResult(r: OShare.ChangeResult, msg: => String): BasicResult =
|
||||||
|
r match {
|
||||||
|
case OShare.ChangeResult.Success(_) => BasicResult(true, msg)
|
||||||
|
case OShare.ChangeResult.PublishUntilInPast =>
|
||||||
|
BasicResult(false, "Until date must not be in the past")
|
||||||
|
}
|
||||||
|
|
||||||
|
def mkShareDetail(r: RShare): ShareDetail =
|
||||||
|
ShareDetail(
|
||||||
|
r.id,
|
||||||
|
r.query,
|
||||||
|
r.name,
|
||||||
|
r.enabled,
|
||||||
|
r.publishAt,
|
||||||
|
r.publishUntil,
|
||||||
|
r.password.isDefined,
|
||||||
|
r.views,
|
||||||
|
r.lastAccess
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE "item_share" (
|
||||||
|
"id" varchar(254) not null primary key,
|
||||||
|
"cid" varchar(254) not null,
|
||||||
|
"name" varchar(254),
|
||||||
|
"query" varchar(2000) not null,
|
||||||
|
"enabled" boolean not null,
|
||||||
|
"pass" varchar(254),
|
||||||
|
"publish_at" timestamp not null,
|
||||||
|
"publish_until" timestamp not null,
|
||||||
|
"views" int not null,
|
||||||
|
"last_access" timestamp,
|
||||||
|
foreign key ("cid") references "collective"("cid") on delete cascade
|
||||||
|
)
|
@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE `item_share` (
|
||||||
|
`id` varchar(254) not null primary key,
|
||||||
|
`cid` varchar(254) not null,
|
||||||
|
`name` varchar(254),
|
||||||
|
`query` varchar(2000) not null,
|
||||||
|
`enabled` boolean not null,
|
||||||
|
`pass` varchar(254),
|
||||||
|
`publish_at` timestamp not null,
|
||||||
|
`publish_until` timestamp not null,
|
||||||
|
`views` int not null,
|
||||||
|
`last_access` timestamp,
|
||||||
|
foreign key (`cid`) references `collective`(`cid`) on delete cascade
|
||||||
|
)
|
@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE "item_share" (
|
||||||
|
"id" varchar(254) not null primary key,
|
||||||
|
"cid" varchar(254) not null,
|
||||||
|
"name" varchar(254),
|
||||||
|
"query" varchar(2000) not null,
|
||||||
|
"enabled" boolean not null,
|
||||||
|
"pass" varchar(254),
|
||||||
|
"publish_at" timestamp not null,
|
||||||
|
"publish_until" timestamp not null,
|
||||||
|
"views" int not null,
|
||||||
|
"last_access" timestamp,
|
||||||
|
foreign key ("cid") references "collective"("cid") on delete cascade
|
||||||
|
)
|
@ -9,6 +9,7 @@ package docspell.store
|
|||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
|
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
|
import cats.~>
|
||||||
import fs2._
|
import fs2._
|
||||||
|
|
||||||
import docspell.store.file.FileStore
|
import docspell.store.file.FileStore
|
||||||
@ -19,6 +20,7 @@ import doobie._
|
|||||||
import doobie.hikari.HikariTransactor
|
import doobie.hikari.HikariTransactor
|
||||||
|
|
||||||
trait Store[F[_]] {
|
trait Store[F[_]] {
|
||||||
|
def transform: ConnectionIO ~> F
|
||||||
|
|
||||||
def transact[A](prg: ConnectionIO[A]): F[A]
|
def transact[A](prg: ConnectionIO[A]): F[A]
|
||||||
|
|
||||||
|
@ -6,8 +6,10 @@
|
|||||||
|
|
||||||
package docspell.store.impl
|
package docspell.store.impl
|
||||||
|
|
||||||
|
import cats.arrow.FunctionK
|
||||||
import cats.effect.Async
|
import cats.effect.Async
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
import cats.~>
|
||||||
|
|
||||||
import docspell.store.file.FileStore
|
import docspell.store.file.FileStore
|
||||||
import docspell.store.migrate.FlywayMigrate
|
import docspell.store.migrate.FlywayMigrate
|
||||||
@ -22,6 +24,9 @@ final class StoreImpl[F[_]: Async](
|
|||||||
xa: Transactor[F]
|
xa: Transactor[F]
|
||||||
) extends Store[F] {
|
) extends Store[F] {
|
||||||
|
|
||||||
|
def transform: ConnectionIO ~> F =
|
||||||
|
FunctionK.lift(transact)
|
||||||
|
|
||||||
def migrate: F[Int] =
|
def migrate: F[Int] =
|
||||||
FlywayMigrate.run[F](jdbc).map(_.migrationsExecuted)
|
FlywayMigrate.run[F](jdbc).map(_.migrationsExecuted)
|
||||||
|
|
||||||
|
@ -6,20 +6,25 @@
|
|||||||
|
|
||||||
package docspell.store.records
|
package docspell.store.records
|
||||||
|
|
||||||
import cats.data.NonEmptyList
|
import cats.data.{NonEmptyList, OptionT}
|
||||||
|
|
||||||
import docspell.common._
|
import docspell.common._
|
||||||
import docspell.query.ItemQuery
|
import docspell.query.ItemQuery
|
||||||
|
import docspell.store.qb.DSL._
|
||||||
import docspell.store.qb._
|
import docspell.store.qb._
|
||||||
|
|
||||||
|
import doobie._
|
||||||
|
import doobie.implicits._
|
||||||
|
|
||||||
final case class RShare(
|
final case class RShare(
|
||||||
id: Ident,
|
id: Ident,
|
||||||
cid: Ident,
|
cid: Ident,
|
||||||
|
name: Option[String],
|
||||||
query: ItemQuery,
|
query: ItemQuery,
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
password: Option[Password],
|
password: Option[Password],
|
||||||
publishedAt: Timestamp,
|
publishAt: Timestamp,
|
||||||
publishedUntil: Timestamp,
|
publishUntil: Timestamp,
|
||||||
views: Int,
|
views: Int,
|
||||||
lastAccess: Option[Timestamp]
|
lastAccess: Option[Timestamp]
|
||||||
) {}
|
) {}
|
||||||
@ -31,11 +36,12 @@ object RShare {
|
|||||||
|
|
||||||
val id = Column[Ident]("id", this)
|
val id = Column[Ident]("id", this)
|
||||||
val cid = Column[Ident]("cid", this)
|
val cid = Column[Ident]("cid", this)
|
||||||
|
val name = Column[String]("name", this)
|
||||||
val query = Column[ItemQuery]("query", this)
|
val query = Column[ItemQuery]("query", this)
|
||||||
val enabled = Column[Boolean]("enabled", this)
|
val enabled = Column[Boolean]("enabled", this)
|
||||||
val password = Column[Password]("password", this)
|
val password = Column[Password]("pass", this)
|
||||||
val publishedAt = Column[Timestamp]("published_at", this)
|
val publishedAt = Column[Timestamp]("publish_at", this)
|
||||||
val publishedUntil = Column[Timestamp]("published_until", this)
|
val publishedUntil = Column[Timestamp]("publish_until", this)
|
||||||
val views = Column[Int]("views", this)
|
val views = Column[Int]("views", this)
|
||||||
val lastAccess = Column[Timestamp]("last_access", this)
|
val lastAccess = Column[Timestamp]("last_access", this)
|
||||||
|
|
||||||
@ -43,6 +49,7 @@ object RShare {
|
|||||||
NonEmptyList.of(
|
NonEmptyList.of(
|
||||||
id,
|
id,
|
||||||
cid,
|
cid,
|
||||||
|
name,
|
||||||
query,
|
query,
|
||||||
enabled,
|
enabled,
|
||||||
password,
|
password,
|
||||||
@ -56,4 +63,47 @@ object RShare {
|
|||||||
val T: Table = Table(None)
|
val T: Table = Table(None)
|
||||||
def as(alias: String): Table = Table(Some(alias))
|
def as(alias: String): Table = Table(Some(alias))
|
||||||
|
|
||||||
|
def insert(r: RShare): ConnectionIO[Int] =
|
||||||
|
DML.insert(
|
||||||
|
T,
|
||||||
|
T.all,
|
||||||
|
fr"${r.id},${r.cid},${r.name},${r.query},${r.enabled},${r.password},${r.publishAt},${r.publishUntil},${r.views},${r.lastAccess}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def incAccess(id: Ident): ConnectionIO[Int] =
|
||||||
|
for {
|
||||||
|
curTime <- Timestamp.current[ConnectionIO]
|
||||||
|
n <- DML.update(
|
||||||
|
T,
|
||||||
|
T.id === id,
|
||||||
|
DML.set(T.views.increment(1), T.lastAccess.setTo(curTime))
|
||||||
|
)
|
||||||
|
} yield n
|
||||||
|
|
||||||
|
def updateData(r: RShare, removePassword: Boolean): ConnectionIO[Int] =
|
||||||
|
DML.update(
|
||||||
|
T,
|
||||||
|
T.id === r.id && T.cid === r.cid,
|
||||||
|
DML.set(
|
||||||
|
T.name.setTo(r.name),
|
||||||
|
T.query.setTo(r.query),
|
||||||
|
T.enabled.setTo(r.enabled),
|
||||||
|
T.publishedUntil.setTo(r.publishUntil)
|
||||||
|
) ++ (if (r.password.isDefined || removePassword)
|
||||||
|
List(T.password.setTo(r.password))
|
||||||
|
else Nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
def findOne(id: Ident, cid: Ident): OptionT[ConnectionIO, RShare] =
|
||||||
|
OptionT(
|
||||||
|
Select(select(T.all), from(T), T.id === id && T.cid === cid).build
|
||||||
|
.query[RShare]
|
||||||
|
.option
|
||||||
|
)
|
||||||
|
|
||||||
|
def findAllByCollective(cid: Ident): ConnectionIO[List[RShare]] =
|
||||||
|
Select(select(T.all), from(T), T.cid === cid).build.query[RShare].to[List]
|
||||||
|
|
||||||
|
def deleteByIdAndCid(id: Ident, cid: Ident): ConnectionIO[Int] =
|
||||||
|
DML.delete(T, T.id === id && T.cid === cid)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ module Api exposing
|
|||||||
, addCorrOrg
|
, addCorrOrg
|
||||||
, addCorrPerson
|
, addCorrPerson
|
||||||
, addMember
|
, addMember
|
||||||
|
, addShare
|
||||||
, addTag
|
, addTag
|
||||||
, addTagsMultiple
|
, addTagsMultiple
|
||||||
, attachmentPreviewURL
|
, attachmentPreviewURL
|
||||||
@ -40,6 +41,7 @@ module Api exposing
|
|||||||
, deleteOrg
|
, deleteOrg
|
||||||
, deletePerson
|
, deletePerson
|
||||||
, deleteScanMailbox
|
, deleteScanMailbox
|
||||||
|
, deleteShare
|
||||||
, deleteSource
|
, deleteSource
|
||||||
, deleteTag
|
, deleteTag
|
||||||
, deleteUser
|
, deleteUser
|
||||||
@ -72,6 +74,8 @@ module Api exposing
|
|||||||
, getPersonsLight
|
, getPersonsLight
|
||||||
, getScanMailbox
|
, getScanMailbox
|
||||||
, getSentMails
|
, getSentMails
|
||||||
|
, getShare
|
||||||
|
, getShares
|
||||||
, getSources
|
, getSources
|
||||||
, getTagCloud
|
, getTagCloud
|
||||||
, getTags
|
, getTags
|
||||||
@ -147,6 +151,7 @@ module Api exposing
|
|||||||
, unconfirmMultiple
|
, unconfirmMultiple
|
||||||
, updateNotifyDueItems
|
, updateNotifyDueItems
|
||||||
, updateScanMailbox
|
, updateScanMailbox
|
||||||
|
, updateShare
|
||||||
, upload
|
, upload
|
||||||
, uploadAmend
|
, uploadAmend
|
||||||
, uploadSingle
|
, uploadSingle
|
||||||
@ -215,6 +220,9 @@ import Api.Model.ScanMailboxSettingsList exposing (ScanMailboxSettingsList)
|
|||||||
import Api.Model.SearchStats exposing (SearchStats)
|
import Api.Model.SearchStats exposing (SearchStats)
|
||||||
import Api.Model.SecondFactor exposing (SecondFactor)
|
import Api.Model.SecondFactor exposing (SecondFactor)
|
||||||
import Api.Model.SentMails exposing (SentMails)
|
import Api.Model.SentMails exposing (SentMails)
|
||||||
|
import Api.Model.ShareData exposing (ShareData)
|
||||||
|
import Api.Model.ShareDetail exposing (ShareDetail)
|
||||||
|
import Api.Model.ShareList exposing (ShareList)
|
||||||
import Api.Model.SimpleMail exposing (SimpleMail)
|
import Api.Model.SimpleMail exposing (SimpleMail)
|
||||||
import Api.Model.SourceAndTags exposing (SourceAndTags)
|
import Api.Model.SourceAndTags exposing (SourceAndTags)
|
||||||
import Api.Model.SourceList exposing (SourceList)
|
import Api.Model.SourceList exposing (SourceList)
|
||||||
@ -2206,6 +2214,57 @@ disableOtp flags otp receive =
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- Share
|
||||||
|
|
||||||
|
|
||||||
|
getShares : Flags -> (Result Http.Error ShareList -> msg) -> Cmd msg
|
||||||
|
getShares flags receive =
|
||||||
|
Http2.authGet
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/share"
|
||||||
|
, account = getAccount flags
|
||||||
|
, expect = Http.expectJson receive Api.Model.ShareList.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getShare : Flags -> String -> (Result Http.Error ShareDetail -> msg) -> Cmd msg
|
||||||
|
getShare flags id receive =
|
||||||
|
Http2.authGet
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/share/" ++ id
|
||||||
|
, account = getAccount flags
|
||||||
|
, expect = Http.expectJson receive Api.Model.ShareDetail.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
addShare : Flags -> ShareData -> (Result Http.Error IdResult -> msg) -> Cmd msg
|
||||||
|
addShare flags share receive =
|
||||||
|
Http2.authPost
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/share"
|
||||||
|
, account = getAccount flags
|
||||||
|
, body = Http.jsonBody (Api.Model.ShareData.encode share)
|
||||||
|
, expect = Http.expectJson receive Api.Model.IdResult.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updateShare : Flags -> String -> ShareData -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||||
|
updateShare flags id share receive =
|
||||||
|
Http2.authPut
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/share/" ++ id
|
||||||
|
, account = getAccount flags
|
||||||
|
, body = Http.jsonBody (Api.Model.ShareData.encode share)
|
||||||
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
deleteShare : Flags -> String -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||||
|
deleteShare flags id receive =
|
||||||
|
Http2.authDelete
|
||||||
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/share/" ++ id
|
||||||
|
, account = getAccount flags
|
||||||
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- Helper
|
--- Helper
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ init =
|
|||||||
|
|
||||||
emptyModel : DatePicker
|
emptyModel : DatePicker
|
||||||
emptyModel =
|
emptyModel =
|
||||||
DatePicker.initFromDate (Date.fromCalendarDate 2019 Aug 21)
|
DatePicker.initFromDate (Date.fromCalendarDate 2021 Oct 31)
|
||||||
|
|
||||||
|
|
||||||
defaultSettings : Settings
|
defaultSettings : Settings
|
||||||
|
282
modules/webapp/src/main/elm/Comp/ShareForm.elm
Normal file
282
modules/webapp/src/main/elm/Comp/ShareForm.elm
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Comp.ShareForm exposing (Model, Msg, getShare, init, setShare, update, view)
|
||||||
|
|
||||||
|
import Api.Model.ShareData exposing (ShareData)
|
||||||
|
import Api.Model.ShareDetail exposing (ShareDetail)
|
||||||
|
import Comp.Basic as B
|
||||||
|
import Comp.DatePicker
|
||||||
|
import Comp.PasswordInput
|
||||||
|
import Data.Flags exposing (Flags)
|
||||||
|
import DatePicker exposing (DatePicker)
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.Attributes exposing (..)
|
||||||
|
import Html.Events exposing (onCheck, onInput)
|
||||||
|
import Messages.Comp.ShareForm exposing (Texts)
|
||||||
|
import Styles as S
|
||||||
|
import Util.Maybe
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ share : ShareDetail
|
||||||
|
, name : Maybe String
|
||||||
|
, query : String
|
||||||
|
, enabled : Bool
|
||||||
|
, passwordModel : Comp.PasswordInput.Model
|
||||||
|
, password : Maybe String
|
||||||
|
, passwordSet : Bool
|
||||||
|
, clearPassword : Bool
|
||||||
|
, untilModel : DatePicker
|
||||||
|
, untilDate : Maybe Int
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init : ( Model, Cmd Msg )
|
||||||
|
init =
|
||||||
|
let
|
||||||
|
( dp, dpc ) =
|
||||||
|
Comp.DatePicker.init
|
||||||
|
in
|
||||||
|
( { share = Api.Model.ShareDetail.empty
|
||||||
|
, name = Nothing
|
||||||
|
, query = ""
|
||||||
|
, enabled = False
|
||||||
|
, passwordModel = Comp.PasswordInput.init
|
||||||
|
, password = Nothing
|
||||||
|
, passwordSet = False
|
||||||
|
, clearPassword = False
|
||||||
|
, untilModel = dp
|
||||||
|
, untilDate = Nothing
|
||||||
|
}
|
||||||
|
, Cmd.map UntilDateMsg dpc
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
isValid : Model -> Bool
|
||||||
|
isValid model =
|
||||||
|
model.query /= "" && model.untilDate /= Nothing
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= SetName String
|
||||||
|
| SetQuery String
|
||||||
|
| SetShare ShareDetail
|
||||||
|
| ToggleEnabled
|
||||||
|
| ToggleClearPassword
|
||||||
|
| PasswordMsg Comp.PasswordInput.Msg
|
||||||
|
| UntilDateMsg Comp.DatePicker.Msg
|
||||||
|
|
||||||
|
|
||||||
|
setShare : ShareDetail -> Msg
|
||||||
|
setShare share =
|
||||||
|
SetShare share
|
||||||
|
|
||||||
|
|
||||||
|
getShare : Model -> Maybe ( String, ShareData )
|
||||||
|
getShare model =
|
||||||
|
if isValid model then
|
||||||
|
Just
|
||||||
|
( model.share.id
|
||||||
|
, { name = model.name
|
||||||
|
, query = model.query
|
||||||
|
, enabled = model.enabled
|
||||||
|
, password = model.password
|
||||||
|
, removePassword =
|
||||||
|
if model.share.id == "" then
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
else
|
||||||
|
Just model.clearPassword
|
||||||
|
, publishUntil = Maybe.withDefault 0 model.untilDate
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
else
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
|
||||||
|
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||||
|
update _ msg model =
|
||||||
|
case msg of
|
||||||
|
SetShare s ->
|
||||||
|
( { model
|
||||||
|
| share = s
|
||||||
|
, name = s.name
|
||||||
|
, query = s.query
|
||||||
|
, enabled = s.enabled
|
||||||
|
, password = Nothing
|
||||||
|
, passwordSet = s.password
|
||||||
|
, clearPassword = False
|
||||||
|
, untilDate =
|
||||||
|
if s.publishUntil > 0 then
|
||||||
|
Just s.publishUntil
|
||||||
|
|
||||||
|
else
|
||||||
|
Nothing
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
SetName n ->
|
||||||
|
( { model | name = Util.Maybe.fromString n }, Cmd.none )
|
||||||
|
|
||||||
|
SetQuery n ->
|
||||||
|
( { model | query = n }, Cmd.none )
|
||||||
|
|
||||||
|
ToggleEnabled ->
|
||||||
|
( { model | enabled = not model.enabled }, Cmd.none )
|
||||||
|
|
||||||
|
ToggleClearPassword ->
|
||||||
|
( { model | clearPassword = not model.clearPassword }, Cmd.none )
|
||||||
|
|
||||||
|
PasswordMsg lm ->
|
||||||
|
let
|
||||||
|
( pm, pw ) =
|
||||||
|
Comp.PasswordInput.update lm model.passwordModel
|
||||||
|
in
|
||||||
|
( { model
|
||||||
|
| passwordModel = pm
|
||||||
|
, password = pw
|
||||||
|
}
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
UntilDateMsg lm ->
|
||||||
|
let
|
||||||
|
( dp, event ) =
|
||||||
|
Comp.DatePicker.updateDefault lm model.untilModel
|
||||||
|
|
||||||
|
nextDate =
|
||||||
|
case event of
|
||||||
|
DatePicker.Picked date ->
|
||||||
|
Just (Comp.DatePicker.endOfDay date)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Nothing
|
||||||
|
in
|
||||||
|
( { model | untilModel = dp, untilDate = nextDate }
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- View
|
||||||
|
|
||||||
|
|
||||||
|
view : Texts -> Model -> Html Msg
|
||||||
|
view texts model =
|
||||||
|
div
|
||||||
|
[ class "flex flex-col" ]
|
||||||
|
[ div [ class "mb-4" ]
|
||||||
|
[ label
|
||||||
|
[ for "sharename"
|
||||||
|
, class S.inputLabel
|
||||||
|
]
|
||||||
|
[ text texts.basics.name
|
||||||
|
]
|
||||||
|
, input
|
||||||
|
[ type_ "text"
|
||||||
|
, onInput SetName
|
||||||
|
, placeholder texts.basics.name
|
||||||
|
, value <| Maybe.withDefault "" model.name
|
||||||
|
, id "sharename"
|
||||||
|
, class S.textInput
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, div [ class "mb-4" ]
|
||||||
|
[ label
|
||||||
|
[ for "sharequery"
|
||||||
|
, class S.inputLabel
|
||||||
|
]
|
||||||
|
[ text texts.queryLabel
|
||||||
|
, B.inputRequired
|
||||||
|
]
|
||||||
|
, input
|
||||||
|
[ type_ "text"
|
||||||
|
, onInput SetQuery
|
||||||
|
, placeholder texts.queryLabel
|
||||||
|
, value model.query
|
||||||
|
, id "sharequery"
|
||||||
|
, class S.textInput
|
||||||
|
, classList
|
||||||
|
[ ( S.inputErrorBorder
|
||||||
|
, not (isValid model)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
]
|
||||||
|
, div [ class "mb-4" ]
|
||||||
|
[ label
|
||||||
|
[ class "inline-flex items-center"
|
||||||
|
, for "source-enabled"
|
||||||
|
]
|
||||||
|
[ input
|
||||||
|
[ type_ "checkbox"
|
||||||
|
, onCheck (\_ -> ToggleEnabled)
|
||||||
|
, checked model.enabled
|
||||||
|
, class S.checkboxInput
|
||||||
|
, id "source-enabled"
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, span [ class "ml-2" ]
|
||||||
|
[ text texts.enabled
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "mb-4" ]
|
||||||
|
[ label
|
||||||
|
[ class S.inputLabel
|
||||||
|
]
|
||||||
|
[ text texts.password
|
||||||
|
]
|
||||||
|
, Html.map PasswordMsg
|
||||||
|
(Comp.PasswordInput.view2
|
||||||
|
{ placeholder = texts.password }
|
||||||
|
model.password
|
||||||
|
False
|
||||||
|
model.passwordModel
|
||||||
|
)
|
||||||
|
, div
|
||||||
|
[ class "mb-2"
|
||||||
|
, classList [ ( "hidden", not model.passwordSet ) ]
|
||||||
|
]
|
||||||
|
[ label
|
||||||
|
[ class "inline-flex items-center"
|
||||||
|
, for "clear-password"
|
||||||
|
]
|
||||||
|
[ input
|
||||||
|
[ type_ "checkbox"
|
||||||
|
, onCheck (\_ -> ToggleClearPassword)
|
||||||
|
, checked model.clearPassword
|
||||||
|
, class S.checkboxInput
|
||||||
|
, id "clear-password"
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, span [ class "ml-2" ]
|
||||||
|
[ text texts.clearPassword
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "mb-2 max-w-sm" ]
|
||||||
|
[ label [ class S.inputLabel ]
|
||||||
|
[ text texts.publishUntil
|
||||||
|
, B.inputRequired
|
||||||
|
]
|
||||||
|
, div [ class "relative" ]
|
||||||
|
[ Html.map UntilDateMsg
|
||||||
|
(Comp.DatePicker.viewTimeDefault
|
||||||
|
model.untilDate
|
||||||
|
model.untilModel
|
||||||
|
)
|
||||||
|
, i [ class S.dateInputIcon, class "fa fa-calendar" ] []
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
349
modules/webapp/src/main/elm/Comp/ShareManage.elm
Normal file
349
modules/webapp/src/main/elm/Comp/ShareManage.elm
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Comp.ShareManage exposing (Model, Msg, init, loadShares, update, view)
|
||||||
|
|
||||||
|
import Api
|
||||||
|
import Api.Model.BasicResult exposing (BasicResult)
|
||||||
|
import Api.Model.IdResult exposing (IdResult)
|
||||||
|
import Api.Model.ShareDetail exposing (ShareDetail)
|
||||||
|
import Api.Model.ShareList exposing (ShareList)
|
||||||
|
import Comp.Basic as B
|
||||||
|
import Comp.ItemDetail.Model exposing (Msg(..))
|
||||||
|
import Comp.MenuBar as MB
|
||||||
|
import Comp.ShareForm
|
||||||
|
import Comp.ShareTable
|
||||||
|
import Data.Flags exposing (Flags)
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.Attributes exposing (..)
|
||||||
|
import Html.Events exposing (onClick)
|
||||||
|
import Http
|
||||||
|
import Messages.Comp.ShareManage exposing (Texts)
|
||||||
|
import Styles as S
|
||||||
|
|
||||||
|
|
||||||
|
type FormError
|
||||||
|
= FormErrorNone
|
||||||
|
| FormErrorHttp Http.Error
|
||||||
|
| FormErrorInvalid
|
||||||
|
| FormErrorSubmit String
|
||||||
|
|
||||||
|
|
||||||
|
type ViewMode
|
||||||
|
= Table
|
||||||
|
| Form
|
||||||
|
|
||||||
|
|
||||||
|
type DeleteConfirm
|
||||||
|
= DeleteConfirmOff
|
||||||
|
| DeleteConfirmOn
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ viewMode : ViewMode
|
||||||
|
, shares : List ShareDetail
|
||||||
|
, formModel : Comp.ShareForm.Model
|
||||||
|
, loading : Bool
|
||||||
|
, formError : FormError
|
||||||
|
, deleteConfirm : DeleteConfirm
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init : ( Model, Cmd Msg )
|
||||||
|
init =
|
||||||
|
let
|
||||||
|
( fm, fc ) =
|
||||||
|
Comp.ShareForm.init
|
||||||
|
in
|
||||||
|
( { viewMode = Table
|
||||||
|
, shares = []
|
||||||
|
, formModel = fm
|
||||||
|
, loading = False
|
||||||
|
, formError = FormErrorNone
|
||||||
|
, deleteConfirm = DeleteConfirmOff
|
||||||
|
}
|
||||||
|
, Cmd.map FormMsg fc
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= LoadShares
|
||||||
|
| TableMsg Comp.ShareTable.Msg
|
||||||
|
| FormMsg Comp.ShareForm.Msg
|
||||||
|
| InitNewShare
|
||||||
|
| SetViewMode ViewMode
|
||||||
|
| Submit
|
||||||
|
| RequestDelete
|
||||||
|
| CancelDelete
|
||||||
|
| DeleteShareNow String
|
||||||
|
| LoadSharesResp (Result Http.Error ShareList)
|
||||||
|
| AddShareResp (Result Http.Error IdResult)
|
||||||
|
| UpdateShareResp (Result Http.Error BasicResult)
|
||||||
|
| GetShareResp (Result Http.Error ShareDetail)
|
||||||
|
| DeleteShareResp (Result Http.Error BasicResult)
|
||||||
|
|
||||||
|
|
||||||
|
loadShares : Msg
|
||||||
|
loadShares =
|
||||||
|
LoadShares
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- update
|
||||||
|
|
||||||
|
|
||||||
|
update : Flags -> Msg -> Model -> ( Model, Cmd Msg )
|
||||||
|
update flags msg model =
|
||||||
|
case msg of
|
||||||
|
InitNewShare ->
|
||||||
|
let
|
||||||
|
nm =
|
||||||
|
{ model | viewMode = Form, formError = FormErrorNone }
|
||||||
|
|
||||||
|
share =
|
||||||
|
Api.Model.ShareDetail.empty
|
||||||
|
in
|
||||||
|
update flags (FormMsg (Comp.ShareForm.setShare share)) nm
|
||||||
|
|
||||||
|
SetViewMode vm ->
|
||||||
|
( { model | viewMode = vm, formError = FormErrorNone }
|
||||||
|
, if vm == Table then
|
||||||
|
Api.getShares flags LoadSharesResp
|
||||||
|
|
||||||
|
else
|
||||||
|
Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
FormMsg lm ->
|
||||||
|
let
|
||||||
|
( fm, fc ) =
|
||||||
|
Comp.ShareForm.update flags lm model.formModel
|
||||||
|
in
|
||||||
|
( { model | formModel = fm }, Cmd.map FormMsg fc )
|
||||||
|
|
||||||
|
TableMsg lm ->
|
||||||
|
let
|
||||||
|
action =
|
||||||
|
Comp.ShareTable.update lm
|
||||||
|
|
||||||
|
nextModel =
|
||||||
|
{ model | viewMode = Form, formError = FormErrorNone }
|
||||||
|
in
|
||||||
|
case action of
|
||||||
|
Comp.ShareTable.Edit share ->
|
||||||
|
update flags (FormMsg <| Comp.ShareForm.setShare share) nextModel
|
||||||
|
|
||||||
|
RequestDelete ->
|
||||||
|
( { model | deleteConfirm = DeleteConfirmOn }, Cmd.none )
|
||||||
|
|
||||||
|
CancelDelete ->
|
||||||
|
( { model | deleteConfirm = DeleteConfirmOff }, Cmd.none )
|
||||||
|
|
||||||
|
DeleteShareNow id ->
|
||||||
|
( { model | deleteConfirm = DeleteConfirmOff, loading = True }
|
||||||
|
, Api.deleteShare flags id DeleteShareResp
|
||||||
|
)
|
||||||
|
|
||||||
|
LoadShares ->
|
||||||
|
( { model | loading = True }, Api.getShares flags LoadSharesResp )
|
||||||
|
|
||||||
|
LoadSharesResp (Ok list) ->
|
||||||
|
( { model | loading = False, shares = list.items, formError = FormErrorNone }, Cmd.none )
|
||||||
|
|
||||||
|
LoadSharesResp (Err err) ->
|
||||||
|
( { model | loading = False, formError = FormErrorHttp err }, Cmd.none )
|
||||||
|
|
||||||
|
Submit ->
|
||||||
|
case Comp.ShareForm.getShare model.formModel of
|
||||||
|
Just ( id, data ) ->
|
||||||
|
if id == "" then
|
||||||
|
( { model | loading = True }, Api.addShare flags data AddShareResp )
|
||||||
|
|
||||||
|
else
|
||||||
|
( { model | loading = True }, Api.updateShare flags id data UpdateShareResp )
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
( { model | formError = FormErrorInvalid }, Cmd.none )
|
||||||
|
|
||||||
|
AddShareResp (Ok res) ->
|
||||||
|
if res.success then
|
||||||
|
( model, Api.getShare flags res.id GetShareResp )
|
||||||
|
|
||||||
|
else
|
||||||
|
( { model | loading = False, formError = FormErrorSubmit res.message }, Cmd.none )
|
||||||
|
|
||||||
|
AddShareResp (Err err) ->
|
||||||
|
( { model | loading = False, formError = FormErrorHttp err }, Cmd.none )
|
||||||
|
|
||||||
|
UpdateShareResp (Ok res) ->
|
||||||
|
if res.success then
|
||||||
|
( model, Api.getShare flags model.formModel.share.id GetShareResp )
|
||||||
|
|
||||||
|
else
|
||||||
|
( { model | loading = False, formError = FormErrorSubmit res.message }, Cmd.none )
|
||||||
|
|
||||||
|
UpdateShareResp (Err err) ->
|
||||||
|
( { model | loading = False, formError = FormErrorHttp err }, Cmd.none )
|
||||||
|
|
||||||
|
GetShareResp (Ok share) ->
|
||||||
|
let
|
||||||
|
nextModel =
|
||||||
|
{ model | formError = FormErrorNone, loading = False }
|
||||||
|
in
|
||||||
|
update flags (FormMsg <| Comp.ShareForm.setShare share) nextModel
|
||||||
|
|
||||||
|
GetShareResp (Err err) ->
|
||||||
|
( { model | formError = FormErrorHttp err }, Cmd.none )
|
||||||
|
|
||||||
|
DeleteShareResp (Ok res) ->
|
||||||
|
if res.success then
|
||||||
|
update flags (SetViewMode Table) { model | loading = False }
|
||||||
|
|
||||||
|
else
|
||||||
|
( { model | formError = FormErrorSubmit res.message, loading = False }, Cmd.none )
|
||||||
|
|
||||||
|
DeleteShareResp (Err err) ->
|
||||||
|
( { model | formError = FormErrorHttp err, loading = False }, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- view
|
||||||
|
|
||||||
|
|
||||||
|
view : Texts -> Flags -> Model -> Html Msg
|
||||||
|
view texts _ model =
|
||||||
|
if model.viewMode == Table then
|
||||||
|
viewTable texts model
|
||||||
|
|
||||||
|
else
|
||||||
|
viewForm texts model
|
||||||
|
|
||||||
|
|
||||||
|
viewTable : Texts -> Model -> Html Msg
|
||||||
|
viewTable texts model =
|
||||||
|
div [ class "flex flex-col" ]
|
||||||
|
[ MB.view
|
||||||
|
{ start =
|
||||||
|
[]
|
||||||
|
, end =
|
||||||
|
[ MB.PrimaryButton
|
||||||
|
{ tagger = InitNewShare
|
||||||
|
, title = texts.createNewShare
|
||||||
|
, icon = Just "fa fa-plus"
|
||||||
|
, label = texts.newShare
|
||||||
|
}
|
||||||
|
]
|
||||||
|
, rootClasses = "mb-4"
|
||||||
|
}
|
||||||
|
, Html.map TableMsg (Comp.ShareTable.view texts.shareTable model.shares)
|
||||||
|
, B.loadingDimmer
|
||||||
|
{ label = ""
|
||||||
|
, active = model.loading
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewForm : Texts -> Model -> Html Msg
|
||||||
|
viewForm texts model =
|
||||||
|
let
|
||||||
|
newShare =
|
||||||
|
model.formModel.share.id == ""
|
||||||
|
in
|
||||||
|
Html.form [ class "relative" ]
|
||||||
|
[ if newShare then
|
||||||
|
h1 [ class S.header2 ]
|
||||||
|
[ text texts.createNewShare
|
||||||
|
]
|
||||||
|
|
||||||
|
else
|
||||||
|
h1 [ class S.header2 ]
|
||||||
|
[ text <| Maybe.withDefault texts.noName model.formModel.share.name
|
||||||
|
, div [ class "opacity-50 text-sm" ]
|
||||||
|
[ text "Id: "
|
||||||
|
, text model.formModel.share.id
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, MB.view
|
||||||
|
{ start =
|
||||||
|
[ MB.PrimaryButton
|
||||||
|
{ tagger = Submit
|
||||||
|
, title = "Submit this form"
|
||||||
|
, icon = Just "fa fa-save"
|
||||||
|
, label = texts.basics.submit
|
||||||
|
}
|
||||||
|
, MB.SecondaryButton
|
||||||
|
{ tagger = SetViewMode Table
|
||||||
|
, title = texts.basics.backToList
|
||||||
|
, icon = Just "fa fa-arrow-left"
|
||||||
|
, label = texts.basics.cancel
|
||||||
|
}
|
||||||
|
]
|
||||||
|
, end =
|
||||||
|
if not newShare then
|
||||||
|
[ MB.DeleteButton
|
||||||
|
{ tagger = RequestDelete
|
||||||
|
, title = texts.deleteThisShare
|
||||||
|
, icon = Just "fa fa-trash"
|
||||||
|
, label = texts.basics.delete
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
, rootClasses = "mb-4"
|
||||||
|
}
|
||||||
|
, div
|
||||||
|
[ classList
|
||||||
|
[ ( "hidden", model.formError == FormErrorNone )
|
||||||
|
]
|
||||||
|
, class "my-2"
|
||||||
|
, class S.errorMessage
|
||||||
|
]
|
||||||
|
[ case model.formError of
|
||||||
|
FormErrorNone ->
|
||||||
|
text ""
|
||||||
|
|
||||||
|
FormErrorHttp err ->
|
||||||
|
text (texts.httpError err)
|
||||||
|
|
||||||
|
FormErrorInvalid ->
|
||||||
|
text texts.correctFormErrors
|
||||||
|
|
||||||
|
FormErrorSubmit m ->
|
||||||
|
text m
|
||||||
|
]
|
||||||
|
, Html.map FormMsg (Comp.ShareForm.view texts.shareForm model.formModel)
|
||||||
|
, B.loadingDimmer
|
||||||
|
{ active = model.loading
|
||||||
|
, label = texts.basics.loading
|
||||||
|
}
|
||||||
|
, B.contentDimmer
|
||||||
|
(model.deleteConfirm == DeleteConfirmOn)
|
||||||
|
(div [ class "flex flex-col" ]
|
||||||
|
[ div [ class "text-lg" ]
|
||||||
|
[ i [ class "fa fa-info-circle mr-2" ] []
|
||||||
|
, text texts.reallyDeleteShare
|
||||||
|
]
|
||||||
|
, div [ class "mt-4 flex flex-row items-center" ]
|
||||||
|
[ B.deleteButton
|
||||||
|
{ label = texts.basics.yes
|
||||||
|
, icon = "fa fa-check"
|
||||||
|
, disabled = False
|
||||||
|
, handler = onClick (DeleteShareNow model.formModel.share.id)
|
||||||
|
, attrs = [ href "#" ]
|
||||||
|
}
|
||||||
|
, B.secondaryButton
|
||||||
|
{ label = texts.basics.no
|
||||||
|
, icon = "fa fa-times"
|
||||||
|
, disabled = False
|
||||||
|
, handler = onClick CancelDelete
|
||||||
|
, attrs = [ href "#", class "ml-2" ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
87
modules/webapp/src/main/elm/Comp/ShareTable.elm
Normal file
87
modules/webapp/src/main/elm/Comp/ShareTable.elm
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Comp.ShareTable exposing
|
||||||
|
( Msg(..)
|
||||||
|
, SelectAction(..)
|
||||||
|
, update
|
||||||
|
, view
|
||||||
|
)
|
||||||
|
|
||||||
|
import Api.Model.ShareDetail exposing (ShareDetail)
|
||||||
|
import Comp.Basic as B
|
||||||
|
import Html exposing (..)
|
||||||
|
import Html.Attributes exposing (..)
|
||||||
|
import Messages.Comp.ShareTable exposing (Texts)
|
||||||
|
import Styles as S
|
||||||
|
import Util.Html
|
||||||
|
import Util.String
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= Select ShareDetail
|
||||||
|
|
||||||
|
|
||||||
|
type SelectAction
|
||||||
|
= Edit ShareDetail
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> SelectAction
|
||||||
|
update msg =
|
||||||
|
case msg of
|
||||||
|
Select share ->
|
||||||
|
Edit share
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--- View
|
||||||
|
|
||||||
|
|
||||||
|
view : Texts -> List ShareDetail -> Html Msg
|
||||||
|
view texts shares =
|
||||||
|
table [ class S.tableMain ]
|
||||||
|
[ thead []
|
||||||
|
[ tr []
|
||||||
|
[ th [ class "" ] []
|
||||||
|
, th [ class "text-left" ]
|
||||||
|
[ text texts.basics.id
|
||||||
|
]
|
||||||
|
, th [ class "text-left" ]
|
||||||
|
[ text texts.basics.name
|
||||||
|
]
|
||||||
|
, th [ class "text-center" ]
|
||||||
|
[ text texts.enabled
|
||||||
|
]
|
||||||
|
, th [ class "text-center" ]
|
||||||
|
[ text texts.publishUntil
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, tbody []
|
||||||
|
(List.map (renderShareLine texts) shares)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
renderShareLine : Texts -> ShareDetail -> Html Msg
|
||||||
|
renderShareLine texts share =
|
||||||
|
tr
|
||||||
|
[ class S.tableRow
|
||||||
|
]
|
||||||
|
[ B.editLinkTableCell texts.basics.edit (Select share)
|
||||||
|
, td [ class "text-left py-4 md:py-2" ]
|
||||||
|
[ text (Util.String.ellipsis 8 share.id)
|
||||||
|
]
|
||||||
|
, td [ class "text-left py-4 md:py-2" ]
|
||||||
|
[ text (Maybe.withDefault "-" share.name)
|
||||||
|
]
|
||||||
|
, td [ class "w-px px-2 text-center" ]
|
||||||
|
[ Util.Html.checkbox2 share.enabled
|
||||||
|
]
|
||||||
|
, td [ class "hidden sm:table-cell text-center" ]
|
||||||
|
[ texts.formatDateTime share.publishUntil |> text
|
||||||
|
]
|
||||||
|
]
|
@ -58,11 +58,11 @@ module Data.Icons exposing
|
|||||||
, personIcon2
|
, personIcon2
|
||||||
, search
|
, search
|
||||||
, searchIcon
|
, searchIcon
|
||||||
|
, share
|
||||||
|
, shareIcon
|
||||||
, showQr
|
, showQr
|
||||||
, showQrIcon
|
, showQrIcon
|
||||||
, source
|
|
||||||
, source2
|
, source2
|
||||||
, sourceIcon
|
|
||||||
, sourceIcon2
|
, sourceIcon2
|
||||||
, tag
|
, tag
|
||||||
, tag2
|
, tag2
|
||||||
@ -79,9 +79,14 @@ import Html exposing (Html, i)
|
|||||||
import Html.Attributes exposing (class)
|
import Html.Attributes exposing (class)
|
||||||
|
|
||||||
|
|
||||||
source : String
|
share : String
|
||||||
source =
|
share =
|
||||||
"upload icon"
|
"fa fa-share-alt"
|
||||||
|
|
||||||
|
|
||||||
|
shareIcon : String -> Html msg
|
||||||
|
shareIcon classes =
|
||||||
|
i [ class (classes ++ " " ++ share) ] []
|
||||||
|
|
||||||
|
|
||||||
source2 : String
|
source2 : String
|
||||||
@ -89,11 +94,6 @@ source2 =
|
|||||||
"fa fa-upload"
|
"fa fa-upload"
|
||||||
|
|
||||||
|
|
||||||
sourceIcon : String -> Html msg
|
|
||||||
sourceIcon classes =
|
|
||||||
i [ class (source ++ " " ++ classes) ] []
|
|
||||||
|
|
||||||
|
|
||||||
sourceIcon2 : String -> Html msg
|
sourceIcon2 : String -> Html msg
|
||||||
sourceIcon2 classes =
|
sourceIcon2 classes =
|
||||||
i [ class (source2 ++ " " ++ classes) ] []
|
i [ class (source2 ++ " " ++ classes) ] []
|
||||||
|
46
modules/webapp/src/main/elm/Messages/Comp/ShareForm.elm
Normal file
46
modules/webapp/src/main/elm/Messages/Comp/ShareForm.elm
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Messages.Comp.ShareForm exposing
|
||||||
|
( Texts
|
||||||
|
, de
|
||||||
|
, gb
|
||||||
|
)
|
||||||
|
|
||||||
|
import Messages.Basics
|
||||||
|
|
||||||
|
|
||||||
|
type alias Texts =
|
||||||
|
{ basics : Messages.Basics.Texts
|
||||||
|
, queryLabel : String
|
||||||
|
, enabled : String
|
||||||
|
, password : String
|
||||||
|
, publishUntil : String
|
||||||
|
, clearPassword : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gb : Texts
|
||||||
|
gb =
|
||||||
|
{ basics = Messages.Basics.gb
|
||||||
|
, queryLabel = "Query"
|
||||||
|
, enabled = "Enabled"
|
||||||
|
, password = "Password"
|
||||||
|
, publishUntil = "Publish Until"
|
||||||
|
, clearPassword = "Remove password"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
de : Texts
|
||||||
|
de =
|
||||||
|
{ basics = Messages.Basics.de
|
||||||
|
, queryLabel = "Abfrage"
|
||||||
|
, enabled = "Aktiv"
|
||||||
|
, password = "Passwort"
|
||||||
|
, publishUntil = "Publiziert bis"
|
||||||
|
, clearPassword = "Passwort entfernen"
|
||||||
|
}
|
74
modules/webapp/src/main/elm/Messages/Comp/ShareManage.elm
Normal file
74
modules/webapp/src/main/elm/Messages/Comp/ShareManage.elm
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Messages.Comp.ShareManage exposing
|
||||||
|
( Texts
|
||||||
|
, de
|
||||||
|
, gb
|
||||||
|
)
|
||||||
|
|
||||||
|
import Http
|
||||||
|
import Messages.Basics
|
||||||
|
import Messages.Comp.HttpError
|
||||||
|
import Messages.Comp.ShareForm
|
||||||
|
import Messages.Comp.ShareTable
|
||||||
|
|
||||||
|
|
||||||
|
type alias Texts =
|
||||||
|
{ basics : Messages.Basics.Texts
|
||||||
|
, shareTable : Messages.Comp.ShareTable.Texts
|
||||||
|
, shareForm : Messages.Comp.ShareForm.Texts
|
||||||
|
, httpError : Http.Error -> String
|
||||||
|
, newShare : String
|
||||||
|
, copyToClipboard : String
|
||||||
|
, openInNewTab : String
|
||||||
|
, publicUrl : String
|
||||||
|
, reallyDeleteShare : String
|
||||||
|
, createNewShare : String
|
||||||
|
, deleteThisShare : String
|
||||||
|
, errorGeneratingQR : String
|
||||||
|
, correctFormErrors : String
|
||||||
|
, noName : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gb : Texts
|
||||||
|
gb =
|
||||||
|
{ basics = Messages.Basics.gb
|
||||||
|
, httpError = Messages.Comp.HttpError.gb
|
||||||
|
, shareTable = Messages.Comp.ShareTable.gb
|
||||||
|
, shareForm = Messages.Comp.ShareForm.gb
|
||||||
|
, newShare = "New share"
|
||||||
|
, copyToClipboard = "Copy to clipboard"
|
||||||
|
, openInNewTab = "Open in new tab/window"
|
||||||
|
, publicUrl = "Public URL"
|
||||||
|
, reallyDeleteShare = "Really delete this share?"
|
||||||
|
, createNewShare = "Create new share"
|
||||||
|
, deleteThisShare = "Delete this share"
|
||||||
|
, errorGeneratingQR = "Error generating QR Code"
|
||||||
|
, correctFormErrors = "Please correct the errors in the form."
|
||||||
|
, noName = "No Name"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
de : Texts
|
||||||
|
de =
|
||||||
|
{ basics = Messages.Basics.de
|
||||||
|
, shareTable = Messages.Comp.ShareTable.de
|
||||||
|
, shareForm = Messages.Comp.ShareForm.de
|
||||||
|
, httpError = Messages.Comp.HttpError.de
|
||||||
|
, newShare = "Neue Freigabe"
|
||||||
|
, copyToClipboard = "In die Zwischenablage kopieren"
|
||||||
|
, openInNewTab = "Im neuen Tab/Fenster öffnen"
|
||||||
|
, publicUrl = "Öffentliche URL"
|
||||||
|
, reallyDeleteShare = "Diese Freigabe wirklich entfernen?"
|
||||||
|
, createNewShare = "Neue Freigabe erstellen"
|
||||||
|
, deleteThisShare = "Freigabe löschen"
|
||||||
|
, errorGeneratingQR = "Fehler beim Generieren des QR-Code"
|
||||||
|
, correctFormErrors = "Bitte korrigiere die Fehler im Formular."
|
||||||
|
, noName = "Ohne Name"
|
||||||
|
}
|
42
modules/webapp/src/main/elm/Messages/Comp/ShareTable.elm
Normal file
42
modules/webapp/src/main/elm/Messages/Comp/ShareTable.elm
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{-
|
||||||
|
Copyright 2020 Eike K. & Contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-}
|
||||||
|
|
||||||
|
|
||||||
|
module Messages.Comp.ShareTable exposing
|
||||||
|
( Texts
|
||||||
|
, de
|
||||||
|
, gb
|
||||||
|
)
|
||||||
|
|
||||||
|
import Messages.Basics
|
||||||
|
import Messages.DateFormat as DF
|
||||||
|
import Messages.UiLanguage
|
||||||
|
|
||||||
|
|
||||||
|
type alias Texts =
|
||||||
|
{ basics : Messages.Basics.Texts
|
||||||
|
, formatDateTime : Int -> String
|
||||||
|
, enabled : String
|
||||||
|
, publishUntil : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
gb : Texts
|
||||||
|
gb =
|
||||||
|
{ basics = Messages.Basics.gb
|
||||||
|
, formatDateTime = DF.formatDateTimeLong Messages.UiLanguage.English
|
||||||
|
, enabled = "Enabled"
|
||||||
|
, publishUntil = "Publish Until"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
de : Texts
|
||||||
|
de =
|
||||||
|
{ basics = Messages.Basics.de
|
||||||
|
, formatDateTime = DF.formatDateTimeLong Messages.UiLanguage.German
|
||||||
|
, enabled = "Aktiv"
|
||||||
|
, publishUntil = "Publiziert bis"
|
||||||
|
}
|
@ -15,6 +15,7 @@ import Http
|
|||||||
import Messages.Basics
|
import Messages.Basics
|
||||||
import Messages.Comp.CollectiveSettingsForm
|
import Messages.Comp.CollectiveSettingsForm
|
||||||
import Messages.Comp.HttpError
|
import Messages.Comp.HttpError
|
||||||
|
import Messages.Comp.ShareManage
|
||||||
import Messages.Comp.SourceManage
|
import Messages.Comp.SourceManage
|
||||||
import Messages.Comp.UserManage
|
import Messages.Comp.UserManage
|
||||||
|
|
||||||
@ -24,12 +25,14 @@ type alias Texts =
|
|||||||
, userManage : Messages.Comp.UserManage.Texts
|
, userManage : Messages.Comp.UserManage.Texts
|
||||||
, collectiveSettingsForm : Messages.Comp.CollectiveSettingsForm.Texts
|
, collectiveSettingsForm : Messages.Comp.CollectiveSettingsForm.Texts
|
||||||
, sourceManage : Messages.Comp.SourceManage.Texts
|
, sourceManage : Messages.Comp.SourceManage.Texts
|
||||||
|
, shareManage : Messages.Comp.ShareManage.Texts
|
||||||
, httpError : Http.Error -> String
|
, httpError : Http.Error -> String
|
||||||
, collectiveSettings : String
|
, collectiveSettings : String
|
||||||
, insights : String
|
, insights : String
|
||||||
, sources : String
|
, sources : String
|
||||||
, settings : String
|
, settings : String
|
||||||
, users : String
|
, users : String
|
||||||
|
, shares : String
|
||||||
, user : String
|
, user : String
|
||||||
, collective : String
|
, collective : String
|
||||||
, size : String
|
, size : String
|
||||||
@ -44,12 +47,14 @@ gb =
|
|||||||
, userManage = Messages.Comp.UserManage.gb
|
, userManage = Messages.Comp.UserManage.gb
|
||||||
, collectiveSettingsForm = Messages.Comp.CollectiveSettingsForm.gb
|
, collectiveSettingsForm = Messages.Comp.CollectiveSettingsForm.gb
|
||||||
, sourceManage = Messages.Comp.SourceManage.gb
|
, sourceManage = Messages.Comp.SourceManage.gb
|
||||||
|
, shareManage = Messages.Comp.ShareManage.gb
|
||||||
, httpError = Messages.Comp.HttpError.gb
|
, httpError = Messages.Comp.HttpError.gb
|
||||||
, collectiveSettings = "Collective Settings"
|
, collectiveSettings = "Collective Settings"
|
||||||
, insights = "Insights"
|
, insights = "Insights"
|
||||||
, sources = "Sources"
|
, sources = "Sources"
|
||||||
, settings = "Settings"
|
, settings = "Settings"
|
||||||
, users = "Users"
|
, users = "Users"
|
||||||
|
, shares = "Shares"
|
||||||
, user = "User"
|
, user = "User"
|
||||||
, collective = "Collective"
|
, collective = "Collective"
|
||||||
, size = "Size"
|
, size = "Size"
|
||||||
@ -64,12 +69,14 @@ de =
|
|||||||
, userManage = Messages.Comp.UserManage.de
|
, userManage = Messages.Comp.UserManage.de
|
||||||
, collectiveSettingsForm = Messages.Comp.CollectiveSettingsForm.de
|
, collectiveSettingsForm = Messages.Comp.CollectiveSettingsForm.de
|
||||||
, sourceManage = Messages.Comp.SourceManage.de
|
, sourceManage = Messages.Comp.SourceManage.de
|
||||||
|
, shareManage = Messages.Comp.ShareManage.de
|
||||||
, httpError = Messages.Comp.HttpError.de
|
, httpError = Messages.Comp.HttpError.de
|
||||||
, collectiveSettings = "Kollektiveinstellungen"
|
, collectiveSettings = "Kollektiveinstellungen"
|
||||||
, insights = "Statistiken"
|
, insights = "Statistiken"
|
||||||
, sources = "Quellen"
|
, sources = "Quellen"
|
||||||
, settings = "Einstellungen"
|
, settings = "Einstellungen"
|
||||||
, users = "Benutzer"
|
, users = "Benutzer"
|
||||||
|
, shares = "Freigaben"
|
||||||
, user = "Benutzer"
|
, user = "Benutzer"
|
||||||
, collective = "Kollektiv"
|
, collective = "Kollektiv"
|
||||||
, size = "Größe"
|
, size = "Größe"
|
||||||
|
@ -17,6 +17,7 @@ import Api.Model.BasicResult exposing (BasicResult)
|
|||||||
import Api.Model.CollectiveSettings exposing (CollectiveSettings)
|
import Api.Model.CollectiveSettings exposing (CollectiveSettings)
|
||||||
import Api.Model.ItemInsights exposing (ItemInsights)
|
import Api.Model.ItemInsights exposing (ItemInsights)
|
||||||
import Comp.CollectiveSettingsForm
|
import Comp.CollectiveSettingsForm
|
||||||
|
import Comp.ShareManage
|
||||||
import Comp.SourceManage
|
import Comp.SourceManage
|
||||||
import Comp.UserManage
|
import Comp.UserManage
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
@ -28,6 +29,7 @@ type alias Model =
|
|||||||
, sourceModel : Comp.SourceManage.Model
|
, sourceModel : Comp.SourceManage.Model
|
||||||
, userModel : Comp.UserManage.Model
|
, userModel : Comp.UserManage.Model
|
||||||
, settingsModel : Comp.CollectiveSettingsForm.Model
|
, settingsModel : Comp.CollectiveSettingsForm.Model
|
||||||
|
, shareModel : Comp.ShareManage.Model
|
||||||
, insights : ItemInsights
|
, insights : ItemInsights
|
||||||
, formState : FormState
|
, formState : FormState
|
||||||
}
|
}
|
||||||
@ -48,10 +50,14 @@ init flags =
|
|||||||
|
|
||||||
( cm, cc ) =
|
( cm, cc ) =
|
||||||
Comp.CollectiveSettingsForm.init flags Api.Model.CollectiveSettings.empty
|
Comp.CollectiveSettingsForm.init flags Api.Model.CollectiveSettings.empty
|
||||||
|
|
||||||
|
( shm, shc ) =
|
||||||
|
Comp.ShareManage.init
|
||||||
in
|
in
|
||||||
( { currentTab = Just InsightsTab
|
( { currentTab = Just InsightsTab
|
||||||
, sourceModel = sm
|
, sourceModel = sm
|
||||||
, userModel = Comp.UserManage.emptyModel
|
, userModel = Comp.UserManage.emptyModel
|
||||||
|
, shareModel = shm
|
||||||
, settingsModel = cm
|
, settingsModel = cm
|
||||||
, insights = Api.Model.ItemInsights.empty
|
, insights = Api.Model.ItemInsights.empty
|
||||||
, formState = InitialState
|
, formState = InitialState
|
||||||
@ -59,6 +65,7 @@ init flags =
|
|||||||
, Cmd.batch
|
, Cmd.batch
|
||||||
[ Cmd.map SourceMsg sc
|
[ Cmd.map SourceMsg sc
|
||||||
, Cmd.map SettingsFormMsg cc
|
, Cmd.map SettingsFormMsg cc
|
||||||
|
, Cmd.map ShareMsg shc
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -68,6 +75,7 @@ type Tab
|
|||||||
| UserTab
|
| UserTab
|
||||||
| InsightsTab
|
| InsightsTab
|
||||||
| SettingsTab
|
| SettingsTab
|
||||||
|
| ShareTab
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
@ -79,3 +87,4 @@ type Msg
|
|||||||
| GetInsightsResp (Result Http.Error ItemInsights)
|
| GetInsightsResp (Result Http.Error ItemInsights)
|
||||||
| CollectiveSettingsResp (Result Http.Error CollectiveSettings)
|
| CollectiveSettingsResp (Result Http.Error CollectiveSettings)
|
||||||
| SubmitResp (Result Http.Error BasicResult)
|
| SubmitResp (Result Http.Error BasicResult)
|
||||||
|
| ShareMsg Comp.ShareManage.Msg
|
||||||
|
@ -9,6 +9,7 @@ module Page.CollectiveSettings.Update exposing (update)
|
|||||||
|
|
||||||
import Api
|
import Api
|
||||||
import Comp.CollectiveSettingsForm
|
import Comp.CollectiveSettingsForm
|
||||||
|
import Comp.ShareManage
|
||||||
import Comp.SourceManage
|
import Comp.SourceManage
|
||||||
import Comp.UserManage
|
import Comp.UserManage
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
@ -36,6 +37,9 @@ update flags msg model =
|
|||||||
SettingsTab ->
|
SettingsTab ->
|
||||||
update flags Init m
|
update flags Init m
|
||||||
|
|
||||||
|
ShareTab ->
|
||||||
|
update flags (ShareMsg Comp.ShareManage.loadShares) m
|
||||||
|
|
||||||
SourceMsg m ->
|
SourceMsg m ->
|
||||||
let
|
let
|
||||||
( m2, c2 ) =
|
( m2, c2 ) =
|
||||||
@ -43,6 +47,13 @@ update flags msg model =
|
|||||||
in
|
in
|
||||||
( { model | sourceModel = m2 }, Cmd.map SourceMsg c2 )
|
( { model | sourceModel = m2 }, Cmd.map SourceMsg c2 )
|
||||||
|
|
||||||
|
ShareMsg lm ->
|
||||||
|
let
|
||||||
|
( sm, sc ) =
|
||||||
|
Comp.ShareManage.update flags lm model.shareModel
|
||||||
|
in
|
||||||
|
( { model | shareModel = sm }, Cmd.map ShareMsg sc )
|
||||||
|
|
||||||
UserMsg m ->
|
UserMsg m ->
|
||||||
let
|
let
|
||||||
( m2, c2 ) =
|
( m2, c2 ) =
|
||||||
|
@ -10,6 +10,7 @@ module Page.CollectiveSettings.View2 exposing (viewContent, viewSidebar)
|
|||||||
import Api.Model.TagCount exposing (TagCount)
|
import Api.Model.TagCount exposing (TagCount)
|
||||||
import Comp.Basic as B
|
import Comp.Basic as B
|
||||||
import Comp.CollectiveSettingsForm
|
import Comp.CollectiveSettingsForm
|
||||||
|
import Comp.ShareManage
|
||||||
import Comp.SourceManage
|
import Comp.SourceManage
|
||||||
import Comp.UserManage
|
import Comp.UserManage
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
@ -60,6 +61,17 @@ viewSidebar texts visible _ _ model =
|
|||||||
[ class "ml-3" ]
|
[ class "ml-3" ]
|
||||||
[ text texts.sources ]
|
[ text texts.sources ]
|
||||||
]
|
]
|
||||||
|
, a
|
||||||
|
[ href "#"
|
||||||
|
, onClick (SetTab ShareTab)
|
||||||
|
, class S.sidebarLink
|
||||||
|
, menuEntryActive model ShareTab
|
||||||
|
]
|
||||||
|
[ Icons.shareIcon ""
|
||||||
|
, span
|
||||||
|
[ class "ml-3" ]
|
||||||
|
[ text texts.shares ]
|
||||||
|
]
|
||||||
, a
|
, a
|
||||||
[ href "#"
|
[ href "#"
|
||||||
, onClick (SetTab SettingsTab)
|
, onClick (SetTab SettingsTab)
|
||||||
@ -105,6 +117,9 @@ viewContent texts flags settings model =
|
|||||||
Just SourceTab ->
|
Just SourceTab ->
|
||||||
viewSources texts flags settings model
|
viewSources texts flags settings model
|
||||||
|
|
||||||
|
Just ShareTab ->
|
||||||
|
viewShares texts flags model
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
@ -230,6 +245,21 @@ viewSources texts flags settings model =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewShares : Texts -> Flags -> Model -> List (Html Msg)
|
||||||
|
viewShares texts flags model =
|
||||||
|
[ h1
|
||||||
|
[ class S.header1
|
||||||
|
, class "inline-flex items-center"
|
||||||
|
]
|
||||||
|
[ Icons.shareIcon ""
|
||||||
|
, div [ class "ml-3" ]
|
||||||
|
[ text texts.shares
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, Html.map ShareMsg (Comp.ShareManage.view texts.shareManage flags model.shareModel)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
viewUsers : Texts -> UiSettings -> Model -> List (Html Msg)
|
viewUsers : Texts -> UiSettings -> Model -> List (Html Msg)
|
||||||
viewUsers texts settings model =
|
viewUsers texts settings model =
|
||||||
[ h1
|
[ h1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user