From e0ecbc1bb64bbd72530cb1b1b3f2785312960eea Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Wed, 25 Aug 2021 18:49:46 +0200 Subject: [PATCH 01/21] test: add favicon --- assets/apple-icon-180x180.png | Bin 0 -> 10380 bytes assets/favicon-16x16.png | Bin 0 -> 1064 bytes assets/favicon-32x32.png | Bin 0 -> 1537 bytes assets/favicon-96x96.png | Bin 0 -> 5111 bytes assets/favicon.ico | Bin 0 -> 1150 bytes assets/manifest.webmanifest | 41 ++++++++++++++++++++++++++++++++++ 6 files changed, 41 insertions(+) create mode 100644 assets/apple-icon-180x180.png create mode 100644 assets/favicon-16x16.png create mode 100644 assets/favicon-32x32.png create mode 100644 assets/favicon-96x96.png create mode 100644 assets/favicon.ico create mode 100644 assets/manifest.webmanifest diff --git a/assets/apple-icon-180x180.png b/assets/apple-icon-180x180.png new file mode 100644 index 0000000000000000000000000000000000000000..7616ebd510bb21218fa88b6549daeb3ec4322ad8 GIT binary patch literal 10380 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 HGw}N#*`MiG literal 0 HcmV?d00001 diff --git a/assets/favicon-16x16.png b/assets/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..2dcbf454a2305d79bdc84ed8fa386cb877999b04 GIT binary patch literal 1064 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstUx|vage(c z!@6@aFM%9|WRD45bDP46hOx7_4S6Fo+k-*%fF5l=v3l z6XMFiFp+^_f`GtuKECOkoKv{CrZO>2Vq~1az|hacG*MrFX>#&zS=qV4!CU$HXD~BQ z($ZR#le0fNdw*No>5h&w%E}8|T-ME*bA^|88YAN*7M96@fm@uM);Tz=jf>l5YrC3< zXPUhHyonPpG&P-!irQggvszJcJ_AGFq)8VDt4`51d%Jdf{;nlaTudE}XdG(&!j`?8=###)$_HT{?AVmY_y*a&~(D%Lgmo zJ$ZA8V_Wm_(6eXn9(K5Uxtlpj(>_45eXV(WzrOM$+CT;}NPtWX$tcZDva)i?PcF?( z%`3683IHlCW-wg-f44rG8nB&KRvD?8=@}&qhL%SB3`MO#HR4F-K-Gk2rj%rWlw4k; z$N^L$iKN6gGdH!QvLKZK>?-|&ymb3bVSZ@l_=W&gF&LU!0E5xO$k3uBe$OkQ5`H9e zf-|d984R49rYy311C$Cwk_rv-WJt@*Ndj2sm5l8dO#g|K=&CHt?&fez~JfX=d#Wzp$P!B CAZP^u literal 0 HcmV?d00001 diff --git a/assets/favicon-32x32.png b/assets/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..8741c62bd311716569c2b0a951827615f9849693 GIT binary patch literal 1537 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 gB>+JP2t)XQ5q!YpF literal 0 HcmV?d00001 diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8d3db0787d1fe3cee902f0f9e490bd540835630e GIT binary patch literal 1150 zcmZQzU<5(|0R|wcz>vYhz#zuJz@P!dKp~(AL>x#lFaYI*xFHzK2NDw*82%47Jeh^% z|8!nnvfVXPNa+8TfB>-m83F?T=SoZCG;1ms*Z=AK{3!CvO-%lu?dbuFZSe5;zuwgq zMIJf4rf_ilUtwzce`ie0e~{WW_V)ju9Xs~_aB(qM&))QOI2&0Xj16+zN(+nsE6mLP z?}(26e|g4?|0kQ8{!ioP2J2sCZT``l0@qB_{U&NLktc135YWFHV{A|JwZdV08y_bN`=g zY{XCxa~Cce6t@Qp3jWWQkO12Ya}O@{*u+74X9x=ZpT@(3Hy*L+rL`EW93Ynl0AMD` AsQ>@~ literal 0 HcmV?d00001 diff --git a/assets/manifest.webmanifest b/assets/manifest.webmanifest new file mode 100644 index 000000000..013d4a6a5 --- /dev/null +++ b/assets/manifest.webmanifest @@ -0,0 +1,41 @@ +{ + "name": "App", + "icons": [ + { + "src": "\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ] +} \ No newline at end of file From 4c0357410f5cbcc5b5ea534021bdb8b90742c213 Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Fri, 27 Aug 2021 16:59:35 +0200 Subject: [PATCH 02/21] chore(ui): change favicon and logo --- assets/favicon-0.png | Bin 1027 -> 0 bytes assets/favicon-16x16.png | Bin 1064 -> 0 bytes assets/favicon-32x32.png | Bin 1537 -> 0 bytes assets/favicon-5.png | Bin 557 -> 0 bytes assets/favicon-96x96.png | Bin 5111 -> 0 bytes assets/favicon.svg | 30 +---------------- assets/fraport_logo_2016_text.svg | 18 ++++++++++ assets/fraport_logo_notext.svg | 37 +++++++++++++++++++++ assets/fraport_logo_text.svg | 3 ++ assets/lmu/logo.svg | 6 ---- assets/lmu/sigillum.svg | 19 ----------- frontend/src/utils/asidenav/asidenav.sass | 2 +- src/Foundation/SiteLayout.hs | 2 +- src/Settings/StaticFiles.hs | 3 +- templates/widgets/asidenav/asidenav.hamlet | 8 ++--- webpack.config.js | 7 +--- 16 files changed, 65 insertions(+), 70 deletions(-) delete mode 100644 assets/favicon-0.png delete mode 100644 assets/favicon-16x16.png delete mode 100644 assets/favicon-32x32.png delete mode 100644 assets/favicon-5.png delete mode 100644 assets/favicon-96x96.png create mode 100644 assets/fraport_logo_2016_text.svg create mode 100755 assets/fraport_logo_notext.svg create mode 100644 assets/fraport_logo_text.svg delete mode 100644 assets/lmu/logo.svg delete mode 100644 assets/lmu/sigillum.svg diff --git a/assets/favicon-0.png b/assets/favicon-0.png deleted file mode 100644 index 68830769b8363c1ee3c0cfc7b9fc2e6b82ab5527..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1027 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5Fo{p?&#~tz_78O`%fY(kgt&J z5#-CjP^HGe(9pub@Czu^@PdJ%)PRBERRRNp)eHs(@q#(K0&Rd2IRQQ)u0Wb|vLokY zXU@qETvMF5raEy>a^jxo!~tYab`t1!;O=+e0&*uia8GjL0J0}Lb4_vts^OaA%r)78 z10)C*2dQ`D1dkB5x zzZWoQa7b_nxPSkAKth7T{qy%1Y*3ic-@l+>FY9NZYQ`jQcNb-?q#P>-2Bu0+7srr_ zTW{~44GMM?X}#zY@KZ2AS);3=@_&2Eztn@;HC8ScB$osI}XzP6T^L^MgqSpUKSHf^l zegSX!Kj%yL>(wqZRXaXn0VN<5P*LBYx8QTVYX8Z!6L}{vN`P^T=*1t6@O{Mi0R}i zhKKP+xhqWeKG?_|vv&f9f`SR=)-za+tT)}~4>Vo1#5JNMC9x#cD!C{XNHG{07+UHY z80i|Ch8S2_85mg^T4);>S{WF)gsG>aXvob^$xN$6XfO#eG_*1`urjoSXb94oBoA`G Mr>mdKI;Vst0OQ>(-2eap diff --git a/assets/favicon-16x16.png b/assets/favicon-16x16.png deleted file mode 100644 index 2dcbf454a2305d79bdc84ed8fa386cb877999b04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1064 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstUx|vage(c z!@6@aFM%9|WRD45bDP46hOx7_4S6Fo+k-*%fF5l=v3l z6XMFiFp+^_f`GtuKECOkoKv{CrZO>2Vq~1az|hacG*MrFX>#&zS=qV4!CU$HXD~BQ z($ZR#le0fNdw*No>5h&w%E}8|T-ME*bA^|88YAN*7M96@fm@uM);Tz=jf>l5YrC3< zXPUhHyonPpG&P-!irQggvszJcJ_AGFq)8VDt4`51d%Jdf{;nlaTudE}XdG(&!j`?8=###)$_HT{?AVmY_y*a&~(D%Lgmo zJ$ZA8V_Wm_(6eXn9(K5Uxtlpj(>_45eXV(WzrOM$+CT;}NPtWX$tcZDva)i?PcF?( z%`3683IHlCW-wg-f44rG8nB&KRvD?8=@}&qhL%SB3`MO#HR4F-K-Gk2rj%rWlw4k; z$N^L$iKN6gGdH!QvLKZK>?-|&ymb3bVSZ@l_=W&gF&LU!0E5xO$k3uBe$OkQ5`H9e zf-|d984R49rYy311C$Cwk_rv-WJt@*Ndj2sm5l8dO#g|K=&CHt?&fez~JfX=d#Wzp$P!B CAZP^u diff --git a/assets/favicon-32x32.png b/assets/favicon-32x32.png deleted file mode 100644 index 8741c62bd311716569c2b0a951827615f9849693..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1537 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 gB>+JP2t)XQ5q!YpF diff --git a/assets/favicon.svg b/assets/favicon.svg index 3ac8d6f3f..fa5b2449a 100644 --- a/assets/favicon.svg +++ b/assets/favicon.svg @@ -1,29 +1 @@ - - - - - - image/svg+xml - - - - - - - - + \ No newline at end of file diff --git a/assets/fraport_logo_2016_text.svg b/assets/fraport_logo_2016_text.svg new file mode 100644 index 000000000..88b756641 --- /dev/null +++ b/assets/fraport_logo_2016_text.svg @@ -0,0 +1,18 @@ + + + + + + image/svg+xml + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/fraport_logo_notext.svg b/assets/fraport_logo_notext.svg new file mode 100755 index 000000000..1572ba45a --- /dev/null +++ b/assets/fraport_logo_notext.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + diff --git a/assets/fraport_logo_text.svg b/assets/fraport_logo_text.svg new file mode 100644 index 000000000..27a9b6d8d --- /dev/null +++ b/assets/fraport_logo_text.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/lmu/logo.svg b/assets/lmu/logo.svg deleted file mode 100644 index 6b72bb7b9..000000000 --- a/assets/lmu/logo.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/lmu/sigillum.svg b/assets/lmu/sigillum.svg deleted file mode 100644 index 78538233a..000000000 --- a/assets/lmu/sigillum.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - diff --git a/frontend/src/utils/asidenav/asidenav.sass b/frontend/src/utils/asidenav/asidenav.sass index 5bca8b347..56f1647c7 100644 --- a/frontend/src/utils/asidenav/asidenav.sass +++ b/frontend/src/utils/asidenav/asidenav.sass @@ -134,7 +134,7 @@ .asidenav__logo-lmu width: 80px - height: 100% + height: 80% .asidenav__logo-uni2work display: flex diff --git a/src/Foundation/SiteLayout.hs b/src/Foundation/SiteLayout.hs index ebec84d65..278314986 100644 --- a/src/Foundation/SiteLayout.hs +++ b/src/Foundation/SiteLayout.hs @@ -457,7 +457,7 @@ siteLayout' overrideHeading widget = do asidenav :: WidgetFor UniWorX () asidenav = $(widgetFile "widgets/asidenav/asidenav") where - logo = preEscapedToMarkup $ decodeUtf8 $(embedFile "assets/lmu/logo.svg") + logo = preEscapedToMarkup $ decodeUtf8 $(embedFile "assets/fraport_logo_notext.svg") footer :: WidgetFor UniWorX () footer = $(widgetFile "widgets/footer/footer") where isNavFooter = has $ _1 . _NavFooter diff --git a/src/Settings/StaticFiles.hs b/src/Settings/StaticFiles.hs index a4138d193..475573948 100644 --- a/src/Settings/StaticFiles.hs +++ b/src/Settings/StaticFiles.hs @@ -3,8 +3,7 @@ -- prevents rebuilds if files change, that are not directly used (like -- webpack bundles) module Settings.StaticFiles - ( img_lmu_sigillum_svg - , webpackLinks_main + ( webpackLinks_main , embeddedStatic , module Yesod.EmbeddedStatic ) where diff --git a/templates/widgets/asidenav/asidenav.hamlet b/templates/widgets/asidenav/asidenav.hamlet index 67f6d890e..e3a63c379 100644 --- a/templates/widgets/asidenav/asidenav.hamlet +++ b/templates/widgets/asidenav/asidenav.hamlet @@ -4,9 +4,7 @@ $newline never
#{logo} - - - _{MsgLogo} +
$if null favouriteTerms && is _Just muid @@ -49,6 +47,4 @@ $newline never $if anyFavToggle
_{MsgFavouritesToggleTip} - -
- + \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 7203cb659..6e71b96dc 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -145,12 +145,7 @@ async function webpackConfig() { path.resolve(__dirname, 'well-known'), ] }), - new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), - new CopyPlugin({ - patterns: [ - { from: 'assets/lmu/sigillum.svg', to: path.resolve(__dirname, 'static', 'img/lmu/sigillum.svg') }, - ] - }), + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), new webpack.DefinePlugin({ VERSION: JSON.stringify(packageVersion) }), From c6264f75b4034c76168f07c2731af1b77ae8b16c Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Fri, 27 Aug 2021 17:00:22 +0200 Subject: [PATCH 03/21] fix(haddock): hoogle.sh fails on a comment, turned into normal comment --- src/Model/Types/Submission.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/Types/Submission.hs b/src/Model/Types/Submission.hs index 676b64776..3855ef5a9 100644 --- a/src/Model/Types/Submission.hs +++ b/src/Model/Types/Submission.hs @@ -149,7 +149,7 @@ data AuthorshipStatementSubmissionState deriving (Eq, Read, Show, Enum, Bounded, Generic, Typeable) deriving anyclass (Universe, Finite) -deriving stock instance Ord AuthorshipStatementSubmissionState -- ^ Larger roughly encodes better; summaries are taken with `max` +deriving stock instance Ord AuthorshipStatementSubmissionState -- Larger roughly encodes better; summaries are taken with max nullaryPathPiece ''AuthorshipStatementSubmissionState $ camelToPathPiece' 1 pathPieceCsv ''AuthorshipStatementSubmissionState From 72d481e5d69e15ac7f1cfb7bc4a1eed84208690f Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Mon, 30 Aug 2021 17:08:23 +0200 Subject: [PATCH 04/21] style(asidenav): use logo with text --- frontend/src/utils/asidenav/asidenav.sass | 2 +- src/Foundation/SiteLayout.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/utils/asidenav/asidenav.sass b/frontend/src/utils/asidenav/asidenav.sass index 56f1647c7..5bca8b347 100644 --- a/frontend/src/utils/asidenav/asidenav.sass +++ b/frontend/src/utils/asidenav/asidenav.sass @@ -134,7 +134,7 @@ .asidenav__logo-lmu width: 80px - height: 80% + height: 100% .asidenav__logo-uni2work display: flex diff --git a/src/Foundation/SiteLayout.hs b/src/Foundation/SiteLayout.hs index 278314986..be39543fc 100644 --- a/src/Foundation/SiteLayout.hs +++ b/src/Foundation/SiteLayout.hs @@ -457,7 +457,7 @@ siteLayout' overrideHeading widget = do asidenav :: WidgetFor UniWorX () asidenav = $(widgetFile "widgets/asidenav/asidenav") where - logo = preEscapedToMarkup $ decodeUtf8 $(embedFile "assets/fraport_logo_notext.svg") + logo = preEscapedToMarkup $ decodeUtf8 $(embedFile "assets/fraport_logo_text.svg") footer :: WidgetFor UniWorX () footer = $(widgetFile "widgets/footer/footer") where isNavFooter = has $ _1 . _NavFooter From 5a0f2dfc3822d950118aa70770124edddb7512c2 Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Wed, 1 Sep 2021 15:34:06 +0200 Subject: [PATCH 05/21] chore(frontend): switch default theme --- models/users.model | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/users.model b/models/users.model index 707da5e2f..e5d35e43e 100644 --- a/models/users.model +++ b/models/users.model @@ -24,7 +24,7 @@ User json -- Each Uni2work user has a corresponding row in this table; create title Text Maybe -- For upcoming name customisation maxFavourites Int default=12 -- max number of non-manual entries in favourites bar (pruned only if below a set importance threshold) maxFavouriteTerms Int default=2 -- max number of term-sections in favourites bar - theme Theme default='Default' -- Color-theme of the frontend; user-defined + theme Theme default='SkyLove' -- Color-theme of the frontend; user-defined dateTimeFormat DateTimeFormat "default='%a %d %b %Y %R'" -- preferred Date+Time display format for user; user-defined dateFormat DateTimeFormat "default='%d.%m.%Y'" -- preferred Date-only display format for user; user-defined timeFormat DateTimeFormat "default='%R'" -- preferred Time-only display format for user; user-defined From 686976c3c21afd50488c1b7407e393c1fefb5676 Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Fri, 3 Sep 2021 13:22:47 +0200 Subject: [PATCH 06/21] chore(frontend): replace default theme colors with fraport blue. unprofessional, but ok for a start --- frontend/src/app.sass | 10 +++++----- frontend/src/utils/asidenav/asidenav.sass | 2 +- models/users.model | 2 +- templates/default-layout-wrapper.hamlet | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/src/app.sass b/frontend/src/app.sass index 8665aad07..70d767b22 100644 --- a/frontend/src/app.sass +++ b/frontend/src/app.sass @@ -49,12 +49,12 @@ body body // DEFAULT LMU THEME - --color-lmu-green: #0a9342 + --color-lmu-green: #000091 --color-primary: var(--color-lmu-green) - --color-light: #31cc72 - --color-lighter: #35db7a - --color-dark: #087536 - --color-darker: #075728 + --color-light: #8c96c8 + --color-lighter: #cccce9 + --color-dark: #6666bd + --color-darker: #3333a7 --color-link: var(--color-font) --color-link-hover: var(--color-font) --color-lmu-box-border: var(--color-lightwhite) diff --git a/frontend/src/utils/asidenav/asidenav.sass b/frontend/src/utils/asidenav/asidenav.sass index 5bca8b347..9b4a97b98 100644 --- a/frontend/src/utils/asidenav/asidenav.sass +++ b/frontend/src/utils/asidenav/asidenav.sass @@ -133,7 +133,7 @@ color: var(--color-primary) .asidenav__logo-lmu - width: 80px + width: 160px height: 100% .asidenav__logo-uni2work diff --git a/models/users.model b/models/users.model index e5d35e43e..707da5e2f 100644 --- a/models/users.model +++ b/models/users.model @@ -24,7 +24,7 @@ User json -- Each Uni2work user has a corresponding row in this table; create title Text Maybe -- For upcoming name customisation maxFavourites Int default=12 -- max number of non-manual entries in favourites bar (pruned only if below a set importance threshold) maxFavouriteTerms Int default=2 -- max number of term-sections in favourites bar - theme Theme default='SkyLove' -- Color-theme of the frontend; user-defined + theme Theme default='Default' -- Color-theme of the frontend; user-defined dateTimeFormat DateTimeFormat "default='%a %d %b %Y %R'" -- preferred Date+Time display format for user; user-defined dateFormat DateTimeFormat "default='%d.%m.%Y'" -- preferred Date-only display format for user; user-defined timeFormat DateTimeFormat "default='%R'" -- preferred Time-only display format for user; user-defined diff --git a/templates/default-layout-wrapper.hamlet b/templates/default-layout-wrapper.hamlet index 9626ad816..eeb49873b 100644 --- a/templates/default-layout-wrapper.hamlet +++ b/templates/default-layout-wrapper.hamlet @@ -7,7 +7,7 @@ $newline never $case currentTheme $of ThemeDefault - + $of ThemeLavender $of ThemeNeutralBlue From c4fc67d4e8acf86630cc01ce425a84b42a0a3b4c Mon Sep 17 00:00:00 2001 From: Steffen Jost Date: Tue, 7 Sep 2021 12:57:53 +0200 Subject: [PATCH 07/21] chore: remove workflows --- .../categories/workflows/de-de-formal.msg | 163 --- .../uniworx/categories/workflows/en-eu.msg | 163 --- .../navigation/breadcrumbs/de-de-formal.msg | 25 - .../utils/navigation/breadcrumbs/en-eu.msg | 25 - .../utils/navigation/menu/de-de-formal.msg | 18 - .../uniworx/utils/navigation/menu/en-eu.msg | 18 - models/workflows.model | 53 - package.yaml | 14 - routes | 48 +- src/Application.hs | 1 - src/CryptoID.hs | 24 - src/Foundation/Authorization.hs | 343 +---- src/Foundation/I18n.hs | 25 +- src/Foundation/Navigation.hs | 381 +---- src/Foundation/Routes.hs | 12 - src/Foundation/Yesod/Middleware.hs | 41 +- src/Handler/Utils/Form.hs | 11 +- src/Handler/Utils/Workflow.hs | 29 - src/Handler/Utils/Workflow/CanonicalRoute.hs | 93 -- src/Handler/Utils/Workflow/EdgeForm.hs | 624 -------- src/Handler/Utils/Workflow/Form.hs | 252 ---- src/Handler/Utils/Workflow/Restriction.hs | 29 - src/Handler/Utils/Workflow/Workflow.hs | 99 -- src/Handler/Workflow.hs | 7 - src/Handler/Workflow/Definition.hs | 9 - src/Handler/Workflow/Definition/Delete.hs | 52 - src/Handler/Workflow/Definition/Edit.hs | 102 -- src/Handler/Workflow/Definition/Form.hs | 44 - .../Workflow/Definition/Instantiate.hs | 61 - src/Handler/Workflow/Definition/List.hs | 143 -- src/Handler/Workflow/Definition/New.hs | 62 - src/Handler/Workflow/Instance.hs | 10 - src/Handler/Workflow/Instance/Delete.hs | 21 - src/Handler/Workflow/Instance/Edit.hs | 27 - src/Handler/Workflow/Instance/Form.hs | 80 -- src/Handler/Workflow/Instance/Initiate.hs | 91 -- src/Handler/Workflow/Instance/List.hs | 247 ---- src/Handler/Workflow/Instance/New.hs | 83 -- src/Handler/Workflow/Instance/Update.hs | 123 -- src/Handler/Workflow/Workflow.hs | 9 - src/Handler/Workflow/Workflow/Delete.hs | 21 - src/Handler/Workflow/Workflow/Edit.hs | 21 - src/Handler/Workflow/Workflow/List.hs | 527 ------- src/Handler/Workflow/Workflow/New.hs | 10 - src/Handler/Workflow/Workflow/Workflow.hs | 280 ---- src/Jobs/Handler/Files.hs | 19 - src/Model/Migration/Definitions.hs | 51 - src/Model/Types.hs | 1 - src/Model/Types/Security.hs | 3 +- src/Model/Types/Workflow.hs | 1269 ----------------- src/Utils/Lens.hs | 15 - src/Utils/Workflow.hs | 224 --- src/Utils/Workflow/Lint.hs | 142 -- test/Database/Fill.hs | 60 +- wflint.sh | 9 - wflint/WFLint.hs | 29 - 56 files changed, 42 insertions(+), 6301 deletions(-) delete mode 100644 messages/uniworx/categories/workflows/de-de-formal.msg delete mode 100644 messages/uniworx/categories/workflows/en-eu.msg delete mode 100644 models/workflows.model delete mode 100644 src/Handler/Utils/Workflow.hs delete mode 100644 src/Handler/Utils/Workflow/CanonicalRoute.hs delete mode 100644 src/Handler/Utils/Workflow/EdgeForm.hs delete mode 100644 src/Handler/Utils/Workflow/Form.hs delete mode 100644 src/Handler/Utils/Workflow/Restriction.hs delete mode 100644 src/Handler/Utils/Workflow/Workflow.hs delete mode 100644 src/Handler/Workflow.hs delete mode 100644 src/Handler/Workflow/Definition.hs delete mode 100644 src/Handler/Workflow/Definition/Delete.hs delete mode 100644 src/Handler/Workflow/Definition/Edit.hs delete mode 100644 src/Handler/Workflow/Definition/Form.hs delete mode 100644 src/Handler/Workflow/Definition/Instantiate.hs delete mode 100644 src/Handler/Workflow/Definition/List.hs delete mode 100644 src/Handler/Workflow/Definition/New.hs delete mode 100644 src/Handler/Workflow/Instance.hs delete mode 100644 src/Handler/Workflow/Instance/Delete.hs delete mode 100644 src/Handler/Workflow/Instance/Edit.hs delete mode 100644 src/Handler/Workflow/Instance/Form.hs delete mode 100644 src/Handler/Workflow/Instance/Initiate.hs delete mode 100644 src/Handler/Workflow/Instance/List.hs delete mode 100644 src/Handler/Workflow/Instance/New.hs delete mode 100644 src/Handler/Workflow/Instance/Update.hs delete mode 100644 src/Handler/Workflow/Workflow.hs delete mode 100644 src/Handler/Workflow/Workflow/Delete.hs delete mode 100644 src/Handler/Workflow/Workflow/Edit.hs delete mode 100644 src/Handler/Workflow/Workflow/List.hs delete mode 100644 src/Handler/Workflow/Workflow/New.hs delete mode 100644 src/Handler/Workflow/Workflow/Workflow.hs delete mode 100644 src/Model/Types/Workflow.hs delete mode 100644 src/Utils/Workflow.hs delete mode 100644 src/Utils/Workflow/Lint.hs delete mode 100755 wflint.sh delete mode 100644 wflint/WFLint.hs 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/package.yaml b/package.yaml index 2793c89b4..a872fbd1c 100644 --- a/package.yaml +++ b/package.yaml @@ -297,20 +297,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..cfa04f60a 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -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..2d3a82ed2 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" @@ -294,7 +290,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 +433,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 +490,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/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} -