-- SPDX-FileCopyrightText: 2022 Gregor Kleen ,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.Text as Text 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 = BtnCourseRegisterAdd | 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 BtnCourseRegisterAdd = [BCIsButton, BCPrimary] 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 { crActAddParticipantIdent :: UserSearchKey , crActAddParticipantUser :: (UserId, User) } | CourseRegisterActionAddTutorialMemberData { crActAddTutorialMemberIdent :: UserSearchKey , crActAddTutorialMemberUser :: (UserId, User) , crActAddTutorialMemberTutorial :: TutorialIdent } -- | CourseRegisterActionUnknownPersonData -- pseudo-action; just for display -- { crActUnknownPersonIdent :: Text -- } deriving (Eq, Ord, Show, Generic, Typeable) makeLenses_ ''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 = \case CourseRegisterActionAddParticipantData{..} -> [whamlet|^{userWidget (view _2 crActAddParticipantUser)} (#{crActAddParticipantIdent})|] CourseRegisterActionAddTutorialMemberData{..} -> [whamlet|^{userWidget (view _2 crActAddTutorialMemberUser)} (#{crActAddTutorialMemberIdent}), _{MsgCourseParticipantsRegisterTutorialField}: #{crActAddTutorialMemberTutorial}|] 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 today <- localDay . TZ.utcToLocalTimeTZ appTZ <$> liftIO getCurrentTime currentRoute <- fromMaybe (error "postCAddUserR called from 404-handler") <$> getCurrentRoute piConfirmPost <- throwExceptT . runMaybeT $ encodedSecretBoxOpen =<< MaybeT (lift . lookupPostParam $ toPathPiece PostCourseUserAddConfirmAction) $logErrorS "CAddUserR" . tshow $ Aeson.encode piConfirmPost let piConfirmRes :: FormResult CourseRegisterActionData piConfirmRes = maybe FormMissing FormSuccess piConfirmPost case piConfirmRes of FormSuccess res'' -> do let res' = [res''] forM_ res' $ \case CourseRegisterActionAddTutorialMemberData{..} -> do registeredUsers <- registerUsers cid $ Map.singleton crActAddTutorialMemberIdent (Just $ view _1 crActAddTutorialMemberUser) tutId <- upsertNewTutorial cid crActAddTutorialMemberTutorial registerTutorialMembers tutId registeredUsers redirect $ CTutorialR tid ssh csh crActAddTutorialMemberTutorial TUsersR CourseRegisterActionAddParticipantData{..} -> do void . registerUsers cid $ Map.singleton crActAddParticipantIdent (Just $ view _1 crActAddParticipantUser) redirect $ CourseR tid ssh csh CUsersR FormMissing -> return () FormFailure errs -> forM_ errs $ addMessage Error . toHtml ((usersToAdd :: FormResult AddUserRequest, formWgt), formEncoding) <- runFormPost . renderWForm FormStandard $ do let cfCommaSeparatedSet :: forall m. Functor m => Field m Text -> Field m (Set Text) cfCommaSeparatedSet = guardField (not . Set.null) . convertField (Set.fromList . mapMaybe (assertM' (not . Text.null) . Text.strip) . Text.splitOn ",") (Text.intercalate ", " . Set.toList) 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