diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0df25fe1f..30ed5af8e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -142,31 +142,6 @@ uniworx:exe:uniworx: retry: 2 interruptible: true -uniworx:exe:uniworx-wflint: - stage: backend:build - script: - - xzcat uniworx:lib:uniworx.nar.xz | nix-store --import - - source .gitlab-ci/construct-flake-url.sh - - nix -L build -o result "${FLAKE}#uniworx:exe:uniworx-wflint" - - nix-store --export $(nix-store -qR result) | xz -T0 -2 > uniworx:exe:uniworx-wflint.nar.xz - before_script: *nix-before - needs: - - job: node dependencies # transitive - artifacts: false - - job: well known # transitive - artifacts: false - - job: frontend # tranitive - artifacts: false - - job: uniworx:lib:uniworx - artifacts: true - artifacts: - paths: - - uniworx:exe:uniworx-wflint.nar.xz - name: "${CI_JOB_NAME}-${CI_COMMIT_SHORT_SHA}" - expire_in: "1 day" - retry: 2 - interruptible: true - uniworx:exe:uniworxdb: stage: backend:build script: diff --git a/flake.nix b/flake.nix index 99125eb0b..d3d99e706 100644 --- a/flake.nix +++ b/flake.nix @@ -87,7 +87,7 @@ backendSource = pkgs.lib.sourceByRegex ./. [ "^(\.hlint|package|stack-flake)\.yaml$" "^stack\.yaml\.lock$" - "^(assets|app|hlint|load|messages|models|src|templates|test|testdata|wflint)(/.*)?$" + "^(assets|app|hlint|load|messages|models|src|templates|test|testdata)(/.*)?$" "^config(/(archive-types|mimetypes|personalised-sheet-files-collate|settings\.yml|submission-blacklist|test-settings\.yml|video-types|wordlist\.txt))?$" "^routes$" "^testdata(/.*)?$" diff --git a/messages/uniworx/categories/I18n/de-de-formal.msg b/messages/uniworx/categories/I18n/de-de-formal.msg index 280fbf7a0..e3300f6aa 100644 --- a/messages/uniworx/categories/I18n/de-de-formal.msg +++ b/messages/uniworx/categories/I18n/de-de-formal.msg @@ -1,7 +1,11 @@ -SummerTerm year@Integer: Sommersemester #{year} -WinterTerm year@Integer: Wintersemester #{year}/#{succ year} -SummerTermShort year@Integer: SoSe #{year} -WinterTermShort year@Integer: WiSe #{year}/#{mod (succ year) 100} +Quarter1st year@Integer: Erstes Quartal #{year} +Quarter2nd year@Integer: Zweites Quartal #{year} +Quarter3rd year@Integer: Drittes Quartal #{year} +Quarter4th year@Integer: Viertes Quartal #{year} +Quarter1stShort year@Integer: #{year}/Q1 +Quarter2ndShort year@Integer: #{year}/Q2 +Quarter3rdShort year@Integer: #{year}/Q3 +Quarter4thShort year@Integer: #{year}/Q4 CorByProportionOnly proportion@Rational: #{rationalToFixed3 proportion} Anteile CorByProportionIncludingTutorial proportion@Rational: #{rationalToFixed3 proportion} Anteile - Tutorium CorByProportionExcludingTutorial proportion@Rational: #{rationalToFixed3 proportion} Anteile + Tutorium diff --git a/messages/uniworx/categories/I18n/en-eu.msg b/messages/uniworx/categories/I18n/en-eu.msg index 800426582..f18480470 100644 --- a/messages/uniworx/categories/I18n/en-eu.msg +++ b/messages/uniworx/categories/I18n/en-eu.msg @@ -1,7 +1,11 @@ -SummerTerm year: Summer semester #{year} -WinterTerm year: Winter semester #{year}/#{succ year} -SummerTermShort year: Summer #{year} -WinterTermShort year: Winter #{year}/#{mod (succ year) 100} +Quarter1st year@Integer: First Quarter of #{year} +Quarter2nd year@Integer: Second Quarter of #{year} +Quarter3rd year@Integer: Third Quarter of #{year} +Quarter4th year@Integer: Last Quarter of #{year} +Quarter1stShort year@Integer: #{year}/Q1st +Quarter2ndShort year@Integer: #{year}/Q2nd +Quarter3rdShort year@Integer: #{year}/Q3rd +Quarter4thShort year@Integer: #{year}/Q4th CorByProportionOnly proportion: #{rationalToFixed3 proportion} parts CorByProportionIncludingTutorial proportion: #{rationalToFixed3 proportion} parts - tutorials CorByProportionExcludingTutorial proportion: #{rationalToFixed3 proportion} parts + tutorials diff --git a/messages/uniworx/categories/workflows/de-de-formal.msg b/messages/uniworx/categories/workflows/de-de-formal.msg deleted file mode 100644 index 6cd756c84..000000000 --- a/messages/uniworx/categories/workflows/de-de-formal.msg +++ /dev/null @@ -1,163 +0,0 @@ -WorkflowScopeKindGlobal: Systemweit -WorkflowScopeKindTerm: Pro Semester -WorkflowScopeKindSchool: Pro Institut -WorkflowScopeKindTermSchool: Pro Institut & Semester -WorkflowScopeKindCourse: Pro Veranstaltung -WorkflowDefinitionScope: Bereich -WorkflowDefinitionName !ident-ok: Name -WorkflowDefinitionDescriptions: Beschreibung -WorkflowDefinitionDescriptionsLanguageExists: Eine Beschreibung in dieser Sprache existiert bereits -WorkflowDefinitionGraph: Spezifikation -WorkflowDefinitionKeyDoesNotExist renderedCryptoID@Text: Referenziert ID existiert nicht: #{renderedCryptoID} -WorkflowDefinitionFiles: Dateien -WorkflowFileIdentDoesNotExist fileIdent@Text: Referenzierte Datei existiert nicht: #{fileIdent} -WorkflowUserDoesNotExist userIdent@Text: Referenzierter Benutzer existiert nicht: #{userIdent} -WorkflowDefinitionFileIdentExists: Eine Datei mit dieser ID existiert bereits -WorkflowDefinitionFileIdent: Dateireferenz -WorkflowDefinitionFile: Datei -WorkflowDefinitionCreated: Workflow-Definition angelegt -WorkflowDefinitionEdited: Workflow-Definition ersetzt -WorkflowDefinitionCollision: Es existiert bereits eine Workflow-Definition mit diesem Namen und Bereich -WorkflowDefinitionNewTitle: Workflow-Definition anlegen -WorkflowDefinitionEditTitle: Workflow-Definition Bearbeiten -WorkflowDefinitionInstanceCategory: Kategorie -WorkflowDefinitionWarningLinterIssuesMessage: Es sind Linter issues aufgetreten -WorkflowDefinitionWarningLinterIssues: Es sind folgende Linter issues aufgetreten: - -WorkflowDefinitionListTitle: Workflow-Definitionen -WorkflowDefinitionInstanceCount: Instanzen -WorkflowDefinitionWorkflowCount !ident-ok: Workflows -WorkflowDefinitionConcreteInstanceCount num@Int64: #{num} Instanzen -WorkflowDefinitionConcreteWorkflowCount num@Int64: #{num} Workflows -WorkflowDefinitionDeleteQuestion: Wollen Sie die unten aufgeführte Workflow-Definition wirklich löschen? -WorkflowDefinitionDeleted: Workflow-Definition gelöscht -WorkflowDefinitionInstantiateTitle: Workflow-Definition instanziieren -WorkflowDefinitionInstantiated: Instanz angelegt - -WorkflowScope: Bereich -WorkflowInstanceName !ident-ok: Name -WorkflowInstanceCategory: Kategorie -WorkflowInstanceCollision: Es existiert bereits eine Workflow-Instanz mit diesem Namen und Bereich -WorkflowInstanceListTitle: Workflow-Instanzen -WorkflowInstanceDescription: Instanz-Beschreibung -WorkflowInstanceDescriptions: Instanz-Beschreibung -WorkflowInstanceDescriptionsLanguageExists: Eine Instanz-Beschreibung in dieser Sprache existiert bereits -WorkflowInstanceCreated: Instanz angelegt -WorkflowInstanceDescriptionTitle: Instanz-Titel -WorkflowInstanceWorkflowCount !ident-ok: Workflows - -WorkflowInstanceInitiateSuccess: Workflow erfolgreich initiiert - -WorkflowDescriptionLanguage: Sprach-Code (RFC1766) -WorkflowDescriptionTitle: Titel -WorkflowDescription: Beschreibung - -GlobalWorkflowInstancesHeading: Workflows (Systemweit) -GlobalWorkflowInstancesTitle: Workflows (Systemweit) - -GlobalWorkflowInstanceInitiateHeading workflowInstanceTitle@Text: Workflow initiieren: #{workflowInstanceTitle} -GlobalWorkflowInstanceInitiateTitle: Workflow initiieren - -SchoolWorkflowInstancesHeading ssh@SchoolId !ident-ok: Workflows (#{ssh}) -SchoolWorkflowInstancesTitle ssh@SchoolId !ident-ok: Workflows (#{ssh}) - -SchoolWorkflowInstanceInitiateHeading ssh@SchoolId workflowInstanceTitle@Text: Workflow initiieren: #{ssh}, #{workflowInstanceTitle} -SchoolWorkflowInstanceInitiateTitle ssh@SchoolId: Workflow initiieren: #{ssh} - -WorkflowInstanceInitiateHeadingDisabled: Workflow initiieren -WorkflowInstanceInitiateTitleDisabled: Workflow initiieren - -WorkflowEdgeNumberedVariant edgeLabel@Text i@Natural: #{edgeLabel} (Variante #{i}) -WorkflowEdgeFormEdge: Aktion -WorkflowEdgeFormHiddenPayload i@Natural: Versteckter Datensatz #{i} -WorkflowEdgeFormPayloadOneFieldRequired: Es muss mindestens ein Feld pro Datensatz ausgefüllt werden -WorkflowEdgeFormPayloadOneFieldRequiredFor payloadDisplayLabel@Text: Es muss mindestens ein Feld für “#{payloadDisplayLabel}” ausgefüllt werden -WorkflowEdgeFormFieldNumberTooSmall minSci@Scientific: Zahl muss mindestens #{formatScientific Scientific.Generic Nothing minSci} sein -WorkflowEdgeFormFieldNumberTooLarge maxSci@Scientific: Zahl muss höchstens #{formatScientific Scientific.Generic Nothing maxSci} sein -WorkflowEdgeFormFieldUserNotFound: E-Mail Adresse konnte keinem/keiner Benutzer:in zugeordnet werden -WorkflowEdgeFormFieldMultipleNoneAdded: (Noch) keine Einträge -WorkflowEdgeFormFieldCaptureUserLabel: Aktuelle:r Benutzer:in - -WorkflowEdgeFormEnumFieldNothing: Keine Auswahl - -WorkflowEdgeFormFieldDayTooFarPast offset@Integer: Datum liegt zu weit in der Vergangenheit (maximal #{offset} Tage) -WorkflowEdgeFormFieldDayTooFarFuture offset@Integer: Datum liegt zu weit in der Zukunft (maximal #{offset} Tage) - -WorkflowEdgeFormFieldCaptureDateLabel: Aktuelles Datum -WorkflowEdgeFormFieldCaptureTimeLabel: Aktuelle Uhrzeit -WorkflowEdgeFormFieldCaptureDateTimeLabel: Aktuelles Datum/Zeit - -WorkflowWorkflowWorkflowHistoryHeading: Verlauf -WorkflowWorkflowWorkflowEdgeFormHeading: Aktion im Workflow auslösen -WorkflowWorkflowWorkflowEdgeSuccess: Aktion erfolgreich ausgelöst -WorkflowWorkflowWorkflowHistoryUserSelf: Sie selbst -WorkflowWorkflowWorkflowHistoryUserNotLoggedIn: Nicht eingeloggter Benutzer -WorkflowWorkflowWorkflowHistoryUserGone: Gelöschte:r Benutzer:in -WorkflowWorkflowWorkflowHistoryUserHidden: Versteckte:r Benutzer:in -WorkflowWorkflowWorkflowHistoryUserAutomatic: Automatisch -WorkflowWorkflowWorkflowHistoryActionAutomatic: Automatisch -WorkflowWorkflowWorkflowHistoryStateHidden: Versteckter Zustand -WorkflowWorkflowWorkflowHistoryActionLabel: Aktion -WorkflowWorkflowWorkflowHistoryFromLabel: Vorheriger Zustand -WorkflowWorkflowWorkflowHistoryToLabel: Neuer Zustand -WorkflowWorkflowWorkflowHistoryPayloadLabel: Datensatz-Änderungen -WorkflowWorkflowFilesArchiveName wwCID@CryptoFileNameWorkflowWorkflow wpl@WorkflowPayloadLabel stCID@CryptoUUIDWorkflowStateIndex !ident-ok: #{foldCase (toPathPiece wwCID)}-#{foldCase (toPathPiece stCID)}-#{foldCase (foldMap unidecode (toPathPiece wpl))}.zip -WorkflowWorkflowWorkflowStateHeading: Zustand/Daten -WorkflowWorkflowWorkflowPayloadHeading: Aktueller Datensatz -WorkflowWorkflowWorkflowStateStateLabel: Aktueller Zustand -WorkflowWorkflowWorkflowStateStateHidden: Versteckter Zustand -WorkflowWorkflowWorkflowHistoryLabelOthers: Aktionen Anderer -WorkflowWorkflowWorkflowHistoryLabelOwn: Eigene Aktionen - -WorkflowPayloadFiles: Datei(en) -WorkflowPayloadBoolTrue: Ja -WorkflowPayloadBoolFalse: Nein -WorkflowPayloadUserGone: Gelöschte:r Benutzer:in - -TopWorkflowInstancesHeading !ident-ok: Workflows -TopWorkflowInstancesTitle !ident-ok: Workflows - -GlobalWorkflowWorkflowWorkflowHeading workflowWorkflowId@CryptoFileNameWorkflowWorkflow !ident-ok: Workflow #{toPathPiece workflowWorkflowId} -GlobalWorkflowWorkflowWorkflowTitle workflowWorkflowId@CryptoFileNameWorkflowWorkflow !ident-ok: Workflow #{toPathPiece workflowWorkflowId} - -SchoolWorkflowWorkflowWorkflowHeading ssh@SchoolId workflowWorkflowId@CryptoFileNameWorkflowWorkflow !ident-ok: Workflow #{ssh}, #{toPathPiece workflowWorkflowId} -SchoolWorkflowWorkflowWorkflowTitle ssh@SchoolId workflowWorkflowId@CryptoFileNameWorkflowWorkflow !ident-ok: Workflow #{ssh}, #{toPathPiece workflowWorkflowId} - -WorkflowWorkflowListScopeTitle rScope@RouteWorkflowScope: Laufende Workflows - _{rScope} -WorkflowWorkflowListScopeHeading rScope@RouteWorkflowScope: Laufende Workflows (_{rScope}) -WorkflowWorkflowListInstanceTitle: Laufende Workflows für Instanz -WorkflowWorkflowListInstanceHeading: Laufende Workflows für Instanz -WorkflowWorkflowListNamedInstanceTitle rScope@RouteWorkflowScope wiTitle@Text: Laufende Workflows - _{rScope}, #{wiTitle} -WorkflowWorkflowListNamedInstanceHeading rScope@RouteWorkflowScope wiTitle@Text: Laufende Workflows (_{rScope}, #{wiTitle}) -WorkflowWorkflowListNamedInstanceTitleDisabled rScope@RouteWorkflowScope: Laufende Workflows - _{rScope} -WorkflowWorkflowListNamedInstanceHeadingDisabled rScope@RouteWorkflowScope: Laufende Workflows (_{rScope}) -WorkflowWorkflowListTopTitle: Laufende Workflows -WorkflowWorkflowListTopHeading: Laufende Workflows -AdminWorkflowWorkflowListTitle: Laufende Workflows -AdminWorkflowWorkflowListHeading: Laufende Workflows - -WorkflowWorkflowListNumber: Nummer -WorkflowWorkflowListScope: Bereich -WorkflowWorkflowListInstance: Instanz -WorkflowWorkflowListCurrentState: Aktueller Zustand -WorkflowWorkflowListLastActionTime: Zeitpunkt, letzte Aktion -WorkflowWorkflowListLastActionUser: Benutzer:in, letzte Aktion -WorkflowWorkflowListIsFinal: Abgeschlossen? - -WorkflowGraphFormUploadIsDirectory: Upload ist Verzeichnis -WorkflowGraphFormInvalidNumberOfFiles: Es muss genau eine Datei hochgeladen werden -WorkflowCourseOption tid@TermId ssh@SchoolId coursen@CourseName !ident-ok: #{tid} - #{ssh} - #{coursen} -YAMLFieldDecodeFailure yamlFailure@String: Konnte YAML nicht parsen: #{yamlFailure} - -WGFTextInput: Textfeld -WGFFileUpload: Dateifeld -WorkflowWorkflowListPersons: Beteiligte Benutzer - -BtnWorkflowInstanceUpdate !ident-ok: Update -WorkflowInstanceUpdateNoActions: Keine Updates verfügbar -WorkflowInstanceUpdateUpdatedGraph: Definitions-Update erfolgreich angewandt -WorkflowInstanceUpdateUpdatedCategory: Kategorie-Update erfolgreich angewandt -WorkflowInstanceUpdateDeletedDescriptionLanguage lang@Lang: Beschreibung/Titel in Sprache „#{lang}“ gelöscht -WorkflowInstanceUpdateUpdatedDescriptionLanguage lang@Lang: Beschreibung/Titel-Update für Sprache „#{lang}“ angewandt - -WorkflowsDisabled: Workflows sind temporär deaktiviert. \ No newline at end of file diff --git a/messages/uniworx/categories/workflows/en-eu.msg b/messages/uniworx/categories/workflows/en-eu.msg deleted file mode 100644 index 2dcc37915..000000000 --- a/messages/uniworx/categories/workflows/en-eu.msg +++ /dev/null @@ -1,163 +0,0 @@ -WorkflowScopeKindGlobal: Global -WorkflowScopeKindTerm: Per term -WorkflowScopeKindSchool: Per school -WorkflowScopeKindTermSchool: Per school & term -WorkflowScopeKindCourse: Per course -WorkflowDefinitionScope: Scope -WorkflowDefinitionName: Name -WorkflowDefinitionDescriptions: Description -WorkflowDefinitionDescriptionsLanguageExists: A description in this language already exists -WorkflowDescriptionLanguage: Language code (RFC1766) -WorkflowDescriptionTitle: Title -WorkflowDescription: Description - -GlobalWorkflowInstancesHeading: Workflows (system-wide) -GlobalWorkflowInstancesTitle: Workflows (system-wide) - -GlobalWorkflowInstanceInitiateHeading workflowInstanceTitle: Initiate workflow: #{workflowInstanceTitle} -GlobalWorkflowInstanceInitiateTitle: Initiate workflow - -SchoolWorkflowInstancesHeading ssh: Workflows (#{ssh}) -SchoolWorkflowInstancesTitle ssh: Workflows (#{ssh}) - -SchoolWorkflowInstanceInitiateHeading ssh workflowInstanceTitle: Initiate workflow: #{ssh}, #{workflowInstanceTitle} -SchoolWorkflowInstanceInitiateTitle ssh: Initiate workflow: #{ssh} - -WorkflowInstanceInitiateHeadingDisabled: Initiate Workflow -WorkflowInstanceInitiateTitleDisabled: Initiate Workflow - -WorkflowEdgeNumberedVariant edgeLabel i: #{edgeLabel} (variant #{i}) -WorkflowEdgeFormEdge: Action -WorkflowEdgeFormHiddenPayload i: Hidden dataset #{i} -WorkflowEdgeFormPayloadOneFieldRequired: At least one field per dataset needs to be filled -WorkflowEdgeFormPayloadOneFieldRequiredFor payloadDisplayLabel: At least one field for “#{payloadDisplayLabel}” needs to be filled -WorkflowEdgeFormFieldNumberTooSmall minSci: Number must be at least #{formatScientific Scientific.Generic Nothing minSci} -WorkflowEdgeFormFieldNumberTooLarge maxSci: Number must be at most #{formatScientific Scientific.Generic Nothing maxSci} -WorkflowEdgeFormFieldUserNotFound: Email could not be resolved to an user -WorkflowEdgeFormFieldMultipleNoneAdded: No entries (yet) -WorkflowEdgeFormFieldCaptureUserLabel: Current user - -WorkflowEdgeFormEnumFieldNothing: No selection - -WorkflowEdgeFormFieldDayTooFarPast offset: Date is too far in the past (maximum #{offset} days) -WorkflowEdgeFormFieldDayTooFarFuture offset: Date is too far in the future (maximum #{offset} days) - -WorkflowEdgeFormFieldCaptureDateLabel: Current date -WorkflowEdgeFormFieldCaptureTimeLabel: Current time -WorkflowEdgeFormFieldCaptureDateTimeLabel: Current date/time - -WorkflowWorkflowWorkflowHistoryHeading: History -WorkflowWorkflowWorkflowEdgeFormHeading: Trigger action within workflow -WorkflowWorkflowWorkflowEdgeSuccess: Successfully triggered action -WorkflowWorkflowWorkflowHistoryUserSelf: You -WorkflowWorkflowWorkflowHistoryUserNotLoggedIn: Not-logged in user -WorkflowWorkflowWorkflowHistoryUserGone: Deleted user -WorkflowWorkflowWorkflowHistoryUserHidden: Hidden user -WorkflowWorkflowWorkflowHistoryUserAutomatic: Automatic -WorkflowWorkflowWorkflowHistoryActionAutomatic: Automatic -WorkflowWorkflowWorkflowHistoryStateHidden: Hidden state -WorkflowWorkflowWorkflowHistoryActionLabel: Action -WorkflowWorkflowWorkflowHistoryFromLabel: Previous state -WorkflowWorkflowWorkflowHistoryToLabel: New state -WorkflowWorkflowWorkflowHistoryPayloadLabel: Data changes -WorkflowWorkflowFilesArchiveName wwCID wpl stCID: #{foldCase (toPathPiece wwCID)}-#{foldCase (toPathPiece stCID)}-#{foldCase (foldMap unidecode (toPathPiece wpl))}.zip -WorkflowWorkflowWorkflowStateHeading: State/Data -WorkflowWorkflowWorkflowPayloadHeading: Current data -WorkflowWorkflowWorkflowStateStateLabel: Current state -WorkflowWorkflowWorkflowStateStateHidden: Hidden state -WorkflowWorkflowWorkflowHistoryLabelOthers: Other users' actions -WorkflowWorkflowWorkflowHistoryLabelOwn: Your actions - -WorkflowPayloadFiles: File(s) -WorkflowPayloadBoolTrue: Yes -WorkflowPayloadBoolFalse: No -WorkflowPayloadUserGone: Deleted user - -TopWorkflowInstancesHeading: Workflows -TopWorkflowInstancesTitle: Workflows - -GlobalWorkflowWorkflowWorkflowHeading workflowWorkflowId: Workflow #{toPathPiece workflowWorkflowId} -GlobalWorkflowWorkflowWorkflowTitle workflowWorkflowId: Workflow #{toPathPiece workflowWorkflowId} - -SchoolWorkflowWorkflowWorkflowHeading ssh workflowWorkflowId: Workflow #{ssh}, #{toPathPiece workflowWorkflowId} -SchoolWorkflowWorkflowWorkflowTitle ssh workflowWorkflowId: Workflow #{ssh}, #{toPathPiece workflowWorkflowId} - -WorkflowWorkflowListScopeTitle rScope: Running workflows - _{rScope} -WorkflowWorkflowListScopeHeading rScope: Running workflows (_{rScope}) -WorkflowWorkflowListInstanceTitle: Running workflows for an instance -WorkflowWorkflowListInstanceHeading: Running workflows for an instance -WorkflowWorkflowListNamedInstanceTitle rScope wiTitle: Running workflows - _{rScope}, #{wiTitle} -WorkflowWorkflowListNamedInstanceHeading rScope wiTitle: Running workflows (_{rScope}, #{wiTitle}) -WorkflowWorkflowListNamedInstanceTitleDisabled rScope: Running Workflows - _{rScope} -WorkflowWorkflowListNamedInstanceHeadingDisabled rScope: Running Workflows (_{rScope}) -WorkflowWorkflowListTopTitle: Running workflows -WorkflowWorkflowListTopHeading: Running workflows -AdminWorkflowWorkflowListTitle: Running workflows -AdminWorkflowWorkflowListHeading: Running workflows - -WorkflowWorkflowListNumber: Number -WorkflowWorkflowListScope: Scope -WorkflowWorkflowListInstance: Instance -WorkflowWorkflowListCurrentState: Current state -WorkflowWorkflowListLastActionTime: Timestamp of last action -WorkflowWorkflowListLastActionUser: User for last action -WorkflowWorkflowListIsFinal: Finalised? - -WorkflowDefinitionGraph: Specification -WorkflowDefinitionKeyDoesNotExist renderedCryptoID: Referenced id does not exist: #{renderedCryptoID} -WorkflowDefinitionFiles: Files -WorkflowFileIdentDoesNotExist fileIdent: Referenced file does not exist: #{fileIdent} -WorkflowUserDoesNotExist userIdent: Referenced user does not exist: #{userIdent} -WorkflowDefinitionFileIdentExists: A file with the given reference id already exists -WorkflowDefinitionFileIdent: File reference id -WorkflowDefinitionFile: File -WorkflowDefinitionCreated: Successfully created workflow definition -WorkflowDefinitionEdited: Successfully replaced workflow definition -WorkflowDefinitionCollision: A workflow definition with this name already exists -WorkflowDefinitionNewTitle: Create new workflow definition -WorkflowDefinitionEditTitle: Edit workflow definition -WorkflowDefinitionInstanceCategory: Category -WorkflowDefinitionWarningLinterIssuesMessage: There were linter issues -WorkflowDefinitionWarningLinterIssues: There are the following linter issues: - -WorkflowDefinitionListTitle: Workflow definitions -WorkflowDefinitionInstanceCount: Instances -WorkflowDefinitionWorkflowCount: Workflows -WorkflowDefinitionConcreteInstanceCount num: #{num} instances -WorkflowDefinitionConcreteWorkflowCount num: #{num} workflows -WorkflowDefinitionDeleteQuestion: Do you really want to delete the workflow definition listed below? -WorkflowDefinitionDeleted: Successfully deleted workflow definition -WorkflowDefinitionInstantiateTitle: Instantiate workflow definition -WorkflowDefinitionInstantiated: Instance created - -WorkflowScope: Scope -WorkflowInstanceName: Name -WorkflowInstanceCategory: Category -WorkflowInstanceCollision: There already exists a workflow instance with the given name and category -WorkflowInstanceListTitle: Workflow instances -WorkflowInstanceDescription: Instance description -WorkflowInstanceDescriptions: Instance description -WorkflowInstanceDescriptionsLanguageExists: A instance description in the given language already exists -WorkflowInstanceCreated: Instance created -WorkflowInstanceDescriptionTitle: Instance title -WorkflowInstanceWorkflowCount: Workflows - -WorkflowInstanceInitiateSuccess: Successfully initiated workflow - -WorkflowGraphFormUploadIsDirectory: Upload is a directory -WorkflowGraphFormInvalidNumberOfFiles: You need to upload exactly one file -WorkflowCourseOption tid ssh coursen: #{tid} - #{ssh} - #{coursen} -YAMLFieldDecodeFailure yamlFailure: Could not parse YAML: #{yamlFailure} - -WGFTextInput: Text field -WGFFileUpload: File field -WorkflowWorkflowListPersons: Involved users - -BtnWorkflowInstanceUpdate: Update -WorkflowInstanceUpdateNoActions: No updates available -WorkflowInstanceUpdateUpdatedGraph: Successfully applied updated definition -WorkflowInstanceUpdateUpdatedCategory: Successfully applied updated category -WorkflowInstanceUpdateDeletedDescriptionLanguage lang: Successfully deleted description/title for language “#{lang}” -WorkflowInstanceUpdateUpdatedDescriptionLanguage lang: Successfully applied updated description/title for language “#{lang}” - -WorkflowsDisabled: Workflows are temporarily disabled. diff --git a/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg b/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg index c79919c59..579243035 100644 --- a/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg +++ b/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg @@ -80,31 +80,6 @@ BreadcrumbFaq !ident-ok: FAQ BreadcrumbSheetPersonalisedFiles: Personalisierte Dateien herunterladen BreadcrumbCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen BreadcrumbAdminCrontab !ident-ok: Crontab -BreadcrumbAdminWorkflowDefinitionList: Workflow-Definitionen -BreadcrumbAdminWorkflowDefinitionNew: Neue Workflow-Definition -BreadcrumbAdminWorkflowDefinitionEdit renderedWorkflowScope'@Text wfdn@WorkflowDefinitionName !ident-ok: #{wfdn} (#{renderedWorkflowScope'}) -BreadcrumbAdminWorkflowDefinitionDelete: Löschen -BreadcrumbAdminWorkflowDefinitionInstantiate: Instanziieren -BreadcrumbAdminWorkflowInstanceList: Workflow-Instanzen -BreadcrumbAdminWorkflowInstanceNew: Neue Workflow-Instanz -BreadcrumbAdminWorkflowInstanceEdit: Instanz bearbeiten -BreadcrumbAdminWorkflowWorkflowList: Initiierte Workflows -BreadcrumbAdminWorkflowWorkflowNew: Workflow initiieren -BreadcrumbWorkflowInstanceEdit win@WorkflowInstanceName !ident-ok: #{win} -BreadcrumbWorkflowInstanceDelete: Löschen -BreadcrumbWorkflowInstanceWorkflowList: Laufende Workflows -BreadcrumbWorkflowInstanceInitiate: Workflow starten -BreadcrumbWorkflowInstanceList !ident-ok: Workflows -BreadcrumbWorkflowInstanceNew: Neuer Workflow -BreadcrumbWorkflowInstanceUpdate !ident-ok: Update -BreadcrumbWorkflowWorkflowList: Laufende Workflows -BreadcrumbWorkflowWorkflow workflow@CryptoFileNameWorkflowWorkflow !ident-ok: #{toPathPiece workflow} -BreadcrumbWorkflowWorkflowFiles: Dateien -BreadcrumbWorkflowWorkflowEdit: Editieren -BreadcrumbWorkflowWorkflowDelete: Löschen -BreadcrumbGlobalWorkflowInstanceList: Systemweite Workflows -BreadcrumbTopWorkflowInstanceList !ident-ok: Workflows -BreadcrumbTopWorkflowWorkflowList: Laufende Workflows BreadcrumbError: Fehler BreadcrumbUpload !ident-ok: Upload BreadcrumbUserAdd: Benutzer:in anlegen diff --git a/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg b/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg index dfb3eb21a..18843de27 100644 --- a/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg +++ b/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg @@ -80,31 +80,6 @@ BreadcrumbFaq: FAQ BreadcrumbSheetPersonalisedFiles: Download personalised sheet files BreadcrumbCourseSheetPersonalisedFiles: Download template for personalised sheet files BreadcrumbAdminCrontab: Crontab -BreadcrumbAdminWorkflowDefinitionList: Workflow definitions -BreadcrumbAdminWorkflowDefinitionNew: New workflow definition -BreadcrumbAdminWorkflowDefinitionEdit renderedWorkflowScope' wfdn: #{wfdn} (#{renderedWorkflowScope'}) -BreadcrumbAdminWorkflowDefinitionDelete: Delete -BreadcrumbAdminWorkflowDefinitionInstantiate: Instantiate -BreadcrumbAdminWorkflowInstanceList: Workflow instances -BreadcrumbAdminWorkflowInstanceNew: New workflow-instance -BreadcrumbAdminWorkflowInstanceEdit: Edit instance -BreadcrumbAdminWorkflowWorkflowList: Initiated workflows -BreadcrumbAdminWorkflowWorkflowNew: Initiate workflow -BreadcrumbWorkflowInstanceEdit win: #{win} -BreadcrumbWorkflowInstanceDelete: Delete -BreadcrumbWorkflowInstanceWorkflowList: Running workflows -BreadcrumbWorkflowInstanceInitiate: Start workflow -BreadcrumbWorkflowInstanceList: Workflows -BreadcrumbWorkflowInstanceNew: New workflow -BreadcrumbWorkflowInstanceUpdate !ident-ok: Update -BreadcrumbWorkflowWorkflowList: Running workflows -BreadcrumbWorkflowWorkflow workflow: #{toPathPiece workflow} -BreadcrumbWorkflowWorkflowFiles: Files -BreadcrumbWorkflowWorkflowEdit: Edit -BreadcrumbWorkflowWorkflowDelete: Delete -BreadcrumbGlobalWorkflowInstanceList: System-wide workflows -BreadcrumbTopWorkflowInstanceList: Workflows -BreadcrumbTopWorkflowWorkflowList: Running workflows BreadcrumbError: Error BreadcrumbUpload: Upload BreadcrumbUserAdd: Add user diff --git a/messages/uniworx/utils/navigation/menu/de-de-formal.msg b/messages/uniworx/utils/navigation/menu/de-de-formal.msg index 69bc2b39d..c0ceceb83 100644 --- a/messages/uniworx/utils/navigation/menu/de-de-formal.msg +++ b/messages/uniworx/utils/navigation/menu/de-de-formal.msg @@ -115,24 +115,6 @@ MenuFaq !ident-ok: FAQ MenuSheetPersonalisedFiles: Personalisierte Dateien herunterladen MenuCourseSheetPersonalisedFiles: Vorlage für personalisierte Übungsblatt-Dateien herunterladen MenuAdminCrontab !ident-ok: Crontab -MenuAdminWorkflowDefinitionList !ident-ok: Workflows -MenuAdminWorkflowDefinitionNew: Neue Workflow-Definition -MenuAdminWorkflowDefinitionDelete: Löschen -MenuAdminWorkflowInstanceList: Workflow-Instanzen -MenuAdminWorkflowInstanceNew: Neue Workflow-Instanz -MenuAdminWorkflowDefinitionInstantiate: Instanziieren -MenuWorkflowInstanceUpdate !ident-ok: Update -MenuWorkflowInstanceDelete: Löschen -MenuWorkflowInstanceWorkflows: Laufende Workflows -MenuWorkflowInstanceInitiate: Workflow starten -MenuWorkflowInstanceEdit: Bearbeiten -MenuWorkflowWorkflowList: Laufende Workflows -MenuWorkflowWorkflowEdit: Editieren -MenuWorkflowWorkflowDelete: Löschen -MenuGlobalWorkflowInstanceList: Systemweite Workflows -MenuTopWorkflowInstanceList !ident-ok: Workflows -MenuTopWorkflowWorkflowList: Laufende Workflows -MenuTopWorkflowWorkflowListHeader !ident-ok: Workflows MenuGlossary: Begriffsverzeichnis MenuVersion: Versionsgeschichte MenuCourseNewsNew: Neue Kursnachricht diff --git a/messages/uniworx/utils/navigation/menu/en-eu.msg b/messages/uniworx/utils/navigation/menu/en-eu.msg index 3a4a45a16..82cedef44 100644 --- a/messages/uniworx/utils/navigation/menu/en-eu.msg +++ b/messages/uniworx/utils/navigation/menu/en-eu.msg @@ -116,24 +116,6 @@ MenuFaq: FAQ MenuSheetPersonalisedFiles: Download personalised sheet files MenuCourseSheetPersonalisedFiles: Download template for personalised sheet files MenuAdminCrontab: Crontab -MenuAdminWorkflowDefinitionList: Workflows -MenuAdminWorkflowDefinitionNew: New workflow definition -MenuAdminWorkflowDefinitionDelete: Delete -MenuAdminWorkflowInstanceList: Workflow instances -MenuAdminWorkflowInstanceNew: New workflow instance -MenuAdminWorkflowDefinitionInstantiate: Instantiate -MenuWorkflowInstanceUpdate !ident-ok: Update -MenuWorkflowInstanceDelete: Delete -MenuWorkflowInstanceWorkflows: Running workflows -MenuWorkflowInstanceInitiate: Start workflow -MenuWorkflowInstanceEdit: Edit -MenuWorkflowWorkflowList: Running workflows -MenuWorkflowWorkflowEdit: Edit -MenuWorkflowWorkflowDelete: Delete -MenuGlobalWorkflowInstanceList: System-wide workflows -MenuTopWorkflowInstanceList: Workflows -MenuTopWorkflowWorkflowList: Running workflows -MenuTopWorkflowWorkflowListHeader: Workflows MenuGlossary: Glossary MenuVersion: Version history MenuCourseNewsNew: Add course news diff --git a/models/workflows.model b/models/workflows.model deleted file mode 100644 index d20d4e040..000000000 --- a/models/workflows.model +++ /dev/null @@ -1,53 +0,0 @@ -SharedWorkflowGraph - hash WorkflowGraphReference - graph (WorkflowGraph FileReference SqlBackendKey) -- UserId - Primary hash - deriving Generic - -WorkflowDefinition - graph SharedWorkflowGraphId - scope WorkflowScope' - name WorkflowDefinitionName - instanceCategory WorkflowInstanceCategory Maybe - UniqueWorkflowDefinition name scope - deriving Generic - -WorkflowDefinitionDescription - definition WorkflowDefinitionId OnDeleteCascade OnUpdateCascade - language Lang - title Text - description StoredMarkup Maybe - UniqueWorkflowDefinitionDescription definition language - deriving Generic - -WorkflowDefinitionInstanceDescription - definition WorkflowDefinitionId OnDeleteCascade OnUpdateCascade - language Lang - title Text - description StoredMarkup Maybe - UniqueWorkflowDefinitionInstanceDescription definition language - deriving Generic - -WorkflowInstance - definition WorkflowDefinitionId Maybe OnDeleteSetNull OnUpdateCascade - graph SharedWorkflowGraphId - scope (WorkflowScope TermIdentifier SchoolShorthand SqlBackendKey) -- TermId, SchoolId, CourseId - name WorkflowInstanceName - category WorkflowInstanceCategory Maybe - UniqueWorkflowInstance name scope - deriving Generic - -WorkflowInstanceDescription - instance WorkflowInstanceId - language Lang - title Text - description StoredMarkup Maybe - UniqueWorkflowInstanceDescription instance language - deriving Generic - -WorkflowWorkflow - instance WorkflowInstanceId Maybe - scope (WorkflowScope TermIdentifier SchoolShorthand SqlBackendKey) -- TermId, SchoolId, CourseId - graph SharedWorkflowGraphId - state (WorkflowState FileReference SqlBackendKey) -- UserId - deriving Generic diff --git a/nix/uniworx/backend.nix b/nix/uniworx/backend.nix index fc1ceb525..1eb7bbee9 100644 --- a/nix/uniworx/backend.nix +++ b/nix/uniworx/backend.nix @@ -66,8 +66,7 @@ in { components.library.build-tools = with final.pkgs; [ llvm_9 ]; components.exes.uniworx.build-tools = with final.pkgs; [ llvm_9 ]; components.exes.uniworxdb.build-tools = with final.pkgs; [ llvm_9 ]; - components.exes.uniworxload.build-tools = with final.pkgs; [ llvm_9 ]; - components.exes.uniworx-wflint.build-tools = with final.pkgs; [ llvm_9 ]; + components.exes.uniworxload.build-tools = with final.pkgs; [ llvm_9 ]; components.tests.yesod = { build-tools = with final.pkgs; [ llvm_9 final.uniworx.hsPkgs.hspec-discover ]; testWrapper = diff --git a/package-lock.json b/package-lock.json index d629da9f7..1d8aad042 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4831,9 +4831,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001137", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001137.tgz", - "integrity": "sha512-54xKQZTqZrKVHmVz0+UvdZR6kQc7pJDgfhsMYDG19ID1BWoNnDMFm5Q3uSBSU401pBvKYMsHAt9qhEDcxmk8aw==", + "version": "1.0.30001257", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001257.tgz", + "integrity": "sha512-JN49KplOgHSXpIsVSF+LUyhD8PUp6xPpAXeRrrcBh4KBeP7W864jHn6RvzJgDlrReyeVjMFJL3PLpPvKIxlIHA==", "dev": true }, "caseless": { diff --git a/package.yaml b/package.yaml index 5abd5099b..aa1d77c10 100644 --- a/package.yaml +++ b/package.yaml @@ -300,20 +300,6 @@ executables: when: - condition: flag(library-only) buildable: false - uniworx-wflint: - main: WFLint.hs - ghc-options: - - -main-is WFLint - dependencies: - - base - - uniworx - - bytestring - - yaml - other-modules: [] - source-dirs: wflint - when: - - condition: flag(library-only) - buildable: false tests: yesod: main: Main.hs diff --git a/routes b/routes index c7299e84c..cfefc9671 100644 --- a/routes +++ b/routes @@ -60,37 +60,6 @@ /admin/tokens AdminTokensR GET POST /admin/crontab AdminCrontabR GET -/admin/workflows/definitions AdminWorkflowDefinitionListR GET -/admin/workflows/definitions/new AdminWorkflowDefinitionNewR GET POST -/admin/workflows/definitions/#WorkflowScope'/#WorkflowDefinitionName AdminWorkflowDefinitionR: - /edit AWDEditR GET POST - /delete AWDDeleteR GET POST - /instantiate AWDInstantiateR GET POST -/admin/workflows/instances AdminWorkflowInstanceListR GET -/admin/workflows/instances/new AdminWorkflowInstanceNewR GET POST -/admin/workflows/instances/#CryptoUUIDWorkflowInstance AdminWorkflowInstanceR: - /edit AWIEditR GET POST -/admin/workflows/workflows AdminWorkflowWorkflowListR GET -/admin/workflows/workflows/new AdminWorkflowWorkflowNewR GET POST - -/global-workflows/instances GlobalWorkflowInstanceListR GET !free -/global-workflows/instances/new GlobalWorkflowInstanceNewR GET POST -/global-workflows/instances/#WorkflowInstanceName GlobalWorkflowInstanceR: - /edit GWIEditR GET POST - /delete GWIDeleteR GET POST - /workflows GWIWorkflowsR GET !¬empty - /initiate GWIInitiateR GET POST !workflow - /update GWIUpdateR POST -/global-workflows GlobalWorkflowWorkflowListR GET !free -!/global-workflows/#CryptoFileNameWorkflowWorkflow GlobalWorkflowWorkflowR: - / GWWWorkflowR GET POST !workflow - /files/#WorkflowPayloadLabel/#CryptoUUIDWorkflowStateIndex GWWFilesR GET !workflow - /edit GWWEditR GET POST - /delete GWWDeleteR GET POST - -/workflow-instances TopWorkflowInstanceListR GET !free -/workflows TopWorkflowWorkflowListR GET !free - /health HealthR GET !free /instance InstanceR GET !free /info InfoR GET !free @@ -135,25 +104,12 @@ !/term/#TermId TermCourseListR GET !free !/term/#TermId/#SchoolId TermSchoolCourseListR GET !free + /school SchoolListR GET !/school/new SchoolNewR GET POST /school/#SchoolId SchoolR: / SchoolEditR GET POST - - /workflows/instances SchoolWorkflowInstanceListR GET !free - /workflows/instances/new SchoolWorkflowInstanceNewR GET POST - /workflows/instances/#WorkflowInstanceName SchoolWorkflowInstanceR: - /edit SWIEditR GET POST - /delete SWIDeleteR GET POST - /workflows SWIWorkflowsR GET !¬empty - /initiate SWIInitiateR GET POST !workflow - /update SWIUpdateR POST - /workflows SchoolWorkflowWorkflowListR GET !free - !/workflows/#CryptoFileNameWorkflowWorkflow SchoolWorkflowWorkflowR: - / SWWWorkflowR GET POST !workflow - /files/#WorkflowPayloadLabel/#CryptoUUIDWorkflowStateIndex SWWFilesR GET !workflow - /edit SWWEditR GET POST - /delete SWWDeleteR GET POST + /allocation/ AllocationListR GET !free !/allocation/new AllocationNewR GET POST !allocation-admin diff --git a/src/Application.hs b/src/Application.hs index 7d02e6009..c0f54303f 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -1,5 +1,5 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} - + module Application ( getAppSettings, getAppDevSettings , appMain @@ -143,7 +143,6 @@ import Handler.Metrics import Handler.ExternalExam import Handler.Participants import Handler.StorageKey -import Handler.Workflow import Handler.Error import Handler.Upload diff --git a/src/CryptoID.hs b/src/CryptoID.hs index be3e30c80..c74ccf409 100644 --- a/src/CryptoID.hs +++ b/src/CryptoID.hs @@ -53,15 +53,10 @@ decCryptoIDs [ ''SubmissionId , ''CourseEventId , ''TutorialId , ''ExternalExamId - , ''WorkflowInstanceId - , ''WorkflowWorkflowId , ''MaterialFileId , ''AllocationMatchingId ] -type instance CryptoIDNamespace a WorkflowStateIndex = "WorkflowStateIndex" -type CryptoUUIDWorkflowStateIndex = CryptoUUID WorkflowStateIndex - decCryptoIDKeySize -- CryptoIDNamespace (CI FilePath) SubmissionId ~ "Submission" @@ -99,22 +94,3 @@ instance {-# OVERLAPS #-} FromJSONKey (E.CryptoID "User" (CI FilePath)) where fromJSONKey = FromJSONKeyTextParser $ maybe (fail "Could not parse CryptoFileNameUser") return . fromPathPiece instance {-# OVERLAPS #-} ToMarkup (E.CryptoID "User" (CI FilePath)) where toMarkup = toMarkup . toPathPiece - - --- CryptoIDNamespace (CI FilePath) WorkflowWorkflowId ~ "WorkflowWorkflow" -instance {-# OVERLAPS #-} PathPiece (E.CryptoID "WorkflowWorkflow" (CI FilePath)) where - fromPathPiece (Text.unpack -> piece) = do - piece' <- (stripPrefix `on` map CI.mk) "uww" piece - return . CryptoID . CI.mk $ map CI.original piece' - toPathPiece = Text.pack . ("uww" <>) . CI.foldedCase . ciphertext - -instance {-# OVERLAPS #-} ToJSON (E.CryptoID "WorkflowWorkflow" (CI FilePath)) where - toJSON = String . toPathPiece -instance {-# OVERLAPS #-} ToJSONKey (E.CryptoID "WorkflowWorkflow" (CI FilePath)) where - toJSONKey = ToJSONKeyText toPathPiece (text . toPathPiece) -instance {-# OVERLAPS #-} FromJSON (E.CryptoID "WorkflowWorkflow" (CI FilePath)) where - parseJSON = withText "CryptoFileNameWorkflowWorkflow" $ maybe (fail "Could not parse CryptoFileNameWorkflowWorkflow") return . fromPathPiece -instance {-# OVERLAPS #-} FromJSONKey (E.CryptoID "WorkflowWorkflow" (CI FilePath)) where - fromJSONKey = FromJSONKeyTextParser $ maybe (fail "Could not parse CryptoFileNameWorkflowWorkflow") return . fromPathPiece -instance {-# OVERLAPS #-} ToMarkup (E.CryptoID "WorkflowWorkflow" (CI FilePath)) where - toMarkup = toMarkup . toPathPiece diff --git a/src/Foundation/Authorization.hs b/src/Foundation/Authorization.hs index 042dcc374..159f93275 100644 --- a/src/Foundation/Authorization.hs +++ b/src/Foundation/Authorization.hs @@ -14,9 +14,6 @@ module Foundation.Authorization , BearerAuthSite, MonadAP , routeAuthTags , orAR, andAR, notAR, trueAR, falseAR - , evalWorkflowRoleFor, evalWorkflowRoleFor' - , hasWorkflowRole - , mayViewWorkflowAction, mayViewWorkflowAction' , authoritiveApproot , AuthorizationCacheKey(..) ) where @@ -31,12 +28,10 @@ import Foundation.DB import Handler.Utils.ExamOffice.Exam import Handler.Utils.ExamOffice.ExternalExam -import Handler.Utils.Workflow.CanonicalRoute import Handler.Utils.Memcached import Handler.Utils.I18n import Handler.Utils.Routes import Utils.Course (courseIsVisible) -import Utils.Workflow import Utils.Metrics (observeAuthTagEvaluation, AuthTagEvalOutcome(..)) import qualified Data.Set as Set @@ -45,8 +40,8 @@ import qualified Data.HashSet as HashSet import qualified Data.Map as Map import Data.Map ((!?)) import qualified Data.Text as Text -import Data.List (findIndex, inits) -import Data.Semigroup (Last(..)) +import Data.List (findIndex) +-- import Data.Semigroup (Last(..)) import qualified Database.Esqueleto.Legacy as E import qualified Database.Esqueleto.Utils as E @@ -56,14 +51,14 @@ import Control.Monad.Memo.Class (MonadMemo(..), for4) import Data.Aeson.Lens hiding (_Value, key) -import qualified Data.Conduit.Combinators as C +-- import qualified Data.Conduit.Combinators as C import qualified Data.Binary as Binary import GHC.TypeLits (TypeError) import qualified GHC.TypeLits as TypeError (ErrorMessage(..)) -import Utils.VolatileClusterSettings +-- import Utils.VolatileClusterSettings type BearerAuthSite site @@ -466,17 +461,8 @@ maybeCurrentBearerRestrictions = runMaybeT $ do route <- MaybeT getCurrentRoute hoistMaybe $ bearer ^? _bearerRestrictionIx route -workflowsEnabledAuth :: (MonadHandler m, HandlerSite m ~ UniWorX) - => m AuthResult - -> m AuthResult -workflowsEnabledAuth = volatileBool clusterVolatileWorkflowsEnabled (unauthorizedI MsgWorkflowsDisabled) - data AuthorizationCacheKey - = AuthCacheWorkflowWorkflowEdgeActors CryptoFileNameWorkflowWorkflow - | AuthCacheWorkflowWorkflowViewers CryptoFileNameWorkflowWorkflow - | AuthCacheWorkflowInstanceInitiators WorkflowInstanceName RouteWorkflowScope - | AuthCacheWorkflowInstanceWorkflowViewers WorkflowInstanceName RouteWorkflowScope - | AuthCacheSchoolFunctionList SchoolFunction | AuthCacheSystemFunctionList SystemFunction + = AuthCacheSchoolFunctionList SchoolFunction | AuthCacheSystemFunctionList SystemFunction | AuthCacheLecturerList | AuthCacheExternalExamStaffList | AuthCacheCorrectorList | AuthCacheExamCorrectorList | AuthCacheTutorList | AuthCacheSubmissionGroupUserList | AuthCacheCourseRegisteredList TermId SchoolId CourseShorthand | AuthCacheVisibleSystemMessages @@ -1563,69 +1549,27 @@ tagAccessPredicate AuthRegisterGroup = APDB $ \_ _ mAuthId route _ -> case route guard $ not hasOther return Authorized r -> $unsupportedAuthPredicate AuthRegisterGroup r -tagAccessPredicate AuthEmpty = APDB $ \evalCtx eval' mAuthId route _ -> do - mr <- getMsgRenderer - let orAR', _andAR' :: forall m'. Monad m' => m' AuthResult -> m' AuthResult -> m' AuthResult - orAR' = shortCircuitM (is _Authorized) (orAR mr) - _andAR' = shortCircuitM (is _Unauthorized) (andAR mr) - - workflowInstanceWorkflowsEmpty rScope win = workflowsEnabledAuth $ selectLanguageI18n <=< $cachedHereBinary (evalCtx, mAuthId, route) . maybeT (unauthorizedI18n MsgUnauthorizedWorkflowWorkflowsNotEmpty) $ do - roles <- memcacheAuth' (Right diffDay) (AuthCacheWorkflowInstanceWorkflowViewers win rScope) $ do - scope <- fromRouteWorkflowScope rScope - let dbScope = scope ^. _DBWorkflowScope - getWorkflowWorkflows = E.selectSource . E.from $ \(workflowWorkflow `E.InnerJoin` workflowInstance) -> do - E.on $ workflowWorkflow E.^. WorkflowWorkflowInstance E.==. E.just (workflowInstance E.^. WorkflowInstanceId) - E.where_ $ workflowInstance E.^. WorkflowInstanceName E.==. E.val win - E.&&. workflowInstance E.^. WorkflowInstanceScope E.==. E.val dbScope - return workflowWorkflow - workflowRoles (Entity wwId WorkflowWorkflow{..}) = do - wwGraph <- getSharedIdWorkflowGraph workflowWorkflowGraph - let - nodeViewers = do - WorkflowAction{..} <- otoList workflowWorkflowState - (node, WGN{..}) <- itoListOf (_wgNodes . ifolded) wwGraph - guard $ node == wpTo - WorkflowNodeView{..} <- hoistMaybe wgnViewers - return $ toNullable wnvViewers - payloadViewers = do - (prevActs, act) <- zip (inits $ otoList workflowWorkflowState) $ otoList workflowWorkflowState - prevAct <- hoistMaybe $ prevActs ^? _last - payload <- Map.keys $ wpPayload act - guard $ Map.lookup payload (workflowStateCurrentPayloads prevActs) /= Map.lookup payload (wpPayload act) - fmap (toNullable . wpvViewers) . hoistMaybe $ Map.lookup payload . wgnPayloadView =<< Map.lookup (wpTo prevAct) (wgNodes wwGraph) - return . Set.mapMonotonic ((workflowWorkflowScope, wwId), ) $ fold nodeViewers <> fold payloadViewers - lift . runConduit $ getWorkflowWorkflows .| C.foldMapM workflowRoles - let - evalRole ((wwScope, wwId), role) = do - rScope' <- toRouteWorkflowScope $ _DBWorkflowScope # wwScope - cID <- encrypt wwId - let route' = _WorkflowScopeRoute # (rScope', WorkflowWorkflowR cID WWWorkflowR) - lift . evalWriterT $ evalWorkflowRoleFor' eval' mAuthId (Just wwId) role route' False - guardM . fmap (isn't _Authorized) $ ofoldl1' orAR' . mapNonNull evalRole =<< hoistMaybe (fromNullable $ otoList roles) - return AuthorizedI18n - in case route of - r | Just (rScope, WorkflowInstanceR win WIWorkflowsR) <- r ^? _WorkflowScopeRoute - -> workflowInstanceWorkflowsEmpty rScope win - EExamListR -> exceptT return return $ do - authId <- maybeExceptT AuthenticationRequired $ return mAuthId - hasExternalExams <- $cachedHereBinary authId . lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` eexamStaff) -> do - E.on $ eexam E.^. ExternalExamId E.==. eexamStaff E.^. ExternalExamStaffExam - E.where_ $ eexamStaff E.^. ExternalExamStaffUser E.==. E.val authId - E.||. E.exists (E.from $ \externalExamResult -> - E.where_ $ externalExamResult E.^. ExternalExamResultExam E.==. eexam E.^. ExternalExamId - E.&&. externalExamResult E.^. ExternalExamResultUser E.==. E.val authId - ) - guardMExceptT (not hasExternalExams) $ unauthorizedI MsgUnauthorizedExternalExamListNotEmpty - return Authorized - CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNotEmpty) $ do - -- Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh - cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh - assertM_ (<= 0) . $cachedHereBinary cid . lift $ count [ CourseParticipantCourse ==. cid ] - assertM_ not . $cachedHereBinary cid . lift $ E.selectExists . E.from $ \(sheet `E.InnerJoin` submission) -> do - E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet - E.where_ $ sheet E.^. SheetCourse E.==. E.val cid - return Authorized - r -> $unsupportedAuthPredicate AuthEmpty r +tagAccessPredicate AuthEmpty = APDB $ \_ _ mAuthId route _ -> case route of + EExamListR -> exceptT return return $ do + authId <- maybeExceptT AuthenticationRequired $ return mAuthId + hasExternalExams <- $cachedHereBinary authId . lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` eexamStaff) -> do + E.on $ eexam E.^. ExternalExamId E.==. eexamStaff E.^. ExternalExamStaffExam + E.where_ $ eexamStaff E.^. ExternalExamStaffUser E.==. E.val authId + E.||. E.exists (E.from $ \externalExamResult -> + E.where_ $ externalExamResult E.^. ExternalExamResultExam E.==. eexam E.^. ExternalExamId + E.&&. externalExamResult E.^. ExternalExamResultUser E.==. E.val authId + ) + guardMExceptT (not hasExternalExams) $ unauthorizedI MsgUnauthorizedExternalExamListNotEmpty + return Authorized + CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNotEmpty) $ do + -- Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh + cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh + assertM_ (<= 0) . $cachedHereBinary cid . lift $ count [ CourseParticipantCourse ==. cid ] + assertM_ not . $cachedHereBinary cid . lift $ E.selectExists . E.from $ \(sheet `E.InnerJoin` submission) -> do + E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet + E.where_ $ sheet E.^. SheetCourse E.==. E.val cid + return Authorized + r -> $unsupportedAuthPredicate AuthEmpty r tagAccessPredicate AuthMaterials = APDB $ \_ _ _ route _ -> case route of CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnfreeMaterials) $ do Entity _ Course{..} <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh @@ -1745,91 +1689,6 @@ tagAccessPredicate AuthAuthentication = APDB $ \_ _ mAuthId route _ -> case rout guard $ not systemMessageAuthenticatedOnly || isAuthenticated return Authorized r -> $unsupportedAuthPredicate AuthAuthentication r -tagAccessPredicate AuthWorkflow = APDB $ \evalCtx eval' mAuthId route isWrite -> workflowsEnabledAuth $ do - mr <- getMsgRenderer - let orAR', _andAR' :: forall m'. Monad m' => m' AuthResult -> m' AuthResult -> m' AuthResult - orAR' = shortCircuitM (is _Authorized) (orAR mr) - _andAR' = shortCircuitM (is _Unauthorized) (andAR mr) - - - wInitiate win rScope = selectLanguageI18n <=< $memcacheAuthHere' (Right diffDay) (evalCtx, route, mAuthId) . maybeT (unauthorizedI18n MsgUnauthorizedWorkflowInitiate) $ do -- @isWrite@ not included since it should make no difference regarding initiation (in the end that will always be a write) - roles <- memcacheAuth' @(Set (WorkflowRole UserId)) (Right diffDay) (AuthCacheWorkflowInstanceInitiators win rScope) $ do - scope <- MaybeT . $cachedHereBinary rScope . runMaybeT $ fromRouteWorkflowScope rScope - Entity _ WorkflowInstance{..} <- $cachedHereBinary (win, scope) . MaybeT . getBy . UniqueWorkflowInstance win $ scope ^. _DBWorkflowScope - wiGraph <- lift $ getSharedIdWorkflowGraph workflowInstanceGraph - return . fold $ do - WGN{..} <- wiGraph ^.. _wgNodes . folded - WorkflowGraphEdgeInitial{..} <- wgnEdges ^.. folded - return wgeActors - let evalRole role = lift . evalWriterT $ evalWorkflowRoleFor' eval' mAuthId Nothing role route isWrite - guardM . fmap (is _Authorized) $ ofoldl1' orAR' . mapNonNull evalRole =<< hoistMaybe (fromNullable $ toList roles) - return AuthorizedI18n - - wWorkflow isWrite' cID - | isWrite' = maybeT (unauthorizedI MsgUnauthorizedWorkflowWrite) $ do - (wwId, edges) <- memcacheAuth' (Right diffDay) (AuthCacheWorkflowWorkflowEdgeActors cID) $ do - wwId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID - WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId - wwGraph <- lift $ getSharedIdWorkflowGraph workflowWorkflowGraph - - let - wwNode = wpTo $ last workflowWorkflowState - - return . (wwId, ) . (Set.fromList :: _ -> Set (WorkflowRole UserId)) . foldMap toNullable $ do - WGN{..} <- wwGraph ^.. _wgNodes . folded - WorkflowGraphEdgeManual{..} <- wgnEdges ^.. folded - guard $ wgeSource == wwNode - hoistMaybe . fromNullable $ wgeActors ^.. folded - - let - evalRole role = lift . evalWriterT $ evalWorkflowRoleFor' eval' mAuthId (Just wwId) role route isWrite - guardM . fmap (is _Authorized) $ ofoldl1' orAR' . mapNonNull evalRole =<< hoistMaybe (fromNullable $ otoList edges) - return Authorized - | otherwise = flip orAR' (wWorkflow True cID) . maybeT (unauthorizedI MsgUnauthorizedWorkflowRead) $ do - (wwId, roles) <- memcacheAuth' @(WorkflowWorkflowId, Set (WorkflowRole UserId)) (Right diffDay) (AuthCacheWorkflowWorkflowViewers cID) $ do - wwId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID - WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId - wwGraph <- lift $ getSharedIdWorkflowGraph workflowWorkflowGraph - - let - nodeViewers = do - WorkflowAction{..} <- otoList workflowWorkflowState - (node, WGN{..}) <- itoListOf (_wgNodes . ifolded) wwGraph - guard $ node == wpTo - WorkflowNodeView{..} <- hoistMaybe wgnViewers - return $ toNullable wnvViewers - payloadViewers = do - (prevActs, act) <- zip (inits $ otoList workflowWorkflowState) $ otoList workflowWorkflowState - prevAct <- hoistMaybe $ prevActs ^? _last - payload <- Map.keys $ wpPayload act - guard $ Map.lookup payload (workflowStateCurrentPayloads prevActs) /= Map.lookup payload (wpPayload act) - fmap (toNullable . wpvViewers) . hoistMaybe $ Map.lookup payload . wgnPayloadView =<< Map.lookup (wpTo prevAct) (wgNodes wwGraph) - - return (wwId, fold nodeViewers <> fold payloadViewers) - - let - evalRole role = lift . evalWriterT $ evalWorkflowRoleFor' eval' mAuthId (Just wwId) role route isWrite - guardM . fmap (is _Authorized) $ ofoldl1' orAR' . mapNonNull evalRole =<< hoistMaybe (fromNullable $ otoList roles) - return Authorized - wFiles wwCID wpl stCID = maybeT (unauthorizedI MsgUnauthorizedWorkflowFiles) $ do - wwId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt wwCID - WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId - stIx <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decryptWorkflowStateIndex wwId stCID - wwGraph <- lift $ getSharedIdWorkflowGraph workflowWorkflowGraph - act <- workflowStateIndex stIx $ _DBWorkflowState # workflowWorkflowState - let - cState = wpTo act - payloadViewers = Map.findWithDefault Set.empty wpl $ toNullable . wpvViewers <$> Map.findWithDefault Map.empty cState (wgnPayloadView <$> wgNodes wwGraph) - evalRole role = lift . evalWriterT $ evalWorkflowRoleFor' eval' mAuthId (Just wwId) role route isWrite - guardM . anyM (otoList payloadViewers) $ fmap (is _Authorized) . evalRole - guardM . lift . evalWriterT $ mayViewWorkflowAction' eval' mAuthId wwId act - return Authorized - - case route of - _ | Just (rScope, WorkflowInstanceR win WIInitiateR) <- route ^? _WorkflowScopeRoute -> wInitiate win rScope - _ | Just (_, WorkflowWorkflowR cID WWWorkflowR) <- route ^? _WorkflowScopeRoute -> wWorkflow isWrite cID - _ | Just (_, WorkflowWorkflowR wwCID (WWFilesR wpl stCID)) <- route ^? _WorkflowScopeRoute -> wFiles wwCID wpl stCID - r -> $unsupportedAuthPredicate AuthWorkflow r tagAccessPredicate AuthRead = APPure $ \_ _ isWrite -> do MsgRenderer mr <- ask return $ bool Authorized (Unauthorized $ mr MsgUnauthorizedWrite) isWrite @@ -2021,155 +1880,6 @@ wouldHaveReadAccessToIff assumptions route = and2M (not <$> hasReadAccessTo rout wouldHaveWriteAccessToIff assumptions route = and2M (not <$> hasWriteAccessTo route) $ wouldHaveWriteAccessTo assumptions route -evalWorkflowRoleFor' :: forall m backend. - ( HasCallStack - , MonadHandler m, HandlerSite m ~ UniWorX - , MonadAP (ReaderT backend m), MonadIO m - , MonadThrow m - , BackendCompatible SqlReadBackend backend - ) - => (forall m'. MonadAP m' => AuthTagsEval m') - -> Maybe UserId - -> Maybe WorkflowWorkflowId - -> WorkflowRole UserId - -> Route UniWorX - -> Bool - -> WriterT (Set AuthTag) (ReaderT backend m) AuthResult -evalWorkflowRoleFor' eval mAuthId mwwId wRole route isWrite = workflowsEnabledAuth $ do - mr <- getMsgRenderer - - let - orAR' :: forall m'. Monad m' => m' AuthResult -> m' AuthResult -> m' AuthResult - orAR' = shortCircuitM (is _Authorized) (orAR mr) - - orDefault = orAR' $ eval defaultAuthDNF mAuthId route isWrite - - case wRole of - WorkflowRoleUser{..} -> orDefault . lift . exceptT return return $ do - uid <- maybeExceptT AuthenticationRequired $ return mAuthId - unless (uid == workflowRoleUser) $ - throwE =<< unauthorizedI MsgWorkflowRoleUserMismatch - return Authorized - -- `WorkflowRoleInitiator` now means "during initiation". - -- The old meaning can be emulated via `WorkflowRolePayloadReference`. - WorkflowRoleInitiator{} -> orDefault $ if - | is _Nothing mwwId -> return Authorized - | otherwise -> unauthorizedI MsgWorkflowRoleAlreadyInitiated - -- WorkflowRoleInitiator{} -> exceptT return return $ do - -- wwId <- maybeMExceptT (unauthorizedI MsgWorkflowRoleNoInitiator) $ return mwwId - -- WorkflowWorkflow{..} <- maybeMExceptT (unauthorizedI MsgWorkflowRoleNoSuchWorkflowWorkflow) . lift . withReaderT (projectBackend @SqlReadBackend) $ get wwId - -- let WorkflowAction{..} = head workflowWorkflowState - -- wpUser' <- maybeMExceptT (unauthorizedI MsgWorkflowRoleNoInitiator) . return $ review _SqlKey <$> join wpUser - -- lift $ evalWorkflowRoleFor' tagActive mAuthId mwwId (WorkflowRoleUser wpUser') route isWrite - WorkflowRolePayloadReference{..} -> orDefault . exceptT return return $ do - uid <- maybeExceptT AuthenticationRequired $ return mAuthId - wwId <- maybeMExceptT (unauthorizedI MsgWorkflowRoleNoPayload) $ return mwwId - Entity _ WorkflowWorkflow{..} <- maybeMExceptT (unauthorizedI MsgWorkflowRoleNoSuchWorkflowWorkflow) . lift $ getWorkflowWorkflowState wwId - -- WorkflowWorkflow{..} <- maybeMExceptT (unauthorizedI MsgWorkflowRoleNoSuchWorkflowWorkflow) . lift . withReaderT (projectBackend @SqlReadBackend) $ get wwId - let uids = maybe Set.empty getLast . foldMap (fmap Last) . workflowStatePayload workflowRolePayloadLabel $ _DBWorkflowState # workflowWorkflowState - unless (uid `Set.member` uids) $ - throwE =<< unauthorizedI MsgWorkflowRoleUserMismatch - return Authorized - WorkflowRoleAuthorized{..} -> eval (predDNFEntail $ workflowRoleAuthorized `predDNFOr` defaultAuthDNF) mAuthId route isWrite - -evalWorkflowRoleFor :: ( HasCallStack - , MonadAP (ReaderT backend m), MonadIO m - , MonadHandler m, HandlerSite m ~ UniWorX - , MonadThrow m - , BackendCompatible SqlReadBackend backend - ) - => Maybe UserId - -> Maybe WorkflowWorkflowId - -> WorkflowRole UserId - -> Route UniWorX - -> Bool - -> ReaderT backend m AuthResult -evalWorkflowRoleFor mAuthId mwwId wRole route isWrite = workflowsEnabledAuth $ do - isSelf <- (== mAuthId) <$> liftHandler defaultMaybeAuthId - tagActive <- if - | isSelf -> fromMaybe def <$> lookupSessionJson SessionActiveAuthTags - | otherwise -> return . AuthTagActive $ const True - (result, deactivated) <- - let eval :: forall m'. MonadAP m' => AuthTagsEval m' - eval dnf' mAuthId' route' isWrite' = evalAuthTags 'evalWorkflowRoleFor tagActive eval dnf' mAuthId' route' isWrite' - in runWriterT $ evalWorkflowRoleFor' eval mAuthId mwwId wRole route isWrite - when isSelf $ - tellSessionJson SessionInactiveAuthTags deactivated - return result - -hasWorkflowRole :: ( HasCallStack - , MonadAP (ReaderT backend m) - , MonadHandler m, HandlerSite m ~ UniWorX - , MonadThrow m - , BackendCompatible SqlReadBackend backend - ) - => Maybe WorkflowWorkflowId - -> WorkflowRole UserId - -> Route UniWorX - -> Bool - -> ReaderT backend m AuthResult -hasWorkflowRole mwwId wRole route isWrite = workflowsEnabledAuth $ do - mAuthId <- maybeAuthId - evalWorkflowRoleFor mAuthId mwwId wRole route isWrite - -mayViewWorkflowAction' :: forall backend m fileid. - ( HasCallStack - , MonadAP (ReaderT backend m) - , BackendCompatible SqlReadBackend backend - , MonadCrypto m, MonadCryptoKey m ~ CryptoIDKey - , MonadCatch m - , MonadHandler m, HandlerSite m ~ UniWorX - , MonadUnliftIO m - ) - => (forall m'. MonadAP m' => AuthTagsEval m') - -> Maybe UserId - -> WorkflowWorkflowId - -> WorkflowAction fileid UserId - -> WriterT (Set AuthTag) (ReaderT backend m) Bool -mayViewWorkflowAction' eval mAuthId wwId WorkflowAction{..} = volatileBool clusterVolatileWorkflowsEnabled (return False) . hoist (withReaderT $ projectBackend @SqlReadBackend) . maybeT (return False) $ do - Entity _ WorkflowWorkflow{..} <- MaybeT . lift $ getWorkflowWorkflowState wwId - rScope <- hoist lift . toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope - cID <- catchMaybeT (Proxy @CryptoIDError) . lift . lift $ encrypt wwId - WorkflowGraph{..} <- lift . lift $ getSharedIdWorkflowGraph workflowWorkflowGraph - let canonRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR) - evalWorkflowRole'' role = lift $ is _Authorized <$> evalWorkflowRoleFor' eval mAuthId (Just wwId) role canonRoute False - WorkflowNodeView{..} <- hoistMaybe $ Map.lookup wpTo wgNodes >>= wgnViewers - guardM $ orM - [ return $ is _Just mAuthId && wpUser == Just mAuthId - , anyM wnvViewers evalWorkflowRole'' - , anyM (Map.keys wpPayload) $ \payloadLbl -> lift . maybeT (return False) $ do - WorkflowPayloadView{..} <- hoistMaybe . Map.lookup payloadLbl $ Map.findWithDefault Map.empty wpTo (wgnPayloadView <$> wgNodes) - anyM wpvViewers evalWorkflowRole'' - ] - return True - -mayViewWorkflowAction :: forall backend m fileid. - ( HasCallStack - , MonadAP (ReaderT backend m) - , BackendCompatible SqlReadBackend backend - , MonadCrypto m, MonadCryptoKey m ~ CryptoIDKey - , MonadCatch m - , MonadHandler m, HandlerSite m ~ UniWorX - , MonadUnliftIO m - ) - => Maybe UserId - -> WorkflowWorkflowId - -> WorkflowAction fileid UserId - -> ReaderT backend m Bool -mayViewWorkflowAction mAuthId wwId act = volatileBool clusterVolatileWorkflowsEnabled (return False) $ do - isSelf <- (== mAuthId) <$> liftHandler defaultMaybeAuthId - tagActive <- if - | isSelf -> fromMaybe def <$> lookupSessionJson SessionActiveAuthTags - | otherwise -> return . AuthTagActive $ const True - (result, deactivated) <- - let eval :: forall m'. MonadAP m' => AuthTagsEval m' - eval dnf' mAuthId' route' isWrite' = evalAuthTags 'mayViewWorkflowAction tagActive eval dnf' mAuthId' route' isWrite' - in runWriterT $ mayViewWorkflowAction' eval mAuthId wwId act - when isSelf $ - tellSessionJson SessionInactiveAuthTags deactivated - return result - - authoritiveApproot :: Route UniWorX -> ApprootScope authoritiveApproot = \case CourseR _ _ _ (MaterialR _ (MFileR _)) -> ApprootUserGenerated @@ -2183,5 +1893,4 @@ authoritiveApproot = \case CourseR _ _ _ CRegisterTemplateR -> ApprootUserGenerated CourseR _ _ _ CAppsFilesR -> ApprootUserGenerated CourseR _ _ _ (CourseApplicationR _ CAFilesR) -> ApprootUserGenerated - route | Just (_, WorkflowWorkflowR _ (WWFilesR _ _)) <- route ^? _WorkflowScopeRoute -> ApprootUserGenerated _other -> ApprootDefault diff --git a/src/Foundation/I18n.hs b/src/Foundation/I18n.hs index fa357c6f8..6f111b616 100644 --- a/src/Foundation/I18n.hs +++ b/src/Foundation/I18n.hs @@ -4,7 +4,7 @@ module Foundation.I18n ( appLanguages, appLanguagesOpts , UniWorXMessage(..), UniWorXTestMessage(..), UniWorXSettingsMessage(..) - , UniWorXHelpMessage(..), UniWorXNavigationMessage(..), UniWorXWorkflowMessage(..) + , UniWorXHelpMessage(..), UniWorXNavigationMessage(..) , UniWorXCourseMessage(..), UniWorXAllocationMessage(..), UniWorXExamMessage(..) , UniWorXSheetMessage(..), UniWorXAdminMessage(..), UniWorXSubmissionMessage(..) , UniWorXTutorialMessage(..), UniWorXUserMessage(..), UniWorXButtonMessage(..) @@ -26,8 +26,7 @@ module Foundation.I18n , StudyDegreeTerm(..) , ShortStudyFieldType(..) , StudyDegreeTermType(..) - , ErrorResponseTitle(..) - , WorkflowPayloadBool(..) + , ErrorResponseTitle(..) , UniWorXMessages(..) , uniworxMessages , unRenderMessage, unRenderMessage', unRenderMessageLenient @@ -59,9 +58,7 @@ import Data.Text.Lens (packed) import Data.List ((!!)) -import qualified Data.Scientific as Scientific - -import Utils.Workflow (RouteWorkflowScope) +-- import qualified Data.Scientific as Scientific import Foundation.I18n.TH @@ -159,7 +156,6 @@ mkMessageAddition ''UniWorX "Test" "messages/uniworx/test" "de-de-formal" mkMessageAddition ''UniWorX "Settings" "messages/uniworx/categories/settings" "de-de-formal" mkMessageAddition ''UniWorX "Help" "messages/uniworx/categories/help" "de-de-formal" mkMessageAddition ''UniWorX "Navigation" "messages/uniworx/utils/navigation" "de-de-formal" -mkMessageAddition ''UniWorX "Workflow" "messages/uniworx/categories/workflows" "de-de-formal" mkMessageAddition ''UniWorX "Course" "messages/uniworx/categories/courses/courses" "de-de-formal" mkMessageAddition ''UniWorX "Allocation" "messages/uniworx/categories/courses/allocation" "de-de-formal" mkMessageAddition ''UniWorX "Exam" "messages/uniworx/categories/courses/exam" "de-de-formal" @@ -200,14 +196,18 @@ mkMessageVariant ''UniWorX ''FrontendMessage "messages/frontend" "de-de-formal" instance RenderMessage UniWorX TermIdentifier where renderMessage foundation ls TermIdentifier{..} = case season of - Summer -> renderMessage' $ MsgSummerTerm year - Winter -> renderMessage' $ MsgWinterTerm year + Q1 -> renderMessage' $ MsgQuarter1st year + Q2 -> renderMessage' $ MsgQuarter2nd year + Q3 -> renderMessage' $ MsgQuarter3rd year + Q4 -> renderMessage' $ MsgQuarter4th year where renderMessage' = renderMessage foundation ls instance RenderMessage UniWorX ShortTermIdentifier where renderMessage foundation ls (ShortTermIdentifier TermIdentifier{..}) = case season of - Summer -> renderMessage' $ MsgSummerTermShort year - Winter -> renderMessage' $ MsgWinterTermShort year + Q1 -> renderMessage' $ MsgQuarter1stShort year + Q2 -> renderMessage' $ MsgQuarter2ndShort year + Q3 -> renderMessage' $ MsgQuarter3rdShort year + Q4 -> renderMessage' $ MsgQuarter4thShort year where renderMessage' = renderMessage foundation ls instance RenderMessage UniWorX String where @@ -294,7 +294,6 @@ embedRenderMessage ''UniWorX ''ExamOnlinePreset id embedRenderMessage ''UniWorX ''ExamSynchronicityPreset id embedRenderMessage ''UniWorX ''ExamRequiredEquipmentPreset id embedRenderMessage ''UniWorX ''ChangelogItemKind id -embedRenderMessage ''UniWorX ''WorkflowScope' $ ("WorkflowScopeKind" <>) . concat . drop 1 . splitCamel . fromMaybe (error "Expected WorkflowScope' to have '") . stripSuffix "'" embedRenderMessage ''UniWorX ''RoomReference' $ dropSuffix "'" embedRenderMessage ''UniWorX ''AuthenticationMode id @@ -438,9 +437,6 @@ instance HasResolution a => ToMessage (Fixed a) where newtype ErrorResponseTitle = ErrorResponseTitle ErrorResponse embedRenderMessageVariant ''UniWorX ''ErrorResponseTitle ("ErrorResponseTitle" <>) -newtype WorkflowPayloadBool = WorkflowPayloadBool { unWorkflowPayloadBool :: Bool } -embedRenderMessageVariant ''UniWorX ''WorkflowPayloadBool ("WorkflowPayloadBool" <>) - newtype UniWorXMessages = UniWorXMessages [SomeMessage UniWorX] deriving stock (Generic, Typeable) @@ -498,17 +494,6 @@ instance RenderMessage UniWorX ShortWeekDay where embedRenderMessage ''UniWorX ''ButtonSubmit id -instance RenderMessage UniWorX RouteWorkflowScope where - renderMessage foundation ls = \case - WSGlobal -> mr MsgWorkflowScopeGlobal - WSTerm{..} -> mr . ShortTermIdentifier $ unTermKey wisTerm - WSSchool{..} -> mr $ unSchoolKey wisSchool - WSTermSchool{..} -> mr $ MsgWorkflowScopeTermSchool wisTerm wisSchool - WSCourse{ wisCourse = (tid, ssh, csh) } -> mr $ MsgWorkflowScopeCourse tid ssh csh - where - mr :: forall msg. RenderMessage UniWorX msg => msg -> Text - mr = renderMessage foundation ls - unRenderMessage' :: (Ord a, Finite a, RenderMessage master a) => (Text -> Text -> Bool) -> master -> Text -> [a] unRenderMessage' cmp foundation inp = nubOrd $ do diff --git a/src/Foundation/Navigation.hs b/src/Foundation/Navigation.hs index 28303797b..6aa443c3a 100644 --- a/src/Foundation/Navigation.hs +++ b/src/Foundation/Navigation.hs @@ -37,18 +37,6 @@ import Control.Monad.Trans.State (execStateT) import Yesod.Core.Types (HandlerContents) -import qualified Data.Conduit.Combinators as C - -import Utils.Workflow -import Handler.Utils.Workflow.CanonicalRoute - -import qualified Data.Map.Strict as Map -import qualified Data.Set as Set - -import Data.List (inits) - -import Utils.VolatileClusterSettings - type Breadcrumb = (Text, Maybe (Route UniWorX)) @@ -123,33 +111,7 @@ breadcrumb (SchoolR ssh sRoute) = case sRoute of School{..} <- MaybeT $ get ssh isAdmin <- lift $ hasReadAccessTo SchoolListR return (CI.original schoolName, bool Nothing (Just SchoolListR) isAdmin) - - SchoolWorkflowInstanceListR -> i18nCrumb MsgBreadcrumbWorkflowInstanceList . Just $ SchoolR ssh SchoolEditR - SchoolWorkflowInstanceNewR -> i18nCrumb MsgBreadcrumbWorkflowInstanceNew . Just $ SchoolR ssh SchoolWorkflowInstanceListR - SchoolWorkflowInstanceR win sRoute' -> case sRoute' of - SWIEditR -> do - desc <- useRunDB . runMaybeT $ do - guardM . lift . hasReadAccessTo . SchoolR ssh $ SchoolWorkflowInstanceR win SWIWorkflowsR - wiId <- MaybeT . getKeyBy . UniqueWorkflowInstance win . WSSchool $ unSchoolKey ssh - MaybeT $ selectWorkflowInstanceDescription wiId - let bRoute = SchoolR ssh SchoolWorkflowInstanceListR - case desc of - Nothing -> i18nCrumb (MsgBreadcrumbWorkflowInstanceEdit win) $ Just bRoute - Just (Entity _ WorkflowInstanceDescription{..}) -> i18nCrumb workflowInstanceDescriptionTitle $ Just bRoute - SWIDeleteR -> i18nCrumb MsgBreadcrumbWorkflowInstanceDelete . Just . SchoolR ssh $ SchoolWorkflowInstanceR win SWIEditR - SWIWorkflowsR -> i18nCrumb MsgBreadcrumbWorkflowInstanceWorkflowList . Just . SchoolR ssh $ SchoolWorkflowInstanceR win SWIEditR - SWIInitiateR -> useRunDB $ do - mayEdit <- hasReadAccessTo . SchoolR ssh $ SchoolWorkflowInstanceR win SWIEditR - i18nCrumb MsgBreadcrumbWorkflowInstanceInitiate . Just . SchoolR ssh $ if - | mayEdit -> SchoolWorkflowInstanceR win SWIEditR - | otherwise -> SchoolWorkflowInstanceListR - SWIUpdateR -> i18nCrumb MsgBreadcrumbWorkflowInstanceUpdate . Just . SchoolR ssh $ SchoolWorkflowInstanceR win SWIEditR - SchoolWorkflowWorkflowListR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowList . Just $ SchoolR ssh SchoolWorkflowInstanceListR - SchoolWorkflowWorkflowR cID sRoute' -> case sRoute' of - SWWWorkflowR -> i18nCrumb (MsgBreadcrumbWorkflowWorkflow cID) . Just $ SchoolR ssh SchoolWorkflowWorkflowListR - SWWFilesR _ _ -> i18nCrumb MsgBreadcrumbWorkflowWorkflowFiles . Just . SchoolR ssh $ SchoolWorkflowWorkflowR cID SWWWorkflowR - SWWEditR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowEdit . Just . SchoolR ssh $ SchoolWorkflowWorkflowR cID SWWWorkflowR - SWWDeleteR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowDelete . Just . SchoolR ssh $ SchoolWorkflowWorkflowR cID SWWWorkflowR + breadcrumb SchoolNewR = i18nCrumb MsgMenuSchoolNew $ Just SchoolListR breadcrumb (ExamOfficeR EOExamsR) = i18nCrumb MsgMenuExamOfficeExams Nothing @@ -397,50 +359,6 @@ breadcrumb (EExamR tid ssh coursen examn sRoute) = case sRoute of EEStaffInviteR -> i18nCrumb MsgBreadcrumbExternalExamStaffInvite . Just $ EExamR tid ssh coursen examn EEShowR EECorrectR -> i18nCrumb MsgBreadcrumbExternalExamCorrect . Just $ EExamR tid ssh coursen examn EEShowR -breadcrumb AdminWorkflowDefinitionListR = i18nCrumb MsgBreadcrumbAdminWorkflowDefinitionList $ Just AdminR -breadcrumb AdminWorkflowDefinitionNewR = i18nCrumb MsgBreadcrumbAdminWorkflowDefinitionNew $ Just AdminWorkflowDefinitionListR -breadcrumb (AdminWorkflowDefinitionR wfdScope wfdName sRoute) = case sRoute of - AWDEditR -> do - MsgRenderer mr <- getMsgRenderer - i18nCrumb (MsgBreadcrumbAdminWorkflowDefinitionEdit (mr wfdScope) wfdName) $ Just AdminWorkflowDefinitionListR - AWDDeleteR -> i18nCrumb MsgBreadcrumbAdminWorkflowDefinitionDelete . Just $ AdminWorkflowDefinitionR wfdScope wfdName AWDEditR - AWDInstantiateR -> i18nCrumb MsgBreadcrumbAdminWorkflowDefinitionInstantiate . Just $ AdminWorkflowDefinitionR wfdScope wfdName AWDEditR -breadcrumb AdminWorkflowInstanceListR = i18nCrumb MsgBreadcrumbAdminWorkflowInstanceList $ Just AdminWorkflowDefinitionListR -breadcrumb AdminWorkflowInstanceNewR = i18nCrumb MsgBreadcrumbAdminWorkflowInstanceNew $ Just AdminWorkflowInstanceListR -breadcrumb (AdminWorkflowInstanceR _cID sRoute) = case sRoute of - AWIEditR -> i18nCrumb MsgBreadcrumbAdminWorkflowInstanceEdit $ Just AdminWorkflowInstanceListR -breadcrumb AdminWorkflowWorkflowListR = i18nCrumb MsgBreadcrumbAdminWorkflowWorkflowList $ Just AdminWorkflowInstanceListR -breadcrumb AdminWorkflowWorkflowNewR = i18nCrumb MsgBreadcrumbAdminWorkflowWorkflowNew $ Just AdminWorkflowWorkflowListR - -breadcrumb GlobalWorkflowInstanceListR = i18nCrumb MsgBreadcrumbGlobalWorkflowInstanceList Nothing -breadcrumb GlobalWorkflowInstanceNewR = i18nCrumb MsgBreadcrumbWorkflowInstanceNew $ Just GlobalWorkflowInstanceListR -breadcrumb (GlobalWorkflowInstanceR win sRoute) = case sRoute of - GWIEditR -> do - desc <- useRunDB . runMaybeT $ do - guardM . lift . hasReadAccessTo $ GlobalWorkflowInstanceR win GWIWorkflowsR - wiId <- MaybeT . getKeyBy $ UniqueWorkflowInstance win WSGlobal - MaybeT $ selectWorkflowInstanceDescription wiId - case desc of - Nothing -> i18nCrumb (MsgBreadcrumbWorkflowInstanceEdit win) $ Just GlobalWorkflowInstanceListR - Just (Entity _ WorkflowInstanceDescription{..}) -> i18nCrumb workflowInstanceDescriptionTitle $ Just GlobalWorkflowInstanceListR - GWIDeleteR -> i18nCrumb MsgBreadcrumbWorkflowInstanceDelete . Just $ GlobalWorkflowInstanceR win GWIEditR - GWIWorkflowsR -> i18nCrumb MsgBreadcrumbWorkflowInstanceWorkflowList . Just $ GlobalWorkflowInstanceR win GWIEditR - GWIInitiateR -> do - mayEdit <- useRunDB . hasReadAccessTo $ GlobalWorkflowInstanceR win GWIEditR - i18nCrumb MsgBreadcrumbWorkflowInstanceInitiate . Just $ if - | mayEdit -> GlobalWorkflowInstanceR win GWIEditR - | otherwise -> GlobalWorkflowInstanceListR - GWIUpdateR -> i18nCrumb MsgBreadcrumbWorkflowInstanceUpdate . Just $ GlobalWorkflowInstanceR win GWIEditR -breadcrumb GlobalWorkflowWorkflowListR = i18nCrumb MsgBreadcrumbWorkflowWorkflowList $ Just GlobalWorkflowInstanceListR -breadcrumb (GlobalWorkflowWorkflowR cID sRoute) = case sRoute of - GWWWorkflowR -> i18nCrumb (MsgBreadcrumbWorkflowWorkflow cID) $ Just GlobalWorkflowWorkflowListR - GWWFilesR _ _ -> i18nCrumb MsgBreadcrumbWorkflowWorkflowFiles . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR - GWWEditR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowEdit . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR - GWWDeleteR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowDelete . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR - -breadcrumb TopWorkflowInstanceListR = i18nCrumb MsgBreadcrumbTopWorkflowInstanceList Nothing -breadcrumb TopWorkflowWorkflowListR = i18nCrumb MsgBreadcrumbTopWorkflowWorkflowList $ Just TopWorkflowInstanceListR - data NavQuickView = NavQuickViewFavourite @@ -535,9 +453,6 @@ type family ChildrenNavChildren a where data NavigationCacheKey = NavCacheRouteAccess AuthContext NavType (Route UniWorX) - | NavCacheHaveWorkflowWorkflowsRoles RouteWorkflowScope - | NavCacheHaveTopWorkflowInstancesRoles | NavCacheHaveTopWorkflowWorkflowsRoles - | NavCacheHaveTopWorkflowsInstances AuthContext deriving (Generic, Typeable) deriving stock instance Eq (AuthId UniWorX) => Eq NavigationCacheKey @@ -573,8 +488,8 @@ navLinkAccess NavLink{..} = case navAccess' of defaultLinks :: ( MonadHandler m , HandlerSite m ~ UniWorX - , MonadThrow m - , WithRunDB SqlReadBackend (HandlerFor UniWorX) m + -- , MonadThrow m + -- , WithRunDB SqlReadBackend (HandlerFor UniWorX) m , BearerAuthSite UniWorX ) => m [Nav] defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the header. @@ -758,39 +673,6 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navForceActive = False } } - , do - guardVolatile clusterVolatileWorkflowsEnabled - - authCtx <- getAuthContext - (haveInstances, haveWorkflows) <- lift . memcachedBy (Just . Right $ 2 * diffMinute) (NavCacheHaveTopWorkflowsInstances authCtx) . useRunDB $ (,) - <$> haveTopWorkflowInstances - <*> haveTopWorkflowWorkflows - - if | haveInstances -> return NavHeader - { navHeaderRole = NavHeaderPrimary - , navIcon = IconMenuWorkflows - , navLink = NavLink - { navLabel = MsgMenuTopWorkflowInstanceList - , navRoute = TopWorkflowInstanceListR - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - } - | haveWorkflows -> return NavHeader - { navHeaderRole = NavHeaderPrimary - , navIcon = IconMenuWorkflows - , navLink = NavLink - { navLabel = MsgMenuTopWorkflowWorkflowListHeader - , navRoute = TopWorkflowWorkflowListR - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - } - | otherwise -> mzero , return NavHeaderContainer { navHeaderRole = NavHeaderPrimary , navLabel = SomeMessage MsgMenuAdminHeading @@ -844,14 +726,6 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navQuick' = mempty , navForceActive = False } - , NavLink - { navLabel = MsgMenuAdminWorkflowDefinitionList - , navRoute = AdminWorkflowDefinitionListR - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } , NavLink { navLabel = MsgMenuAdminCrontab , navRoute = AdminCrontabR @@ -2509,148 +2383,6 @@ pageActions ParticipantsListR = return , navChildren = [] } ] -pageActions AdminWorkflowDefinitionListR = return - [ NavPageActionPrimary - { navLink = NavLink - { navLabel = MsgMenuAdminWorkflowDefinitionNew - , navRoute = AdminWorkflowDefinitionNewR - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - , navChildren = [] - } - , NavPageActionPrimary - { navLink = NavLink - { navLabel = MsgMenuAdminWorkflowInstanceList - , navRoute = AdminWorkflowInstanceListR - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - , navChildren = [] - } - ] -pageActions (AdminWorkflowDefinitionR wds wdn AWDEditR) = return - [ NavPageActionSecondary - { navLink = NavLink - { navLabel = MsgMenuAdminWorkflowDefinitionDelete - , navRoute = AdminWorkflowDefinitionR wds wdn AWDDeleteR - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = True } - , navQuick' = mempty - , navForceActive = False - } - } - , NavPageActionPrimary - { navLink = NavLink - { navLabel = MsgMenuAdminWorkflowDefinitionInstantiate - , navRoute = AdminWorkflowDefinitionR wds wdn AWDInstantiateR - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = True } - , navQuick' = mempty - , navForceActive = False - } - , navChildren = [] - } - ] -pageActions AdminWorkflowInstanceListR = return - [ NavPageActionPrimary - { navLink = NavLink - { navLabel = MsgMenuAdminWorkflowInstanceNew - , navRoute = AdminWorkflowInstanceNewR - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - , navChildren = [] - } - ] -pageActions route | Just (rScope, WorkflowInstanceListR) <- route ^? _WorkflowScopeRoute = return - [ NavPageActionPrimary - { navLink = NavLink - { navLabel = MsgMenuWorkflowWorkflowList - , navRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowListR) - , navAccess' = NavAccessDB $ haveWorkflowWorkflows rScope - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - , navChildren = [] - } - ] -pageActions route | Just (rScope, WorkflowInstanceR win WIEditR) <- route ^? _WorkflowScopeRoute = return - [ NavPageActionSecondary - { navLink = NavLink - { navLabel = MsgMenuWorkflowInstanceDelete - , navRoute = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIDeleteR) - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - } - , NavPageActionPrimary - { navLink = NavLink - { navLabel = MsgMenuWorkflowInstanceWorkflows - , navRoute = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIWorkflowsR) - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - , navChildren = [] - } - , NavPageActionPrimary - { navLink = NavLink - { navLabel = MsgMenuWorkflowInstanceInitiate - , navRoute = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR) - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - , navChildren = [] - } - ] -pageActions route | Just (rScope, WorkflowWorkflowR cID WWWorkflowR) <- route ^? _WorkflowScopeRoute = return - [ NavPageActionSecondary - { navLink = NavLink - { navLabel = MsgMenuWorkflowWorkflowEdit - , navRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWEditR) - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - } - , NavPageActionSecondary - { navLink = NavLink - { navLabel = MsgMenuWorkflowWorkflowDelete - , navRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWDeleteR) - , navAccess' = NavAccessTrue - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - } - ] -pageActions TopWorkflowInstanceListR = return - [ NavPageActionPrimary - { navLink = NavLink - { navLabel = MsgMenuTopWorkflowWorkflowList - , navRoute = TopWorkflowWorkflowListR - , navAccess' = NavAccessDB haveTopWorkflowWorkflows - , navType = NavTypeLink { navModal = False } - , navQuick' = mempty - , navForceActive = False - } - , navChildren = [] - } - ] pageActions _ = return [] submissionList :: ( MonadIO m @@ -2685,110 +2417,3 @@ pageQuickActions qView route = do -- | Verify that the currently logged in user is lecturer or corrector for at least one sheet for the given course evalAccessCorrector :: (MonadAP m, MonadThrow m) => TermId -> SchoolId -> CourseShorthand -> m AuthResult evalAccessCorrector tid ssh csh = evalAccess (CourseR tid ssh csh CNotesR) False - - -haveWorkflowWorkflows - :: ( MonadHandler m, HandlerSite m ~ UniWorX - , BackendCompatible SqlReadBackend backend - , BearerAuthSite UniWorX - ) - => RouteWorkflowScope - -> ReaderT backend m Bool -haveWorkflowWorkflows rScope = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . $cachedHereBinary rScope . maybeT (return False) $ do - roles <- memcachedBy (Just $ Right diffDay) (NavCacheHaveWorkflowWorkflowsRoles rScope) $ do - scope <- fromRouteWorkflowScope rScope - - let - getWorkflows = E.selectSource . E.from $ \workflowWorkflow -> do - E.where_ $ workflowWorkflow E.^. WorkflowWorkflowScope E.==. E.val (scope ^. _DBWorkflowScope) - return workflowWorkflow - workflowRoles (Entity wwId WorkflowWorkflow{..}) = do - wwGraph <- lift $ getSharedIdWorkflowGraph workflowWorkflowGraph - let - nodeViewers = do - WorkflowAction{..} <- otoList workflowWorkflowState - (node, WGN{..}) <- itoListOf (_wgNodes . ifolded) wwGraph - guard $ node == wpTo - WorkflowNodeView{..} <- hoistMaybe wgnViewers - return $ toNullable wnvViewers - payloadViewers = do - (prevActs, act) <- zip (inits $ otoList workflowWorkflowState) $ otoList workflowWorkflowState - prevAct <- hoistMaybe $ prevActs ^? _last - payload <- Map.keys $ wpPayload act - guard $ Map.lookup payload (workflowStateCurrentPayloads prevActs) /= Map.lookup payload (wpPayload act) - fmap (toNullable . wpvViewers) . hoistMaybe $ Map.lookup payload . wgnPayloadView =<< Map.lookup (wpTo prevAct) (wgNodes wwGraph) - - cID <- encrypt wwId - return . Set.mapMonotonic ((wwId, cID), ) $ fold nodeViewers <> fold payloadViewers - - runConduit $ transPipe lift getWorkflows .| C.foldMapM workflowRoles - - let - evalRole ((wwId, cID), role) = do - let route = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR) - is _Authorized <$> hasWorkflowRole (Just wwId) role route False - - lift $ anyM roles evalRole - -haveTopWorkflowInstances, haveTopWorkflowWorkflows - :: ( MonadHandler m, HandlerSite m ~ UniWorX - , BackendCompatible SqlReadBackend backend - , BearerAuthSite UniWorX - ) - => ReaderT backend m Bool -haveTopWorkflowInstances = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . $cachedHere . maybeT (return False) $ do - roles <- memcachedBy @(Set ((RouteWorkflowScope, WorkflowInstanceName), WorkflowRole UserId)) (Just $ Right diffDay) NavCacheHaveTopWorkflowInstancesRoles $ do - let - getInstances = E.selectSource . E.from $ \workflowInstance -> do - E.where_ . isTopWorkflowScopeSql $ workflowInstance E.^. WorkflowInstanceScope - return workflowInstance - instanceRoles (Entity _ WorkflowInstance{..}) = do - rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowInstanceScope - wiGraph <- lift $ getSharedIdWorkflowGraph workflowInstanceGraph - return . Set.mapMonotonic ((rScope, workflowInstanceName), ) . fold $ do - WGN{..} <- wiGraph ^.. _wgNodes . folded - WorkflowGraphEdgeInitial{..} <- wgnEdges ^.. folded - return wgeActors - runConduit $ transPipe lift getInstances .| C.foldMapM instanceRoles - - let - evalRole :: _ -> ReaderT SqlReadBackend (HandlerFor UniWorX) Bool - evalRole ((rScope, win), role) = do - let route = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR) - is _Authorized <$> hasWorkflowRole Nothing role route False - - lift $ anyM roles evalRole -haveTopWorkflowWorkflows = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . $cachedHere . maybeT (return False) $ do - roles <- memcachedBy (Just $ Right diffDay) NavCacheHaveTopWorkflowWorkflowsRoles $ do - let - getWorkflows = E.selectSource . E.from $ \workflowWorkflow -> do - E.where_ . isTopWorkflowScopeSql $ workflowWorkflow E.^. WorkflowWorkflowScope - return workflowWorkflow - workflowRoles (Entity wwId WorkflowWorkflow{..}) = do - wwGraph <- lift $ getSharedIdWorkflowGraph workflowWorkflowGraph - rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope - let - nodeViewers = do - WorkflowAction{..} <- otoList workflowWorkflowState - (node, WGN{..}) <- itoListOf (_wgNodes . ifolded) wwGraph - guard $ node == wpTo - WorkflowNodeView{..} <- hoistMaybe wgnViewers - return $ toNullable wnvViewers - payloadViewers = do - (prevActs, act) <- zip (inits $ otoList workflowWorkflowState) $ otoList workflowWorkflowState - prevAct <- hoistMaybe $ prevActs ^? _last - payload <- Map.keys $ wpPayload act - guard $ Map.lookup payload (workflowStateCurrentPayloads prevActs) /= Map.lookup payload (wpPayload act) - fmap (toNullable . wpvViewers) . hoistMaybe $ Map.lookup payload . wgnPayloadView =<< Map.lookup (wpTo prevAct) (wgNodes wwGraph) - - cID <- encrypt wwId - return . Set.mapMonotonic ((wwId, cID, rScope), ) $ fold nodeViewers <> fold payloadViewers - runConduit $ transPipe lift getWorkflows .| C.foldMapM workflowRoles - - let - evalRole :: _ -> ReaderT SqlReadBackend (HandlerFor UniWorX) Bool - evalRole ((wwId, cID, rScope), role) = do - let route = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR) - is _Authorized <$> hasWorkflowRole (Just wwId) role route False - - lift $ anyM roles evalRole diff --git a/src/Foundation/Routes.hs b/src/Foundation/Routes.hs index 9e7bc4c76..03051889d 100644 --- a/src/Foundation/Routes.hs +++ b/src/Foundation/Routes.hs @@ -38,12 +38,6 @@ deriving instance Generic SchoolR deriving instance Generic ExamOfficeR deriving instance Generic CourseNewsR deriving instance Generic CourseEventR -deriving instance Generic AdminWorkflowDefinitionR -deriving instance Generic AdminWorkflowInstanceR -deriving instance Generic GlobalWorkflowInstanceR -deriving instance Generic GlobalWorkflowWorkflowR -deriving instance Generic SchoolWorkflowInstanceR -deriving instance Generic SchoolWorkflowWorkflowR deriving instance Generic AMatchingR deriving instance Generic (Route UniWorX) @@ -65,12 +59,6 @@ deriving instance Ord SchoolR deriving instance Ord ExamOfficeR deriving instance Ord CourseNewsR deriving instance Ord CourseEventR -deriving instance Ord AdminWorkflowDefinitionR -deriving instance Ord AdminWorkflowInstanceR -deriving instance Ord GlobalWorkflowInstanceR -deriving instance Ord GlobalWorkflowWorkflowR -deriving instance Ord SchoolWorkflowInstanceR -deriving instance Ord SchoolWorkflowWorkflowR deriving instance Ord AMatchingR deriving instance Ord (Route UniWorX) diff --git a/src/Foundation/Yesod/Middleware.hs b/src/Foundation/Yesod/Middleware.hs index 942150d67..f0c078ec7 100644 --- a/src/Foundation/Yesod/Middleware.hs +++ b/src/Foundation/Yesod/Middleware.hs @@ -13,9 +13,6 @@ import Foundation.Authorization import Foundation.I18n import Utils.Metrics -import Utils.Workflow - -import Handler.Utils.Workflow.CanonicalRoute import qualified Network.Wai as W import qualified Data.Aeson as JSON @@ -23,8 +20,6 @@ import qualified Data.CaseInsensitive as CI import Yesod.Core.Types (GHState(..), HandlerData(..), RunHandlerEnv(rheSite, rheChild)) -import qualified Data.Map as Map - yesodMiddleware :: ( BearerAuthSite UniWorX , BackendCompatible SqlReadBackend (YesodPersistBackend UniWorX) @@ -217,13 +212,9 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) . , ncTutorial , ncExam , ncExternalExam - , ncAdminWorkflowDefinition - , ncWorkflowInstance - , ncWorkflowPayloadLabel , verifySubmission , verifyCourseApplication - , verifyCourseNews - , verifyWorkflowWorkflow + , verifyCourseNews , verifyMaterialVideo , verifyAllocationMatchingLog ] @@ -299,28 +290,6 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) . return $ route & typesUsing @RouteChildren @CourseName . filtered (== coursen) .~ externalExamCourseName & typesUsing @RouteChildren @ExamName . filtered (== examn) .~ externalExamExamName - ncAdminWorkflowDefinition = maybeOrig $ \route -> do - AdminWorkflowDefinitionR wds wdn _ <- return route - Entity _ WorkflowDefinition{..} <- MaybeT . $cachedHereBinary (wds, wdn) . lift . getBy $ UniqueWorkflowDefinition wdn wds - caseChanged wdn workflowDefinitionName - return $ route - & typesUsing @RouteChildren @WorkflowDefinitionName . filtered (== wdn) .~ workflowDefinitionName - ncWorkflowInstance = maybeOrig $ \route -> do - (rScope, WorkflowInstanceR win _) <- hoistMaybe $ route ^? _WorkflowScopeRoute - dbScope <- fmap (view _DBWorkflowScope) . hoist lift $ fromRouteWorkflowScope rScope - Entity _ WorkflowInstance{..} <- lift . lift . getBy404 $ UniqueWorkflowInstance win dbScope - caseChanged win workflowInstanceName - return $ route - & typesUsing @RouteChildren @WorkflowInstanceName . filtered (== win) .~ workflowInstanceName - ncWorkflowPayloadLabel = maybeOrig $ \route -> do - (_, WorkflowWorkflowR cID (WWFilesR wpl _)) <- hoistMaybe $ route ^? _WorkflowScopeRoute - wwId <- decrypt cID - WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId . lift $ get wwId - wwGraph <- lift . lift $ getSharedDBWorkflowGraph workflowWorkflowGraph - [wpl'] <- return . filter (== wpl) . sortOn (CI.original . unWorkflowPayloadLabel) . foldMap Map.keys $ wgnPayloadView <$> wgNodes wwGraph - (caseChanged `on` unWorkflowPayloadLabel) wpl wpl' - return $ route - & typesUsing @RouteChildren @WorkflowPayloadLabel . filtered (== wpl) .~ wpl' verifySubmission = maybeOrig $ \route -> do CSubmissionR _tid _ssh _csh _shn cID sr <- return route sId <- decrypt cID @@ -346,14 +315,6 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) . let newRoute = CNewsR courseTerm courseSchool courseShorthand cID sr tell . Any $ route /= newRoute return newRoute - verifyWorkflowWorkflow = maybeOrig $ \route -> do - (_, WorkflowWorkflowR cID wwR) <- hoistMaybe $ route ^? _WorkflowScopeRoute - wwId <- decrypt cID - WorkflowWorkflow{..} <- lift . lift $ get404 wwId - rScope <- hoist lift . toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope - let newRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID wwR) - tell . Any $ route /= newRoute - return newRoute verifyMaterialVideo = maybeOrig $ \route -> do CMaterialR _tid _ssh _csh _mnm (MVideoR cID) <- return route mfId <- decrypt cID diff --git a/src/Handler/Course/Edit.hs b/src/Handler/Course/Edit.hs index fb426ca94..c9a5e572d 100644 --- a/src/Handler/Course/Edit.hs +++ b/src/Handler/Course/Edit.hs @@ -435,7 +435,7 @@ getCourseNewR = do let newTemplate = courseToForm oldTemplate mempty mempty Nothing in return $ Just $ newTemplate { cfCourseId = Nothing - , cfTerm = TermKey $ TermIdentifier 0 Winter -- invalid, will be ignored; undefined won't work due to strictness + , cfTerm = TermKey $ TermIdentifier 0 Q1 -- invalid, will be ignored; undefined won't work due to strictness , cfRegFrom = Nothing , cfRegTo = Nothing , cfDeRegUntil = Nothing diff --git a/src/Handler/Term.hs b/src/Handler/Term.hs index 015f14bdc..45c820818 100644 --- a/src/Handler/Term.hs +++ b/src/Handler/Term.hs @@ -7,6 +7,7 @@ module Handler.Term import Import import Utils.Course (mayViewCourse) +import Utils.Holidays (bankHolidaysAreaSet, Feiertagsgebiet(..)) import Handler.Utils @@ -19,46 +20,13 @@ import qualified Data.Set as Set import qualified Control.Monad.State.Class as State -import Data.Time.Calendar.WeekDate - - -data TermDay - = TermDayStart | TermDayEnd - | TermDayLectureStart | TermDayLectureEnd - deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable) - deriving anyclass (Universe, Finite) - -guessDay :: TermIdentifier - -> TermDay - -> Day -guessDay TermIdentifier{ year, season = Winter } TermDayStart - = fromGregorian year 10 1 -guessDay TermIdentifier{ year, season = Winter } TermDayEnd - = fromGregorian (succ year) 3 31 -guessDay TermIdentifier{ year, season = Summer } TermDayStart - = fromGregorian year 4 1 -guessDay TermIdentifier{ year, season = Summer } TermDayEnd - = fromGregorian year 9 30 -guessDay tid@TermIdentifier{ year, season = Winter } TermDayLectureStart - = fromWeekDate year (wWeekStart + 2) 1 - where (_, wWeekStart, _) = toWeekDate $ guessDay tid TermDayStart -guessDay tid@TermIdentifier{ year, season = Winter } TermDayLectureEnd - = fromWeekDate (succ year) ((wWeekStart + 21) `div` bool 53 54 longYear) 5 - where longYear = is _Just $ fromWeekDateValid year 53 1 - (_, wWeekStart, _) = toWeekDate $ guessDay tid TermDayStart -guessDay tid@TermIdentifier{ year, season = Summer } TermDayLectureStart - = fromWeekDate year (wWeekStart + 2) 1 - where (_, wWeekStart, _) = toWeekDate $ guessDay tid TermDayStart -guessDay tid@TermIdentifier{ year, season = Summer } TermDayLectureEnd - = fromWeekDate year (wWeekStart + 17) 5 - where (_, wWeekStart, _) = toWeekDate $ guessDay tid TermDayStart validateTerm :: (MonadHandler m, HandlerSite m ~ UniWorX) => FormValidator TermForm m () validateTerm = do TermForm{..} <- State.get - guardValidation MsgTermStartMustMatchName $ tfStart `withinTerm` tfName + guardValidation MsgTermStartMustMatchName $ tfStart `withinTermYear` tfName guardValidation MsgTermEndMustBeAfterStart $ tfStart < tfEnd guardValidation MsgTermLectureEndMustBeAfterStart $ tfLectureStart < tfLectureEnd guardValidation MsgTermStartMustBeBeforeLectureStart $ tfStart <= tfLectureStart @@ -173,13 +141,22 @@ postTermEditR = do let template = case mbLastTerm of Nothing -> mempty (Just Entity{ entityVal=Term{..}}) - -> let ntid = succ termName + -> let ntid = succ termName + tStart = guessDay ntid TermDayStart + tEnd = guessDay ntid TermDayEnd + tLecStart = guessDay ntid TermDayLectureStart + tLecEnd = guessDay ntid TermDayLectureEnd + tHolys = Set.toAscList $ + Set.filter (tStart <=) $ + Set.filter (tEnd >=) $ + Set.unions $ bankHolidaysAreaSet Fraport <$> [getYear tStart..getYear tEnd] in mempty - { tftName = Just ntid - , tftStart = Just $ guessDay ntid TermDayStart - , tftEnd = Just $ guessDay ntid TermDayEnd - , tftLectureStart = Just $ guessDay ntid TermDayLectureStart - , tftLectureEnd = Just $ guessDay ntid TermDayLectureEnd + { tftName = Just ntid + , tftStart = Just tStart + , tftEnd = Just tEnd + , tftLectureStart = Just tLecStart + , tftLectureEnd = Just tLecEnd + , tftHolidays = Just tHolys } termEditHandler Nothing template @@ -228,8 +205,9 @@ termEditHandler mtid template = do lift . audit $ TransactionTermEdit tid addMessageI Success $ MsgTermEdited tid redirect TermShowR - FormMissing -> return () - (FormFailure _) -> addMessageI Warning MsgInvalidInput + FormMissing -> return () + FormFailure [] -> addMessageI Error MsgInvalidInput + FormFailure msgs -> forM_ msgs (addMessage Error . toHtml) defaultLayout $ do setTitleI MsgTermEditHeading wrapForm formWidget def diff --git a/src/Handler/Utils/DateTime.hs b/src/Handler/Utils/DateTime.hs index 7b7dfd322..c39d24103 100644 --- a/src/Handler/Utils/DateTime.hs +++ b/src/Handler/Utils/DateTime.hs @@ -15,7 +15,7 @@ module Handler.Utils.DateTime , addLocalDays , addOneWeek, addWeeks , weeksToAdd - , setYear + , setYear, getYear , ceilingQuarterHour , formatGregorianW ) where @@ -219,6 +219,11 @@ setYear year date = fromGregorian year m d where (_,m,d) = toGregorian date +getYear :: Day -> Integer +getYear date = y + where + (y,_,_) = toGregorian date + addOneWeek :: UTCTime -> UTCTime addOneWeek = addWeeks 1 diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index 34a372192..5929ad04f 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -72,9 +72,8 @@ import qualified Data.ByteString.Base64.URL as Base64 import Data.Aeson.Encode.Pretty (encodePrettyToTextBuilder) import qualified Data.Text.Lazy.Builder as Builder -import qualified Data.Yaml as Yaml - -import Control.Monad.Catch.Pure (runCatch) +-- import qualified Data.Yaml as Yaml +-- import Control.Monad.Catch.Pure (runCatch) import qualified Data.List.NonEmpty as NonEmpty @@ -1436,10 +1435,10 @@ jsonField fieldKind = Field{..} |] fieldEnctype = UrlEncoded +{- was only used in workflows; if needed recreate MsgYAMLFieldDecodeFailure yamlField :: ( ToJSON a, FromJSON a , MonadHandler m - , RenderMessage (HandlerSite m) FormMessage - , RenderMessage (HandlerSite m) UniWorXWorkflowMessage + , RenderMessage (HandlerSite m) FormMessage ) => Field m a yamlField = Field{..} @@ -1454,7 +1453,7 @@ yamlField = Field{..} #{either id (decodeUtf8 . Yaml.encode) val} |] fieldEnctype = UrlEncoded - +-} boolField :: ( MonadHandler m , HandlerSite m ~ UniWorX diff --git a/src/Handler/Utils/Workflow.hs b/src/Handler/Utils/Workflow.hs deleted file mode 100644 index 947e0c6d3..000000000 --- a/src/Handler/Utils/Workflow.hs +++ /dev/null @@ -1,29 +0,0 @@ -module Handler.Utils.Workflow - ( workflowsDisabledWarning - , module Reexport - ) where - -import Import - -import Handler.Utils.I18n - -import Handler.Utils.Workflow.Form as Reexport -import Handler.Utils.Workflow.EdgeForm as Reexport -import Handler.Utils.Workflow.Restriction as Reexport -import Handler.Utils.Workflow.CanonicalRoute as Reexport -import Handler.Utils.Workflow.Workflow as Reexport - - -workflowsDisabledWarning :: ( MonadHandler m - , HandlerSite m ~ UniWorX - , RenderMessage UniWorX titleMsg, RenderMessage UniWorX headingMsg - ) - => titleMsg -> headingMsg - -> m Html - -> m Html -workflowsDisabledWarning tMsg hMsg = volatileBool clusterVolatileWorkflowsEnabled warningHtml - where - warningHtml = liftHandler . siteLayoutMsg hMsg $ do - setTitleI tMsg - - notificationWidget NotificationBroad Warning $(i18nWidgetFile "workflows-disabled") diff --git a/src/Handler/Utils/Workflow/CanonicalRoute.hs b/src/Handler/Utils/Workflow/CanonicalRoute.hs deleted file mode 100644 index 507da9cee..000000000 --- a/src/Handler/Utils/Workflow/CanonicalRoute.hs +++ /dev/null @@ -1,93 +0,0 @@ -module Handler.Utils.Workflow.CanonicalRoute where - -import Import.NoFoundation -import Foundation.Type -import Foundation.Routes - -import Utils.Workflow (RouteWorkflowScope) - - -data WorkflowScopeRoute - = WorkflowInstanceListR - | WorkflowInstanceNewR - | WorkflowInstanceR WorkflowInstanceName WorkflowInstanceR - | WorkflowWorkflowListR - | WorkflowWorkflowR CryptoFileNameWorkflowWorkflow WorkflowWorkflowR - deriving (Eq, Ord, Read, Show, Generic, Typeable) - -data WorkflowInstanceR - = WIEditR | WIDeleteR | WIWorkflowsR | WIInitiateR | WIUpdateR - deriving (Eq, Ord, Read, Show, Generic, Typeable) - -data WorkflowWorkflowR - = WWWorkflowR | WWFilesR WorkflowPayloadLabel CryptoUUIDWorkflowStateIndex | WWEditR | WWDeleteR - deriving (Eq, Ord, Read, Show, Generic, Typeable) - - -_WorkflowScopeRoute :: Prism' (Route UniWorX) (RouteWorkflowScope, WorkflowScopeRoute) -_WorkflowScopeRoute = prism' (uncurry toRoute) toWorkflowScopeRoute - where - toRoute = \case - WSGlobal -> \case - WorkflowInstanceListR -> GlobalWorkflowInstanceListR - WorkflowInstanceNewR -> GlobalWorkflowInstanceNewR - WorkflowInstanceR win subRoute -> GlobalWorkflowInstanceR win $ case subRoute of - WIEditR -> GWIEditR - WIDeleteR -> GWIDeleteR - WIWorkflowsR -> GWIWorkflowsR - WIInitiateR -> GWIInitiateR - WIUpdateR -> GWIUpdateR - WorkflowWorkflowListR -> GlobalWorkflowWorkflowListR - WorkflowWorkflowR wwCID subRoute -> GlobalWorkflowWorkflowR wwCID $ case subRoute of - WWWorkflowR -> GWWWorkflowR - WWFilesR wpl stCID -> GWWFilesR wpl stCID - WWEditR -> GWWEditR - WWDeleteR -> GWWDeleteR - WSSchool ssh -> SchoolR ssh . \case - WorkflowInstanceListR -> SchoolWorkflowInstanceListR - WorkflowInstanceNewR -> SchoolWorkflowInstanceNewR - WorkflowInstanceR win subRoute -> SchoolWorkflowInstanceR win $ case subRoute of - WIEditR -> SWIEditR - WIDeleteR -> SWIDeleteR - WIWorkflowsR -> SWIWorkflowsR - WIInitiateR -> SWIInitiateR - WIUpdateR -> SWIUpdateR - WorkflowWorkflowListR -> SchoolWorkflowWorkflowListR - WorkflowWorkflowR wwCID subRoute -> SchoolWorkflowWorkflowR wwCID $ case subRoute of - WWWorkflowR -> SWWWorkflowR - WWFilesR wpl stCID -> SWWFilesR wpl stCID - WWEditR -> SWWEditR - WWDeleteR -> SWWDeleteR - other -> error $ "not implemented _WorkflowScopeRoute for: " <> show other - toWorkflowScopeRoute = \case - GlobalWorkflowInstanceListR -> Just ( WSGlobal, WorkflowInstanceListR ) - GlobalWorkflowInstanceNewR -> Just ( WSGlobal, WorkflowInstanceNewR ) - GlobalWorkflowInstanceR win subRoute -> Just . (WSGlobal, ) . WorkflowInstanceR win $ case subRoute of - GWIEditR -> WIEditR - GWIDeleteR -> WIDeleteR - GWIWorkflowsR -> WIWorkflowsR - GWIInitiateR -> WIInitiateR - GWIUpdateR -> WIUpdateR - GlobalWorkflowWorkflowListR -> Just ( WSGlobal, WorkflowWorkflowListR ) - GlobalWorkflowWorkflowR wwCID subRoute -> Just . (WSGlobal, ) . WorkflowWorkflowR wwCID $ case subRoute of - GWWWorkflowR -> WWWorkflowR - GWWFilesR wpl stCID -> WWFilesR wpl stCID - GWWEditR -> WWEditR - GWWDeleteR -> WWDeleteR - SchoolR ssh sRoute -> case sRoute of - SchoolWorkflowInstanceListR -> Just ( WSSchool ssh, WorkflowInstanceListR ) - SchoolWorkflowInstanceNewR -> Just ( WSSchool ssh, WorkflowInstanceNewR ) - SchoolWorkflowInstanceR win subRoute -> Just . (WSSchool ssh, ) . WorkflowInstanceR win $ case subRoute of - SWIEditR -> WIEditR - SWIDeleteR -> WIDeleteR - SWIWorkflowsR -> WIWorkflowsR - SWIInitiateR -> WIInitiateR - SWIUpdateR -> WIUpdateR - SchoolWorkflowWorkflowListR -> Just ( WSSchool ssh, WorkflowWorkflowListR ) - SchoolWorkflowWorkflowR wwCID subRoute -> Just . (WSSchool ssh, ) . WorkflowWorkflowR wwCID $ case subRoute of - SWWWorkflowR -> WWWorkflowR - SWWFilesR wpl stCID -> WWFilesR wpl stCID - SWWEditR -> WWEditR - SWWDeleteR -> WWDeleteR - _other -> Nothing - _other -> Nothing diff --git a/src/Handler/Utils/Workflow/EdgeForm.hs b/src/Handler/Utils/Workflow/EdgeForm.hs deleted file mode 100644 index 873f158c2..000000000 --- a/src/Handler/Utils/Workflow/EdgeForm.hs +++ /dev/null @@ -1,624 +0,0 @@ -module Handler.Utils.Workflow.EdgeForm - ( WorkflowEdgeForm(..) - , workflowEdgeForm, WorkflowEdgeFormException(..) - , workflowEdgeFormToAction - ) where - -import Import hiding (StateT) - -import Utils.Form -import Utils.Workflow -import Handler.Utils.Form -import Handler.Utils.Workflow.CanonicalRoute -import Handler.Utils.Widgets -import Handler.Utils.Workflow.Restriction -import Handler.Utils.DateTime - -import qualified ListT - -import Data.RFC5051 (compareUnicode) -import qualified Data.Text as Text -import Text.Unidecode (unidecode) - -import qualified Data.Map as Map -import Data.Map ((!), (!?)) -import qualified Data.Set as Set -import qualified Data.HashMap.Strict.InsOrd as InsOrdHashMap - -import qualified Crypto.MAC.KMAC as Crypto -import qualified Crypto.Saltine.Class as Saltine -import qualified Data.Binary as Binary -import qualified Data.ByteArray as BA -import Crypto.Hash.Algorithms (SHAKE128, SHAKE256) -import Crypto.MAC.KMAC (kmacGetDigest) - -import qualified Control.Monad.State.Class as State -import Control.Monad.Trans.RWS.Lazy (runRWST, mapRWST) -import Control.Monad.Trans.State.Strict (execState, evalStateT) -import Control.Monad.Trans.RWS.Strict (RWST, evalRWST) - -import Data.Bitraversable - -import Data.List (findIndex) -import qualified Data.List as List (delete) - -import qualified Data.Aeson as Aeson -import qualified Data.Scientific as Scientific - -import Numeric.Lens (subtracting) - -import qualified Data.Conduit.Combinators as C - -import qualified Database.Esqueleto.Legacy as E - -import qualified Topograph - -import qualified Text.Blaze as Blaze -import qualified Text.Blaze.Renderer.Text as Blaze - - -{-# ANN module ("HLint: ignore Use newtype instead of data"::String) #-} - - -data WorkflowEdgeForm = WorkflowEdgeForm - { wefEdge :: (WorkflowGraphNodeLabel, WorkflowGraphEdgeLabel) - , wefPayload :: Map WorkflowPayloadLabel (Set (WorkflowFieldPayloadW FileReference UserId)) - } - -data WorkflowEdgeFormException - = WorkflowEdgeFormPayloadFieldReferenceCycle [WorkflowPayloadLabel] - deriving (Eq, Ord, Read, Show, Generic, Typeable) - deriving anyclass (Exception) - -workflowEdgeForm :: ( MonadHandler m - , HandlerSite m ~ UniWorX - , MonadHandler m' - , HandlerSite m' ~ UniWorX - , MonadUnliftIO m' - , MonadThrow m' - ) - => Either WorkflowInstanceId WorkflowWorkflowId - -> Maybe WorkflowEdgeForm - -> SqlPersistT m' (Maybe (AForm m WorkflowEdgeForm)) -workflowEdgeForm mwwId mPrev = runMaybeT $ do - MsgRenderer mr <- getMsgRenderer - - ctx' <- bitraverse (MaybeT . getEntity) (MaybeT . getWorkflowWorkflowState) mwwId - let (scope, sharedGraphId) = case ctx' of - Left (Entity _ WorkflowInstance{..}) -> ( _DBWorkflowScope # workflowInstanceScope - , workflowInstanceGraph - ) - Right (Entity _ WorkflowWorkflow{..}) -> ( _DBWorkflowScope # workflowWorkflowScope - , workflowWorkflowGraph - ) - graph <- lift $ getSharedIdWorkflowGraph sharedGraphId - let wState = ctx ^? _Right . _workflowWorkflowState . to last . _wpTo - wPayload' = ctx ^? _Right . _workflowWorkflowState . re _DBWorkflowState - ctx = bimap entityVal entityVal ctx' - mAuthId <- maybeAuthId - wPayload <- case mwwId of - Right wwId -> workflowStateCurrentPayloads <$> filterM (lift . hoist liftHandler . mayViewWorkflowAction mAuthId wwId) (maybe [] otoList wPayload') - Left _ -> return Map.empty - - rScope <- toRouteWorkflowScope scope - - -- edges :: [((WorkflowGraphNodeLabel, WorkflowGraphEdgeLabel), (I18nText, Map WorkflowPayloadLabel (NonNull (Set (WorkflowPayloadSpec FileReference UserId)))))] - edges <- ListT.toList $ do - (nodeLabel, WGN{..}) <- ListT.fromFoldable . Map.toList $ wgNodes graph - (edgeLabel, edge) <- ListT.fromFoldable $ Map.toList wgnEdges - ((nodeLabel, edgeLabel), ) <$> case edge of - WorkflowGraphEdgeManual{..} -> do - guard $ Just wgeSource == wState - wwId <- hoistMaybe $ mwwId ^? _Right - cID <- lift $ encrypt wwId - guardM . anyM (Set.toList wgeActors) $ \role -> - lift . lift $ is _Authorized <$> hasWorkflowRole (Just wwId) role (_WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)) True - return (wgeDisplayLabel, (wgeForm, wgeMessages)) - WorkflowGraphEdgeInitial{..} -> do - guard $ is _Nothing wState - win <- hoistMaybe $ ctx ^? _Left . _workflowInstanceName - guardM . anyM (Set.toList wgeActors) $ \role -> - lift . lift $ is _Authorized <$> hasWorkflowRole Nothing role (_WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR)) True - return (wgeDisplayLabel, (wgeForm, wgeMessages)) - _other -> mzero - - guard . not $ null edges - - -- edgesOptList :: OptionList (WorkflowGraphNodeLabel, WorkflowGraphEdgeLabel) - edgesOptList <- do - sBoxKey <- secretBoxKey - - let olReadExternal ciphertext = do - edgeIdent <- fromMaybeT . exceptTMaybe $ encodedSecretBoxOpen' sBoxKey ciphertext - guard $ any (\(edgeIdent', _) -> edgeIdent == edgeIdent') edges - return edgeIdent - olOptions' <- ListT.toList $ do - (edgeIdent, (edgeLabel, _)) <- ListT.fromFoldable edges - optionDisplay <- lift $ selectLanguageI18n edgeLabel - let optionInternalValue = edgeIdent - optionExternalValue <- encodedSecretBox' sBoxKey SecretBoxShort edgeIdent - return Option{..} - let olOptions = concat $ do - let optSort = (compareUnicode `on` (Text.toLower . optionDisplay)) - <> comparing (fallbackSortKey . optionInternalValue) - where fallbackSortKey = toDigest . kmaclazy ("workflow-edge-sorting" :: ByteString) (Saltine.encode sBoxKey) . Binary.encode . (mwwId, ) - where toDigest :: Crypto.KMAC (SHAKE256 256) -> ByteString - toDigest = BA.convert - opts <- sortBy optSort olOptions' - & foldr (\opt@Option{..} -> InsOrdHashMap.insertWith (<>) (Text.concatMap (pack . unidecode) optionDisplay) [opt]) InsOrdHashMap.empty - & InsOrdHashMap.elems - - if - | [_] <- opts - -> return opts - | otherwise -> do - return $ zipWith (\Option{..} i -> Option{ optionDisplay = mr $ MsgWorkflowEdgeNumberedVariant optionDisplay i, ..}) opts [1..] - return OptionList{..} - - let edges' = flip sortOn edges $ \(edgeIdent, _) -> flip findIndex (olOptions edgesOptList) $ (== edgeIdent) . optionInternalValue - - let edgeForms :: Map (WorkflowGraphNodeLabel, WorkflowGraphEdgeLabel) (AForm Handler WorkflowEdgeForm) - edgeForms = Map.fromList . flip map edges' $ \(edgeIdent@(tState, _), (_, (WorkflowGraphEdgeForm{..}, edgeMessages))) -> (edgeIdent, ) . fmap (WorkflowEdgeForm edgeIdent) . wFormToAForm . fmap sequenceA $ do - forM_ edgeMessages $ \WorkflowEdgeMessage{..} -> void . runMaybeT $ do - let hasWorkflowRole' role = liftHandler . runDB $ case ctx' of - Right (Entity wwId _) -> do - cID <- encrypt wwId - is _Authorized <$> hasWorkflowRole (Just wwId) role (_WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)) True - Left (Entity _ WorkflowInstance{..}) - -> is _Authorized <$> hasWorkflowRole Nothing role (_WorkflowScopeRoute # (rScope, WorkflowInstanceR workflowInstanceName WIInitiateR)) True - guardM $ anyM (otoList wemViewers) hasWorkflowRole' - whenIsJust wemRestriction $ guard . checkWorkflowRestriction wPayload' - let messageStatus = wemStatus - messageIcon = Nothing - messageContent <- selectLanguageI18n wemContent - lift $ wformMessage Message{..} - - let fieldSort :: [(WorkflowPayloadLabel, [[(Either WorkflowGraphEdgeFormOrder ByteString, WorkflowPayloadSpec FileReference UserId)]])] - -> _ - fieldSort - = sortOn ((,) <$> foldOf (_2 . folded . folded . _1 . _Left) <*> foldMapOf (_2 . folded . folded . _1 . _Right) (Just . Min)) - . over (traverse . _2) (sortOn $ (,) <$> foldOf (folded . _1 . _Left) <*> foldMapOf (folded . _1 . _Right) (Just . Min)) - . over (traverse . _2 . traverse) (sortOn $ (,) <$> preview (_1 . _Left) <*> preview (_1 . _Right)) - orderedFields <- lift . lift . fmap fieldSort . for (Map.toList wgefFields) $ \(payloadLabel, Set.toList . toNullable -> payloadSpecs) -> fmap (payloadLabel, ) . for payloadSpecs $ \(Map.toList . toNullable -> payloadSpecs') -> for payloadSpecs' $ \(payloadOrder, payloadSpec) -> if - | payloadOrder /= mempty -> return (Left payloadOrder, payloadSpec) - | otherwise -> do - sBoxKey <- secretBoxKey - payloadSpec' <- traverseOf (typesCustom @WorkflowChildren @(WorkflowPayloadSpec FileReference UserId) @(WorkflowPayloadSpec FileReference CryptoUUIDUser) @UserId @CryptoUUIDUser) encrypt payloadSpec - let toDigest :: Crypto.KMAC (SHAKE256 256) -> ByteString - toDigest = BA.convert - fallbackSortKey = toDigest . kmaclazy ("workflow-edge-form-payload-field-sorting" :: ByteString) (Saltine.encode sBoxKey) $ Aeson.encode (mwwId, payloadSpec') - return (Right fallbackSortKey, payloadSpec) - - orderedFields' <- flip evalStateT 1 . for orderedFields $ \x@(payloadLabel, _) -> do - let generateDisplayLabel = State.state $ \n -> (mr $ MsgWorkflowEdgeFormHiddenPayload n, succ n) - (mayView, payloadDisplayLabel) <- hoist (lift . lift . runDB) . maybeT ((False, ) <$> generateDisplayLabel) $ - let - displayNameFromState s = do - WorkflowPayloadView{..} <- hoistMaybe . Map.lookup payloadLabel $ Map.findWithDefault Map.empty s (wgnPayloadView <$> wgNodes graph) - wRoute <- case ctx' of - Right (Entity wwId _) -> do - cID <- encrypt wwId - return $ _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR) - Left (Entity _ WorkflowInstance{..}) - -> return $ _WorkflowScopeRoute # (rScope, WorkflowInstanceR workflowInstanceName WIInitiateR) - guardM . anyM (Set.toList $ toNullable wpvViewers) $ \role -> - lift . lift $ is _Authorized <$> hasWorkflowRole (mwwId ^? _Right) role wRoute False - (True, ) <$> selectLanguageI18n wpvDisplayLabel - in displayNameFromState tState <|> maybe mzero displayNameFromState wState - return ((mayView, payloadDisplayLabel), x) - - fields <- for orderedFields' $ \((mayView, payloadDisplayLabel), (payloadLabel, payloadSpecs)) -> (payloadLabel, ) <$> do - let payloadSpecs' = payloadSpecs ^.. folded . folded . _2 - payloadFields = workflowEdgePayloadFields payloadSpecs' $ fmap otoList . Map.lookup payloadLabel =<< prevSrc - where prevSrc = asum - [ wefPayload <$> assertM ((== edgeIdent) . wefEdge) mPrev - , guardOn mayView wPayload - ] - ((payloadRes, isOptional), payloadFieldViews) <- wFormFields payloadFields - return ((payloadDisplayLabel, getAll isOptional), (payloadRes, payloadFieldViews)) - - fields' <- - let payloadReferenceAdjacency = fieldsMap <&> setOf (_2 . _1 . folded . _Left) - fieldsMap :: Map WorkflowPayloadLabel ((Text, Bool), ([Either WorkflowPayloadLabel (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))))], [FieldView UniWorX])) - fieldsMap = Map.fromList fields - resolveReferences :: forall i. Topograph.G WorkflowPayloadLabel i -> [(WorkflowPayloadLabel, ((Text, Bool), ([(Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))))], [FieldView UniWorX])))] - resolveReferences Topograph.G{gVertices, gFromVertex} = resort . Map.toList . flip execState Map.empty . for topoOrder $ \payloadLabel -> whenIsJust (Map.lookup payloadLabel fieldsMap) $ \(payloadDisplay, (payloadRes, payloadFieldViews)) -> State.modify' $ \oldState -> - let payloadRes' = flip concatMap payloadRes $ \case - Right res -> pure res - Left ref -> Map.lookup ref oldState ^. _Just . _2 . _1 - in Map.insert payloadLabel (payloadDisplay, (payloadRes', payloadFieldViews)) oldState - where - topoOrder = map gFromVertex gVertices - resort = sortOn $ \(payloadLabel, _) -> findIndex (views _1 (== payloadLabel)) fields - in either (throwM . WorkflowEdgeFormPayloadFieldReferenceCycle) return $ Topograph.runG payloadReferenceAdjacency resolveReferences - - fmap Map.fromList . for fields' $ \(payloadLabel, ((payloadDisplayLabel, isOptional), (payloadRes, payloadFieldViews))) -> (payloadLabel, ) <$> do - $logWarnS "WorkflowEdgeForm" $ toPathPiece payloadLabel <> ": " <> tshow payloadRes - let payloadRes' = let res = foldMap (views _2 . fmap $ maybe Set.empty (Set.fromList . otoList)) payloadRes - in if | doErrMsg -> FormFailure $ view _FormFailure res ++ pure (mr $ MsgWorkflowEdgeFormPayloadOneFieldRequiredFor payloadDisplayLabel) - | otherwise -> res - doErrMsg = flip none payloadRes $ \res -> view _1 res || hasn't (_2 . _FormSuccess) res - addErrMsg pErrs = Just - [shamlet| - $newline never - $maybe errs <- pErrs - #{errs} -
- #{mr MsgWorkflowEdgeFormPayloadOneFieldRequired} - |] - case payloadFieldViews of - [] -> return () - [fv] -> lift . tell . pure $ fv - & _fvRequired .~ not isOptional - & _fvErrors %~ bool id addErrMsg doErrMsg - _other -> do - fvId <- newIdent - let fvLabel = toHtml payloadDisplayLabel - fvTooltip = Nothing - fvInput = renderFieldViews FormWorkflowDataset $ payloadFieldViews - & traverse . _fvRequired .~ not isOptional - fvErrors = bool id addErrMsg doErrMsg Nothing - fvRequired = not isOptional - in lift . tell $ pure FieldView{..} - return payloadRes' - - return . hoistAForm liftHandler . multiActionAOpts edgeForms (return edgesOptList) actFS $ wefEdge <$> mPrev - where - actFS = fslI MsgWorkflowEdgeFormEdge - -workflowEdgePayloadFields :: [WorkflowPayloadSpec FileReference UserId] - -> Maybe [WorkflowFieldPayloadW FileReference UserId] - -> WForm Handler ([Either WorkflowPayloadLabel (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))))], All) -- ^ @isFilled@, @foldMap ala All . map isOptional@ -workflowEdgePayloadFields specs = evalRWST (forM specs $ runExceptT . renderSpecField) Nothing . fromMaybe [] - where - renderSpecField :: WorkflowPayloadSpec FileReference UserId - -> ExceptT WorkflowPayloadLabel (RWST (Maybe (Text -> Text)) All [WorkflowFieldPayloadW FileReference UserId] (MForm (WriterT [FieldView UniWorX] Handler))) (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))) - renderSpecField (WorkflowPayloadSpec (specField :: WorkflowPayloadField FileReference UserId payload)) = do - let f' :: forall payload' payload''. - _ - => (payload' -> Maybe (NonEmpty payload'')) - -> Bool -- ^ @isOpt@ - -> Field Handler payload' - -> FieldSettings UniWorX - -> Maybe payload' - -> _ (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))) - f' toNonEmpty' isOpt fld fs mx = lift . (<* tell (All isOpt)) . lift $ over (_2 . mapped) (fmap (fmap . review $ _WorkflowFieldPayloadW . _WorkflowFieldPayload) . toNonEmpty' =<<) . bool (is (_FormSuccess . _Just) &&& id) (True, ) isOpt <$> wopt fld fs (Just <$> mx) - f :: forall payload'. - _ - => Bool -- ^ @isOpt@ - -> Field Handler payload' - -> FieldSettings UniWorX - -> Maybe payload' - -> _ (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))) - f = f' (nonEmpty . pure) - extractPrevs :: forall payload' m xs. - ( IsWorkflowFieldPayload' FileReference UserId payload' - , State.MonadState [WorkflowFieldPayloadW FileReference UserId] m - ) - => (payload' -> Maybe xs -> Maybe xs) - -> m (Maybe xs) - extractPrevs accum = State.state $ foldl' go (Nothing, []) . map (matching $ _WorkflowFieldPayloadW @payload' @FileReference @UserId . _WorkflowFieldPayload) - where go (mPrev', xs) (Left x) = (mPrev', xs ++ [x]) - go (acc, xs) (Right p) = case accum p acc of - acc'@(Just _) -> (acc', xs) - Nothing -> (acc, xs ++ [_WorkflowFieldPayloadW @payload' @FileReference @UserId . _WorkflowFieldPayload # p]) - extractPrev :: forall payload' m. - ( IsWorkflowFieldPayload' FileReference UserId payload' - , State.MonadState [WorkflowFieldPayloadW FileReference UserId] m - ) - => m (Maybe payload') - extractPrev = extractPrevs $ \p -> \case - Nothing -> Just p - Just _ -> Nothing - - delTyp :: forall payload' m. - ( State.MonadState [WorkflowFieldPayloadW FileReference UserId] m - , IsWorkflowFieldPayload' FileReference UserId payload' - ) => Proxy payload' -> m () - delTyp _ = State.modify $ \xs' -> - let go [] = [] - go (x:xs) | is (_WorkflowFieldPayloadW @payload') x = xs - | otherwise = x : go xs - in go xs' - - wSetTooltip' :: _ => Maybe Html -> t' (t (MForm (WriterT [FieldView UniWorX] Handler))) a -> t' (t (MForm (WriterT [FieldView UniWorX] Handler))) a - wSetTooltip' tip = hoist (hoist (wSetTooltip tip)) - - - MsgRenderer mr <- getMsgRenderer - LanguageSelectI18n{..} <- getLanguageSelectI18n - mNudge <- ask - - case specField of - WorkflowPayloadFieldText{..} | Nothing <- wpftPresets -> do - prev <- extractPrev @Text - wSetTooltip' (fmap slI18n wpftTooltip) $ - f wpftOptional - (bool (textField & cfStrip) (textareaField & isoField _Wrapped & cfStrip) wpftLarge) - ( fsl (slI18n wpftLabel) - & maybe id (addPlaceholder . slI18n) wpftPlaceholder - & maybe id (addName . ($ "text")) mNudge - ) - (prev <|> wpftDefault) - WorkflowPayloadFieldText{..} | Just (otoList -> opts) <- wpftPresets -> do - prev <- extractPrev @Text - sBoxKey <- secretBoxKey - let offerNothing = wpftOptional || minLength 2 specs - optList = do - WorkflowPayloadTextPreset{..} <- opts - let optionExternalValue = toPathPiece @(Digest (SHAKE128 128)) . kmacGetDigest . kmaclazy ("payload-field-text-enum" :: ByteString) (Saltine.encode sBoxKey) $ Binary.encode optionInternalValue - optionInternalValue = wptpText - return ( Option - { optionDisplay = slI18n wptpLabel - , .. - } - , toWidget . slI18n <$> wptpTooltip - ) - readExternal = flip Map.lookup . Map.fromList $ map (views _1 (optionExternalValue &&& optionInternalValue)) optList - doExplainedSelectionField = has (folded . _wptpTooltip . _Just) opts - wSetTooltip' (fmap slI18n wpftTooltip) $ - f wpftOptional - (bool (selectField' (guardOn offerNothing $ SomeMessage MsgWorkflowEdgeFormEnumFieldNothing) . return $ OptionList (optList ^.. folded . _1) readExternal) - (explainedSelectionField (guardOn offerNothing (SomeMessage MsgWorkflowEdgeFormEnumFieldNothing, Nothing)) $ return (optList, readExternal)) - doExplainedSelectionField - ) - ( fsl (slI18n wpftLabel) - & maybe id (addName . ($ "text")) mNudge - ) - (prev <|> wpftDefault <|> preview (_head . _1 . to optionInternalValue) optList) - WorkflowPayloadFieldNumber{..} -> do - prev <- extractPrev @Scientific - wSetTooltip' (fmap slI18n wpfnTooltip) $ - f wpfnOptional - ( fractionalField - & maybe id (\wpfnMin' -> checkBool (>= wpfnMin') $ MsgWorkflowEdgeFormFieldNumberTooSmall wpfnMin') wpfnMin - & maybe id (\wpfnMax' -> checkBool (>= wpfnMax') $ MsgWorkflowEdgeFormFieldNumberTooSmall wpfnMax') wpfnMax - & maybe id (\wpfnStep' -> flip convertField id . over (maybe id subtracting wpfnMin) $ \n -> fromInteger (round $ n / wpfnStep') * wpfnStep') wpfnStep - ) - ( fsl (slI18n wpfnLabel) - & maybe id (addPlaceholder . slI18n) wpfnPlaceholder - & maybe id (addAttr "min" . tshow . formatScientific Scientific.Fixed Nothing) wpfnMin - & maybe id (addAttr "max" . tshow . formatScientific Scientific.Fixed Nothing) wpfnMax - & maybe (addAttr "step" "any") (addAttr "step" . tshow . formatScientific Scientific.Fixed Nothing) wpfnStep - & maybe id (addName . ($ "number")) mNudge - ) - (prev <|> wpfnDefault) - WorkflowPayloadFieldBool{..} -> do - prev <- extractPrev @Bool - wSetTooltip' (fmap slI18n wpfbTooltip) $ - f (is _Just wpfbOptional) - (maybe checkBoxField (boolField . Just . SomeMessage . slI18n) wpfbOptional) - ( fsl (slI18n wpfbLabel) - & maybe id (addName . ($ "bool")) mNudge - ) - (prev <|> wpfbDefault) - WorkflowPayloadFieldDay{..} -> do - cDay <- localDay . utcToLocalTime <$> liftIO getCurrentTime - let checkPast, checkFuture :: (MonadHandler m, HandlerSite m ~ UniWorX) => Field m Day -> Field m Day - checkPast | Just offset <- wpfdMaxPast - = checkBool ((<= offset) . (cDay `diffDays`)) $ MsgWorkflowEdgeFormFieldDayTooFarPast offset - | otherwise = id - checkFuture | Just offset <- wpfdMaxFuture - = checkBool ((<= offset) . (`diffDays` cDay)) $ MsgWorkflowEdgeFormFieldDayTooFarFuture offset - | otherwise = id - prev <- extractPrev @Day - wSetTooltip' (fmap slI18n wpfdTooltip) $ - f wpfdOptional - ( dayField & checkPast & checkFuture ) - ( fsl (slI18n wpfdLabel) - & maybe id (addName . ($ "day")) mNudge - ) - (prev <|> wpfdDefault) - WorkflowPayloadFieldFile{..} -> do - fRefs <- extractPrevs @FileReference $ \p -> if - | fieldMultiple wpffConfig -> Just . maybe (Set.singleton p) (Set.insert p) - | otherwise -> \case - Nothing -> Just $ Set.singleton p - Just _ -> Nothing - let wpffConfig' = wpffConfig & _fieldAdditionalFiles %~ (fRefs' <>) - where fRefs' = review _FileReferenceFileReferenceTitleMap . Map.fromList $ do - FileReference{..} <- Set.toList =<< hoistMaybe fRefs - return (fileReferenceTitle, (fileReferenceContent, fileReferenceModified, FileFieldUserOption False True)) - wSetTooltip' (fmap slI18n wpffTooltip) $ - f' (nonEmpty . Set.toList) wpffOptional - (convertFieldM (\p -> runConduit $ transPipe liftHandler p .| C.foldMap Set.singleton) yieldMany . genericFileField $ return wpffConfig') - ( fsl (slI18n wpffLabel) - & maybe id (addName . ($ "file")) mNudge - ) - fRefs - WorkflowPayloadFieldUser{..} -> do - fRefs <- extractPrev @UserId - let suggestions uid = E.from $ \user -> do - E.where_ $ user E.^. UserId E.==. E.val uid - return user - wSetTooltip' (fmap slI18n wpfuTooltip) $ - f wpfuOptional - (checkMap (first $ const MsgWorkflowEdgeFormFieldUserNotFound) Right . userField False $ suggestions <$> fRefs) - ( fslI (slI18n wpfuLabel) - & maybe id (addName . ($ "user")) mNudge - ) - (fRefs <|> wpfuDefault) - WorkflowPayloadFieldCaptureUser -> do - mAuthId <- liftHandler maybeAuth - case mAuthId of - Just (Entity uid User{userDisplayName, userSurname}) -> do - fvId <- newIdent - State.modify . List.delete $ _WorkflowFieldPayloadW . _WorkflowFieldPayload # uid - lift . lift . lift . tell $ pure FieldView - { fvLabel = [shamlet|#{mr MsgWorkflowEdgeFormFieldCaptureUserLabel}|] - , fvTooltip = Nothing - , fvId - , fvInput = [whamlet| - $newline never - - ^{nameWidget userDisplayName userSurname} - |] - , fvErrors = Nothing - , fvRequired = False - } - (True, FormSuccess . Just . (:| []) $ _WorkflowFieldPayloadW . _WorkflowFieldPayload # uid) <$ tell (All True) - Nothing -> (False, FormMissing) <$ tell (All False) - WorkflowPayloadFieldCaptureDateTime{..} -> do - let - cLabel = case wpfcdtPrecision of - WFCaptureDate -> MsgWorkflowEdgeFormFieldCaptureDateLabel - WFCaptureTime -> MsgWorkflowEdgeFormFieldCaptureTimeLabel - WFCaptureDateTime -> MsgWorkflowEdgeFormFieldCaptureDateTimeLabel - - fvId <- newIdent - lift . lift . lift . tell $ pure FieldView - { fvLabel = Blaze.toMarkup $ slI18n wpfcdtLabel - , fvTooltip = slI18n <$> wpfcdtTooltip - , fvId - , fvInput = [whamlet| - $newline never - - _{cLabel} - |] - , fvErrors = Nothing - , fvRequired = False - } - - t <- liftIO getCurrentTime - case wpfcdtPrecision of - WFCaptureDate -> do - delTyp $ Proxy @Day - (True, FormSuccess . Just . (:| []) . review (_WorkflowFieldPayloadW . _WorkflowFieldPayload) . localDay $ utcToLocalTime t) <$ tell (All True) - WFCaptureTime -> do - delTyp $ Proxy @TimeOfDay - (True, FormSuccess . Just . (:| []) . review (_WorkflowFieldPayloadW . _WorkflowFieldPayload) . localTimeOfDay $ utcToLocalTime t) <$ tell (All True) - WFCaptureDateTime -> do - delTyp $ Proxy @UTCTime - (True, FormSuccess . Just $ (_WorkflowFieldPayloadW . _WorkflowFieldPayload # t) :| []) <$ tell (All True) - WorkflowPayloadFieldReference{..} -> throwE wpfrTarget - WorkflowPayloadFieldMultiple{..} -> do - fRefs <- nonEmpty <$> State.state (maybe (, []) (splitAt . fromIntegral) $ (+ wpfmMin) <$> wpfmRange) - miIdent <- newIdent - wSetTooltip' (fmap slI18n wpfmTooltip) $ - let mPrev' :: Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)) - mPrev' = fRefs <|> wpfmDefault - mPrev :: Maybe (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId), Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))) - mPrev = Just . Map.fromList . zip [0..] . ensureLength . map (\x -> (Just x, Just $ x :| [])) $ mPrev' ^.. _Just . folded - where - ensureLength :: forall a b. [(Maybe a, Maybe b)] -> [(Maybe a, Maybe b)] - ensureLength = (\l -> (l ++) $ replicate (fromIntegral wpfmMin - length l) (Nothing, Nothing)) . maybe id (take . fromIntegral) ((+ wpfmMin) <$> wpfmRange) - mangleResult :: forall a. - FormResult (Map ListPosition (a, Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))) - -> (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))) - -- FieldMultiple are always filled since `massInput` ensures cardinality constraints (iff @mPrev'@ correctly initializes `massInput` with a list of fields of the appropriate length) - mangleResult res = case matching _FormSuccess res of - Right ress - -> (True, FormSuccess . nonEmpty $ ress ^.. folded . _2 . _Just . folded) - Left res' - -> (False, res') - runMI :: forall a. - WForm (ExceptT WorkflowPayloadLabel Handler) a - -> ExceptT WorkflowPayloadLabel (RWST (Maybe (Text -> Text)) All [WorkflowFieldPayloadW FileReference UserId] (MForm (WriterT [FieldView UniWorX] Handler))) a - runMI mx = do - r <- lift $ lift ask - s <- lift $ lift State.get - ((a, s', w), w') <- ExceptT . lift . lift . lift . runExceptT . runWriterT $ runRWST mx r s - lift . lift $ do - State.put s' - tell w - lift $ tell w' - lift . tell . All $ wpfmMin <= 0 - return a - - miAdd :: ListPosition - -> Natural - -> ListLength - -> (Text -> Text) - -> FieldView UniWorX - -> Maybe (Html -> MForm (ExceptT WorkflowPayloadLabel Handler) (FormResult (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId)) -> FormResult (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId)))), Widget)) - miAdd pos dim liveliness nudge submitView = guardOn (miAllowAdd pos dim liveliness) $ over (mapped . _1 . _FormSuccess) tweakRes . miForm nudge (Left submitView) - where tweakRes :: Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)) - -> Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId)) - -> FormResult (Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId))) - tweakRes newDat prevData = pure . Map.fromList . zip [startKey..] . map Just $ newDat ^.. _Just . folded - where startKey = maybe 0 succ $ fst <$> Map.lookupMax prevData - - miCell :: ListPosition - -> Maybe (WorkflowFieldPayloadW FileReference UserId) - -> Maybe (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))) - -> (Text -> Text) - -> (Html -> MForm (ExceptT WorkflowPayloadLabel Handler) (FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))), Widget)) - miCell _pos dat mPrev'' nudge = miForm nudge . Right $ fromMaybe (fmap (:| []) dat) mPrev'' - - miForm :: (Text -> Text) - -> Either (FieldView UniWorX) (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))) - -> (Html -> MForm (ExceptT WorkflowPayloadLabel Handler) (FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))), Widget)) - miForm nudge mode csrf = do - let runSpecRender :: WriterT [FieldView UniWorX] Handler (Either WorkflowPayloadLabel (Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))), Ints, Enctype) - -> ExceptT WorkflowPayloadLabel Handler (((Bool, FormResult (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId)))), [FieldView UniWorX]), Ints, Enctype) - runSpecRender mSR = do - ((eRes, s, w), fvs) <- lift $ runWriterT mSR - ExceptT . return $ (, s, w) . (, fvs) <$> eRes - ((fFilled, fmRes), fvs') <- mapRWST runSpecRender . fmap (view _1) $ evalRWST (runExceptT $ renderSpecField wpfmSub) (Just $ fromMaybe id mNudge . nudge) (mode ^.. _Right . _Just . folded) - - let fFilled' = fFilled || isn't _FormSuccess fmRes - fmRes' | not fFilled' = FormFailure . pure . maybe (mr MsgValueRequired) (mr . valueRequired) $ fvs ^? _head . to fvLabel' - | otherwise = fmRes - fvLabel' = toStrict . Blaze.renderMarkup . Blaze.contents . fvLabel -- Dirty, but probably good enough; if not: `censor` writer with actual `Text` in `renderSpecField` and discard that information in `workflowEdgePayloadFields` - fvs | not fFilled' = fvs' <&> \fv -> fv { fvErrors = Just - [shamlet| - $newline never - $maybe errs <- fvErrors fv - #{errs} -
- #{mr (valueRequired (fvLabel' fv))} - |] - } - | otherwise = fvs' - valueRequired :: forall msg. _ => msg -> ValueRequired UniWorX - valueRequired = ValueRequired - - return ( fmRes' - , case mode of - Left btn -> $(widgetFile "widgets/massinput/workflow-payload-field-multiple/add") - Right _ -> $(widgetFile "widgets/massinput/workflow-payload-field-multiple/cell") - ) - - miDelete :: forall m. - Monad m - => Map ListPosition (Maybe (WorkflowFieldPayloadW FileReference UserId)) - -> ListPosition - -> MaybeT m (Map ListPosition ListPosition) - miDelete dat pos = do - ListLength l <- hoistMaybe . preview liveCoords $ Map.keysSet dat - guard $ l > wpfmMin - miDeleteList dat pos - - miAllowAdd :: ListPosition - -> Natural - -> ListLength - -> Bool - miAllowAdd _ _ (ListLength l) = maybe True (l <) $ (+ wpfmMin) <$> wpfmRange - - miAddEmpty :: ListPosition - -> Natural - -> ListLength - -> Set ListPosition - miAddEmpty _ _ _ = Set.empty - - miButtonAction :: forall p. - p -> Maybe (SomeRoute UniWorX) - miButtonAction _ = Nothing - - miLayout :: MassInputLayout ListLength (Maybe (WorkflowFieldPayloadW FileReference UserId)) (Maybe (NonEmpty (WorkflowFieldPayloadW FileReference UserId))) - miLayout lLength _ cellWdgts delButtons addWdgts = $(widgetFile "widgets/massinput/workflow-payload-field-multiple/layout") - in runMI . fmap mangleResult $ massInputW MassInput{..} (fslI $ slI18n wpfmLabel) False mPrev - - -workflowEdgeFormToAction :: ( MonadHandler m - , HandlerSite m ~ UniWorX - ) - => WorkflowEdgeForm - -> m (WorkflowAction FileReference UserId) -workflowEdgeFormToAction WorkflowEdgeForm{..} = do - wpUser <- Just <$> maybeAuthId - wpTime <- liftIO getCurrentTime - return WorkflowAction{..} - where - (wpTo, wpVia) = wefEdge - wpPayload = wefPayload diff --git a/src/Handler/Utils/Workflow/Form.hs b/src/Handler/Utils/Workflow/Form.hs deleted file mode 100644 index 8dfc47982..000000000 --- a/src/Handler/Utils/Workflow/Form.hs +++ /dev/null @@ -1,252 +0,0 @@ -module Handler.Utils.Workflow.Form - ( FileIdent - , WorkflowGraphForm(..), FormWorkflowGraph - , workflowGraphForm - , toWorkflowGraphForm, fromWorkflowGraphForm - , WorkflowDescriptionsFormScope(..) - , workflowDescriptionsForm - ) where - -import Import -import Utils.Form -import Utils.Workflow - -import Handler.Utils.Form - -import qualified Data.Conduit.Combinators as C - -import qualified Data.Map as Map -import Data.Map ((!)) -import qualified Data.Set as Set -import qualified Data.CaseInsensitive as CI - -import Data.Bimap (Bimap) -import qualified Data.Bimap as Bimap - -import qualified Control.Monad.State.Class as State - -import qualified Data.List.NonEmpty as NonEmpty - -import qualified Data.Aeson as JSON - -import Utils.Workflow.Lint - -import qualified Data.Yaml as Yaml - -import Control.Monad.Catch.Pure (runCatch) - -import Handler.Utils.Files (sourceFile) - - -newtype FileIdent = FileIdent (CI Text) - deriving (Eq, Ord, Read, Show, Generic, Typeable) - deriving newtype (ToMessage, ToJSON, FromJSON) - -makeWrapped ''FileIdent - -newtype instance FileReferenceTitleMap FileIdent add = FileIdentFileReferenceTitleMap - { unFileIdentFileReferenceTitleMap :: Map FilePath (FileIdentFileReferenceTitleMapElem add) - } deriving (Eq, Ord, Read, Show, Generic, Typeable) - deriving newtype (Semigroup, Monoid) -data FileIdentFileReferenceTitleMapElem add = FileIdentFileReferenceTitleMapElem - { fIdentTitleMapIdent :: FileIdent - , fIdentTitleMapAdditional :: add - } deriving (Eq, Ord, Read, Show, Generic, Typeable) -makePrisms ''FileIdentFileReferenceTitleMapElem - -instance FileReferenceTitleMapConvertible add FileIdent FileIdent where - _FileReferenceTitleMap = iso unFileIdentFileReferenceTitleMap FileIdentFileReferenceTitleMap . traverse . _FileIdentFileReferenceTitleMapElem - -instance FileReferenceTitleMapConvertible add FileIdent FileReference where - _FileReferenceTitleMap = iso unFileIdentFileReferenceTitleMap FileReferenceFileReferenceTitleMap . iso Map.toList Map.fromList . traverse . iso (view $ _2 . _FileIdentFileReferenceTitleMapElem) (\(FileReference{..}, additional) -> (fileReferenceTitle, FileReferenceFileReferenceTitleMapElem fileReferenceContent fileReferenceModified additional)) - -instance FileReferenceTitleMapConvertible add FileReference FileIdent where - _FileReferenceTitleMap = iso unFileReferenceFileReferenceTitleMap FileIdentFileReferenceTitleMap . itraverse . (\f fileReferenceTitle FileReferenceFileReferenceTitleMapElem{ fRefTitleMapContent = fileReferenceContent, fRefTitleMapModified = fileReferenceModified, fRefTitleMapAdditional } -> review _FileIdentFileReferenceTitleMapElem <$> f (FileReference{..}, fRefTitleMapAdditional)) - -instance ToJSON (FileField FileIdent) where - toJSON FileField{..} = JSON.object $ catMaybes - [ ("ident" JSON..=) <$> fieldIdent - , pure $ "unpack-zips" JSON..= fieldUnpackZips - , pure $ "multiple" JSON..= fieldMultiple - , pure $ "restrict-extensions" JSON..= fieldRestrictExtensions - , pure $ "max-file-size" JSON..= fieldMaxFileSize - , pure $ "additional-files" JSON..= addFiles' - ] - where addFiles' = unFileIdentFileReferenceTitleMap fieldAdditionalFiles <&> \FileIdentFileReferenceTitleMapElem{..} -> JSON.object - [ "ident" JSON..= fIdentTitleMapIdent - , "include" JSON..= fIdentTitleMapAdditional - ] -instance FromJSON (FileField FileIdent) where - parseJSON = JSON.withObject "FileField" $ \o -> do - fieldIdent <- o JSON..:? "ident" - fieldUnpackZips <- o JSON..: "unpack-zips" - fieldMultiple <- o JSON..: "multiple" - fieldRestrictExtensions <- o JSON..:? "restrict-extensions" - fieldMaxFileSize <- o JSON..:? "max-file-size" - fieldAllEmptyOk <- o JSON..:? "all-empty-ok" JSON..!= True - addFiles' <- o JSON..:? "additional-files" JSON..!= mempty - fieldAdditionalFiles <- fmap FileIdentFileReferenceTitleMap . for addFiles' $ JSON.withObject "FileIdentFileReferenceTitleMapElem" $ \o' -> do - fIdentTitleMapIdent <- o' JSON..: "Ident" - fIdentTitleMapAdditional <- o' JSON..: "include" - return FileIdentFileReferenceTitleMapElem{..} - return FileField{..} - - - -type FormWorkflowGraph = WorkflowGraph FileIdent CryptoUUIDUser - -data WorkflowGraphForm = WorkflowGraphForm - { wgfGraph :: FormWorkflowGraph - , wgfFiles :: Map FileIdent FileReference - } deriving (Generic, Typeable) - -makeLenses_ ''WorkflowGraphForm - -data WorkflowGraphFormMode = WGFTextInput | WGFFileUpload - deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable) - deriving anyclass (Universe, Finite) -nullaryPathPiece ''WorkflowGraphFormMode $ camelToPathPiece' 1 -embedRenderMessage ''UniWorX ''WorkflowGraphFormMode id - - -workflowGraphForm :: Maybe WorkflowGraphForm -> AForm DB WorkflowGraphForm -workflowGraphForm template = validateAForm validateWorkflowGraphForm . hoistAForm lift $ WorkflowGraphForm - <$> multiActionA (mapF wgfGraphOptions) (fslI MsgWorkflowDefinitionGraph) (Just WGFFileUpload) - <*> filesForm - where - wgfGraphOptions = \case - WGFTextInput -> apreq yamlField (fslI MsgWorkflowDefinitionGraph) (wgfGraph <$> template) - WGFFileUpload -> apreq (checkMMap toGraph fromGraph . singleFileField . foldMap fromGraph $ wgfGraph <$> template) (fslI MsgWorkflowDefinitionGraph) (wgfGraph <$> template) - where - toGraph :: FileUploads -> Handler (Either (SomeMessage UniWorX) FormWorkflowGraph) - toGraph uploads = runExceptT $ do - fRefs <- lift . runConduit $ uploads .| C.take 2 .| C.foldMap pure - fRef <- case fRefs of - [fRef] -> return fRef - _other -> throwE $ SomeMessage MsgWorkflowGraphFormInvalidNumberOfFiles - mContent <- for (fileContent $ sourceFile fRef) $ \fContent -> lift . runDB . runConduit $ fContent .| C.fold - content <- maybe (throwE $ SomeMessage MsgWorkflowGraphFormUploadIsDirectory) return mContent - either (throwE . SomeMessage . MsgYAMLFieldDecodeFailure . displayException) return . runCatch $ Yaml.decodeThrow content - fromGraph :: FormWorkflowGraph -> FileUploads - fromGraph g = yieldM . runDB $ do - fileModified <- liftIO getCurrentTime - fRef <- sinkFile $ File - { fileTitle = "graph.yaml" - , fileContent = Just . yield $ Yaml.encode g - , fileModified - } - insert_ SessionFile - { sessionFileContent = fileReferenceContent fRef - , sessionFileTouched = fileReferenceModified fRef - } - return fRef - - filesForm = Map.fromList <$> massInputAccumEditA fileAdd fileEdit (const Nothing) fileLayout ("workflow-definition-files" :: Text) (fslI MsgWorkflowDefinitionFiles) False (Map.toList . wgfFiles <$> template) - where fileAdd nudge submitView csrf = do - (formRes, formView) <- fileForm nudge Nothing csrf - MsgRenderer mr <- getMsgRenderer - let res' = formRes <&> \newFile@(newFileIdent, _) oldFiles -> if - | any (\(oldFileIdent, _) -> newFileIdent == oldFileIdent) oldFiles - -> FormFailure [mr MsgWorkflowDefinitionFileIdentExists] - | otherwise - -> FormSuccess $ pure newFile - return (res', $(widgetFile "widgets/massinput/workflowDefinitionFiles/add")) - fileEdit nudge = fileForm nudge . Just - fileForm :: (Text -> Text) -> Maybe (FileIdent, FileReference) -> Form (FileIdent, FileReference) - fileForm nudge fileTemplate csrf = do - (fileIdentRes, fileIdentView) <- mpreq (isoField _Unwrapped ciField) (fslI MsgWorkflowDefinitionFileIdent & addName (nudge "ident")) (view _1 <$> fileTemplate) - (fileRes, fileView) <- mpreq (singleFileField $ maybe (return ()) (views _2 yield) fileTemplate) (fslI MsgWorkflowDefinitionFile & addName (nudge "file")) (views _2 yield <$> fileTemplate) - fileRes' <- liftHandler . runDB $ case fileRes of - FormSuccess uploads -> maybe FormMissing FormSuccess <$> runConduit (transPipe liftHandler uploads .| C.head) - FormFailure errs -> return $ FormFailure errs - FormMissing -> return FormMissing - return ((,) <$> fileIdentRes <*> fileRes', $(widgetFile "widgets/massinput/workflowDefinitionFiles/form")) - fileLayout :: MassInputLayout ListLength (FileIdent, FileReference) (FileIdent, FileReference) - fileLayout lLength _ cellWdgts delButtons addWdgts = $(widgetFile "widgets/massinput/workflowDefinitionFiles/layout") - -validateWorkflowGraphForm :: FormValidator WorkflowGraphForm DB () -validateWorkflowGraphForm = do - fIdentsReferenced <- uses _wgfGraph . setOf $ typesCustom @WorkflowChildren - fIdentsAvailable <- uses _wgfFiles Map.keysSet - forM_ (fIdentsReferenced `Set.difference` fIdentsAvailable) $ tellValidationError . MsgWorkflowFileIdentDoesNotExist . views _Wrapped CI.original - - graph <- use _wgfGraph - for_ (lintWorkflowGraph graph) $ \lintIssues -> do - addMessageModal Warning (i18n MsgWorkflowDefinitionWarningLinterIssuesMessage) $ Right - [whamlet| - $newline never - _{MsgWorkflowDefinitionWarningLinterIssues} -