Allow a collective to disable the integration endpoint

This commit is contained in:
Eike Kettner
2020-05-23 13:53:36 +02:00
parent f74f8e5198
commit f16632bc7f
14 changed files with 138 additions and 71 deletions

View File

@ -14,7 +14,7 @@ trait OCollective[F[_]] {
def find(name: Ident): F[Option[RCollective]] def find(name: Ident): F[Option[RCollective]]
def updateLanguage(collective: Ident, lang: Language): F[AddResult] def updateSettings(collective: Ident, lang: OCollective.Settings): F[AddResult]
def listUser(collective: Ident): F[Vector[RUser]] def listUser(collective: Ident): F[Vector[RUser]]
@ -45,6 +45,9 @@ object OCollective {
type InsightData = QCollective.InsightData type InsightData = QCollective.InsightData
val insightData = QCollective.InsightData val insightData = QCollective.InsightData
type Settings = RCollective.Settings
val Settings = RCollective.Settings
sealed trait PassChangeResult sealed trait PassChangeResult
object PassChangeResult { object PassChangeResult {
case object UserNotFound extends PassChangeResult case object UserNotFound extends PassChangeResult
@ -85,9 +88,9 @@ object OCollective {
def find(name: Ident): F[Option[RCollective]] = def find(name: Ident): F[Option[RCollective]] =
store.transact(RCollective.findById(name)) store.transact(RCollective.findById(name))
def updateLanguage(collective: Ident, lang: Language): F[AddResult] = def updateSettings(collective: Ident, sett: Settings): F[AddResult] =
store store
.transact(RCollective.updateLanguage(collective, lang)) .transact(RCollective.updateSettings(collective, sett))
.attempt .attempt
.map(AddResult.fromUpdate) .map(AddResult.fromUpdate)

View File

@ -88,7 +88,13 @@ object OSignup {
for { for {
id2 <- Ident.randomId[F] id2 <- Ident.randomId[F]
now <- Timestamp.current[F] now <- Timestamp.current[F]
c = RCollective(data.collName, CollectiveState.Active, Language.German, now) c = RCollective(
data.collName,
CollectiveState.Active,
Language.German,
true,
now
)
u = RUser( u = RUser(
id2, id2,
data.login, data.login,

View File

@ -618,10 +618,9 @@ paths:
$ref: "#/components/schemas/CollectiveSettings" $ref: "#/components/schemas/CollectiveSettings"
post: post:
tags: [ Collective ] tags: [ Collective ]
summary: Set document language of the collective summary: Update settings for a collective
description: | description: |
Updates settings for a collective, which currently is just the Updates settings for a collective.
document language.
security: security:
- authTokenHeader: [] - authTokenHeader: []
requestBody: requestBody:
@ -2692,10 +2691,16 @@ components:
Settings for a collective. Settings for a collective.
required: required:
- language - language
- integrationEnabled
properties: properties:
language: language:
type: string type: string
format: language format: language
integrationEnabled:
type: boolean
description: |
Whether the collective has the integration endpoint
enabled.
SourceList: SourceList:
description: | description: |
A list of sources. A list of sources.

View File

@ -4,6 +4,7 @@ import cats.effect._
import cats.implicits._ import cats.implicits._
import docspell.backend.BackendApp import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken import docspell.backend.auth.AuthToken
import docspell.backend.ops.OCollective
import docspell.restapi.model._ import docspell.restapi.model._
import docspell.restserver.conv.Conversions import docspell.restserver.conv.Conversions
import docspell.restserver.http4s._ import docspell.restserver.http4s._
@ -28,16 +29,17 @@ object CollectiveRoutes {
case req @ POST -> Root / "settings" => case req @ POST -> Root / "settings" =>
for { for {
settings <- req.as[CollectiveSettings] settings <- req.as[CollectiveSettings]
sett = OCollective.Settings(settings.language, settings.integrationEnabled)
res <- res <-
backend.collective backend.collective
.updateLanguage(user.account.collective, settings.language) .updateSettings(user.account.collective, sett)
resp <- Ok(Conversions.basicResult(res, "Language updated.")) resp <- Ok(Conversions.basicResult(res, "Settings updated."))
} yield resp } yield resp
case GET -> Root / "settings" => case GET -> Root / "settings" =>
for { for {
collDb <- backend.collective.find(user.account.collective) collDb <- backend.collective.find(user.account.collective)
sett = collDb.map(c => CollectiveSettings(c.language)) sett = collDb.map(c => CollectiveSettings(c.language, c.integrationEnabled))
resp <- sett.toResponse() resp <- sett.toResponse()
} yield resp } yield resp

View File

@ -59,7 +59,7 @@ object IntegrationEndpointRoutes {
): EitherT[F, Response[F], Unit] = ): EitherT[F, Response[F], Unit] =
for { for {
opt <- EitherT.liftF(backend.collective.find(coll)) opt <- EitherT.liftF(backend.collective.find(coll))
res <- EitherT.cond[F](opt.isDefined, (), Response.notFound[F]) res <- EitherT.cond[F](opt.exists(_.integrationEnabled), (), Response.notFound[F])
} yield res } yield res
def uploadFile[F[_]: Effect]( def uploadFile[F[_]: Effect](

View File

@ -12,7 +12,8 @@ case class Flags(
appName: String, appName: String,
baseUrl: LenientUri, baseUrl: LenientUri,
signupMode: SignupConfig.Mode, signupMode: SignupConfig.Mode,
docspellAssetPath: String docspellAssetPath: String,
integrationEnabled: Boolean
) )
object Flags { object Flags {
@ -21,7 +22,8 @@ object Flags {
cfg.appName, cfg.appName,
cfg.baseUrl, cfg.baseUrl,
cfg.backend.signup.mode, cfg.backend.signup.mode,
s"/app/assets/docspell-webapp/${BuildInfo.version}" s"/app/assets/docspell-webapp/${BuildInfo.version}",
cfg.integrationEndpoint.enabled
) )
implicit val jsonEncoder: Encoder[Flags] = implicit val jsonEncoder: Encoder[Flags] =

View File

@ -0,0 +1,7 @@
ALTER TABLE `collective`
ADD COLUMN (`integration_enabled` BOOLEAN);
UPDATE `collective` SET `integration_enabled` = true;
ALTER TABLE `collective`
MODIFY `integration_enabled` BOOLEAN NOT NULL;

View File

@ -0,0 +1,7 @@
ALTER TABLE "collective"
ADD COLUMN "integration_enabled" BOOLEAN;
UPDATE "collective" SET "integration_enabled" = true;
ALTER TABLE "collective"
ALTER COLUMN "integration_enabled" SET NOT NULL;

View File

@ -11,6 +11,7 @@ case class RCollective(
id: Ident, id: Ident,
state: CollectiveState, state: CollectiveState,
language: Language, language: Language,
integrationEnabled: Boolean,
created: Timestamp created: Timestamp
) )
@ -20,12 +21,13 @@ object RCollective {
object Columns { object Columns {
val id = Column("cid") val id = Column("cid")
val state = Column("state") val state = Column("state")
val language = Column("doclang") val language = Column("doclang")
val created = Column("created") val integration = Column("integration_enabled")
val created = Column("created")
val all = List(id, state, language, created) val all = List(id, state, language, integration, created)
} }
import Columns._ import Columns._
@ -34,7 +36,7 @@ object RCollective {
val sql = insertRow( val sql = insertRow(
table, table,
Columns.all, Columns.all,
fr"${value.id},${value.state},${value.language},${value.created}" fr"${value.id},${value.state},${value.language},${value.integrationEnabled},${value.created}"
) )
sql.update.run sql.update.run
} }
@ -56,6 +58,16 @@ object RCollective {
def updateLanguage(cid: Ident, lang: Language): ConnectionIO[Int] = def updateLanguage(cid: Ident, lang: Language): ConnectionIO[Int] =
updateRow(table, id.is(cid), language.setTo(lang)).update.run updateRow(table, id.is(cid), language.setTo(lang)).update.run
def updateSettings(cid: Ident, settings: Settings): ConnectionIO[Int] =
updateRow(
table,
id.is(cid),
commas(
language.setTo(settings.language),
integration.setTo(settings.integrationEnabled)
)
).update.run
def findById(cid: Ident): ConnectionIO[Option[RCollective]] = { def findById(cid: Ident): ConnectionIO[Option[RCollective]] = {
val sql = selectSimple(all, table, id.is(cid)) val sql = selectSimple(all, table, id.is(cid))
sql.query[RCollective].option sql.query[RCollective].option
@ -75,4 +87,6 @@ object RCollective {
val sql = selectSimple(all, table, Fragment.empty) ++ orderBy(order(Columns).f) val sql = selectSimple(all, table, Fragment.empty) ++ orderBy(order(Columns).f)
sql.query[RCollective].stream sql.query[RCollective].stream
} }
case class Settings(language: Language, integrationEnabled: Boolean)
} }

View File

@ -1,4 +1,4 @@
module Comp.Settings exposing module Comp.CollectiveSettingsForm exposing
( Model ( Model
, Msg , Msg
, getSettings , getSettings
@ -13,10 +13,12 @@ import Data.Flags exposing (Flags)
import Data.Language exposing (Language) import Data.Language exposing (Language)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onCheck)
type alias Model = type alias Model =
{ langModel : Comp.Dropdown.Model Language { langModel : Comp.Dropdown.Model Language
, intEnabled : Bool
, initSettings : CollectiveSettings , initSettings : CollectiveSettings
} }
@ -39,6 +41,7 @@ init settings =
, options = Data.Language.all , options = Data.Language.all
, selected = Just lang , selected = Just lang
} }
, intEnabled = settings.integrationEnabled
, initSettings = settings , initSettings = settings
} }
@ -51,10 +54,12 @@ getSettings model =
|> Maybe.map Data.Language.toIso3 |> Maybe.map Data.Language.toIso3
|> Maybe.withDefault model.initSettings.language |> Maybe.withDefault model.initSettings.language
) )
model.intEnabled
type Msg type Msg
= LangDropdownMsg (Comp.Dropdown.Msg Language) = LangDropdownMsg (Comp.Dropdown.Msg Language)
| ToggleIntegrationEndpoint
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe CollectiveSettings ) update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe CollectiveSettings )
@ -77,12 +82,42 @@ update _ msg model =
in in
( nextModel, Cmd.map LangDropdownMsg c2, nextSettings ) ( nextModel, Cmd.map LangDropdownMsg c2, nextSettings )
ToggleIntegrationEndpoint ->
let
nextModel =
{ model | intEnabled = not model.intEnabled }
in
( nextModel, Cmd.none, Just (getSettings nextModel) )
view : Model -> Html Msg
view model = view : Flags -> Model -> Html Msg
view flags model =
div [ class "ui form" ] div [ class "ui form" ]
[ div [ class "field" ] [ div [ class "field" ]
[ label [] [ text "Document Language" ] [ label [] [ text "Document Language" ]
, Html.map LangDropdownMsg (Comp.Dropdown.view model.langModel) , Html.map LangDropdownMsg (Comp.Dropdown.view model.langModel)
, span [ class "small-info" ]
[ text "The language of your documents. This helps text recognition (OCR) and text analysis."
]
]
, div
[ classList
[ ( "field", True )
, ( "invisible hidden", not flags.config.integrationEnabled )
]
]
[ div [ class "ui checkbox" ]
[ input
[ type_ "checkbox"
, onCheck (\_ -> ToggleIntegrationEndpoint)
, checked model.intEnabled
]
[]
, label [] [ text "Enable integration endpoint" ]
, span [ class "small-info" ]
[ text "The integration endpoint allows (local) applications to submit files. "
, text "You can choose to disable it for your collective."
]
]
] ]
] ]

View File

@ -14,6 +14,7 @@ type alias Config =
, baseUrl : String , baseUrl : String
, signupMode : String , signupMode : String
, docspellAssetPath : String , docspellAssetPath : String
, integrationEnabled : Bool
} }

View File

@ -8,7 +8,7 @@ module Page.CollectiveSettings.Data exposing
import Api.Model.BasicResult exposing (BasicResult) 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.Settings import Comp.CollectiveSettingsForm
import Comp.SourceManage import Comp.SourceManage
import Comp.UserManage import Comp.UserManage
import Http import Http
@ -18,7 +18,7 @@ type alias Model =
{ currentTab : Maybe Tab { currentTab : Maybe Tab
, sourceModel : Comp.SourceManage.Model , sourceModel : Comp.SourceManage.Model
, userModel : Comp.UserManage.Model , userModel : Comp.UserManage.Model
, settingsModel : Comp.Settings.Model , settingsModel : Comp.CollectiveSettingsForm.Model
, insights : ItemInsights , insights : ItemInsights
, submitResult : Maybe BasicResult , submitResult : Maybe BasicResult
} }
@ -29,7 +29,7 @@ emptyModel =
{ currentTab = Just InsightsTab { currentTab = Just InsightsTab
, sourceModel = Comp.SourceManage.emptyModel , sourceModel = Comp.SourceManage.emptyModel
, userModel = Comp.UserManage.emptyModel , userModel = Comp.UserManage.emptyModel
, settingsModel = Comp.Settings.init Api.Model.CollectiveSettings.empty , settingsModel = Comp.CollectiveSettingsForm.init Api.Model.CollectiveSettings.empty
, insights = Api.Model.ItemInsights.empty , insights = Api.Model.ItemInsights.empty
, submitResult = Nothing , submitResult = Nothing
} }
@ -46,7 +46,7 @@ type Msg
= SetTab Tab = SetTab Tab
| SourceMsg Comp.SourceManage.Msg | SourceMsg Comp.SourceManage.Msg
| UserMsg Comp.UserManage.Msg | UserMsg Comp.UserManage.Msg
| SettingsMsg Comp.Settings.Msg | SettingsFormMsg Comp.CollectiveSettingsForm.Msg
| Init | Init
| GetInsightsResp (Result Http.Error ItemInsights) | GetInsightsResp (Result Http.Error ItemInsights)
| CollectiveSettingsResp (Result Http.Error CollectiveSettings) | CollectiveSettingsResp (Result Http.Error CollectiveSettings)

View File

@ -2,7 +2,7 @@ module Page.CollectiveSettings.Update exposing (update)
import Api import Api
import Api.Model.BasicResult exposing (BasicResult) import Api.Model.BasicResult exposing (BasicResult)
import Comp.Settings import Comp.CollectiveSettingsForm
import Comp.SourceManage import Comp.SourceManage
import Comp.UserManage import Comp.UserManage
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
@ -45,10 +45,10 @@ update flags msg model =
in in
( { model | userModel = m2 }, Cmd.map UserMsg c2 ) ( { model | userModel = m2 }, Cmd.map UserMsg c2 )
SettingsMsg m -> SettingsFormMsg m ->
let let
( m2, c2, msett ) = ( m2, c2, msett ) =
Comp.Settings.update flags m model.settingsModel Comp.CollectiveSettingsForm.update flags m model.settingsModel
cmd = cmd =
case msett of case msett of
@ -58,7 +58,9 @@ update flags msg model =
Just sett -> Just sett ->
Api.setCollectiveSettings flags sett SubmitResp Api.setCollectiveSettings flags sett SubmitResp
in in
( { model | settingsModel = m2, submitResult = Nothing }, Cmd.batch [ cmd, Cmd.map SettingsMsg c2 ] ) ( { model | settingsModel = m2, submitResult = Nothing }
, Cmd.batch [ cmd, Cmd.map SettingsFormMsg c2 ]
)
Init -> Init ->
( { model | submitResult = Nothing } ( { model | submitResult = Nothing }
@ -75,7 +77,7 @@ update flags msg model =
( model, Cmd.none ) ( model, Cmd.none )
CollectiveSettingsResp (Ok data) -> CollectiveSettingsResp (Ok data) ->
( { model | settingsModel = Comp.Settings.init data }, Cmd.none ) ( { model | settingsModel = Comp.CollectiveSettingsForm.init data }, Cmd.none )
CollectiveSettingsResp (Err _) -> CollectiveSettingsResp (Err _) ->
( model, Cmd.none ) ( model, Cmd.none )

View File

@ -1,7 +1,7 @@
module Page.CollectiveSettings.View exposing (view) module Page.CollectiveSettings.View exposing (view)
import Api.Model.NameCount exposing (NameCount) import Api.Model.NameCount exposing (NameCount)
import Comp.Settings import Comp.CollectiveSettingsForm
import Comp.SourceManage import Comp.SourceManage
import Comp.UserManage import Comp.UserManage
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
@ -41,8 +41,8 @@ view flags model =
[ classActive (model.currentTab == Just SettingsTab) "link icon item" [ classActive (model.currentTab == Just SettingsTab) "link icon item"
, onClick (SetTab SettingsTab) , onClick (SetTab SettingsTab)
] ]
[ i [ class "language icon" ] [] [ i [ class "cog icon" ] []
, text "Document Language" , text "Settings"
] ]
, div , div
[ classActive (model.currentTab == Just UserTab) "link icon item" [ classActive (model.currentTab == Just UserTab) "link icon item"
@ -67,7 +67,7 @@ view flags model =
viewInsights model viewInsights model
Just SettingsTab -> Just SettingsTab ->
viewSettings model viewSettings flags model
Nothing -> Nothing ->
[] []
@ -176,42 +176,25 @@ viewUsers model =
] ]
viewSettings : Model -> List (Html Msg) viewSettings : Flags -> Model -> List (Html Msg)
viewSettings model = viewSettings flags model =
[ div [ class "ui grid" ] [ h2 [ class "ui header" ]
[ div [ class "row" ] [ i [ class "cog icon" ] []
[ div [ class "sixteen wide colum" ] , text "Settings"
[ h2 [ class "ui header" ] ]
[ i [ class "ui language icon" ] [] , div [ class "ui segment" ]
, div [ class "content" ] [ Html.map SettingsFormMsg (Comp.CollectiveSettingsForm.view flags model.settingsModel)
[ text "Document Language" ]
] , div
] [ classList
] [ ( "ui message", True )
] , ( "hidden", Util.Maybe.isEmpty model.submitResult )
, div [ class "row" ] , ( "success", Maybe.map .success model.submitResult |> Maybe.withDefault False )
[ div [ class "six wide column" ] , ( "error", Maybe.map .success model.submitResult |> Maybe.map not |> Maybe.withDefault False )
[ div [ class "ui basic segment" ]
[ text "The language of your documents. This helps text recognition (OCR) and text analysis."
]
]
]
, div [ class "row" ]
[ div [ class "six wide column" ]
[ Html.map SettingsMsg (Comp.Settings.view model.settingsModel)
, div
[ classList
[ ( "ui message", True )
, ( "hidden", Util.Maybe.isEmpty model.submitResult )
, ( "success", Maybe.map .success model.submitResult |> Maybe.withDefault False )
, ( "error", Maybe.map .success model.submitResult |> Maybe.map not |> Maybe.withDefault False )
]
]
[ Maybe.map .message model.submitResult
|> Maybe.withDefault ""
|> text
]
]
] ]
] ]
[ Maybe.map .message model.submitResult
|> Maybe.withDefault ""
|> text
]
] ]