-- SPDX-FileCopyrightText: 2022 Sarah Vaupel -- -- SPDX-License-Identifier: AGPL-3.0-or-later module Handler.Course.ParticipantInvite ( getCAddUserR, postCAddUserR ) where import Import import Handler.Utils import Handler.Utils.Avs import Jobs.Queue import qualified Data.Aeson as Aeson import qualified Data.CaseInsensitive as CI import Data.Map ((!)) import qualified Data.Map as Map import qualified Data.Time.Zones as TZ import qualified Data.Set as Set import Control.Monad.Except (MonadError(..)) import Generics.Deriving.Monoid (memptydefault, mappenddefault) type UserSearchKey = Text type TutorialIdent = CI Text data ButtonCourseRegisterMode = BtnCourseRegisterConfirm | BtnCourseRegisterAbort deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) instance Universe ButtonCourseRegisterMode instance Finite ButtonCourseRegisterMode embedRenderMessage ''UniWorX ''ButtonCourseRegisterMode id nullaryPathPiece ''ButtonCourseRegisterMode $ camelToPathPiece' 1 instance Button UniWorX ButtonCourseRegisterMode where btnLabel x = [whamlet|_{x}|] btnClasses BtnCourseRegisterConfirm = [BCIsButton, BCPrimary] btnClasses BtnCourseRegisterAbort = [BCIsButton, BCDanger] btnValidate _ BtnCourseRegisterAbort = False btnValidate _ _ = True data CourseRegisterAction = CourseRegisterActionAddParticipant | CourseRegisterActionAddTutorialMember -- | CourseRegisterActionUnknownPerson deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) instance Universe CourseRegisterAction instance Finite CourseRegisterAction data CourseRegisterActionData = CourseRegisterActionAddParticipantData { crActIdent :: UserSearchKey , crActUser :: (UserId, User) } | CourseRegisterActionAddTutorialMemberData { crActIdent :: UserSearchKey , crActUser :: (UserId, User) , crActTutorial :: TutorialIdent } -- | CourseRegisterActionUnknownPersonData -- pseudo-action; just for display -- { crActUnknownPersonIdent :: Text -- } deriving (Eq, Ord, Show, Generic, Typeable) makeLenses_ ''CourseRegisterActionData makePrisms ''CourseRegisterActionData instance Aeson.FromJSON CourseRegisterActionData where parseJSON = Aeson.genericParseJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 2 } instance Aeson.ToJSON CourseRegisterActionData where toJSON = Aeson.genericToJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 2 } toEncoding = Aeson.genericToEncoding defaultOptions { fieldLabelModifier = camelToPathPiece' 2 } _classifyRegisterAction :: CourseRegisterActionData -> CourseRegisterAction _classifyRegisterAction = \case CourseRegisterActionAddParticipantData{} -> CourseRegisterActionAddParticipant CourseRegisterActionAddTutorialMemberData{} -> CourseRegisterActionAddTutorialMember --CourseRegisterActionUnknownPersonData{} -> CourseRegisterActionUnknownPerson courseRegisterRenderActionClass :: CourseRegisterAction -> Widget courseRegisterRenderActionClass = \case CourseRegisterActionAddParticipant -> [whamlet|_{MsgCourseParticipantsRegisterActionAddParticipants}|] CourseRegisterActionAddTutorialMember -> [whamlet|_{MsgCourseParticipantsRegisterActionAddTutorialMembers}|] courseRegisterRenderAction :: CourseRegisterActionData -> Widget courseRegisterRenderAction act = [whamlet|^{userWidget (view _2 (crActUser act))} (#{crActIdent act})|] data AddUserRequest = AddUserRequest { auReqUsers :: Set UserSearchKey , auReqTutorial :: Maybe TutorialIdent } deriving (Eq, Ord, Read, Show, Generic, Typeable) data AddParticipantsResult = AddParticipantsResult { aurNotFound :: Set UserSearchKey , aurAlreadyRegistered , aurAlreadyTutorialMember , aurRegisterSuccess , aurTutorialSuccess :: Set UserId } deriving (Read, Show, Generic, Typeable) instance Semigroup AddParticipantsResult where (<>) = mappenddefault instance Monoid AddParticipantsResult where mempty = memptydefault mappend = (<>) getCAddUserR, postCAddUserR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getCAddUserR = postCAddUserR postCAddUserR tid ssh csh = do cid <- runDB . getKeyBy404 $ TermSchoolCourseShort tid ssh csh currentRoute <- fromMaybe (error "postCAddUserR called from 404-handler") <$> getCurrentRoute confirmedActs :: Set CourseRegisterActionData <- fmap Set.fromList . throwExceptT . mapMM encodedSecretBoxOpen . lookupPostParams $ toPathPiece PostCourseUserAddConfirmAction $logDebugS "CAddUserR confirmedActs" . tshow $ Set.map Aeson.encode confirmedActs unless (Set.null confirmedActs) $ do -- TODO: check that all acts are member of availableActs let users = Map.fromList . fmap (\act -> (crActIdent act, Just . view _1 $ crActUser act)) $ Set.toList confirmedActs tutActs = Set.filter (is _CourseRegisterActionAddTutorialMemberData) confirmedActs actTutorial = crActTutorial <$> Set.lookupMin tutActs -- tutorial ident must be the same for every added member! registeredUsers <- registerUsers cid users forM_ actTutorial $ \tutName -> do tutId <- upsertNewTutorial cid tutName registerTutorialMembers tutId registeredUsers if | Just tutName <- actTutorial , Set.size tutActs == Set.size confirmedActs -> redirect $ CTutorialR tid ssh csh tutName TUsersR | otherwise -> redirect $ CourseR tid ssh csh CUsersR ((usersToAdd :: FormResult AddUserRequest, formWgt), formEncoding) <- runFormPost . renderWForm FormStandard $ do today <- localDay . TZ.utcToLocalTimeTZ appTZ <$> liftIO getCurrentTime auReqUsers <- wreq (textField & cfCommaSeparatedSet) (fslI MsgCourseParticipantsRegisterUsersField & setTooltip MsgCourseParticipantsRegisterUsersFieldTip) mempty auReqTutorial <- optionalActionW ( areq (textField & cfCI) (fslI MsgCourseParticipantsRegisterTutorialField & setTooltip MsgCourseParticipantsRegisterTutorialFieldTip) (Just . CI.mk $ tshow today) ) -- TODO: use user date display setting ( fslI MsgCourseParticipantsRegisterTutorialOption ) ( Just True ) return $ AddUserRequest <$> auReqUsers <*> auReqTutorial formResult usersToAdd $ \AddUserRequest{..} -> do avsUsers :: Map UserSearchKey (Maybe UserId) <- sequenceA $ Map.fromSet upsertAvsUser auReqUsers let (usersFound, usersNotFound) = partition (is _Just . view _2) $ Map.toList avsUsers unless (null usersNotFound) $ let msgContent = [whamlet| $newline never