feat(workflows): wire up ws-school

This commit is contained in:
Gregor Kleen 2020-11-24 21:41:24 +01:00
parent f2fb7d8c26
commit 82b3a6364c
32 changed files with 600 additions and 128 deletions

View File

@ -456,7 +456,7 @@ MaterialVideoDownload: Herunterladen
Unauthorized: Sie haben hierfür keine explizite Berechtigung. Unauthorized: Sie haben hierfür keine explizite Berechtigung.
UnauthorizedAnd l@Text r@Text: (#{l} UND #{r}) UnauthorizedAnd l@Text r@Text: (#{l} UND #{r})
UnauthorizedOr l@Text r@Text: (#{l} ODER #{r}) UnauthorizedOr l@Text r@Text: (#{l} ODER #{r})
UnauthorizedNot r@Text: (NICHT #{r}) UnauthorizedNot r@Text: #{r}
UnauthorizedNoToken: Ihrer Anfrage war kein Authorisierungs-Token beigefügt. UnauthorizedNoToken: Ihrer Anfrage war kein Authorisierungs-Token beigefügt.
UnauthorizedTokenExpired: Ihr Authorisierungs-Token ist abgelaufen. UnauthorizedTokenExpired: Ihr Authorisierungs-Token ist abgelaufen.
UnauthorizedTokenNotStarted: Ihr Authorisierungs-Token ist noch nicht gültig. UnauthorizedTokenNotStarted: Ihr Authorisierungs-Token ist noch nicht gültig.
@ -1428,10 +1428,13 @@ MenuWorkflowInstanceDelete: Löschen
MenuWorkflowInstanceWorkflows: Laufende Workflows MenuWorkflowInstanceWorkflows: Laufende Workflows
MenuWorkflowInstanceInitiate: Workflow starten MenuWorkflowInstanceInitiate: Workflow starten
MenuWorkflowInstanceEdit: Bearbeiten MenuWorkflowInstanceEdit: Bearbeiten
MenuWorkflowWorkflowList: Laufende Workflows
MenuWorkflowWorkflowEdit: Editieren MenuWorkflowWorkflowEdit: Editieren
MenuWorkflowWorkflowDelete: Löschen MenuWorkflowWorkflowDelete: Löschen
MenuGlobalWorkflowInstanceList: Workflows MenuGlobalWorkflowInstanceList: Systemweite Workflows
MenuGlobalWorkflowWorkflowList: Laufende Workflows MenuTopWorkflowInstanceList: Workflows
MenuTopWorkflowWorkflowList: Laufende Workflows
MenuTopWorkflowWorkflowListHeader: Workflows
BreadcrumbSubmissionFile: Datei BreadcrumbSubmissionFile: Datei
BreadcrumbSubmissionUserInvite: Einladung zur Abgabe BreadcrumbSubmissionUserInvite: Einladung zur Abgabe
@ -1524,12 +1527,14 @@ BreadcrumbWorkflowInstanceWorkflowList: Laufende Workflows
BreadcrumbWorkflowInstanceInitiate: Workflow starten BreadcrumbWorkflowInstanceInitiate: Workflow starten
BreadcrumbWorkflowInstanceList: Workflows BreadcrumbWorkflowInstanceList: Workflows
BreadcrumbWorkflowInstanceNew: Neuer Workflow BreadcrumbWorkflowInstanceNew: Neuer Workflow
BreadcrumbWorkflowWorkflowList: Laufende Workflows
BreadcrumbWorkflowWorkflow workflow@CryptoFileNameWorkflowWorkflow: #{toPathPiece workflow} BreadcrumbWorkflowWorkflow workflow@CryptoFileNameWorkflowWorkflow: #{toPathPiece workflow}
BreadcrumbWorkflowWorkflowFiles: Dateien BreadcrumbWorkflowWorkflowFiles: Dateien
BreadcrumbWorkflowWorkflowEdit: Editieren BreadcrumbWorkflowWorkflowEdit: Editieren
BreadcrumbWorkflowWorkflowDelete: Löschen BreadcrumbWorkflowWorkflowDelete: Löschen
BreadcrumbGlobalWorkflowInstanceList: Workflows BreadcrumbGlobalWorkflowInstanceList: Systemweite Workflows
BreadcrumbGlobalWorkflowWorkflowList: Laufende Workflows BreadcrumbTopWorkflowInstanceList: Workflows
BreadcrumbTopWorkflowWorkflowList: Laufende Workflows
ExternalExamEdit coursen@CourseName examn@ExamName: Bearbeiten: #{coursen}, #{examn} ExternalExamEdit coursen@CourseName examn@ExamName: Bearbeiten: #{coursen}, #{examn}
ExternalExamGrades coursen@CourseName examn@ExamName: Prüfungsleistungen: #{coursen}, #{examn} ExternalExamGrades coursen@CourseName examn@ExamName: Prüfungsleistungen: #{coursen}, #{examn}
@ -1571,7 +1576,7 @@ AuthTagParticipant: Nutzer ist mit Kurs assoziiert
AuthTagApplicant: Nutzer ist mit Bewerber zum Kurs AuthTagApplicant: Nutzer ist mit Bewerber zum Kurs
AuthTagRegisterGroup: Nutzer ist nicht Mitglied eines anderen Tutoriums mit der selben Registrierungs-Gruppe AuthTagRegisterGroup: Nutzer ist nicht Mitglied eines anderen Tutoriums mit der selben Registrierungs-Gruppe
AuthTagCapacity: Kapazität ist ausreichend AuthTagCapacity: Kapazität ist ausreichend
AuthTagEmpty: Kurs hat keine Teilnehmer AuthTagEmpty: Ressource ist „leer“
AuthTagMaterials: Kursmaterialien sind freigegeben AuthTagMaterials: Kursmaterialien sind freigegeben
AuthTagOwner: Nutzer ist Besitzer AuthTagOwner: Nutzer ist Besitzer
AuthTagPersonalisedSheetFiles: Nutzer verfügt über personalisierte Übungsblatt-Dateien AuthTagPersonalisedSheetFiles: Nutzer verfügt über personalisierte Übungsblatt-Dateien
@ -3039,6 +3044,12 @@ GlobalWorkflowInstancesTitle: Workflows (Systemweit)
GlobalWorkflowInstanceInitiateHeading workflowInstanceTitle@Text: Worklow initiieren: #{workflowInstanceTitle} GlobalWorkflowInstanceInitiateHeading workflowInstanceTitle@Text: Worklow initiieren: #{workflowInstanceTitle}
GlobalWorkflowInstanceInitiateTitle: Worklow initiieren GlobalWorkflowInstanceInitiateTitle: Worklow initiieren
SchoolWorkflowInstancesHeading ssh@SchoolId: Workflows (#{ssh})
SchoolWorkflowInstancesTitle ssh@SchoolId: Workflows (#{ssh})
SchoolWorkflowInstanceInitiateHeading ssh@SchoolId workflowInstanceTitle@Text: Worklow initiieren: #{ssh}, #{workflowInstanceTitle}
SchoolWorkflowInstanceInitiateTitle ssh@SchoolId: Worklow initiieren: #{ssh}
WorkflowEdgeNumberedVariant edgeLabel@Text i@Natural: #{edgeLabel} (Variante #{i}) WorkflowEdgeNumberedVariant edgeLabel@Text i@Natural: #{edgeLabel} (Variante #{i})
WorkflowEdgeFormEdge: Aktion WorkflowEdgeFormEdge: Aktion
WorkflowEdgeFormHiddenPayload i@Natural: Versteckter Datensatz #{i} WorkflowEdgeFormHiddenPayload i@Natural: Versteckter Datensatz #{i}
@ -3075,9 +3086,15 @@ WorkflowPayloadBoolTrue: Ja
WorkflowPayloadBoolFalse: Nein WorkflowPayloadBoolFalse: Nein
WorkflowPayloadUserGone: Gelöschter Benutzer WorkflowPayloadUserGone: Gelöschter Benutzer
TopWorkflowInstancesHeading: Workflows
TopWorkflowInstancesTitle: Workflows
GlobalWorkflowWorkflowWorkflowHeading workflowWorkflowId@CryptoFileNameWorkflowWorkflow: Workflow #{toPathPiece workflowWorkflowId} GlobalWorkflowWorkflowWorkflowHeading workflowWorkflowId@CryptoFileNameWorkflowWorkflow: Workflow #{toPathPiece workflowWorkflowId}
GlobalWorkflowWorkflowWorkflowTitle workflowWorkflowId@CryptoFileNameWorkflowWorkflow: Workflow #{toPathPiece workflowWorkflowId} GlobalWorkflowWorkflowWorkflowTitle workflowWorkflowId@CryptoFileNameWorkflowWorkflow: Workflow #{toPathPiece workflowWorkflowId}
SchoolWorkflowWorkflowWorkflowHeading ssh@SchoolId workflowWorkflowId@CryptoFileNameWorkflowWorkflow: Workflow #{ssh}, #{toPathPiece workflowWorkflowId}
SchoolWorkflowWorkflowWorkflowTitle ssh@SchoolId workflowWorkflowId@CryptoFileNameWorkflowWorkflow: Workflow #{ssh}, #{toPathPiece workflowWorkflowId}
ChangelogItemFeature: Feature ChangelogItemFeature: Feature
ChangelogItemBugfix: Bugfix ChangelogItemBugfix: Bugfix

View File

@ -1532,7 +1532,7 @@ AuthTagParticipant: User participates in course
AuthTagApplicant: User is applicant for course AuthTagApplicant: User is applicant for course
AuthTagRegisterGroup: User is not participant in any tutorial of the same registration group AuthTagRegisterGroup: User is not participant in any tutorial of the same registration group
AuthTagCapacity: Capacity is sufficient AuthTagCapacity: Capacity is sufficient
AuthTagEmpty: Course is empty AuthTagEmpty: Resource is “empty”
AuthTagMaterials: Course material is publicly accessable AuthTagMaterials: Course material is publicly accessable
AuthTagOwner: User is owner AuthTagOwner: User is owner
AuthTagPersonalisedSheetFiles: User has been assigned personalised sheet files AuthTagPersonalisedSheetFiles: User has been assigned personalised sheet files

View File

@ -321,6 +321,7 @@ tests:
- yesod-persistent - yesod-persistent
- quickcheck-io - quickcheck-io
- network-arbitrary - network-arbitrary
- lens-properties
ghc-options: ghc-options:
- -fno-warn-orphans - -fno-warn-orphans
- -threaded -rtsopts "-with-rtsopts=-N -T" - -threaded -rtsopts "-with-rtsopts=-N -T"

29
routes
View File

@ -69,20 +69,23 @@
/admin/workflows/workflows AdminWorkflowWorkflowListR GET /admin/workflows/workflows AdminWorkflowWorkflowListR GET
/admin/workflows/workflows/new AdminWorkflowWorkflowNewR GET POST /admin/workflows/workflows/new AdminWorkflowWorkflowNewR GET POST
/workflow-instances GlobalWorkflowInstanceListR GET !¬empty /global-workflows/instances GlobalWorkflowInstanceListR GET !free
/workflow-instances/new GlobalWorkflowInstanceNewR GET POST /global-workflows/instances/new GlobalWorkflowInstanceNewR GET POST
/workflow-instances/#WorkflowInstanceName GlobalWorkflowInstanceR: /global-workflows/instances/#WorkflowInstanceName GlobalWorkflowInstanceR:
/edit GWIEditR GET POST /edit GWIEditR GET POST
/delete GWIDeleteR GET POST /delete GWIDeleteR GET POST
/workflows GWIWorkflowsR GET /workflows GWIWorkflowsR GET !¬empty
/initiate GWIInitiateR GET POST !workflow /initiate GWIInitiateR GET POST !workflow
/workflows GlobalWorkflowWorkflowListR GET !¬empty /global-workflows GlobalWorkflowWorkflowListR GET !free
/workflows/#CryptoFileNameWorkflowWorkflow GlobalWorkflowWorkflowR: !/global-workflows/#CryptoFileNameWorkflowWorkflow GlobalWorkflowWorkflowR:
/ GWWWorkflowR GET POST !workflow / GWWWorkflowR GET POST !workflow
/files/#WorkflowPayloadLabel/#CryptoUUIDWorkflowStateIndex GWWFilesR GET !workflow /files/#WorkflowPayloadLabel/#CryptoUUIDWorkflowStateIndex GWWFilesR GET !workflow
/edit GWWEditR GET POST /edit GWWEditR GET POST
/delete GWWDeleteR GET POST /delete GWWDeleteR GET POST
/workflow-instances TopWorkflowInstanceListR GET !free
/workflows TopWorkflowWorkflowListR GET !free
/health HealthR GET !free /health HealthR GET !free
/instance InstanceR GET !free /instance InstanceR GET !free
/info InfoR GET !free /info InfoR GET !free
@ -132,6 +135,20 @@
/school/#SchoolId SchoolR: /school/#SchoolId SchoolR:
/ SchoolEditR GET POST / 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
/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/ AllocationListR GET !free
/allocation/#TermId/#SchoolId/#AllocationShorthand AllocationR: /allocation/#TermId/#SchoolId/#AllocationShorthand AllocationR:
/ AShowR GET POST !free / AShowR GET POST !free

View File

@ -1199,55 +1199,47 @@ tagAccessPredicate AuthRegisterGroup = APDB $ \_ mAuthId route _ -> case route o
guard $ not hasOther guard $ not hasOther
return Authorized return Authorized
r -> $unsupportedAuthPredicate AuthRegisterGroup r r -> $unsupportedAuthPredicate AuthRegisterGroup r
tagAccessPredicate AuthEmpty = APDB $ \_ mAuthId route _ -> do tagAccessPredicate AuthEmpty = APDB $ \_ mAuthId route _
let wInstances rScope = maybeT (unauthorizedI MsgUnauthorizedWorkflowInstancesNotEmpty) $ do -> let workflowInstanceWorkflowsEmpty rScope win = maybeT (unauthorizedI MsgUnauthorizedWorkflowWorkflowsNotEmpty) $ do
scope <- fromRouteWorkflowScope rScope scope <- fromRouteWorkflowScope rScope
let dbScope = scope ^. _DBWorkflowScope
let checkAccess (Entity _ WorkflowInstance{..}) getWorkflowWorkflows = E.selectSource . E.from $ \(workflowWorkflow `E.InnerJoin` workflowInstance) -> do
= fmap (is _Authorized) . flip (evalAccessFor mAuthId) True $ _WorkflowScopeRoute # (rScope, WorkflowInstanceR workflowInstanceName WIInitiateR) E.on $ workflowWorkflow E.^. WorkflowWorkflowInstance E.==. E.just (workflowInstance E.^. WorkflowInstanceId)
getInstances = E.selectSource . E.from $ \workflowInstance -> do E.where_ $ workflowInstance E.^. WorkflowInstanceName E.==. E.val win
E.where_ $ workflowInstance E.^. WorkflowInstanceScope E.==. E.val (scope ^. _DBWorkflowScope) E.&&. workflowInstance E.^. WorkflowInstanceScope E.==. E.val dbScope
return workflowInstance return ( workflowWorkflow E.^. WorkflowWorkflowId
, workflowWorkflow E.^. WorkflowWorkflowScope
guardM . lift . fmap not . $cachedHereBinary scope . runConduit $ getInstances .| C.mapM checkAccess .| C.or )
return Authorized checkAccess (E.Value wwId, E.Value wwScope) = maybeT (return False) $ do
cID <- encrypt wwId
wWorkflows rScope = maybeT (unauthorizedI MsgUnauthorizedWorkflowWorkflowsNotEmpty) $ do rScope' <- toRouteWorkflowScope $ _DBWorkflowScope # wwScope
scope <- fromRouteWorkflowScope rScope guardM . fmap (is _Authorized) . flip (evalAccessFor mAuthId) False $ _WorkflowScopeRoute # (rScope', WorkflowWorkflowR cID WWWorkflowR)
return True
let checkAccess (E.Value wwId) = do guardM . fmap not . lift . runConduit $ getWorkflowWorkflows .| C.mapM checkAccess .| C.or
cID <- encrypt wwId return Authorized
fmap (is _Authorized) . flip (evalAccessFor mAuthId) False $ _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR) in case route of
getWorkflows = E.selectSource . E.from $ \workflowWorkflow -> do r | Just (rScope, WorkflowInstanceR win WIWorkflowsR) <- r ^? _WorkflowScopeRoute
E.where_ $ workflowWorkflow E.^. WorkflowWorkflowScope E.==. E.val (scope ^. _DBWorkflowScope) -> workflowInstanceWorkflowsEmpty rScope win
return $ workflowWorkflow E.^. WorkflowWorkflowId EExamListR -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
guardM . lift . fmap not . $cachedHereBinary scope . runConduit $ getWorkflows .| C.mapM checkAccess .| C.or hasExternalExams <- $cachedHereBinary authId . lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` eexamStaff) -> do
return Authorized E.on $ eexam E.^. ExternalExamId E.==. eexamStaff E.^. ExternalExamStaffExam
E.where_ $ eexamStaff E.^. ExternalExamStaffUser E.==. E.val authId
case route of E.||. E.exists (E.from $ \externalExamResult ->
_ | Just (rScope, WorkflowInstanceListR) <- route ^? _WorkflowScopeRoute -> wInstances rScope E.where_ $ externalExamResult E.^. ExternalExamResultExam E.==. eexam E.^. ExternalExamId
_ | Just (rScope, WorkflowWorkflowListR) <- route ^? _WorkflowScopeRoute -> wWorkflows rScope E.&&. externalExamResult E.^. ExternalExamResultUser E.==. E.val authId
EExamListR -> exceptT return return $ do )
authId <- maybeExceptT AuthenticationRequired $ return mAuthId guardMExceptT (not hasExternalExams) $ unauthorizedI MsgUnauthorizedExternalExamListNotEmpty
hasExternalExams <- $cachedHereBinary authId . lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` eexamStaff) -> do return Authorized
E.on $ eexam E.^. ExternalExamId E.==. eexamStaff E.^. ExternalExamStaffExam CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNotEmpty) $ do
E.where_ $ eexamStaff E.^. ExternalExamStaffUser E.==. E.val authId -- Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
E.||. E.exists (E.from $ \externalExamResult -> cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
E.where_ $ externalExamResult E.^. ExternalExamResultExam E.==. eexam E.^. ExternalExamId assertM_ (<= 0) . $cachedHereBinary cid . lift $ count [ CourseParticipantCourse ==. cid ]
E.&&. externalExamResult E.^. ExternalExamResultUser E.==. E.val authId assertM_ not . $cachedHereBinary cid . lift $ E.selectExists . E.from $ \(sheet `E.InnerJoin` submission) -> do
) E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
guardMExceptT (not hasExternalExams) $ unauthorizedI MsgUnauthorizedExternalExamListNotEmpty E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
return Authorized return Authorized
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNotEmpty) $ do r -> $unsupportedAuthPredicate AuthEmpty r
-- 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 tagAccessPredicate AuthMaterials = APDB $ \_ _ route _ -> case route of
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnfreeMaterials) $ do CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnfreeMaterials) $ do
Entity _ Course{..} <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh Entity _ Course{..} <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh

View File

@ -421,12 +421,12 @@ instance RenderMessage UniWorX ShortWeekDay where
embedRenderMessage ''UniWorX ''ButtonSubmit id embedRenderMessage ''UniWorX ''ButtonSubmit id
instance RenderMessage UniWorX (WorkflowScope TermIdentifier SchoolShorthand (TermId, SchoolId, CourseShorthand)) where instance RenderMessage UniWorX (WorkflowScope TermId SchoolId (TermId, SchoolId, CourseShorthand)) where
renderMessage foundation ls = \case renderMessage foundation ls = \case
WSGlobal -> mr MsgWorkflowScopeGlobal WSGlobal -> mr MsgWorkflowScopeGlobal
WSTerm{..} -> mr $ ShortTermIdentifier wisTerm WSTerm{..} -> mr . ShortTermIdentifier $ unTermKey wisTerm
WSSchool{..} -> mr wisSchool WSSchool{..} -> mr $ unSchoolKey wisSchool
WSTermSchool{..} -> mr $ MsgWorkflowScopeTermSchool (TermKey wisTerm) (SchoolKey wisSchool) WSTermSchool{..} -> mr $ MsgWorkflowScopeTermSchool wisTerm wisSchool
WSCourse{ wisCourse = (tid, ssh, csh) } -> mr $ MsgWorkflowScopeCourse tid ssh csh WSCourse{ wisCourse = (tid, ssh, csh) } -> mr $ MsgWorkflowScopeCourse tid ssh csh
where where
mr :: forall msg. RenderMessage UniWorX msg => msg -> Text mr :: forall msg. RenderMessage UniWorX msg => msg -> Text

View File

@ -35,6 +35,11 @@ import Control.Monad.Trans.State (execStateT)
import Yesod.Core.Types (HandlerContents) import Yesod.Core.Types (HandlerContents)
import qualified Data.Conduit.Combinators as C
import Utils.Workflow
import Handler.Utils.Workflow.CanonicalRoute
-- Define breadcrumbs. -- Define breadcrumbs.
i18nCrumb :: (RenderMessage (HandlerSite m) msg, MonadHandler m) i18nCrumb :: (RenderMessage (HandlerSite m) msg, MonadHandler m)
@ -94,9 +99,29 @@ instance BearerAuthSite UniWorX => YesodBreadcrumbs UniWorX where
breadcrumb AdminCrontabR = i18nCrumb MsgBreadcrumbAdminCrontab $ Just AdminR breadcrumb AdminCrontabR = i18nCrumb MsgBreadcrumbAdminCrontab $ Just AdminR
breadcrumb SchoolListR = i18nCrumb MsgMenuSchoolList $ Just AdminR breadcrumb SchoolListR = i18nCrumb MsgMenuSchoolList $ Just AdminR
breadcrumb (SchoolR ssh SchoolEditR) = maybeT (i18nCrumb MsgBreadcrumbSchool $ Just SchoolListR) $ do breadcrumb (SchoolR ssh sRoute) = case sRoute of
School{..} <- MaybeT . runDBRead $ get ssh SchoolEditR -> maybeT (i18nCrumb MsgBreadcrumbSchool $ Just SchoolListR) $ do
return (CI.original schoolName, Just SchoolListR) School{..} <- MaybeT . runDBRead $ get ssh
isAdmin <- 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 -> i18nCrumb (MsgBreadcrumbWorkflowInstanceEdit win) . Just $ SchoolR ssh SchoolWorkflowInstanceListR
SWIDeleteR -> i18nCrumb MsgBreadcrumbWorkflowInstanceDelete . Just . SchoolR ssh $ SchoolWorkflowInstanceR win SWIEditR
SWIWorkflowsR -> i18nCrumb MsgBreadcrumbWorkflowInstanceWorkflowList . Just . SchoolR ssh $ SchoolWorkflowInstanceR win SWIEditR
SWIInitiateR -> do
mayEdit <- hasReadAccessTo . SchoolR ssh $ SchoolWorkflowInstanceR win SWIEditR
i18nCrumb MsgBreadcrumbWorkflowInstanceInitiate . Just . SchoolR ssh $ if
| mayEdit -> SchoolWorkflowInstanceR win SWIEditR
| otherwise -> SchoolWorkflowInstanceListR
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 SchoolNewR = i18nCrumb MsgMenuSchoolNew $ Just SchoolListR
breadcrumb (ExamOfficeR EOExamsR) = i18nCrumb MsgMenuExamOfficeExams Nothing breadcrumb (ExamOfficeR EOExamsR) = i18nCrumb MsgMenuExamOfficeExams Nothing
@ -346,7 +371,7 @@ instance BearerAuthSite UniWorX => YesodBreadcrumbs UniWorX where
breadcrumb AdminWorkflowWorkflowListR = i18nCrumb MsgBreadcrumbAdminWorkflowWorkflowList $ Just AdminWorkflowInstanceListR breadcrumb AdminWorkflowWorkflowListR = i18nCrumb MsgBreadcrumbAdminWorkflowWorkflowList $ Just AdminWorkflowInstanceListR
breadcrumb AdminWorkflowWorkflowNewR = i18nCrumb MsgBreadcrumbAdminWorkflowWorkflowNew $ Just AdminWorkflowWorkflowListR breadcrumb AdminWorkflowWorkflowNewR = i18nCrumb MsgBreadcrumbAdminWorkflowWorkflowNew $ Just AdminWorkflowWorkflowListR
breadcrumb GlobalWorkflowInstanceListR = i18nCrumb MsgBreadcrumbWorkflowInstanceList Nothing breadcrumb GlobalWorkflowInstanceListR = i18nCrumb MsgBreadcrumbGlobalWorkflowInstanceList Nothing
breadcrumb GlobalWorkflowInstanceNewR = i18nCrumb MsgBreadcrumbWorkflowInstanceNew $ Just GlobalWorkflowInstanceListR breadcrumb GlobalWorkflowInstanceNewR = i18nCrumb MsgBreadcrumbWorkflowInstanceNew $ Just GlobalWorkflowInstanceListR
breadcrumb (GlobalWorkflowInstanceR win sRoute) = case sRoute of breadcrumb (GlobalWorkflowInstanceR win sRoute) = case sRoute of
GWIEditR -> i18nCrumb (MsgBreadcrumbWorkflowInstanceEdit win) $ Just GlobalWorkflowInstanceListR GWIEditR -> i18nCrumb (MsgBreadcrumbWorkflowInstanceEdit win) $ Just GlobalWorkflowInstanceListR
@ -357,13 +382,16 @@ instance BearerAuthSite UniWorX => YesodBreadcrumbs UniWorX where
i18nCrumb MsgBreadcrumbWorkflowInstanceInitiate . Just $ if i18nCrumb MsgBreadcrumbWorkflowInstanceInitiate . Just $ if
| mayEdit -> GlobalWorkflowInstanceR win GWIEditR | mayEdit -> GlobalWorkflowInstanceR win GWIEditR
| otherwise -> GlobalWorkflowInstanceListR | otherwise -> GlobalWorkflowInstanceListR
breadcrumb GlobalWorkflowWorkflowListR = i18nCrumb MsgBreadcrumbGlobalWorkflowWorkflowList $ Just GlobalWorkflowInstanceListR breadcrumb GlobalWorkflowWorkflowListR = i18nCrumb MsgBreadcrumbWorkflowWorkflowList $ Just GlobalWorkflowInstanceListR
breadcrumb (GlobalWorkflowWorkflowR cID sRoute) = case sRoute of breadcrumb (GlobalWorkflowWorkflowR cID sRoute) = case sRoute of
GWWWorkflowR -> i18nCrumb (MsgBreadcrumbWorkflowWorkflow cID) $ Just GlobalWorkflowInstanceListR GWWWorkflowR -> i18nCrumb (MsgBreadcrumbWorkflowWorkflow cID) $ Just GlobalWorkflowWorkflowListR
GWWFilesR _ _ -> i18nCrumb MsgBreadcrumbWorkflowWorkflowFiles . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR GWWFilesR _ _ -> i18nCrumb MsgBreadcrumbWorkflowWorkflowFiles . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR
GWWEditR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowEdit . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR GWWEditR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowEdit . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR
GWWDeleteR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowDelete . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR GWWDeleteR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowDelete . Just $ GlobalWorkflowWorkflowR cID GWWWorkflowR
breadcrumb TopWorkflowInstanceListR = i18nCrumb MsgBreadcrumbTopWorkflowInstanceList Nothing
breadcrumb TopWorkflowWorkflowListR = i18nCrumb MsgBreadcrumbTopWorkflowWorkflowList $ Just TopWorkflowInstanceListR
data NavQuickView data NavQuickView
= NavQuickViewFavourite = NavQuickViewFavourite
@ -465,7 +493,11 @@ navLinkAccess NavLink{..} = handle shortCircuit $ liftHandler navAccess' `and2M`
$memcachedByHere (Just $ Right 120) (authCtx, nt, route) $ $memcachedByHere (Just $ Right 120) (authCtx, nt, route) $
bool hasWriteAccessTo hasReadAccessTo (is _NavTypeLink nt) route bool hasWriteAccessTo hasReadAccessTo (is _NavTypeLink nt) route
defaultLinks :: (MonadHandler m, HandlerSite m ~ UniWorX, BearerAuthSite UniWorX) => m [Nav] defaultLinks :: ( MonadHandler m
, HandlerSite m ~ UniWorX
, BearerAuthSite UniWorX
, BackendCompatible SqlReadBackend (YesodPersistBackend UniWorX)
) => m [Nav]
defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the header. defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the header.
[ return NavHeader [ return NavHeader
{ navHeaderRole = NavHeaderSecondary { navHeaderRole = NavHeaderSecondary
@ -647,18 +679,36 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the
, navForceActive = False , navForceActive = False
} }
} }
, return NavHeader , do
{ navHeaderRole = NavHeaderPrimary (haveInstances, haveWorkflows) <- liftHandler . runDB $ (,)
, navIcon = IconMenuWorkflows <$> haveTopWorkflowInstances
, navLink = NavLink <*> haveTopWorkflowWorkflows
{ navLabel = MsgMenuGlobalWorkflowInstanceList
, navRoute = GlobalWorkflowInstanceListR if | haveInstances -> return NavHeader
, navAccess' = return True { navHeaderRole = NavHeaderPrimary
, navType = NavTypeLink { navModal = False } , navIcon = IconMenuWorkflows
, navQuick' = mempty , navLink = NavLink
, navForceActive = False { navLabel = MsgMenuTopWorkflowInstanceList
} , navRoute = TopWorkflowInstanceListR
} , navAccess' = return True
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
}
| haveWorkflows -> return NavHeader
{ navHeaderRole = NavHeaderPrimary
, navIcon = IconMenuWorkflows
, navLink = NavLink
{ navLabel = MsgMenuTopWorkflowWorkflowListHeader
, navRoute = TopWorkflowWorkflowListR
, navAccess' = return True
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
}
| otherwise -> mzero
, return NavHeaderContainer , return NavHeaderContainer
{ navHeaderRole = NavHeaderPrimary { navHeaderRole = NavHeaderPrimary
, navLabel = SomeMessage MsgAdminHeading , navLabel = SomeMessage MsgAdminHeading
@ -791,6 +841,7 @@ pageActions :: ( MonadHandler m
, HandlerSite m ~ UniWorX , HandlerSite m ~ UniWorX
, MonadCatch m , MonadCatch m
, BearerAuthSite UniWorX , BearerAuthSite UniWorX
, BackendCompatible SqlReadBackend (YesodPersistBackend UniWorX)
) )
=> Route UniWorX -> m [Nav] => Route UniWorX -> m [Nav]
pageActions NewsR = return pageActions NewsR = return
@ -2395,12 +2446,12 @@ pageActions AdminWorkflowInstanceListR = return
, navChildren = [] , navChildren = []
} }
] ]
pageActions GlobalWorkflowInstanceListR = return pageActions route | Just (rScope, WorkflowInstanceListR) <- route ^? _WorkflowScopeRoute = return
[ NavPageActionPrimary [ NavPageActionPrimary
{ navLink = NavLink { navLink = NavLink
{ navLabel = MsgMenuGlobalWorkflowWorkflowList { navLabel = MsgMenuWorkflowWorkflowList
, navRoute = GlobalWorkflowWorkflowListR , navRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowListR)
, navAccess' = return True , navAccess' = runDB $ haveWorkflowWorkflows rScope
, navType = NavTypeLink { navModal = False } , navType = NavTypeLink { navModal = False }
, navQuick' = mempty , navQuick' = mempty
, navForceActive = False , navForceActive = False
@ -2408,11 +2459,11 @@ pageActions GlobalWorkflowInstanceListR = return
, navChildren = [] , navChildren = []
} }
] ]
pageActions (GlobalWorkflowInstanceR win GWIEditR) = return pageActions route | Just (rScope, WorkflowInstanceR win WIEditR) <- route ^? _WorkflowScopeRoute = return
[ NavPageActionSecondary [ NavPageActionSecondary
{ navLink = NavLink { navLink = NavLink
{ navLabel = MsgMenuWorkflowInstanceDelete { navLabel = MsgMenuWorkflowInstanceDelete
, navRoute = GlobalWorkflowInstanceR win GWIDeleteR , navRoute = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIDeleteR)
, navAccess' = return True , navAccess' = return True
, navType = NavTypeLink { navModal = False } , navType = NavTypeLink { navModal = False }
, navQuick' = mempty , navQuick' = mempty
@ -2422,7 +2473,7 @@ pageActions (GlobalWorkflowInstanceR win GWIEditR) = return
, NavPageActionPrimary , NavPageActionPrimary
{ navLink = NavLink { navLink = NavLink
{ navLabel = MsgMenuWorkflowInstanceWorkflows { navLabel = MsgMenuWorkflowInstanceWorkflows
, navRoute = GlobalWorkflowInstanceR win GWIWorkflowsR , navRoute = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIWorkflowsR)
, navAccess' = return True , navAccess' = return True
, navType = NavTypeLink { navModal = False } , navType = NavTypeLink { navModal = False }
, navQuick' = mempty , navQuick' = mempty
@ -2433,7 +2484,7 @@ pageActions (GlobalWorkflowInstanceR win GWIEditR) = return
, NavPageActionPrimary , NavPageActionPrimary
{ navLink = NavLink { navLink = NavLink
{ navLabel = MsgMenuWorkflowInstanceInitiate { navLabel = MsgMenuWorkflowInstanceInitiate
, navRoute = GlobalWorkflowInstanceR win GWIInitiateR , navRoute = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR)
, navAccess' = return True , navAccess' = return True
, navType = NavTypeLink { navModal = False } , navType = NavTypeLink { navModal = False }
, navQuick' = mempty , navQuick' = mempty
@ -2442,11 +2493,11 @@ pageActions (GlobalWorkflowInstanceR win GWIEditR) = return
, navChildren = [] , navChildren = []
} }
] ]
pageActions (GlobalWorkflowWorkflowR cID GWWWorkflowR) = return pageActions route | Just (rScope, WorkflowWorkflowR cID WWWorkflowR) <- route ^? _WorkflowScopeRoute = return
[ NavPageActionSecondary [ NavPageActionSecondary
{ navLink = NavLink { navLink = NavLink
{ navLabel = MsgMenuWorkflowWorkflowEdit { navLabel = MsgMenuWorkflowWorkflowEdit
, navRoute = GlobalWorkflowWorkflowR cID GWWEditR , navRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWEditR)
, navAccess' = return True , navAccess' = return True
, navType = NavTypeLink { navModal = False } , navType = NavTypeLink { navModal = False }
, navQuick' = mempty , navQuick' = mempty
@ -2456,7 +2507,7 @@ pageActions (GlobalWorkflowWorkflowR cID GWWWorkflowR) = return
, NavPageActionSecondary , NavPageActionSecondary
{ navLink = NavLink { navLink = NavLink
{ navLabel = MsgMenuWorkflowWorkflowDelete { navLabel = MsgMenuWorkflowWorkflowDelete
, navRoute = GlobalWorkflowWorkflowR cID GWWDeleteR , navRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWDeleteR)
, navAccess' = return True , navAccess' = return True
, navType = NavTypeLink { navModal = False } , navType = NavTypeLink { navModal = False }
, navQuick' = mempty , navQuick' = mempty
@ -2464,6 +2515,19 @@ pageActions (GlobalWorkflowWorkflowR cID GWWWorkflowR) = return
} }
} }
] ]
pageActions TopWorkflowInstanceListR = return
[ NavPageActionPrimary
{ navLink = NavLink
{ navLabel = MsgMenuTopWorkflowWorkflowList
, navRoute = TopWorkflowWorkflowListR
, navAccess' = runDB haveTopWorkflowWorkflows
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
, navChildren = []
}
]
pageActions _ = return [] pageActions _ = return []
submissionList :: ( MonadIO m submissionList :: ( MonadIO m
@ -2487,6 +2551,7 @@ pageQuickActions :: ( MonadCatch m
, MonadHandler m , MonadHandler m
, HandlerSite m ~ UniWorX , HandlerSite m ~ UniWorX
, BearerAuthSite UniWorX , BearerAuthSite UniWorX
, BackendCompatible SqlReadBackend (YesodPersistBackend UniWorX)
) )
=> NavQuickView -> Route UniWorX -> m [NavLink] => NavQuickView -> Route UniWorX -> m [NavLink]
pageQuickActions qView route = do pageQuickActions qView route = do
@ -2499,3 +2564,55 @@ evalAccessCorrector
:: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX, BearerAuthSite UniWorX) :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX, BearerAuthSite UniWorX)
=> TermId -> SchoolId -> CourseShorthand -> m AuthResult => TermId -> SchoolId -> CourseShorthand -> m AuthResult
evalAccessCorrector tid ssh csh = evalAccess (CourseR tid ssh csh CNotesR) False evalAccessCorrector tid ssh csh = evalAccess (CourseR tid ssh csh CNotesR) False
_haveWorkflowInstances, haveWorkflowWorkflows
:: ( MonadHandler m, HandlerSite m ~ UniWorX
, BackendCompatible SqlReadBackend backend
, BearerAuthSite UniWorX
)
=> WorkflowScope TermId SchoolId (TermId, SchoolId, CourseShorthand)
-> ReaderT backend m Bool
_haveWorkflowInstances rScope = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . maybeT (return False) $ do
scope <- fromRouteWorkflowScope rScope
let checkAccess (Entity _ WorkflowInstance{..})
= hasReadAccessTo $ _WorkflowScopeRoute # (rScope, WorkflowInstanceR workflowInstanceName WIInitiateR)
getInstances = E.selectSource . E.from $ \workflowInstance -> do
E.where_ $ workflowInstance E.^. WorkflowInstanceScope E.==. E.val (scope ^. _DBWorkflowScope)
return workflowInstance
$cachedHereBinary scope . runConduit $ transPipe lift getInstances .| C.mapM checkAccess .| C.or
haveWorkflowWorkflows rScope = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . maybeT (return False) $ do
scope <- fromRouteWorkflowScope rScope
let checkAccess (E.Value wwId) = do
cID <- lift . lift $ encrypt wwId
hasReadAccessTo $ _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)
getWorkflows = E.selectSource . E.from $ \workflowWorkflow -> do
E.where_ $ workflowWorkflow E.^. WorkflowWorkflowScope E.==. E.val (scope ^. _DBWorkflowScope)
return $ workflowWorkflow E.^. WorkflowWorkflowId
$cachedHereBinary scope . runConduit $ transPipe lift getWorkflows .| C.mapM checkAccess .| C.or
haveTopWorkflowInstances, haveTopWorkflowWorkflows
:: ( MonadHandler m, HandlerSite m ~ UniWorX
, BackendCompatible SqlReadBackend backend
, BearerAuthSite UniWorX
)
=> ReaderT backend m Bool
haveTopWorkflowInstances = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . maybeT (return False) $
let checkAccess (Entity _ WorkflowInstance{..}) = do
rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowInstanceScope
hasReadAccessTo $ _WorkflowScopeRoute # (rScope, WorkflowInstanceR workflowInstanceName WIInitiateR)
getInstances = selectSource [] []
isTop (Entity _ WorkflowInstance{..}) = isTopWorkflowScope workflowInstanceScope
in $cachedHere . runConduit $ transPipe lift getInstances .| C.filter isTop .| C.mapM checkAccess .| C.or
haveTopWorkflowWorkflows = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . maybeT (return False) $
let checkAccess (Entity wwId WorkflowWorkflow{..}) = do
rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope
cID <- lift . lift $ encrypt wwId
hasReadAccessTo $ _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)
getWorkflows = selectSource [] []
isTop (Entity _ WorkflowWorkflow{..}) = isTopWorkflowScope workflowWorkflowScope
in $cachedHere . runConduit $ transPipe lift getWorkflows .| C.filter isTop .| C.mapM checkAccess .| C.or

View File

@ -41,6 +41,8 @@ deriving instance Generic CourseEventR
deriving instance Generic AdminWorkflowDefinitionR deriving instance Generic AdminWorkflowDefinitionR
deriving instance Generic GlobalWorkflowInstanceR deriving instance Generic GlobalWorkflowInstanceR
deriving instance Generic GlobalWorkflowWorkflowR deriving instance Generic GlobalWorkflowWorkflowR
deriving instance Generic SchoolWorkflowInstanceR
deriving instance Generic SchoolWorkflowWorkflowR
deriving instance Generic (Route UniWorX) deriving instance Generic (Route UniWorX)
instance Ord (Route Auth) where instance Ord (Route Auth) where
@ -64,6 +66,8 @@ deriving instance Ord CourseEventR
deriving instance Ord AdminWorkflowDefinitionR deriving instance Ord AdminWorkflowDefinitionR
deriving instance Ord GlobalWorkflowInstanceR deriving instance Ord GlobalWorkflowInstanceR
deriving instance Ord GlobalWorkflowWorkflowR deriving instance Ord GlobalWorkflowWorkflowR
deriving instance Ord SchoolWorkflowInstanceR
deriving instance Ord SchoolWorkflowWorkflowR
deriving instance Ord (Route UniWorX) deriving instance Ord (Route UniWorX)
data RouteChildren data RouteChildren

View File

@ -53,15 +53,16 @@ data MemcachedLimitKeyFavourites
deriving anyclass (Hashable, Binary) deriving anyclass (Hashable, Binary)
siteLayoutMsg :: (RenderMessage site msg, site ~ UniWorX, BearerAuthSite UniWorX, BackendCompatible SqlBackend (YesodPersistBackend UniWorX), Button UniWorX ButtonSubmit) => msg -> WidgetFor UniWorX () -> HandlerFor UniWorX Html siteLayoutMsg :: (RenderMessage site msg, site ~ UniWorX, BearerAuthSite UniWorX, BackendCompatible SqlBackend (YesodPersistBackend UniWorX), BackendCompatible SqlReadBackend (YesodPersistBackend UniWorX), Button UniWorX ButtonSubmit) => msg -> WidgetFor UniWorX () -> HandlerFor UniWorX Html
siteLayoutMsg = siteLayout . i18n siteLayoutMsg = siteLayout . i18n
{-# DEPRECATED siteLayoutMsg' "Use siteLayoutMsg" #-} {-# DEPRECATED siteLayoutMsg' "Use siteLayoutMsg" #-}
siteLayoutMsg' :: (RenderMessage site msg, site ~ UniWorX, BearerAuthSite UniWorX, BackendCompatible SqlBackend (YesodPersistBackend UniWorX), Button UniWorX ButtonSubmit) => msg -> WidgetFor UniWorX () -> HandlerFor UniWorX Html siteLayoutMsg' :: (RenderMessage site msg, site ~ UniWorX, BearerAuthSite UniWorX, BackendCompatible SqlBackend (YesodPersistBackend UniWorX), BackendCompatible SqlReadBackend (YesodPersistBackend UniWorX), Button UniWorX ButtonSubmit) => msg -> WidgetFor UniWorX () -> HandlerFor UniWorX Html
siteLayoutMsg' = siteLayoutMsg siteLayoutMsg' = siteLayoutMsg
siteLayout :: ( BearerAuthSite UniWorX siteLayout :: ( BearerAuthSite UniWorX
, BackendCompatible SqlBackend (YesodPersistBackend UniWorX) , BackendCompatible SqlBackend (YesodPersistBackend UniWorX)
, BackendCompatible SqlReadBackend (YesodPersistBackend UniWorX)
, Button UniWorX ButtonSubmit , Button UniWorX ButtonSubmit
) )
=> WidgetFor UniWorX () -- ^ `pageHeading` => WidgetFor UniWorX () -- ^ `pageHeading`
@ -70,6 +71,7 @@ siteLayout = siteLayout' . Just
siteLayout' :: ( BearerAuthSite UniWorX siteLayout' :: ( BearerAuthSite UniWorX
, BackendCompatible SqlBackend (YesodPersistBackend UniWorX) , BackendCompatible SqlBackend (YesodPersistBackend UniWorX)
, BackendCompatible SqlReadBackend (YesodPersistBackend UniWorX)
, Button UniWorX ButtonSubmit , Button UniWorX ButtonSubmit
) )
=> Maybe (WidgetFor UniWorX ()) -- ^ `pageHeading` => Maybe (WidgetFor UniWorX ()) -- ^ `pageHeading`

View File

@ -21,6 +21,7 @@ errorHandler :: ( MonadSecretBox (HandlerFor UniWorX)
, BearerAuthSite UniWorX , BearerAuthSite UniWorX
, Button UniWorX ButtonSubmit , Button UniWorX ButtonSubmit
, BackendCompatible SqlBackend (YesodPersistBackend UniWorX) , BackendCompatible SqlBackend (YesodPersistBackend UniWorX)
, BackendCompatible SqlReadBackend (YesodPersistBackend UniWorX)
) )
=> ErrorResponse -> HandlerFor UniWorX TypedContent => ErrorResponse -> HandlerFor UniWorX TypedContent
errorHandler err = do errorHandler err = do

View File

@ -610,8 +610,8 @@ examAutoOccurrence (hash -> seed) rule ExamAutoOccurrenceConfig{..} occurrences
pad res pad res
| ExamRoomMatriculation <- rule | ExamRoomMatriculation <- rule
, Just minAlpha <- Set.lookupMin rangeAlphabet , Just minAlpha <- Set.lookupMin rangeAlphabet
= let maxLength = maybe 0 maximum . fromNullable $ res ^.. folded . folded . (_eaomrStart <> _eaomrEnd <> _eaomrSpecial) . to length = let maxLength' = maybe 0 maximum . fromNullable $ res ^.. folded . folded . (_eaomrStart <> _eaomrEnd <> _eaomrSpecial) . to length
padSuff cs = replicate (maxLength - length cs) minAlpha ++ cs padSuff cs = replicate (maxLength' - length cs) minAlpha ++ cs
in Set.map (appEndo $ foldMap Endo [ over l padSuff | l <- [_eaomrStart, _eaomrEnd, _eaomrSpecial]]) <$> res in Set.map (appEndo $ foldMap Endo [ over l padSuff | l <- [_eaomrStart, _eaomrEnd, _eaomrSpecial]]) <$> res
| otherwise | otherwise
= res = res

View File

@ -44,6 +44,20 @@ _WorkflowScopeRoute = prism' (uncurry toRoute) toWorkflowScopeRoute
WWFilesR wpl stCID -> GWWFilesR wpl stCID WWFilesR wpl stCID -> GWWFilesR wpl stCID
WWEditR -> GWWEditR WWEditR -> GWWEditR
WWDeleteR -> GWWDeleteR 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
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 other -> error $ "not implemented _WorkflowScopeRoute for: " <> show other
toWorkflowScopeRoute = \case toWorkflowScopeRoute = \case
GlobalWorkflowInstanceListR -> Just ( WSGlobal, WorkflowInstanceListR ) GlobalWorkflowInstanceListR -> Just ( WSGlobal, WorkflowInstanceListR )
@ -59,4 +73,19 @@ _WorkflowScopeRoute = prism' (uncurry toRoute) toWorkflowScopeRoute
GWWFilesR wpl stCID -> WWFilesR wpl stCID GWWFilesR wpl stCID -> WWFilesR wpl stCID
GWWEditR -> WWEditR GWWEditR -> WWEditR
GWWDeleteR -> WWDeleteR 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
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 _other -> Nothing

View File

@ -1,15 +1,23 @@
module Handler.Workflow.Instance.Delete module Handler.Workflow.Instance.Delete
( getGWIDeleteR, postGWIDeleteR ( getGWIDeleteR, postGWIDeleteR
, getSWIDeleteR, postSWIDeleteR
, workflowInstanceDeleteR , workflowInstanceDeleteR
) where ) where
import Import import Import
import Utils.Workflow
getGWIDeleteR, postGWIDeleteR :: WorkflowInstanceName -> Handler Html getGWIDeleteR, postGWIDeleteR :: WorkflowInstanceName -> Handler Html
getGWIDeleteR = postGWIDeleteR getGWIDeleteR = postGWIDeleteR
postGWIDeleteR win postGWIDeleteR win
= workflowInstanceDeleteR <=< runDB . getKeyBy404 $ UniqueWorkflowInstance win WSGlobal = workflowInstanceDeleteR <=< runDB . getKeyBy404 $ UniqueWorkflowInstance win WSGlobal
getSWIDeleteR, postSWIDeleteR :: SchoolId -> WorkflowInstanceName -> Handler Html
getSWIDeleteR = postSWIDeleteR
postSWIDeleteR ssh win
= workflowInstanceDeleteR <=< runDB . getKeyBy404 . UniqueWorkflowInstance win . view _DBWorkflowScope $ WSSchool ssh
workflowInstanceDeleteR :: WorkflowInstanceId -> Handler Html workflowInstanceDeleteR :: WorkflowInstanceId -> Handler Html
workflowInstanceDeleteR = error "not implemented" workflowInstanceDeleteR = error "not implemented"

View File

@ -1,15 +1,23 @@
module Handler.Workflow.Instance.Edit module Handler.Workflow.Instance.Edit
( getGWIEditR, postGWIEditR ( getGWIEditR, postGWIEditR
, getSWIEditR, postSWIEditR
, workflowInstanceEditR , workflowInstanceEditR
) where ) where
import Import import Import
import Utils.Workflow
getGWIEditR, postGWIEditR :: WorkflowInstanceName -> Handler Html getGWIEditR, postGWIEditR :: WorkflowInstanceName -> Handler Html
getGWIEditR = postGWIEditR getGWIEditR = postGWIEditR
postGWIEditR win postGWIEditR win
= workflowInstanceEditR <=< runDB . getKeyBy404 $ UniqueWorkflowInstance win WSGlobal = workflowInstanceEditR <=< runDB . getKeyBy404 $ UniqueWorkflowInstance win WSGlobal
getSWIEditR, postSWIEditR :: SchoolId -> WorkflowInstanceName -> Handler Html
getSWIEditR = postSWIEditR
postSWIEditR ssh win
= workflowInstanceEditR <=< runDB . getKeyBy404 . UniqueWorkflowInstance win . view _DBWorkflowScope $ WSSchool ssh
workflowInstanceEditR :: WorkflowInstanceId -> Handler Html workflowInstanceEditR :: WorkflowInstanceId -> Handler Html
workflowInstanceEditR = error "not implemented" workflowInstanceEditR = error "not implemented"

View File

@ -1,5 +1,6 @@
module Handler.Workflow.Instance.Initiate module Handler.Workflow.Instance.Initiate
( getGWIInitiateR, postGWIInitiateR ( getGWIInitiateR, postGWIInitiateR
, getSWIInitiateR, postSWIInitiateR
, workflowInstanceInitiateR , workflowInstanceInitiateR
) where ) where
@ -22,6 +23,11 @@ getGWIInitiateR = postGWIInitiateR
postGWIInitiateR win postGWIInitiateR win
= workflowInstanceInitiateR <=< runDB . getKeyBy404 $ UniqueWorkflowInstance win WSGlobal = workflowInstanceInitiateR <=< runDB . getKeyBy404 $ UniqueWorkflowInstance win WSGlobal
getSWIInitiateR, postSWIInitiateR :: SchoolId -> WorkflowInstanceName -> Handler Html
getSWIInitiateR = postSWIInitiateR
postSWIInitiateR ssh win
= workflowInstanceInitiateR <=< runDB . getKeyBy404 . UniqueWorkflowInstance win . view _DBWorkflowScope $ WSSchool ssh
workflowInstanceInitiateR :: WorkflowInstanceId -> Handler Html workflowInstanceInitiateR :: WorkflowInstanceId -> Handler Html
workflowInstanceInitiateR wiId = do workflowInstanceInitiateR wiId = do
(WorkflowInstance{..}, ((edgeAct, edgeView'), edgeEnc), rScope, mDesc) <- runDB $ do (WorkflowInstance{..}, ((edgeAct, edgeView'), edgeEnc), rScope, mDesc) <- runDB $ do
@ -66,6 +72,7 @@ workflowInstanceInitiateR wiId = do
(heading, title) <- case rScope of (heading, title) <- case rScope of
WSGlobal -> return (MsgGlobalWorkflowInstanceInitiateHeading $ maybe (CI.original workflowInstanceName) workflowInstanceDescriptionTitle mDesc, MsgGlobalWorkflowInstanceInitiateTitle) WSGlobal -> return (MsgGlobalWorkflowInstanceInitiateHeading $ maybe (CI.original workflowInstanceName) workflowInstanceDescriptionTitle mDesc, MsgGlobalWorkflowInstanceInitiateTitle)
WSSchool ssh -> return (MsgSchoolWorkflowInstanceInitiateHeading ssh $ maybe (CI.original workflowInstanceName) workflowInstanceDescriptionTitle mDesc, MsgSchoolWorkflowInstanceInitiateTitle ssh)
_other -> error "not implemented" _other -> error "not implemented"
siteLayoutMsg heading $ do siteLayoutMsg heading $ do

View File

@ -3,7 +3,9 @@
module Handler.Workflow.Instance.List module Handler.Workflow.Instance.List
( getAdminWorkflowInstanceListR ( getAdminWorkflowInstanceListR
, getGlobalWorkflowInstanceListR , getGlobalWorkflowInstanceListR
, getSchoolWorkflowInstanceListR
, workflowInstanceListR , workflowInstanceListR
, getTopWorkflowInstanceListR
) where ) where
import Import import Import
@ -19,6 +21,8 @@ import qualified Data.CaseInsensitive as CI
import qualified Data.List.NonEmpty as NonEmpty import qualified Data.List.NonEmpty as NonEmpty
import qualified Data.Map as Map
type WorkflowInstanceTableExpr = E.SqlExpr (Entity WorkflowInstance) type WorkflowInstanceTableExpr = E.SqlExpr (Entity WorkflowInstance)
@ -51,11 +55,11 @@ getAdminWorkflowInstanceListR :: Handler Html
getAdminWorkflowInstanceListR = do getAdminWorkflowInstanceListR = do
instancesTable <- runDB $ do instancesTable <- runDB $ do
scopeOptions <- do scopeOptions <- do
scopes <- fmap (map E.unValue) . E.select . E.from $ \workflowInstance -> scopes <- fmap (map $ review _DBWorkflowScope . E.unValue) . E.select . E.from $ \workflowInstance ->
return $ workflowInstance E.^. WorkflowInstanceScope return $ workflowInstance E.^. WorkflowInstanceScope
fmap mkOptionList . for scopes $ \scope -> do fmap mkOptionList . for scopes $ \scope -> do
eScope <- traverseOf _wisCourse (encrypt . (review _SqlKey :: SqlBackendKey -> CourseId)) scope :: DB (WorkflowScope TermIdentifier SchoolShorthand CryptoUUIDCourse) eScope <- traverseOf _wisCourse encrypt scope :: DB (WorkflowScope TermId SchoolId CryptoUUIDCourse)
wScope <- forOf _wisCourse scope $ fmap ((,,) <$> courseTerm <*> courseSchool <*> courseShorthand) . getJust . review _SqlKey wScope <- maybeT notFound $ toRouteWorkflowScope scope
MsgRenderer mr <- getMsgRenderer MsgRenderer mr <- getMsgRenderer
return Option return Option
{ optionDisplay = mr wScope { optionDisplay = mr wScope
@ -83,8 +87,8 @@ getAdminWorkflowInstanceListR = do
dbtColonnade :: Colonnade Sortable WorkflowInstanceData _ dbtColonnade :: Colonnade Sortable WorkflowInstanceData _
dbtColonnade = mconcat dbtColonnade = mconcat
[ sortable (Just "name") (i18nCell MsgWorkflowInstanceName) $ views (resultWorkflowInstance . _entityVal . _workflowInstanceName) i18nCell [ sortable (Just "name") (i18nCell MsgWorkflowInstanceName) $ views (resultWorkflowInstance . _entityVal . _workflowInstanceName) i18nCell
, sortable (Just "scope") (i18nCell MsgWorkflowScope) . views (resultWorkflowInstance . _entityVal . _workflowInstanceScope) $ \scope -> , sortable (Just "scope") (i18nCell MsgWorkflowScope) . views (resultWorkflowInstance . _entityVal . _workflowInstanceScope . re _DBWorkflowScope) $
sqlCell . fmap i18n . forOf _wisCourse scope $ fmap ((,,) <$> courseTerm <*> courseSchool <*> courseShorthand) . getJust . review _SqlKey sqlCell . maybeT (return mempty) . fmap i18n . toRouteWorkflowScope
, sortable (Just "title") (i18nCell MsgWorkflowInstanceDescriptionTitle) $ maybe mempty i18nCell . preview (resultDescription . _entityVal . _workflowInstanceDescriptionTitle) , sortable (Just "title") (i18nCell MsgWorkflowInstanceDescriptionTitle) $ maybe mempty i18nCell . preview (resultDescription . _entityVal . _workflowInstanceDescriptionTitle)
, sortable (Just "workflows") (i18nCell MsgWorkflowInstanceWorkflowCount) $ maybe mempty i18nCell . views resultWorkflowCount (assertM' (> 0)) , sortable (Just "workflows") (i18nCell MsgWorkflowInstanceWorkflowCount) $ maybe mempty i18nCell . views resultWorkflowCount (assertM' (> 0))
, sortable (Just "description") (i18nCell MsgWorkflowInstanceDescription) $ maybe mempty modalCell . preview (resultDescription . _entityVal . _workflowInstanceDescriptionDescription . _Just) , sortable (Just "description") (i18nCell MsgWorkflowInstanceDescription) $ maybe mempty modalCell . preview (resultDescription . _entityVal . _workflowInstanceDescriptionDescription . _Just)
@ -124,6 +128,10 @@ getAdminWorkflowInstanceListR = do
getGlobalWorkflowInstanceListR :: Handler Html getGlobalWorkflowInstanceListR :: Handler Html
getGlobalWorkflowInstanceListR = workflowInstanceListR WSGlobal getGlobalWorkflowInstanceListR = workflowInstanceListR WSGlobal
getSchoolWorkflowInstanceListR :: SchoolId -> Handler Html
getSchoolWorkflowInstanceListR = workflowInstanceListR . WSSchool
workflowInstanceListR :: WorkflowScope TermId SchoolId (TermId, SchoolId, CourseShorthand) -> Handler Html workflowInstanceListR :: WorkflowScope TermId SchoolId (TermId, SchoolId, CourseShorthand) -> Handler Html
workflowInstanceListR rScope = do workflowInstanceListR rScope = do
@ -154,6 +162,7 @@ workflowInstanceListR rScope = do
(heading, title) <- case rScope of (heading, title) <- case rScope of
WSGlobal -> return (MsgGlobalWorkflowInstancesHeading, MsgGlobalWorkflowInstancesTitle) WSGlobal -> return (MsgGlobalWorkflowInstancesHeading, MsgGlobalWorkflowInstancesTitle)
WSSchool ssh -> return (MsgSchoolWorkflowInstancesHeading ssh, MsgSchoolWorkflowInstancesTitle ssh)
_other -> error "not implemented" _other -> error "not implemented"
siteLayoutMsg heading $ do siteLayoutMsg heading $ do
@ -163,3 +172,46 @@ workflowInstanceListR rScope = do
toInitiateRoute win = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR) toInitiateRoute win = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR)
toEditRoute win = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIEditR) toEditRoute win = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIEditR)
toListRoute win = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIWorkflowsR) toListRoute win = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIWorkflowsR)
getTopWorkflowInstanceListR :: Handler Html
getTopWorkflowInstanceListR = do
gInstances <- runDB $ do
wis <- selectList [] []
wis' <- fmap catMaybes . forM wis $ \wi@(Entity wiId WorkflowInstance{..}) -> runMaybeT $ do
guard $ isTopWorkflowScope workflowInstanceScope
rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowInstanceScope
descs <- lift $ selectList [ WorkflowInstanceDescriptionInstance ==. wiId ] []
desc <- lift . runMaybeT $ do
langs <- hoistMaybe . NonEmpty.nonEmpty $ map (workflowInstanceDescriptionLanguage . entityVal) descs
lang <- selectLanguage langs
hoistMaybe . preview _head $ do
Entity _ desc@WorkflowInstanceDescription{..} <- descs
guard $ workflowInstanceDescriptionLanguage == lang
return desc
mayInitiate <- hasWriteAccessTo $ toInitiateRoute' rScope workflowInstanceName
mayEdit <- hasReadAccessTo $ toEditRoute' rScope workflowInstanceName
mayList <- hasReadAccessTo $ toListRoute' rScope workflowInstanceName
guard $ mayInitiate || mayEdit || mayList
return (rScope, [(wi, desc)])
let iSortProj (Entity _ WorkflowInstance{..}, mDesc)
= ( NTop workflowInstanceCategory
, workflowInstanceDescriptionTitle <$> mDesc
, workflowInstanceName
)
return $ sortOn iSortProj <$> Map.fromListWith (<>) wis'
siteLayoutMsg MsgTopWorkflowInstancesHeading $ do
setTitleI MsgTopWorkflowInstancesTitle
let instanceList rScope instances = $(widgetFile "workflows/instances")
where
toInitiateRoute = toInitiateRoute' rScope
toEditRoute = toEditRoute' rScope
toListRoute = toListRoute' rScope
showHeadings = Map.keys gInstances /= [WSGlobal]
$(widgetFile "workflows/top-instances")
where
toInitiateRoute' rScope win = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR)
toEditRoute' rScope win = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIEditR)
toListRoute' rScope win = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIWorkflowsR)

View File

@ -2,6 +2,7 @@ module Handler.Workflow.Instance.New
( getAdminWorkflowInstanceNewR, postAdminWorkflowInstanceNewR ( getAdminWorkflowInstanceNewR, postAdminWorkflowInstanceNewR
, adminWorkflowInstanceNewR , adminWorkflowInstanceNewR
, getGlobalWorkflowInstanceNewR, postGlobalWorkflowInstanceNewR , getGlobalWorkflowInstanceNewR, postGlobalWorkflowInstanceNewR
, getSchoolWorkflowInstanceNewR, postSchoolWorkflowInstanceNewR
, workflowInstanceNewR , workflowInstanceNewR
) where ) where
@ -72,5 +73,9 @@ getGlobalWorkflowInstanceNewR, postGlobalWorkflowInstanceNewR :: Handler Html
getGlobalWorkflowInstanceNewR = postGlobalWorkflowInstanceNewR getGlobalWorkflowInstanceNewR = postGlobalWorkflowInstanceNewR
postGlobalWorkflowInstanceNewR = workflowInstanceNewR WSGlobal postGlobalWorkflowInstanceNewR = workflowInstanceNewR WSGlobal
getSchoolWorkflowInstanceNewR, postSchoolWorkflowInstanceNewR :: SchoolId -> Handler Html
getSchoolWorkflowInstanceNewR = postSchoolWorkflowInstanceNewR
postSchoolWorkflowInstanceNewR = workflowInstanceNewR . WSSchool
workflowInstanceNewR :: WorkflowScope TermId SchoolId CourseId -> Handler Html workflowInstanceNewR :: WorkflowScope TermId SchoolId CourseId -> Handler Html
workflowInstanceNewR = error "not implemented" workflowInstanceNewR = error "not implemented"

View File

@ -1,5 +1,6 @@
module Handler.Workflow.Workflow.Delete module Handler.Workflow.Workflow.Delete
( getGWWDeleteR, postGWWDeleteR ( getGWWDeleteR, postGWWDeleteR
, getSWWDeleteR, postSWWDeleteR
, workflowDeleteR , workflowDeleteR
) where ) where
@ -13,5 +14,10 @@ getGWWDeleteR = postGWWDeleteR
postGWWDeleteR cID postGWWDeleteR cID
= workflowDeleteR <=< runDB . maybeT notFound $ ensureScope WSGlobal cID = workflowDeleteR <=< runDB . maybeT notFound $ ensureScope WSGlobal cID
getSWWDeleteR, postSWWDeleteR :: SchoolId -> CryptoFileNameWorkflowWorkflow -> Handler Html
getSWWDeleteR = postSWWDeleteR
postSWWDeleteR ssh cID
= workflowDeleteR <=< runDB . maybeT notFound $ ensureScope (WSSchool ssh) cID
workflowDeleteR :: WorkflowWorkflowId -> Handler Html workflowDeleteR :: WorkflowWorkflowId -> Handler Html
workflowDeleteR = error "not implemented" workflowDeleteR = error "not implemented"

View File

@ -1,5 +1,6 @@
module Handler.Workflow.Workflow.Edit module Handler.Workflow.Workflow.Edit
( getGWWEditR, postGWWEditR ( getGWWEditR, postGWWEditR
, getSWWEditR, postSWWEditR
, workflowEditR , workflowEditR
) where ) where
@ -13,5 +14,10 @@ getGWWEditR = postGWWEditR
postGWWEditR cID postGWWEditR cID
= workflowEditR <=< runDB . maybeT notFound $ ensureScope WSGlobal cID = workflowEditR <=< runDB . maybeT notFound $ ensureScope WSGlobal cID
getSWWEditR, postSWWEditR :: SchoolId -> CryptoFileNameWorkflowWorkflow -> Handler Html
getSWWEditR = postSWWEditR
postSWWEditR ssh cID
= workflowEditR <=< runDB . maybeT notFound $ ensureScope (WSSchool ssh) cID
workflowEditR :: WorkflowWorkflowId -> Handler Html workflowEditR :: WorkflowWorkflowId -> Handler Html
workflowEditR = error "not implemented" workflowEditR = error "not implemented"

View File

@ -1,28 +1,117 @@
module Handler.Workflow.Workflow.List module Handler.Workflow.Workflow.List
( getGlobalWorkflowWorkflowListR ( getGlobalWorkflowWorkflowListR
, getSchoolWorkflowWorkflowListR
, workflowWorkflowListR , workflowWorkflowListR
, getGWIWorkflowsR , getGWIWorkflowsR
, getSWIWorkflowsR
, workflowInstanceWorkflowsR , workflowInstanceWorkflowsR
, getAdminWorkflowWorkflowListR , getAdminWorkflowWorkflowListR
, getTopWorkflowWorkflowListR
) where ) where
import Import import Import
import Utils.Workflow
import Handler.Utils.Workflow.CanonicalRoute
getGlobalWorkflowWorkflowListR :: Handler Html getGlobalWorkflowWorkflowListR :: Handler Html
getGlobalWorkflowWorkflowListR = workflowWorkflowListR WSGlobal getGlobalWorkflowWorkflowListR = workflowWorkflowListR WSGlobal
getSchoolWorkflowWorkflowListR :: SchoolId -> Handler Html
getSchoolWorkflowWorkflowListR = workflowWorkflowListR . WSSchool
workflowWorkflowListR :: WorkflowScope TermId SchoolId CourseId -> Handler Html workflowWorkflowListR :: WorkflowScope TermId SchoolId CourseId -> Handler Html
workflowWorkflowListR = error "not implemented" workflowWorkflowListR scope = do -- not implemented; TODO: FIXME
wfRoutes <- runDB $ do
rScope <- maybeT notFound $ toRouteWorkflowScope scope
wfs <- selectKeysList [ WorkflowWorkflowScope ==. view _DBWorkflowScope scope ] []
flip mapMaybeM wfs $ \wfId -> do
cID <- encrypt wfId
let route = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)
guardM $ hasReadAccessTo route
return (cID, route)
defaultLayout
[whamlet|
$newline never
<ul>
$forall (cID, route) <- wfRoutes
<li>
<a href=@{route}>
#{toPathPiece cID}
|]
getGWIWorkflowsR :: WorkflowInstanceName -> Handler Html getGWIWorkflowsR :: WorkflowInstanceName -> Handler Html
getGWIWorkflowsR win getGWIWorkflowsR win
= workflowInstanceWorkflowsR <=< runDB . getKeyBy404 $ UniqueWorkflowInstance win WSGlobal = workflowInstanceWorkflowsR <=< runDB . getKeyBy404 $ UniqueWorkflowInstance win WSGlobal
getSWIWorkflowsR :: SchoolId -> WorkflowInstanceName -> Handler Html
getSWIWorkflowsR ssh win
= workflowInstanceWorkflowsR <=< runDB . getKeyBy404 . UniqueWorkflowInstance win . view _DBWorkflowScope $ WSSchool ssh
workflowInstanceWorkflowsR :: WorkflowInstanceId -> Handler Html workflowInstanceWorkflowsR :: WorkflowInstanceId -> Handler Html
workflowInstanceWorkflowsR = error "not implemented" workflowInstanceWorkflowsR wiId = do -- not implemented; TODO: FIXME
wfRoutes <- runDB $ do
wfs <- selectList [ WorkflowWorkflowInstance ==. Just wiId ] []
flip mapMaybeM wfs $ \(Entity wfId WorkflowWorkflow{..}) -> do
rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope
cID <- encrypt wfId
let route = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)
guardM $ hasReadAccessTo route
return (cID, route)
defaultLayout
[whamlet|
$newline never
<ul>
$forall (cID, route) <- wfRoutes
<li>
<a href=@{route}>
#{toPathPiece cID}
|]
getAdminWorkflowWorkflowListR :: Handler Html getAdminWorkflowWorkflowListR :: Handler Html
getAdminWorkflowWorkflowListR = error "not implemented" getAdminWorkflowWorkflowListR = do -- not implemented; TODO: FIXME
wfRoutes <- runDB $ do
wfs <- selectList [] []
flip mapMaybeM wfs $ \(Entity wfId WorkflowWorkflow{..}) -> do
rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope
cID <- encrypt wfId
let route = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)
return (cID, route)
defaultLayout
[whamlet|
$newline never
<ul>
$forall (cID, route) <- wfRoutes
<li>
<a href=@{route}>
#{toPathPiece cID}
|]
getTopWorkflowWorkflowListR :: Handler Html
getTopWorkflowWorkflowListR = do -- not implemented; TODO: FIXME
wfRoutes <- runDB $ do
wfs <- selectList [] []
flip mapMaybeM wfs $ \(Entity wfId WorkflowWorkflow{..}) -> do
guard $ isTopWorkflowScope workflowWorkflowScope
rScope <- toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope
cID <- encrypt wfId
let route = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWWorkflowR)
guardM $ hasReadAccessTo route
return (cID, route)
defaultLayout
[whamlet|
$newline never
<ul>
$forall (cID, route) <- wfRoutes
<li>
<a href=@{route}>
#{toPathPiece cID}
|]

View File

@ -1,6 +1,6 @@
module Handler.Workflow.Workflow.Workflow module Handler.Workflow.Workflow.Workflow
( getGWWWorkflowR, postGWWWorkflowR ( getGWWWorkflowR, postGWWWorkflowR, getGWWFilesR
, getGWWFilesR , getSWWWorkflowR, postSWWWorkflowR, getSWWFilesR
, workflowR , workflowR
) where ) where
@ -61,10 +61,21 @@ makePrisms ''WorkflowHistoryItemActor
getGWWWorkflowR, postGWWWorkflowR :: CryptoFileNameWorkflowWorkflow -> Handler Html getGWWWorkflowR, postGWWWorkflowR :: CryptoFileNameWorkflowWorkflow -> Handler Html
getGWWWorkflowR = postGWWWorkflowR getGWWWorkflowR = postGWWWorkflowR
postGWWWorkflowR cID = do postGWWWorkflowR cID = workflowR <=< runDB . maybeT notFound $ ensureScope WSGlobal cID
wId <- runDB . maybeT notFound $ ensureScope WSGlobal cID
workflowR wId getGWWFilesR :: CryptoFileNameWorkflowWorkflow -> WorkflowPayloadLabel -> CryptoUUIDWorkflowStateIndex -> Handler TypedContent
getGWWFilesR wwCID wpl stCID = do
wId <- runDB . maybeT notFound $ ensureScope WSGlobal wwCID
getWorkflowFilesR wId wpl stCID
getSWWWorkflowR, postSWWWorkflowR :: SchoolId -> CryptoFileNameWorkflowWorkflow -> Handler Html
getSWWWorkflowR = postSWWWorkflowR
postSWWWorkflowR ssh cID = workflowR <=< runDB . maybeT notFound $ ensureScope (WSSchool ssh) cID
getSWWFilesR :: SchoolId -> CryptoFileNameWorkflowWorkflow -> WorkflowPayloadLabel -> CryptoUUIDWorkflowStateIndex -> Handler TypedContent
getSWWFilesR ssh wwCID wpl stCID = do
wId <- runDB . maybeT notFound $ ensureScope (WSSchool ssh) wwCID
getWorkflowFilesR wId wpl stCID
workflowR :: WorkflowWorkflowId -> Handler Html workflowR :: WorkflowWorkflowId -> Handler Html
workflowR wwId = do workflowR wwId = do
@ -215,6 +226,7 @@ workflowR wwId = do
(heading, title) <- case rScope of (heading, title) <- case rScope of
WSGlobal -> return (MsgGlobalWorkflowWorkflowWorkflowHeading cID, MsgGlobalWorkflowWorkflowWorkflowTitle cID) WSGlobal -> return (MsgGlobalWorkflowWorkflowWorkflowHeading cID, MsgGlobalWorkflowWorkflowWorkflowTitle cID)
WSSchool ssh -> return (MsgSchoolWorkflowWorkflowWorkflowHeading ssh cID, MsgSchoolWorkflowWorkflowWorkflowTitle ssh cID)
_other -> error "not implemented" _other -> error "not implemented"
siteLayoutMsg heading $ do siteLayoutMsg heading $ do
@ -246,10 +258,9 @@ workflowR wwId = do
$(widgetFile "workflows/workflow") $(widgetFile "workflows/workflow")
getGWWFilesR :: CryptoFileNameWorkflowWorkflow -> WorkflowPayloadLabel -> CryptoUUIDWorkflowStateIndex -> Handler TypedContent getWorkflowFilesR :: WorkflowWorkflowId -> WorkflowPayloadLabel -> CryptoUUIDWorkflowStateIndex -> Handler TypedContent
getGWWFilesR wwCID wpl stCID = do getWorkflowFilesR wwId wpl stCID = do
fRefs <- runDB $ do fRefs <- runDB $ do
wwId <- decrypt wwCID
WorkflowWorkflow{..} <- get404 wwId WorkflowWorkflow{..} <- get404 wwId
stIx <- decrypt stCID stIx <- decrypt stCID
payloads <- maybeT notFound . workflowStateSection stIx $ _DBWorkflowState # workflowWorkflowState payloads <- maybeT notFound . workflowStateSection stIx $ _DBWorkflowState # workflowWorkflowState
@ -261,6 +272,7 @@ getGWWFilesR wwCID wpl stCID = do
when (null payloads'') notFound when (null payloads'') notFound
return payloads'' return payloads''
wwCID <- encrypt wwId
archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack) . ap getMessageRender . pure $ MsgWorkflowWorkflowFilesArchiveName wwCID wpl stCID archiveName <- fmap (flip addExtension (unpack extensionZip) . unpack) . ap getMessageRender . pure $ MsgWorkflowWorkflowFilesArchiveName wwCID wpl stCID
serveSomeFiles archiveName $ yieldMany fRefs serveSomeFiles archiveName $ yieldMany fRefs

View File

@ -889,6 +889,29 @@ diffTimeout timeoutLength timeoutRes act = fromMaybe timeoutRes <$> timeout time
= let (MkFixed micro :: Micro) = realToFrac timeoutLength = let (MkFixed micro :: Micro) = realToFrac timeoutLength
in fromInteger micro in fromInteger micro
--------------
-- Foldable --
--------------
minLength :: ( Integral n
, MonoFoldable mono
)
=> n -> mono -> Bool
-- ^ @minLegth n xs = length xs >= n@
minLength l = go l . otoList
where
go l' _ | l' <= 0 = True
go l' xs = case xs of
_ : xs' -> go (pred l') xs'
[] -> False
maxLength :: ( Integral n
, MonoFoldable mono
)
=> n -> mono -> Bool
-- ^ @maxLegth n xs = length xs <= n@
maxLength l = not . minLength (succ l)
------------ ------------
-- Writer -- -- Writer --
------------ ------------

View File

@ -6,6 +6,7 @@ module Utils.Workflow
, _DBWorkflowGraph , _DBWorkflowGraph
, _DBWorkflowState , _DBWorkflowState
, decryptWorkflowStateIndex, encryptWorkflowStateIndex , decryptWorkflowStateIndex, encryptWorkflowStateIndex
, isTopWorkflowScope
) where ) where
import Import.NoFoundation import Import.NoFoundation
@ -84,3 +85,7 @@ decryptWorkflowStateIndex :: ( MonadCrypto m
decryptWorkflowStateIndex wwId cID = do decryptWorkflowStateIndex wwId cID = do
cIDKey <- workflowStateIndexCryptoIDKey wwId cIDKey <- workflowStateIndexCryptoIDKey wwId
$cachedHereBinary (wwId, cID) . flip runReaderT cIDKey $ I.decrypt cID $cachedHereBinary (wwId, cID) . flip runReaderT cIDKey $ I.decrypt cID
isTopWorkflowScope :: WorkflowScope termid schoolid courseid -> Bool
isTopWorkflowScope = (`elem` [WSGlobal', WSTerm', WSSchool', WSTermSchool']) . classifyWorkflowScope

View File

@ -0,0 +1,8 @@
$newline never
$forall (rScope, instances) <- Map.toList gInstances
<section>
$if showHeadings
<h2>
<a href=@{review _WorkflowScopeRoute (rScope, WorkflowInstanceListR)}>
_{rScope}
^{instanceList rScope instances}

View File

@ -1302,7 +1302,7 @@ fillDb = do
thesesWorkflowDef = WorkflowDefinition{..} thesesWorkflowDef = WorkflowDefinition{..}
where workflowDefinitionInstanceCategory = Just "theses" where workflowDefinitionInstanceCategory = Just "theses"
workflowDefinitionName = "theses" workflowDefinitionName = "theses"
workflowDefinitionScope = WSGlobal' -- TODO workflowDefinitionScope = WSSchool'
wdId <- insert thesesWorkflowDef wdId <- insert thesesWorkflowDef
insert_ WorkflowDefinitionDescription insert_ WorkflowDefinitionDescription
{ workflowDefinitionDescriptionDefinition = wdId { workflowDefinitionDescriptionDefinition = wdId
@ -1320,7 +1320,7 @@ fillDb = do
thesesWorkflowInst = WorkflowInstance{..} thesesWorkflowInst = WorkflowInstance{..}
where workflowInstanceDefinition = Just wdId where workflowInstanceDefinition = Just wdId
workflowInstanceGraph = workflowDefinitionGraph workflowInstanceGraph = workflowDefinitionGraph
workflowInstanceScope = WSGlobal -- TODO workflowInstanceScope = WSSchool $ unSchoolKey ifi
workflowInstanceName = workflowDefinitionName thesesWorkflowDef workflowInstanceName = workflowDefinitionName thesesWorkflowDef
workflowInstanceCategory = workflowDefinitionInstanceCategory thesesWorkflowDef workflowInstanceCategory = workflowDefinitionInstanceCategory thesesWorkflowDef
wiId <- insert thesesWorkflowInst wiId <- insert thesesWorkflowInst
@ -1345,4 +1345,3 @@ fillDb = do
-> return () -> return ()
Nothing Nothing
-> insert_ $ ChangelogItemFirstSeen changelogItem firstSeen -> insert_ $ ChangelogItemFirstSeen changelogItem firstSeen

View File

@ -5,8 +5,12 @@ module Database.Persist.Sql.Types.TestInstances
import TestImport import TestImport
import Database.Persist.Sql import Database.Persist.Sql
import Data.Binary (Binary)
deriving newtype instance Arbitrary (BackendKey SqlBackend) deriving newtype instance Arbitrary (BackendKey SqlBackend)
deriving newtype instance Arbitrary (BackendKey SqlWriteBackend) deriving newtype instance Arbitrary (BackendKey SqlWriteBackend)
deriving newtype instance Arbitrary (BackendKey SqlReadBackend) deriving newtype instance Arbitrary (BackendKey SqlReadBackend)
deriving newtype instance Binary (BackendKey SqlBackend)
deriving newtype instance Binary (BackendKey SqlWriteBackend)
deriving newtype instance Binary (BackendKey SqlReadBackend)

View File

@ -94,6 +94,14 @@ instance Arbitrary GlobalWorkflowWorkflowR where
arbitrary = genericArbitrary arbitrary = genericArbitrary
shrink = genericShrink shrink = genericShrink
instance Arbitrary SchoolWorkflowInstanceR where
arbitrary = genericArbitrary
shrink = genericShrink
instance Arbitrary SchoolWorkflowWorkflowR where
arbitrary = genericArbitrary
shrink = genericShrink
instance Arbitrary (Route UniWorX) where instance Arbitrary (Route UniWorX) where
arbitrary = genericArbitrary arbitrary = genericArbitrary
shrink = genericShrink shrink = genericShrink

View File

@ -0,0 +1,30 @@
module Handler.Utils.Workflow.CanonicalRouteSpec where
import TestImport
import Handler.Utils.Workflow.CanonicalRoute
import ModelSpec ()
import FoundationSpec ()
instance Arbitrary WorkflowScopeRoute where
arbitrary = genericArbitrary
shrink = genericShrink
instance CoArbitrary WorkflowScopeRoute
instance Function WorkflowScopeRoute
instance Arbitrary WorkflowInstanceR where
arbitrary = genericArbitrary
shrink = genericShrink
instance CoArbitrary WorkflowInstanceR
instance Function WorkflowInstanceR
instance Arbitrary WorkflowWorkflowR where
arbitrary = genericArbitrary
shrink = genericShrink
instance CoArbitrary WorkflowWorkflowR
instance Function WorkflowWorkflowR
spec :: Spec
spec = describe "_WorkflowSpecRoute" $
before_ (pendingWith "Missing routes") . it "is a prism" . property $ isPrism _WorkflowScopeRoute

View File

@ -24,6 +24,8 @@ import Utils.I18n
instance Arbitrary WorkflowPayloadLabel where instance Arbitrary WorkflowPayloadLabel where
arbitrary = genericArbitrary arbitrary = genericArbitrary
shrink = genericShrink shrink = genericShrink
instance CoArbitrary WorkflowPayloadLabel
instance Function WorkflowPayloadLabel
instance (Arbitrary fileid, Arbitrary userid, Typeable fileid, Typeable userid, Ord fileid, Arbitrary (FileField fileid)) => Arbitrary (WorkflowPayloadSpec fileid userid) where instance (Arbitrary fileid, Arbitrary userid, Typeable fileid, Typeable userid, Ord fileid, Arbitrary (FileField fileid)) => Arbitrary (WorkflowPayloadSpec fileid userid) where
arbitrary = oneof arbitrary = oneof
@ -115,8 +117,14 @@ instance (Arbitrary fileid, Arbitrary userid, Ord fileid, Typeable userid, Typea
instance (Arbitrary payload, IsWorkflowFieldPayload' fileid userid payload) => Arbitrary (WorkflowFieldPayload fileid userid payload) where instance (Arbitrary payload, IsWorkflowFieldPayload' fileid userid payload) => Arbitrary (WorkflowFieldPayload fileid userid payload) where
arbitrary = review _WorkflowFieldPayload <$> arbitrary arbitrary = review _WorkflowFieldPayload <$> arbitrary
instance (Arbitrary termid, Arbitrary schoolid, Arbitrary courseid) => Arbitrary (WorkflowScope termid schoolid courseid) where
arbitrary = genericArbitrary
instance (CoArbitrary termid, CoArbitrary schoolid, CoArbitrary courseid) => CoArbitrary (WorkflowScope termid schoolid courseid)
instance (Function termid, Function schoolid, Function courseid) => Function (WorkflowScope termid schoolid courseid)
instance Arbitrary WorkflowScope' where instance Arbitrary WorkflowScope' where
arbitrary = genericArbitrary arbitrary = genericArbitrary
shrink = genericShrink
spec :: Spec spec :: Spec

View File

@ -44,26 +44,38 @@ import Text.Blaze.TestInstances ()
import qualified Data.Text.Lazy as LT import qualified Data.Text.Lazy as LT
import Text.Blaze.Html.Renderer.Text (renderHtml)
instance Arbitrary Season where instance Arbitrary Season where
arbitrary = genericArbitrary arbitrary = genericArbitrary
shrink = genericShrink shrink = genericShrink
instance CoArbitrary Season
instance Function Season
instance Arbitrary TermIdentifier where instance Arbitrary TermIdentifier where
arbitrary = do arbitrary = do
season <- arbitrary season <- arbitrary
year <- arbitrary `suchThat` (\y -> abs y >= 100) year <- arbitrary `suchThat` (\y -> abs y >= 100)
return $ TermIdentifier{..} return $ TermIdentifier{..}
shrink = genericShrink shrink = filter ((\y -> abs y >= 100) . year) . genericShrink
instance CoArbitrary TermIdentifier
instance Function TermIdentifier
deriving instance Generic TermId
instance Arbitrary TermId where instance Arbitrary TermId where
arbitrary = TermKey <$> arbitrary arbitrary = genericArbitrary
shrink = map TermKey . shrink . unTermKey shrink = genericShrink
instance CoArbitrary TermId
instance Function TermId
deriving instance Generic SchoolId
instance Arbitrary SchoolId where instance Arbitrary SchoolId where
arbitrary = SchoolKey <$> arbitrary arbitrary = genericArbitrary
shrink = map SchoolKey . shrink . unSchoolKey shrink = genericShrink
instance CoArbitrary SchoolId
instance Function SchoolId
instance Arbitrary Pseudonym where instance Arbitrary Pseudonym where
arbitrary = Pseudonym <$> arbitraryBoundedIntegral arbitrary = Pseudonym <$> arbitraryBoundedIntegral
@ -216,10 +228,6 @@ instance {-# OVERLAPPABLE #-} ToBackendKey SqlBackend record => Arbitrary (Key r
arbitrary = toSqlKey <$> arbitrary arbitrary = toSqlKey <$> arbitrary
shrink = map toSqlKey . shrink . fromSqlKey shrink = map toSqlKey . shrink . fromSqlKey
instance Arbitrary Html where
arbitrary = (preEscapedToHtml :: String -> Html) . getPrintableString <$> arbitrary
shrink = map preEscapedToHtml . shrink . renderHtml
instance Arbitrary StoredMarkup where instance Arbitrary StoredMarkup where
arbitrary = (`suchThat` (not . null . LT.strip . renderHtml . markupOutput)) $ oneof arbitrary = (`suchThat` (not . null . LT.strip . renderHtml . markupOutput)) $ oneof
[ htmlToStoredMarkup <$> arbitrary [ htmlToStoredMarkup <$> arbitrary
@ -320,6 +328,7 @@ instance Arbitrary RoomReference' where
arbitrary = genericArbitrary arbitrary = genericArbitrary
spec :: Spec spec :: Spec
spec = do spec = do
parallel $ do parallel $ do
@ -421,6 +430,8 @@ spec = do
[ persistFieldLaws, jsonLaws, eqLaws, ordLaws ] [ persistFieldLaws, jsonLaws, eqLaws, ordLaws ]
lawsCheckHspec (Proxy @RoomReference') lawsCheckHspec (Proxy @RoomReference')
[ eqLaws, ordLaws, finiteLaws, showReadLaws, pathPieceLaws, boundedEnumLaws ] [ eqLaws, ordLaws, finiteLaws, showReadLaws, pathPieceLaws, boundedEnumLaws ]
lawsCheckHspec (Proxy @(WorkflowScope TermIdentifier SchoolShorthand SqlBackendKey))
[ eqLaws, ordLaws, showLaws, showReadLaws, pathPieceLaws, jsonLaws, persistFieldLaws, binaryLaws ]
describe "TermIdentifier" $ do describe "TermIdentifier" $ do
it "has compatible encoding/decoding to/from Text" . property $ it "has compatible encoding/decoding to/from Text" . property $

View File

@ -172,6 +172,8 @@ instance {-# OVERLAPS #-} (HasCryptoID ns ct pt (ReaderT CryptoIDKey Catch), Arb
arbitrary = arbitrary <&> \pt -> (pt, either (error . show) id . runCatch $ runReaderT (Implicit.encrypt pt) tmpKey) arbitrary = arbitrary <&> \pt -> (pt, either (error . show) id . runCatch $ runReaderT (Implicit.encrypt pt) tmpKey)
where where
tmpKey = unsafePerformIO genKey tmpKey = unsafePerformIO genKey
instance CoArbitrary ct => CoArbitrary (CryptoID ns ct)
instance Function ct => Function (CryptoID ns ct)
instance Arbitrary VerpMode where instance Arbitrary VerpMode where
arbitrary = genericArbitrary arbitrary = genericArbitrary

View File

@ -37,6 +37,7 @@ import Test.QuickCheck.Classes.Universe as X
import Test.QuickCheck.Classes.Binary as X import Test.QuickCheck.Classes.Binary as X
import Test.QuickCheck.Classes.Csv as X import Test.QuickCheck.Classes.Csv as X
import Test.QuickCheck.IO as X import Test.QuickCheck.IO as X
import Control.Lens.Properties as X
import Data.Proxy as X import Data.Proxy as X
import Data.UUID as X (UUID) import Data.UUID as X (UUID)
import System.IO as X (hPrint, hPutStrLn) import System.IO as X (hPrint, hPutStrLn)