diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala b/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala index 1e41db96..eda0f00b 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OCollective.scala @@ -14,7 +14,7 @@ trait OCollective[F[_]] { 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]] @@ -45,6 +45,9 @@ object OCollective { type InsightData = QCollective.InsightData val insightData = QCollective.InsightData + type Settings = RCollective.Settings + val Settings = RCollective.Settings + sealed trait PassChangeResult object PassChangeResult { case object UserNotFound extends PassChangeResult @@ -85,9 +88,9 @@ object OCollective { def find(name: Ident): F[Option[RCollective]] = store.transact(RCollective.findById(name)) - def updateLanguage(collective: Ident, lang: Language): F[AddResult] = + def updateSettings(collective: Ident, sett: Settings): F[AddResult] = store - .transact(RCollective.updateLanguage(collective, lang)) + .transact(RCollective.updateSettings(collective, sett)) .attempt .map(AddResult.fromUpdate) diff --git a/modules/backend/src/main/scala/docspell/backend/signup/OSignup.scala b/modules/backend/src/main/scala/docspell/backend/signup/OSignup.scala index 4b09318d..65cb6276 100644 --- a/modules/backend/src/main/scala/docspell/backend/signup/OSignup.scala +++ b/modules/backend/src/main/scala/docspell/backend/signup/OSignup.scala @@ -88,7 +88,13 @@ object OSignup { for { id2 <- Ident.randomId[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( id2, data.login, diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index dce7205a..e0ecf20f 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -618,10 +618,9 @@ paths: $ref: "#/components/schemas/CollectiveSettings" post: tags: [ Collective ] - summary: Set document language of the collective + summary: Update settings for a collective description: | - Updates settings for a collective, which currently is just the - document language. + Updates settings for a collective. security: - authTokenHeader: [] requestBody: @@ -2692,10 +2691,16 @@ components: Settings for a collective. required: - language + - integrationEnabled properties: language: type: string format: language + integrationEnabled: + type: boolean + description: | + Whether the collective has the integration endpoint + enabled. SourceList: description: | A list of sources. diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala index a5163e88..8e3300c4 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/CollectiveRoutes.scala @@ -4,6 +4,7 @@ import cats.effect._ import cats.implicits._ import docspell.backend.BackendApp import docspell.backend.auth.AuthToken +import docspell.backend.ops.OCollective import docspell.restapi.model._ import docspell.restserver.conv.Conversions import docspell.restserver.http4s._ @@ -28,16 +29,17 @@ object CollectiveRoutes { case req @ POST -> Root / "settings" => for { settings <- req.as[CollectiveSettings] + sett = OCollective.Settings(settings.language, settings.integrationEnabled) res <- backend.collective - .updateLanguage(user.account.collective, settings.language) - resp <- Ok(Conversions.basicResult(res, "Language updated.")) + .updateSettings(user.account.collective, sett) + resp <- Ok(Conversions.basicResult(res, "Settings updated.")) } yield resp case GET -> Root / "settings" => for { 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() } yield resp diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/IntegrationEndpointRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/IntegrationEndpointRoutes.scala index c34fd1f7..6f0361d4 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/IntegrationEndpointRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/IntegrationEndpointRoutes.scala @@ -59,7 +59,7 @@ object IntegrationEndpointRoutes { ): EitherT[F, Response[F], Unit] = for { 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 def uploadFile[F[_]: Effect]( diff --git a/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala b/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala index 463562dc..3326a839 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala @@ -12,7 +12,8 @@ case class Flags( appName: String, baseUrl: LenientUri, signupMode: SignupConfig.Mode, - docspellAssetPath: String + docspellAssetPath: String, + integrationEnabled: Boolean ) object Flags { @@ -21,7 +22,8 @@ object Flags { cfg.appName, cfg.baseUrl, 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] = diff --git a/modules/store/src/main/resources/db/migration/mariadb/V1.6.0__integration_enabled.sql b/modules/store/src/main/resources/db/migration/mariadb/V1.6.0__integration_enabled.sql new file mode 100644 index 00000000..1a039085 --- /dev/null +++ b/modules/store/src/main/resources/db/migration/mariadb/V1.6.0__integration_enabled.sql @@ -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; diff --git a/modules/store/src/main/resources/db/migration/postgresql/V1.6.0__integration_enabled.sql b/modules/store/src/main/resources/db/migration/postgresql/V1.6.0__integration_enabled.sql new file mode 100644 index 00000000..d1421f1d --- /dev/null +++ b/modules/store/src/main/resources/db/migration/postgresql/V1.6.0__integration_enabled.sql @@ -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; diff --git a/modules/store/src/main/scala/docspell/store/records/RCollective.scala b/modules/store/src/main/scala/docspell/store/records/RCollective.scala index 0eaec6f8..843960d2 100644 --- a/modules/store/src/main/scala/docspell/store/records/RCollective.scala +++ b/modules/store/src/main/scala/docspell/store/records/RCollective.scala @@ -11,6 +11,7 @@ case class RCollective( id: Ident, state: CollectiveState, language: Language, + integrationEnabled: Boolean, created: Timestamp ) @@ -20,12 +21,13 @@ object RCollective { object Columns { - val id = Column("cid") - val state = Column("state") - val language = Column("doclang") - val created = Column("created") + val id = Column("cid") + val state = Column("state") + val language = Column("doclang") + 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._ @@ -34,7 +36,7 @@ object RCollective { val sql = insertRow( table, 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 } @@ -56,6 +58,16 @@ object RCollective { def updateLanguage(cid: Ident, lang: Language): ConnectionIO[Int] = 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]] = { val sql = selectSimple(all, table, id.is(cid)) sql.query[RCollective].option @@ -75,4 +87,6 @@ object RCollective { val sql = selectSimple(all, table, Fragment.empty) ++ orderBy(order(Columns).f) sql.query[RCollective].stream } + + case class Settings(language: Language, integrationEnabled: Boolean) } diff --git a/modules/webapp/src/main/elm/Comp/Settings.elm b/modules/webapp/src/main/elm/Comp/CollectiveSettingsForm.elm similarity index 60% rename from modules/webapp/src/main/elm/Comp/Settings.elm rename to modules/webapp/src/main/elm/Comp/CollectiveSettingsForm.elm index 55ccf613..066ef383 100644 --- a/modules/webapp/src/main/elm/Comp/Settings.elm +++ b/modules/webapp/src/main/elm/Comp/CollectiveSettingsForm.elm @@ -1,4 +1,4 @@ -module Comp.Settings exposing +module Comp.CollectiveSettingsForm exposing ( Model , Msg , getSettings @@ -13,10 +13,12 @@ import Data.Flags exposing (Flags) import Data.Language exposing (Language) import Html exposing (..) import Html.Attributes exposing (..) +import Html.Events exposing (onCheck) type alias Model = { langModel : Comp.Dropdown.Model Language + , intEnabled : Bool , initSettings : CollectiveSettings } @@ -39,6 +41,7 @@ init settings = , options = Data.Language.all , selected = Just lang } + , intEnabled = settings.integrationEnabled , initSettings = settings } @@ -51,10 +54,12 @@ getSettings model = |> Maybe.map Data.Language.toIso3 |> Maybe.withDefault model.initSettings.language ) + model.intEnabled type Msg = LangDropdownMsg (Comp.Dropdown.Msg Language) + | ToggleIntegrationEndpoint update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Maybe CollectiveSettings ) @@ -77,12 +82,42 @@ update _ msg model = in ( 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 "field" ] [ label [] [ text "Document Language" ] , 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." + ] + ] ] ] diff --git a/modules/webapp/src/main/elm/Data/Flags.elm b/modules/webapp/src/main/elm/Data/Flags.elm index a129e76f..2be9c862 100644 --- a/modules/webapp/src/main/elm/Data/Flags.elm +++ b/modules/webapp/src/main/elm/Data/Flags.elm @@ -14,6 +14,7 @@ type alias Config = , baseUrl : String , signupMode : String , docspellAssetPath : String + , integrationEnabled : Bool } diff --git a/modules/webapp/src/main/elm/Page/CollectiveSettings/Data.elm b/modules/webapp/src/main/elm/Page/CollectiveSettings/Data.elm index b55ba06c..d12aa44f 100644 --- a/modules/webapp/src/main/elm/Page/CollectiveSettings/Data.elm +++ b/modules/webapp/src/main/elm/Page/CollectiveSettings/Data.elm @@ -8,7 +8,7 @@ module Page.CollectiveSettings.Data exposing import Api.Model.BasicResult exposing (BasicResult) import Api.Model.CollectiveSettings exposing (CollectiveSettings) import Api.Model.ItemInsights exposing (ItemInsights) -import Comp.Settings +import Comp.CollectiveSettingsForm import Comp.SourceManage import Comp.UserManage import Http @@ -18,7 +18,7 @@ type alias Model = { currentTab : Maybe Tab , sourceModel : Comp.SourceManage.Model , userModel : Comp.UserManage.Model - , settingsModel : Comp.Settings.Model + , settingsModel : Comp.CollectiveSettingsForm.Model , insights : ItemInsights , submitResult : Maybe BasicResult } @@ -29,7 +29,7 @@ emptyModel = { currentTab = Just InsightsTab , sourceModel = Comp.SourceManage.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 , submitResult = Nothing } @@ -46,7 +46,7 @@ type Msg = SetTab Tab | SourceMsg Comp.SourceManage.Msg | UserMsg Comp.UserManage.Msg - | SettingsMsg Comp.Settings.Msg + | SettingsFormMsg Comp.CollectiveSettingsForm.Msg | Init | GetInsightsResp (Result Http.Error ItemInsights) | CollectiveSettingsResp (Result Http.Error CollectiveSettings) diff --git a/modules/webapp/src/main/elm/Page/CollectiveSettings/Update.elm b/modules/webapp/src/main/elm/Page/CollectiveSettings/Update.elm index 7b4f8cf8..fa9ab433 100644 --- a/modules/webapp/src/main/elm/Page/CollectiveSettings/Update.elm +++ b/modules/webapp/src/main/elm/Page/CollectiveSettings/Update.elm @@ -2,7 +2,7 @@ module Page.CollectiveSettings.Update exposing (update) import Api import Api.Model.BasicResult exposing (BasicResult) -import Comp.Settings +import Comp.CollectiveSettingsForm import Comp.SourceManage import Comp.UserManage import Data.Flags exposing (Flags) @@ -45,10 +45,10 @@ update flags msg model = in ( { model | userModel = m2 }, Cmd.map UserMsg c2 ) - SettingsMsg m -> + SettingsFormMsg m -> let ( m2, c2, msett ) = - Comp.Settings.update flags m model.settingsModel + Comp.CollectiveSettingsForm.update flags m model.settingsModel cmd = case msett of @@ -58,7 +58,9 @@ update flags msg model = Just sett -> Api.setCollectiveSettings flags sett SubmitResp 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 -> ( { model | submitResult = Nothing } @@ -75,7 +77,7 @@ update flags msg model = ( model, Cmd.none ) CollectiveSettingsResp (Ok data) -> - ( { model | settingsModel = Comp.Settings.init data }, Cmd.none ) + ( { model | settingsModel = Comp.CollectiveSettingsForm.init data }, Cmd.none ) CollectiveSettingsResp (Err _) -> ( model, Cmd.none ) diff --git a/modules/webapp/src/main/elm/Page/CollectiveSettings/View.elm b/modules/webapp/src/main/elm/Page/CollectiveSettings/View.elm index c2c6287a..9e799833 100644 --- a/modules/webapp/src/main/elm/Page/CollectiveSettings/View.elm +++ b/modules/webapp/src/main/elm/Page/CollectiveSettings/View.elm @@ -1,7 +1,7 @@ module Page.CollectiveSettings.View exposing (view) import Api.Model.NameCount exposing (NameCount) -import Comp.Settings +import Comp.CollectiveSettingsForm import Comp.SourceManage import Comp.UserManage import Data.Flags exposing (Flags) @@ -41,8 +41,8 @@ view flags model = [ classActive (model.currentTab == Just SettingsTab) "link icon item" , onClick (SetTab SettingsTab) ] - [ i [ class "language icon" ] [] - , text "Document Language" + [ i [ class "cog icon" ] [] + , text "Settings" ] , div [ classActive (model.currentTab == Just UserTab) "link icon item" @@ -67,7 +67,7 @@ view flags model = viewInsights model Just SettingsTab -> - viewSettings model + viewSettings flags model Nothing -> [] @@ -176,42 +176,25 @@ viewUsers model = ] -viewSettings : Model -> List (Html Msg) -viewSettings model = - [ div [ class "ui grid" ] - [ div [ class "row" ] - [ div [ class "sixteen wide colum" ] - [ h2 [ class "ui header" ] - [ i [ class "ui language icon" ] [] - , div [ class "content" ] - [ text "Document Language" - ] - ] - ] - ] - , div [ class "row" ] - [ div [ class "six wide column" ] - [ 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 - ] - ] +viewSettings : Flags -> Model -> List (Html Msg) +viewSettings flags model = + [ h2 [ class "ui header" ] + [ i [ class "cog icon" ] [] + , text "Settings" + ] + , div [ class "ui segment" ] + [ Html.map SettingsFormMsg (Comp.CollectiveSettingsForm.view flags 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 + ] ]