{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE MultiWayIf, LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE FlexibleInstances #-} module Handler.Profile where import Import import Handler.Utils import Handler.Utils.Table.Cells import Utils.Lens -- import Colonnade hiding (fromMaybe, singleton) -- import Yesod.Colonnade import Data.Monoid (Any(..)) import qualified Data.Map as Map import Data.Map ((!)) import qualified Data.Set as Set -- import qualified Data.Set as Set import qualified Database.Esqueleto as E -- import Database.Esqueleto ((^.)) data SettingsForm = SettingsForm { stgMaxFavourties :: Int , stgTheme :: Theme , stgDateTime :: DateTimeFormat , stgDate :: DateTimeFormat , stgTime :: DateTimeFormat , stgDownloadFiles :: Bool , stgNotificationSettings :: NotificationSettings } makeSettingForm :: Maybe SettingsForm -> Form SettingsForm makeSettingForm template = identForm FIDsettings $ \html -> do let themeList = [Option (display t) t (toPathPiece t) | t <- universeF] (result, widget) <- flip (renderAForm FormStandard) html $ SettingsForm <$> areq (natFieldI $ MsgNatField "Favoriten") -- TODO: natFieldI not working here (fslpI MsgFavoriten "Anzahl Favoriten") (stgMaxFavourties <$> template) <*> areq (selectField . return $ mkOptionList themeList) (fslI MsgTheme) { fsId = Just "theme-select" } (stgTheme <$> template) <*> areq (selectField $ dateTimeFormatOptions SelFormatDateTime) (fslI MsgDateTimeFormat) (stgDateTime <$> template) <*> areq (selectField $ dateTimeFormatOptions SelFormatDate) (fslI MsgDateFormat) (stgDate <$> template) <*> areq (selectField $ dateTimeFormatOptions SelFormatTime) (fslI MsgTimeFormat) (stgTime <$> template) <*> areq checkBoxField (fslI MsgDownloadFiles & setTooltip MsgDownloadFilesTip ) (stgDownloadFiles <$> template) <*> formToAForm (nsFieldView =<< renderAForm FormStandard nsForm mempty) <* submitButton return (result, widget) -- no validation required here where nsForm = fmap (\m -> NotificationSettings $ \nt -> m ! nt) . sequenceA . flip Map.fromSet (Set.fromList universeF) $ \nt -> areq checkBoxField (fslI nt) (flip notificationAllowed nt . stgNotificationSettings <$> template) nsFieldView :: (FormResult NotificationSettings, Widget) -> MForm Handler (FormResult NotificationSettings, [FieldView UniWorX]) nsFieldView (res, fvInput) = do mr <- getMessageRender let fvLabel = toHtml $ mr MsgNotificationSettings fvTooltip = mempty fvRequired = True fvErrors | FormFailure (err:_) <- res = Just $ toHtml err | otherwise = Nothing fvId <- newIdent return (res, pure FieldView{..}) -- areq nsField (fslI MsgNotificationSettings) (stgNotficationSettings <$> template) getProfileR, postProfileR :: Handler Html getProfileR = postProfileR postProfileR = do (uid, User{..}) <- requireAuthPair let settingsTemplate = Just $ SettingsForm { stgMaxFavourties = userMaxFavourites , stgTheme = userTheme , stgDateTime = userDateTimeFormat , stgDate = userDateFormat , stgTime = userTimeFormat , stgDownloadFiles = userDownloadFiles , stgNotificationSettings = userNotificationSettings } ((res,formWidget), formEnctype) <- runFormPost $ makeSettingForm settingsTemplate case res of (FormSuccess SettingsForm{..}) -> do runDB $ do update uid [ UserMaxFavourites =. stgMaxFavourties , UserTheme =. stgTheme , UserDateTimeFormat =. stgDateTime , UserDateFormat =. stgDate , UserTimeFormat =. stgTime , UserDownloadFiles =. stgDownloadFiles , UserNotificationSettings =. stgNotificationSettings ] when (stgMaxFavourties < userMaxFavourites) $ do -- prune Favourites to user-defined size oldFavs <- selectKeysList [ CourseFavouriteUser ==. uid] [ Desc CourseFavouriteTime , OffsetBy $ stgMaxFavourties ] mapM_ delete oldFavs addMessageI Info $ MsgSettingsUpdate redirect ProfileR -- TODO: them change does not happen without redirect (FormFailure msgs) -> forM_ msgs $ (addMessage Warning) . toHtml _ -> return () (admin_rights,lecturer_rights,lecture_corrector,studies) <- runDB $ (,,,) <$> (E.select $ E.from $ \(adright `E.InnerJoin` school) -> do E.where_ $ adright E.^. UserAdminUser E.==. E.val uid E.on $ adright E.^. UserAdminSchool E.==. school E.^. SchoolId return (school E.^. SchoolShorthand) ) <*> (E.select $ E.from $ \(lecright `E.InnerJoin` school) -> do E.where_ $ lecright E.^. UserLecturerUser E.==. E.val uid E.on $ lecright E.^. UserLecturerSchool E.==. school E.^. SchoolId return (school E.^. SchoolShorthand) ) <*> (E.select $ E.distinct $ E.from $ \(sheet `E.InnerJoin` corrector `E.InnerJoin` course) -> do E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId E.on $ sheet E.^. SheetId E.==. corrector E.^. SheetCorrectorSheet E.where_ $ corrector E.^. SheetCorrectorUser E.==. E.val uid return (course E.^. CourseTerm, course E.^. CourseSchool, course E.^. CourseShorthand) ) <*> (E.select $ E.from $ \(studydegree `E.InnerJoin` studyfeat `E.InnerJoin` studyterms) -> do E.where_ $ studyfeat E.^. StudyFeaturesUser E.==. E.val uid E.on $ studyfeat E.^. StudyFeaturesField E.==. studyterms E.^. StudyTermsId E.on $ studyfeat E.^. StudyFeaturesDegree E.==. studydegree E.^. StudyDegreeId return ( ( studydegree E.^. StudyDegreeName , studydegree E.^. StudyDegreeKey ) , ( studyterms E.^. StudyTermsName , studyterms E.^. StudyTermsKey ) , studyfeat E.^. StudyFeaturesType , studyfeat E.^. StudyFeaturesSemester) ) let formText = Just MsgSettings actionUrl = ProfileR settingsForm = $(widgetFile "formPageI18n") defaultLayout $ do setTitle . toHtml $ userIdent <> "'s User page" $(widgetFile "profile") $(widgetFile "dsgvDisclaimer") postProfileDataR :: Handler Html postProfileDataR = do ((btnResult,_), _) <- runFormPost $ buttonForm case btnResult of (FormSuccess BtnDelete) -> do (uid, User{..}) <- requireAuthPair clearCreds False -- Logout-User ((deletedSubmissions,groupSubmissions),deletedSubmissionGroups) <- runDB $ deleteUser uid -- addMessageIHamlet $(addMessageFile Success "templates/deletedUser.hamlet") -- USE THIS ONE -- addMessageI Success $ MsgDeleteUser deletedSubmissions -- when (groupSubmissions > 0) $ addMessageI Info $ MsgDeleteUserGroupSubmissions groupSubmissions defaultLayout $ do $(widgetFile "deletedUser") (FormSuccess BtnAbort ) -> do addMessageI Info MsgAborted redirect ProfileDataR _other -> getProfileDataR deleteUser :: UserId -> DB ((Int,Int),Int64) -- TODO: Restrict deletions for lecturers, tutors and students in course that won't allow deregistration deleteUser duid = do -- E.deleteCount for submissions is not cascading, hence we first select and then delete manually -- We delete all files tied to submissions where the user is the lone submissionUser -- Do not deleteCascade submissions where duid is the corrector: updateWhere [SubmissionRatingBy ==. Just duid] [SubmissionRatingBy =. Nothing] groupSubmissions <- selectSubmissionsWhere (\numBuddies -> numBuddies E.>. E.val (0::Int64)) singleSubmissions <- selectSubmissionsWhere (\numBuddies -> numBuddies E.==. E.val (0::Int64)) deleteCascade duid forM_ singleSubmissions $ \(E.Value submissionId) -> do deleteFileIds <- map E.unValue <$> getSubmissionFiles submissionId deleteCascade submissionId deleteCascadeWhere [FileId <-. deleteFileIds] -- TODO: throws exception for de-duplicated files deletedSubmissionGroups <- deleteSingleSubmissionGroups return ((length singleSubmissions, length groupSubmissions),deletedSubmissionGroups) where selectSubmissionsWhere :: (E.SqlExpr (E.Value Int64) -> E.SqlExpr (E.Value Bool)) -> DB [E.Value (Key Submission)] selectSubmissionsWhere whereBuddies = E.select $ E.from $ \(submission `E.InnerJoin` suser) -> do E.on $ submission E.^. SubmissionId E.==. suser E.^. SubmissionUserSubmission let numBuddies = E.sub_select $ E.from $ \subUsers -> do E.where_ $ subUsers E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId E.&&. subUsers E.^. SubmissionUserUser E.!=. E.val duid return E.countRows E.where_ $ suser E.^. SubmissionUserUser E.==. E.val duid E.&&. (whereBuddies numBuddies) return $ submission E.^. SubmissionId getSubmissionFiles :: SubmissionId -> DB [E.Value (Key File)] getSubmissionFiles subId = E.select $ E.from $ \file -> do E.where_ $ E.exists $ E.from $ \submissionFile -> do E.where_ $ submissionFile E.^. SubmissionFileSubmission E.==. E.val subId E.&&. submissionFile E.^. SubmissionFileFile E.==. file E.^. FileId return $ file E.^. FileId deleteSingleSubmissionGroups = E.deleteCount $ E.from $ \submissionGroup -> do E.where_ $ E.exists $ E.from $ \subGroupUser -> do E.where_ $ subGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId E.&&. subGroupUser E.^. SubmissionGroupUserUser E.==. E.val duid E.where_ $ E.notExists $ E.from $ \subGroupUser -> do E.where_ $ subGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId E.&&. subGroupUser E.^. SubmissionGroupUserUser E.!=. E.val duid getProfileDataR :: Handler Html getProfileDataR = do (uid, User{..}) <- requireAuthPair -- mr <- getMessageRender -- Tabelle mit eigenen Kursen (hasRows, ownedCoursesTable) <- mkOwnedCoursesTable uid -- Tabelle mit allen Teilnehmer: Kurs (link), Datum enrolledCoursesTable <- mkEnrolledCoursesTable uid -- Tabelle mit allen Klausuren und Noten examTable <- return [whamlet| Klausuren werden momentan leider noch nicht unterstützt.|] -- Tabelle mit allen Abgaben und Abgabe-Gruppen submissionTable <- mkSubmissionTable uid -- Tabelle mit allen Abgabegruppen submissionGroupTable <- mkSubmissionGroupTable uid -- Tabelle mit allen Korrektor-Aufgaben correctionsTable <- mkCorrectionsTable uid -- Tabelle mit allen eigenen Tutorials ownTutorialTable <- return [whamlet| Übungsgruppen werden momentan leider noch nicht unterstützt.|] -- Tabelle mit allen Tutorials tutorialTable <- return [whamlet| Übungsgruppen werden momentan leider noch nicht unterstützt.|] -- Delete Button (btnWdgt, btnEnctype) <- generateFormPost (buttonForm :: Form BtnDelete) -- TODO: move this into a Message and/or Widget-File let delWdgt = [whamlet|