chore(tutorial): prepare occurrencesAddBusinessDays for templates
This commit is contained in:
parent
9673916a3b
commit
5446ca5406
@ -279,6 +279,7 @@ upsertNewTutorial cid tutorialName = do
|
|||||||
, tutorialDeregisterUntil = Nothing
|
, tutorialDeregisterUntil = Nothing
|
||||||
, tutorialLastChanged = now
|
, tutorialLastChanged = now
|
||||||
, tutorialTutorControlled = False
|
, tutorialTutorControlled = False
|
||||||
|
, tutorialFirstDay = Nothing
|
||||||
, ..
|
, ..
|
||||||
}
|
}
|
||||||
[ TutorialName =. tutorialName
|
[ TutorialName =. tutorialName
|
||||||
@ -288,21 +289,22 @@ upsertNewTutorial cid tutorialName = do
|
|||||||
audit $ TransactionTutorialEdit tutId
|
audit $ TransactionTutorialEdit tutId
|
||||||
return tutId
|
return tutId
|
||||||
|
|
||||||
tutorialTemplates :: [CI Text]
|
-- tutorialTemplates :: [CI Text]
|
||||||
tutorialTemplates = ["Vorlage", "Template"]
|
-- tutorialTemplates = ["Vorlage", "Template"]
|
||||||
|
|
||||||
|
{-
|
||||||
upsertNewTutorialTemplate :: CourseId -> TutorialName -> Maybe Day -> Handler TutorialId
|
upsertNewTutorialTemplate :: CourseId -> TutorialName -> Maybe Day -> Handler TutorialId
|
||||||
upsertNewTutorialTemplate cid tutorialName anchorDay = runDB $ do
|
upsertNewTutorialTemplate cid tutorialName anchorDay = runDB $ do
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
existingTut <- getBy UniqueTutorial cid tutorialName
|
existingTut <- getBy $ UniqueTutorial cid tutorialName
|
||||||
templateEnt <- selectFirst [TutorialType <-. tutorialTemplates] [Desc TutorialType]
|
templateEnt <- selectFirst [TutorialType <-. tutorialTemplates] [Desc TutorialType]
|
||||||
case (existingTut, anchorDay) of
|
case (existingTut, anchorDay, templateEnt) of
|
||||||
(Just (Entity{entityVal=tid}),_,_) -> return tid -- no need to update, we ignore the anchor day
|
(Just (Entity{entityKey=tid}),_,_) -> return tid -- no need to update, we ignore the anchor day
|
||||||
(Nothing, Just firstDay, Just Entity{entityVal=Tutorial{tutorialFirstDay=Just tmplFirstDay}) -> do
|
(Nothing, Just firstDay, Just Entity{entityVal=Tutorial{tutorialFirstDay=Just tmplFirstDay}}) -> do
|
||||||
Course{..} <- getBy404 cid
|
Course{..} <- get404 cid
|
||||||
Term{termLectureStart} <- getBy404 courseTerm
|
Term{termLectureStart} <- get404 courseTerm
|
||||||
let dayDiff = diffDays firstDay tmplFirstDay
|
let dayDiff = diffDays firstDay tmplFirstDay
|
||||||
addBusinessDays
|
-- addBusinessDays
|
||||||
Entity tutId _ <- upsert
|
Entity tutId _ <- upsert
|
||||||
Tutorial
|
Tutorial
|
||||||
{ tutorialCourse = cid
|
{ tutorialCourse = cid
|
||||||
@ -319,11 +321,10 @@ upsertNewTutorialTemplate cid tutorialName anchorDay = runDB $ do
|
|||||||
, tutorialTutorControlled = False
|
, tutorialTutorControlled = False
|
||||||
, tutorialFirstDay = anchorDay
|
, tutorialFirstDay = anchorDay
|
||||||
, ..
|
, ..
|
||||||
}
|
} []
|
||||||
error "TODO" -- CONTINUE HERE
|
-- error "TODO" -- CONTINUE HERE
|
||||||
audit $ TransactionTutorialEdit tutId
|
audit $ TransactionTutorialEdit tutId
|
||||||
return tutId
|
return tutId
|
||||||
|
|
||||||
_ -> do
|
_ -> do
|
||||||
Entity tutId _ <- upsert
|
Entity tutId _ <- upsert
|
||||||
Tutorial
|
Tutorial
|
||||||
@ -346,7 +347,7 @@ upsertNewTutorialTemplate cid tutorialName anchorDay = runDB $ do
|
|||||||
audit $ TransactionTutorialEdit tutId
|
audit $ TransactionTutorialEdit tutId
|
||||||
return tutId
|
return tutId
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
registerTutorialMembers :: TutorialId -> Set UserId -> Handler ()
|
registerTutorialMembers :: TutorialId -> Set UserId -> Handler ()
|
||||||
registerTutorialMembers tutId (Set.toList -> users) = runDB $ do
|
registerTutorialMembers tutId (Set.toList -> users) = runDB $ do
|
||||||
|
|||||||
@ -26,7 +26,6 @@ getTEditR = postTEditR
|
|||||||
postTEditR tid ssh csh tutn = do
|
postTEditR tid ssh csh tutn = do
|
||||||
(cid, tutid, template) <- runDB $ do
|
(cid, tutid, template) <- runDB $ do
|
||||||
(cid, Entity tutid Tutorial{..}) <- fetchCourseIdTutorial tid ssh csh tutn
|
(cid, Entity tutid Tutorial{..}) <- fetchCourseIdTutorial tid ssh csh tutn
|
||||||
|
|
||||||
tutorIds <- fmap (map E.unValue) . E.select . E.from $ \tutor -> do
|
tutorIds <- fmap (map E.unValue) . E.select . E.from $ \tutor -> do
|
||||||
E.where_ $ tutor E.^. TutorTutorial E.==. E.val tutid
|
E.where_ $ tutor E.^. TutorTutorial E.==. E.val tutid
|
||||||
return $ tutor E.^. TutorUser
|
return $ tutor E.^. TutorUser
|
||||||
@ -56,6 +55,7 @@ postTEditR tid ssh csh tutn = do
|
|||||||
|
|
||||||
formResult newTutResult $ \TutorialForm{..} -> do
|
formResult newTutResult $ \TutorialForm{..} -> do
|
||||||
insertRes <- runDBJobs $ do
|
insertRes <- runDBJobs $ do
|
||||||
|
term <- fetchTermByCID cid
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
insertRes <- myReplaceUnique tutid Tutorial
|
insertRes <- myReplaceUnique tutid Tutorial
|
||||||
{ tutorialName = tfName
|
{ tutorialName = tfName
|
||||||
@ -71,6 +71,7 @@ postTEditR tid ssh csh tutn = do
|
|||||||
, tutorialDeregisterUntil = tfDeregisterUntil
|
, tutorialDeregisterUntil = tfDeregisterUntil
|
||||||
, tutorialLastChanged = now
|
, tutorialLastChanged = now
|
||||||
, tutorialTutorControlled = tfTutorControlled
|
, tutorialTutorControlled = tfTutorControlled
|
||||||
|
, tutorialFirstDay = fst $ occurrencesBounds term tfTime
|
||||||
}
|
}
|
||||||
when (is _Nothing insertRes) $ do
|
when (is _Nothing insertRes) $ do
|
||||||
audit $ TransactionTutorialEdit tutid
|
audit $ TransactionTutorialEdit tutid
|
||||||
|
|||||||
@ -27,6 +27,7 @@ postCTutorialNewR tid ssh csh = do
|
|||||||
formResult newTutResult $ \TutorialForm{..} -> do
|
formResult newTutResult $ \TutorialForm{..} -> do
|
||||||
insertRes <- runDBJobs $ do
|
insertRes <- runDBJobs $ do
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
|
term <- fetchTermByCID cid
|
||||||
insertRes <- insertUnique Tutorial
|
insertRes <- insertUnique Tutorial
|
||||||
{ tutorialName = tfName
|
{ tutorialName = tfName
|
||||||
, tutorialCourse = cid
|
, tutorialCourse = cid
|
||||||
@ -41,6 +42,7 @@ postCTutorialNewR tid ssh csh = do
|
|||||||
, tutorialDeregisterUntil = tfDeregisterUntil
|
, tutorialDeregisterUntil = tfDeregisterUntil
|
||||||
, tutorialLastChanged = now
|
, tutorialLastChanged = now
|
||||||
, tutorialTutorControlled = tfTutorControlled
|
, tutorialTutorControlled = tfTutorControlled
|
||||||
|
, tutorialFirstDay = fst $ occurrencesBounds term tfTime
|
||||||
}
|
}
|
||||||
whenIsJust insertRes $ \tutid -> do
|
whenIsJust insertRes $ \tutid -> do
|
||||||
audit $ TransactionTutorialEdit tutid
|
audit $ TransactionTutorialEdit tutid
|
||||||
|
|||||||
@ -5,14 +5,14 @@
|
|||||||
module Handler.Utils.Occurrences
|
module Handler.Utils.Occurrences
|
||||||
( occurrencesWidget
|
( occurrencesWidget
|
||||||
, occurrencesBounds
|
, occurrencesBounds
|
||||||
, occurrencesAddBusinessDays
|
-- , occurrencesAddBusinessDays
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
import Utils.Holidays (isWeekend)
|
-- import Utils.Holidays (isWeekend)
|
||||||
import Utils.Occurrences
|
import Utils.Occurrences
|
||||||
|
|
||||||
import Handler.Utils.DateTime
|
import Handler.Utils.DateTime
|
||||||
@ -51,24 +51,30 @@ occurrencesBounds Term{..} Occurrences{..} = (Set.lookupMin occDays, Set.lookupM
|
|||||||
getOccDays :: OccurrenceSchedule -> Set Day -> Set Day
|
getOccDays :: OccurrenceSchedule -> Set Day -> Set Day
|
||||||
getOccDays ScheduleWeekly{scheduleDayOfWeek=wday} = Set.union $ daysOfWeekBetween (termLectureStart,termLectureEnd) wday
|
getOccDays ScheduleWeekly{scheduleDayOfWeek=wday} = Set.union $ daysOfWeekBetween (termLectureStart,termLectureEnd) wday
|
||||||
|
|
||||||
occurrencesAddBusinessDays :: Term -> (Day,Day) -> Occurrences -> Occurrences
|
-- occurrencesAddBusinessDays :: Term -> (Day,Day) -> Occurrences -> Occurrences
|
||||||
occurrencesAddBusinessDays Term{..} (dayOld, dayNew) Occurrences{..} = Occurrences newSchedule newExceptions
|
-- occurrencesAddBusinessDays Term{..} (dayOld, dayNew) Occurrences{..} = Occurrences newSchedule newExceptions
|
||||||
where
|
-- where
|
||||||
newSchedule = Set.map switchDayOfWeek occurrencesScheduled
|
-- newSchedule = Set.map switchDayOfWeek occurrencesScheduled
|
||||||
dayDiff = diffDays dayNew dayOld
|
-- dayDiff = diffDays dayNew dayOld
|
||||||
|
|
||||||
switchDayOfWeek :: OccurrenceSchedule -> OccurrenceSchedule
|
-- switchDayOfWeek :: OccurrenceSchedule -> OccurrenceSchedule
|
||||||
switchDayOfWeek _ | 0 == dayDiff `mod` 7 = id
|
-- switchDayOfWeek _ | 0 == dayDiff `mod` 7 = id
|
||||||
os{scheduleDayOfWeek=wday} = os{scheduleDayOfWeek= toEnum (dayDiff + fromEnum wday)}
|
-- switchDayOfWeek os@ScheduleWeekly{scheduleDayOfWeek=wday} = os{scheduleDayOfWeek= toEnum (dayDiff + fromEnum wday)}
|
||||||
|
|
||||||
newExceptions = Set.map advanceExceptions occurrencesExceptions
|
-- newExceptions = snd $ Set.foldr advanceExceptions (dayDiff,mempty) occurrencesExceptions
|
||||||
|
|
||||||
advanceExceptions :: OccurrenceException -> OccurrenceException
|
-- advanceExceptions :: OccurrenceException -> (Integer, Set OccurrenceException) -> (Integer, Set OccurrenceException)
|
||||||
advanceExceptions ex@ExceptOccur{ exceptDay = ed } = ex{ exceptDay = pushSkip ed }
|
-- advanceExceptions ex@ExceptOccur{ exceptDay = ed } (offset, acc) =
|
||||||
advanceExceptions ex@ExxceptNoOccur{ exceptTime = et@LocalTime { localDay = ed } } = ex{ exceptDay = et{ localDay = pushSkip ed}}
|
-- | add
|
||||||
|
|
||||||
pushSkip :: Day -> Day
|
|
||||||
pushSkip = let weekends = [d | d <- [(min termLectureStart termStart)..(max termEnd termLectureEnd)], isWeekend d]
|
|
||||||
offDays = Set.fromList $ termHolidays <> weekends
|
|
||||||
|
|
||||||
in
|
-- advanceExceptions ex@ExceptOccur{ exceptDay = ed } = ex{ exceptDay = pushSkip ed }
|
||||||
|
-- advanceExceptions ex@ExceptNoOccur{ exceptTime = et@LocalTime { localDay = ed } } = ex{ exceptDay = et{ localDay = pushSkip ed}}
|
||||||
|
|
||||||
|
-- pushSkip
|
||||||
|
-- pushSkip :: Day -> Day
|
||||||
|
-- pushSkip = id -- TODO
|
||||||
|
-- -- pushSkip = let weekends = [d | d <- [(min termLectureStart termStart)..(max termEnd termLectureEnd)], isWeekend d]
|
||||||
|
-- -- offDays = Set.fromList $ termHolidays <> weekends
|
||||||
|
|
||||||
|
-- -- in
|
||||||
@ -6,6 +6,7 @@ module Handler.Utils.Term
|
|||||||
( groupHolidays
|
( groupHolidays
|
||||||
, getCurrentTerm
|
, getCurrentTerm
|
||||||
, getActiveTerms
|
, getActiveTerms
|
||||||
|
, fetchTermByCID
|
||||||
, module Utils.Term
|
, module Utils.Term
|
||||||
) where
|
) where
|
||||||
|
|
||||||
@ -61,3 +62,17 @@ getActiveTerms = do
|
|||||||
|
|
||||||
fmap Set.fromDistinctAscList . runConduit $
|
fmap Set.fromDistinctAscList . runConduit $
|
||||||
E.selectSource activeTermsQuery .| C.map E.unValue .| C.sinkList
|
E.selectSource activeTermsQuery .| C.map E.unValue .| C.sinkList
|
||||||
|
|
||||||
|
fetchTermByCID :: ( MonadHandler m
|
||||||
|
, BackendCompatible SqlBackend backend
|
||||||
|
, PersistQueryRead backend, PersistUniqueRead backend
|
||||||
|
)
|
||||||
|
=> CourseId -> ReaderT backend m Term
|
||||||
|
fetchTermByCID cid = do
|
||||||
|
termList <- E.select . E.from $ \(course `E.InnerJoin` term) -> do
|
||||||
|
E.on $ course E.^. CourseTerm E.==. term E.^. TermId
|
||||||
|
E.where_ $ course E.^. CourseId E.==. E.val cid
|
||||||
|
return term
|
||||||
|
case termList of
|
||||||
|
[term] -> return $ entityVal term
|
||||||
|
_other -> notFound
|
||||||
@ -21,6 +21,7 @@ import qualified Data.Text as Text
|
|||||||
-- import Data.Either.Combinators (maybeToRight, mapLeft)
|
-- import Data.Either.Combinators (maybeToRight, mapLeft)
|
||||||
import Text.Read (readMaybe)
|
import Text.Read (readMaybe)
|
||||||
|
|
||||||
|
-- import Data.Time.LocalTime
|
||||||
import Data.Time.Calendar.WeekDate
|
import Data.Time.Calendar.WeekDate
|
||||||
-- import Data.Time.Format.ISO8601
|
-- import Data.Time.Format.ISO8601
|
||||||
|
|
||||||
@ -184,9 +185,21 @@ data OccurrenceException = ExceptOccur
|
|||||||
| ExceptNoOccur
|
| ExceptNoOccur
|
||||||
{ exceptTime :: LocalTime
|
{ exceptTime :: LocalTime
|
||||||
}
|
}
|
||||||
deriving (Eq, Ord, Read, Show, Generic)
|
deriving (Eq, Read, Show, Generic)
|
||||||
deriving anyclass (NFData)
|
deriving anyclass (NFData)
|
||||||
|
|
||||||
|
instance Ord OccurrenceException where
|
||||||
|
compare ExceptOccur{exceptDay=ad, exceptStart=as, exceptEnd=ae} ExceptOccur{exceptDay=bd, exceptStart=bs, exceptEnd=be}
|
||||||
|
= compare (ad,as,ae) (bd,bs,be)
|
||||||
|
compare ExceptOccur{exceptDay=d, exceptStart=s} ExceptNoOccur{exceptTime=e}
|
||||||
|
= -- replaceEq GT $
|
||||||
|
compare (LocalTime d s) e
|
||||||
|
compare ExceptNoOccur{exceptTime=e } ExceptOccur{exceptDay=d, exceptStart=s}
|
||||||
|
= -- replaceEq LT $
|
||||||
|
compare e (LocalTime d s)
|
||||||
|
compare ExceptNoOccur{exceptTime=ae } ExceptNoOccur{exceptTime=be }
|
||||||
|
= compare ae be
|
||||||
|
|
||||||
deriveJSON defaultOptions
|
deriveJSON defaultOptions
|
||||||
{ fieldLabelModifier = camelToPathPiece' 1
|
{ fieldLabelModifier = camelToPathPiece' 1
|
||||||
, constructorTagModifier = camelToPathPiece' 1
|
, constructorTagModifier = camelToPathPiece' 1
|
||||||
@ -207,3 +220,23 @@ derivePersistFieldJSON ''Occurrences
|
|||||||
|
|
||||||
|
|
||||||
nullaryPathPiece ''DayOfWeek camelToPathPiece
|
nullaryPathPiece ''DayOfWeek camelToPathPiece
|
||||||
|
|
||||||
|
|
||||||
|
-- test :: IO [OccurrenceException]
|
||||||
|
-- test = do
|
||||||
|
-- now <- getCurrentTime
|
||||||
|
-- tz <- getCurrentTimeZone
|
||||||
|
-- let lt1 = utcToLocalTime tz now
|
||||||
|
-- tomorrow = addUTCTime nominalDay now
|
||||||
|
-- lt2 = utcToLocalTime tz tomorrow
|
||||||
|
-- yesterday = addUTCTime (negate nominalDay) now
|
||||||
|
-- lt3 = utcToLocalTime tz yesterday
|
||||||
|
-- pure
|
||||||
|
-- [ ExceptOccur (utctDay tomorrow ) midday midnight
|
||||||
|
-- , ExceptOccur (utctDay now ) midnight midnight
|
||||||
|
-- , ExceptOccur (utctDay now ) midday midnight
|
||||||
|
-- , ExceptOccur (utctDay yesterday) midday midnight
|
||||||
|
-- , ExceptNoOccur lt3
|
||||||
|
-- , ExceptNoOccur lt1
|
||||||
|
-- , ExceptNoOccur lt2
|
||||||
|
-- ]
|
||||||
11
src/Utils.hs
11
src/Utils.hs
@ -1745,7 +1745,7 @@ maxOn = maxBy . comparing
|
|||||||
inBetween:: Ord a => a -> (a,a) -> Bool
|
inBetween:: Ord a => a -> (a,a) -> Bool
|
||||||
inBetween x (lower,upper) = lower <= x && x <= upper
|
inBetween x (lower,upper) = lower <= x && x <= upper
|
||||||
|
|
||||||
-- | Given to values and a criterion, returns the unique argument that fulfills the criterion, if it exists
|
-- | Given two values and a criterion, returns the unique argument that fulfills the criterion, if it exists
|
||||||
pickBetter :: a -> a -> (a -> Bool) -> Maybe a
|
pickBetter :: a -> a -> (a -> Bool) -> Maybe a
|
||||||
pickBetter x y crit
|
pickBetter x y crit
|
||||||
| cx == cy = Nothing
|
| cx == cy = Nothing
|
||||||
@ -1755,6 +1755,15 @@ pickBetter x y crit
|
|||||||
cx = crit x
|
cx = crit x
|
||||||
cy = crit y
|
cy = crit y
|
||||||
|
|
||||||
|
reverseOrdering :: Ordering -> Ordering
|
||||||
|
reverseOrdering EQ = EQ
|
||||||
|
reverseOrdering GT = LT
|
||||||
|
reverseOrdering LT = GT
|
||||||
|
|
||||||
|
replaceEq :: Ordering -> Ordering -> Ordering
|
||||||
|
replaceEq r EQ = r
|
||||||
|
replaceEq _ other = other
|
||||||
|
|
||||||
------------
|
------------
|
||||||
-- Random --
|
-- Random --
|
||||||
------------
|
------------
|
||||||
|
|||||||
@ -16,7 +16,8 @@ import Data.FileEmbed (embedFile)
|
|||||||
|
|
||||||
import Utils.Print.Letters
|
import Utils.Print.Letters
|
||||||
import Handler.Utils.Profile
|
import Handler.Utils.Profile
|
||||||
import Handler.Utils.DateTime
|
-- import Handler.Utils.DateTime
|
||||||
|
import Handler.Utils.Occurrences
|
||||||
|
|
||||||
data LetterCourseCertificate = LetterCourseCertificate
|
data LetterCourseCertificate = LetterCourseCertificate
|
||||||
{ ccCourseId :: CourseId
|
{ ccCourseId :: CourseId
|
||||||
|
|||||||
@ -1014,6 +1014,7 @@ fillDb = do
|
|||||||
, tutorialDeregisterUntil = jtt TermDayLectureStart (-5) (Just Monday) toMidnight
|
, tutorialDeregisterUntil = jtt TermDayLectureStart (-5) (Just Monday) toMidnight
|
||||||
, tutorialLastChanged = now
|
, tutorialLastChanged = now
|
||||||
, tutorialTutorControlled = True
|
, tutorialTutorControlled = True
|
||||||
|
, tutorialFirstDay = Just firstDay
|
||||||
}
|
}
|
||||||
insert_ $ Tutor tut1 jost
|
insert_ $ Tutor tut1 jost
|
||||||
void . insert' $ Exam
|
void . insert' $ Exam
|
||||||
|
|||||||
@ -445,6 +445,8 @@ spec = do
|
|||||||
[ eqLaws, showReadLaws, ordLaws, jsonLaws, persistFieldLaws, commutativeSemigroupLaws, commutativeMonoidLaws ]
|
[ eqLaws, showReadLaws, ordLaws, jsonLaws, persistFieldLaws, commutativeSemigroupLaws, commutativeMonoidLaws ]
|
||||||
lawsCheckHspec (Proxy @TermIdentifier)
|
lawsCheckHspec (Proxy @TermIdentifier)
|
||||||
[ eqLaws, showReadLaws, ordLaws, enumLaws, persistFieldLaws, jsonLaws, httpApiDataLaws, pathPieceLaws ]
|
[ eqLaws, showReadLaws, ordLaws, enumLaws, persistFieldLaws, jsonLaws, httpApiDataLaws, pathPieceLaws ]
|
||||||
|
lawsCheckHspec (Proxy @Occurrences)
|
||||||
|
[ eqLaws, showReadLaws, ordLaws, jsonLaws, persistFieldLaws ]
|
||||||
lawsCheckHspec (Proxy @StudyFieldType)
|
lawsCheckHspec (Proxy @StudyFieldType)
|
||||||
[ eqLaws, ordLaws, boundedEnumLaws, showReadLaws, persistFieldLaws ]
|
[ eqLaws, ordLaws, boundedEnumLaws, showReadLaws, persistFieldLaws ]
|
||||||
lawsCheckHspec (Proxy @Theme)
|
lawsCheckHspec (Proxy @Theme)
|
||||||
|
|||||||
@ -93,6 +93,7 @@ instance Arbitrary Tutorial where
|
|||||||
<*> arbitrary
|
<*> arbitrary
|
||||||
<*> arbitrary
|
<*> arbitrary
|
||||||
<*> arbitrary
|
<*> arbitrary
|
||||||
|
<*> arbitrary
|
||||||
shrink = genericShrink
|
shrink = genericShrink
|
||||||
|
|
||||||
instance Arbitrary User where
|
instance Arbitrary User where
|
||||||
|
|||||||
Reference in New Issue
Block a user