module Handler.Exam.Edit ( getEEditR, postEEditR ) where import Import import Handler.Exam.Form import Handler.Exam.CorrectorInvite import qualified Data.Set as Set import Handler.Utils import Handler.Utils.Exam import Handler.Utils.Invitations import Jobs.Queue import qualified Database.Esqueleto.Legacy as E import qualified Database.Esqueleto.Utils as E data ExamEditException = ExamEditExamNameTaken ExamName | ExamEditWouldBreakSheetTypeReference deriving (Eq, Ord, Read, Show, Generic, Typeable) deriving anyclass (Exception) embedRenderMessage ''UniWorX ''ExamEditException id getEEditR, postEEditR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html getEEditR = postEEditR postEEditR tid ssh csh examn = do (template, (editExamAct, (editExamWidget, editExamEnctype))) <- runDBJobs $ do (cid, exam@(Entity eId oldExam)) <- fetchCourseIdExam tid ssh csh examn course <- getEntity404 cid template <- examFormTemplate exam ((editExamResult, editExamWidget), editExamEnctype) <- runFormPost . validateForm (validateExam cid $ Just exam) . examForm course $ Just template editExamAct <- formResultMaybe editExamResult $ \ExamForm{..} -> do res <- trySql @ExamEditException $ do examAuthorshipStatement <- traverse insertAuthorshipStatement efAuthorshipStatement insertRes <- myReplaceUnique eId Exam { examCourse = cid , examName = efName , examGradingRule = efGradingRule , examBonusRule = efBonusRule , examOccurrenceRule = efOccurrenceRule , examExamOccurrenceMapping = examExamOccurrenceMapping oldExam , examVisibleFrom = efVisibleFrom , examRegisterFrom = efRegisterFrom , examRegisterTo = efRegisterTo , examDeregisterUntil = efDeregisterUntil , examPublishOccurrenceAssignments = efPublishOccurrenceAssignments , examStart = efStart , examEnd = efEnd , examFinished = efFinished , examClosed = examClosed oldExam , examPublicStatistics = efPublicStatistics , examGradingMode = efGradingMode , examDescription = efDescription , examExamMode = efExamMode , examStaff = efStaff , examPartsFrom = efPartsFrom , examAuthorshipStatement } when (is _Just insertRes) $ throwM $ ExamEditExamNameTaken efName occIds <- fmap catMaybes . forM (Set.toList efOccurrences) $ traverse decrypt . eofId deleteWhere [ ExamOccurrenceExam ==. eId, ExamOccurrenceId /<-. occIds ] forM_ (Set.toList efOccurrences) $ \case ExamOccurrenceForm{ eofId = Nothing, .. } -> insert_ ExamOccurrence { examOccurrenceExam = eId , examOccurrenceName = eofName , examOccurrenceRoom = eofRoom , examOccurrenceRoomHidden = eofRoomHidden , examOccurrenceCapacity = eofCapacity , examOccurrenceStart = eofStart , examOccurrenceEnd = eofEnd , examOccurrenceDescription = eofDescription } ExamOccurrenceForm{ .. } -> void . runMaybeT $ do cID <- hoistMaybe eofId eofId' <- decrypt cID oldOcc <- MaybeT $ get eofId' guard $ examOccurrenceExam oldOcc == eId lift $ replace eofId' ExamOccurrence { examOccurrenceExam = eId , examOccurrenceName = eofName , examOccurrenceRoom = eofRoom , examOccurrenceRoomHidden = eofRoomHidden , examOccurrenceCapacity = eofCapacity , examOccurrenceStart = eofStart , examOccurrenceEnd = eofEnd , examOccurrenceDescription = eofDescription } pIds <- fmap catMaybes . forM (Set.toList efExamParts) $ traverse decrypt . epfId brokenRefs <- E.selectExists . E.from $ \examPart -> do E.where_ $ examPart E.^. ExamPartExam E.==. E.val eId E.&&. examPart E.^. ExamPartId `E.notIn` E.valList pIds E.where_ . E.exists . E.from $ \sheet -> do let sheetTypeExamPart :: E.SqlExpr (E.Value (Maybe Value)) sheetTypeExamPart = sheet E.^. SheetType E.->. "exam-part" examPartId' :: E.SqlExpr (E.Value Value) examPartId' = E.explicitUnsafeCoerceSqlExprValue @Value "jsonb" . E.explicitUnsafeCoerceSqlExprValue @Text "text" $ examPart E.^. ExamPartId E.where_ $ E.maybe E.false (E.==. examPartId') sheetTypeExamPart when brokenRefs $ throwM ExamEditWouldBreakSheetTypeReference deleteWhere [ ExamPartExam ==. eId, ExamPartId /<-. pIds ] forM_ (Set.toList efExamParts) $ \case ExamPartForm{ epfId = Nothing, .. } -> insert_ ExamPart { examPartExam = eId , examPartNumber = epfNumber , examPartName = epfName , examPartMaxPoints = epfMaxPoints , examPartWeight = epfWeight } ExamPartForm{ .. } -> void . runMaybeT $ do cID <- hoistMaybe epfId epfId' <- decrypt cID oldPart <- MaybeT $ get epfId' guard $ examPartExam oldPart == eId lift $ replace epfId' ExamPart { examPartExam = eId , examPartNumber = epfNumber , examPartName = epfName , examPartMaxPoints = epfMaxPoints , examPartWeight = epfWeight } deleteWhere [ ExamOfficeSchoolExam ==. eId ] insertMany_ [ ExamOfficeSchool ssh' eId | ssh' <- Set.toList efOfficeSchools ] let (invites, adds) = partitionEithers $ Set.toList efCorrectors deleteWhere [ ExamCorrectorExam ==. eId ] insertMany_ $ map (ExamCorrector eId) adds memcachedByInvalidate AuthCacheExamCorrectorList $ Proxy @(Set UserId) deleteWhere [ InvitationFor ==. invRef @ExamCorrector eId, InvitationEmail /<-. invites ] sinkInvitationsF examCorrectorInvitationConfig $ map (, eId, (InvDBDataExamCorrector, InvTokenDataExamCorrector)) invites return insertRes return . Just $ case res of Left exc -> addMessageI Error exc Right _ -> do addMessageI Success $ MsgExamEdited efName redirect $ CExamR tid ssh csh efName EShowR return (template, (editExamAct, (editExamWidget, editExamEnctype))) sequence_ editExamAct let heading = prependCourseTitle tid ssh csh . MsgExamEditHeading $ efName template siteLayoutMsg heading $ do setTitleI heading let editExamForm = wrapForm editExamWidget def { formMethod = POST , formAction = Just . SomeRoute $ CExamR tid ssh csh examn EEditR , formEncoding = editExamEnctype } $(widgetFile "exam-edit")