-- 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 Database.Esqueleto.Legacy as E --import qualified Data.Conduit.List as C (sourceList) --import qualified Data.Conduit.Combinators as C import qualified Data.Aeson as Aeson import qualified Data.CaseInsensitive as CI --import Data.List (genericLength) 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 Control.Monad.State (evalStateT) --import Control.Monad.State.Class (modify) --import qualified Control.Monad.State as State 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 } | CourseRegisterActionAddTutorialMemberData { crActAddTutorialMemberIdent :: UserSearchKey , crActAddTutorialMemberUser :: UserId , crActAddTutorialMemberTutorial :: TutorialIdent } -- | CourseRegisterActionUnknownPersonData -- pseudo-action; just for display -- { crActUnknownPersonIdent :: Text -- } deriving (Eq, Ord, Read, 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|TODO USER (#{crActAddParticipantIdent})|] CourseRegisterActionAddTutorialMemberData{..} -> [whamlet|TODO USER (#{crActAddTutorialMemberIdent})|] --data CourseRegisterActionClass -- = CourseRegisterActionClassNew -- | CourseRegisterActionClassExisting -- | CourseRegisterActionClassMissing -- deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) --instance Universe CourseRegisterActionClass --instance Finite CourseRegisterActionClass --courseRegisterCoarsenActionClass :: CourseRegisterAction -> CourseRegisterActionClass --courseRegisterCoarsenActionClass = \case -- CourseRegisterActionAddParticipant -> CourseRegisterActionClassNew -- CourseRegisterActionAddTutorialMember -> CourseRegisterActionClassExisting -- CourseRegisterActionUnknownPerson -> CourseRegisterActionClassMissing -- --data CourseRegisterActionDiff -- old new key -- = CourseRegisterActionDiffNew -- { crActKey :: Text -- , crActNewUser :: UserId -- crActNewKey :: Maybe key -- , crActNewTutorial :: Maybe TutorialIdent -- crActNew :: new -- } -- | CourseRegisterActionDiffExisting -- { crActKey :: Text -- , crActOldUser :: UserId -- crActOldKey :: key -- , crActOldTutorial :: TutorialIdent -- crActOld :: old -- , crActNewTutorial :: Maybe TutorialIdent -- crActNew :: new -- } -- | CourseRegisterActionDiffMissing -- { crActKey :: Text -- --{ crActOldKey :: key -- --, crActOld :: old -- } -- --makeLenses_ ''CourseRegisterActionDiff --makePrisms ''CourseRegisterActionDiff --data CourseRegisterException -- = CourseRegisterExceptionDuplicateIdent -- { crExcptDuplicateIdent :: Text -- } -- | CourseRegisterException -- { crExcpt :: Text -- } -- deriving (Show, Typeable) -- --makeLenses_ ''CourseRegisterException -- --instance Exception CourseRegisterException --courseRegisterRenderException :: CourseRegisterException -> DB Text --courseRegisterRenderException = ap getMessageRender . pure -- --registerActionDefaultChecked :: CourseRegisterAction -> Bool --registerActionDefaultChecked = (/=) CourseRegisterActionUnknownPerson -- --registerActionDisabled :: CourseRegisterAction -> Bool --registerActionDisabled = (==) CourseRegisterActionUnknownPerson data AddUserRequest = AddUserRequest { auReqUsers :: Set UserSearchKey , auReqTutorial :: Maybe TutorialIdent } deriving (Eq, Ord, Read, Show, Generic, Typeable) --data AddUser = AddUser -- { addUserIdent :: Text -- , addUserTutorial :: 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 ((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