Sheet List Convenience like in UniWorX

This commit is contained in:
Steffen Jost 2019-05-10 11:25:43 +02:00
parent df2df21bf4
commit 86f10ae1ba
10 changed files with 176 additions and 44 deletions

View File

@ -410,6 +410,7 @@ RatingPointsDone: Abgabe zählt als korrigiert, gdw. Punktezahl gesetzt ist
ColumnRatingPoints: Punktzahl ColumnRatingPoints: Punktzahl
Pseudonyms: Pseudonyme Pseudonyms: Pseudonyme
Files: Dateien
FileTitle: Dateiname FileTitle: Dateiname
FileModified: Letzte Änderung FileModified: Letzte Änderung
VisibleFrom: Veröffentlicht VisibleFrom: Veröffentlicht

1
routes
View File

@ -114,6 +114,7 @@
/pseudonym SPseudonymR GET POST !course-registeredANDcorrector-submissions /pseudonym SPseudonymR GET POST !course-registeredANDcorrector-submissions
/corrector-invite/ SCorrInviteR GET POST /corrector-invite/ SCorrInviteR GET POST
!/#SheetFileType/*FilePath SFileR GET !timeANDcourse-registered !timeANDmaterials !corrector !timeANDtutor !/#SheetFileType/*FilePath SFileR GET !timeANDcourse-registered !timeANDmaterials !corrector !timeANDtutor
!/#{ZIPArchiveName SheetFileType} SZipR GET !timeANDcourse-registered !timeANDmaterials !corrector !timeANDtutor
/file MaterialListR GET !course-registered !materials !corrector !tutor /file MaterialListR GET !course-registered !materials !corrector !tutor
/file/new MaterialNewR GET POST /file/new MaterialNewR GET POST
/file/#MaterialName MaterialR: /file/#MaterialName MaterialR:

View File

@ -671,10 +671,17 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
guard visible guard visible
case subRoute of case subRoute of
SFileR SheetExercise _ -> guard $ sheetActiveFrom <= cTime -- Single Files
SFileR SheetHint _ -> guard $ maybe False (<= cTime) sheetHintFrom SFileR SheetExercise _ -> guard $ sheetActiveFrom <= cTime
SFileR SheetSolution _ -> guard $ maybe False (<= cTime) sheetSolutionFrom SFileR SheetHint _ -> guard $ maybe False (<= cTime) sheetHintFrom
SFileR _ _ -> mzero SFileR SheetSolution _ -> guard $ maybe False (<= cTime) sheetSolutionFrom
SFileR _ _ -> mzero
-- Archives of SheetFileType
SZipR (ZIPArchiveName SheetExercise) -> guard $ sheetActiveFrom <= cTime
SZipR (ZIPArchiveName SheetHint ) -> guard $ maybe False (<= cTime) sheetHintFrom
SZipR (ZIPArchiveName SheetSolution) -> guard $ maybe False (<= cTime) sheetSolutionFrom
SZipR _ -> mzero
-- Submissions
SubmissionNewR -> guard active SubmissionNewR -> guard active
SubmissionR _ SAssignR -> guard marking -- Correctors can only be assigned when the Sheet is inactive, since submissions are subject to change SubmissionR _ SAssignR -> guard marking -- Correctors can only be assigned when the Sheet is inactive, since submissions are subject to change
SubmissionR _ _ -> guard active SubmissionR _ _ -> guard active

View File

@ -362,6 +362,5 @@ getMArchiveR tid ssh csh mnm = do
E.on $ materialFile E.^. MaterialFileFile E.==. file E.^. FileId E.on $ materialFile E.^. MaterialFileFile E.==. file E.^. FileId
E.where_ $ materialFile E.^. MaterialFileMaterial E.==. E.val mid E.where_ $ materialFile E.^. MaterialFileMaterial E.==. E.val mid
return file return file
fileSource' = fileSelect .| C.map entityVal
zipComment = Text.encodeUtf8 $ termToText (unTermKey tid) <> "-" <> toPathPiece (unSchoolKey ssh <> "-" <> csh <> ":" <> mnm) zipComment = Text.encodeUtf8 $ termToText (unTermKey tid) <> "-" <> toPathPiece (unSchoolKey ssh <> "-" <> csh <> ":" <> mnm)
fileSource' .| produceZip ZipInfo{..} .| C.map toFlushBuilder fileSelect .| C.map entityVal .| produceZip ZipInfo{..} .| C.map toFlushBuilder

View File

@ -163,6 +163,14 @@ getSheetListR tid ssh csh = do
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
cid <- runDB . getKeyBy404 $ TermSchoolCourseShort tid ssh csh cid <- runDB . getKeyBy404 $ TermSchoolCourseShort tid ssh csh
let let
hasSFT :: (E.Value Bool, E.Value Bool, E.Value Bool, E.Value Bool) -> [SheetFileType]
hasSFT (E.Value hasExercise, E.Value hasHint, E.Value hasSolution, E.Value hasMarking)
= [ sft | sft <- [minBound..maxBound]
, sft /= SheetExercise || hasExercise
, sft /= SheetHint || hasHint
, sft /= SheetSolution || hasSolution
, sft /= SheetMarking || hasMarking
]
lastSheetEdit sheet = E.sub_select . E.from $ \sheetEdit -> do lastSheetEdit sheet = E.sub_select . E.from $ \sheetEdit -> do
E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId
return . E.max_ $ sheetEdit E.^. SheetEditTime return . E.max_ $ sheetEdit E.^. SheetEditTime
@ -178,21 +186,27 @@ getSheetListR tid ssh csh = do
sheetFilter sheetName = (== Authorized) <$> evalAccessDB (CSheetR tid ssh csh sheetName SShowR) False sheetFilter sheetName = (== Authorized) <$> evalAccessDB (CSheetR tid ssh csh sheetName SShowR) False
sheetCol = widgetColonnade . mconcat $ sheetCol = widgetColonnade . mconcat $
[ dbRow [ -- dbRow ,
, sortable (Just "name") (i18nCell MsgSheet) sortable (Just "name") (i18nCell MsgSheet)
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _)} -> anchorCell (CSheetR tid ssh csh sheetName SShowR) (toWidget sheetName) $ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, _)} -> anchorCell (CSheetR tid ssh csh sheetName SShowR) (toWidget sheetName)
, sortable (Just "last-edit") (i18nCell MsgLastEdit) , sortable (Just "last-edit") (i18nCell MsgLastEdit)
$ \DBRow{dbrOutput=(_, E.Value mEditTime, _)} -> foldMap dateTimeCell mEditTime $ \DBRow{dbrOutput=(_, E.Value mEditTime, _, _)} -> foldMap dateTimeCell mEditTime
, sortable (Just "visible-from") (i18nCell MsgAccessibleSince) , sortable (Just "visible-from") (i18nCell MsgAccessibleSince)
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _)} -> foldMap (dateTimeCellVisible now) sheetVisibleFrom $ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, _)} -> foldMap (dateTimeCellVisible now) sheetVisibleFrom
, sortable (toNothing "downloads") (i18nCell MsgFiles)
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, existFiles)} -> mconcat
[ linkEmptyCell link icn | sft <- hasSFT existFiles
, let link = CSheetR tid ssh csh sheetName $ SZipR $ ZIPArchiveName sft
, let icn = toWidget $ sheetFile2markup sft
]
, sortable (Just "submission-since") (i18nCell MsgSheetActiveFrom) , sortable (Just "submission-since") (i18nCell MsgSheetActiveFrom)
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _)} -> dateTimeCell sheetActiveFrom $ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, _)} -> dateTimeCell sheetActiveFrom
, sortable (Just "submission-until") (i18nCell MsgSheetActiveTo) , sortable (Just "submission-until") (i18nCell MsgSheetActiveTo)
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _)} -> dateTimeCell sheetActiveTo $ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, _)} -> dateTimeCell sheetActiveTo
, sortable Nothing (i18nCell MsgSheetType) , sortable Nothing (i18nCell MsgSheetType)
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _)} -> i18nCell sheetType $ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, _, _)} -> i18nCell sheetType
, sortable Nothing (i18nCell MsgSubmission) , sortable Nothing (i18nCell MsgSubmission)
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, mbSub)} -> case mbSub of $ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, mbSub, _)} -> case mbSub of
Nothing -> mempty Nothing -> mempty
(Just (Entity sid Submission{..})) -> (Just (Entity sid Submission{..})) ->
let mkCid = encrypt sid -- TODO: executed twice let mkCid = encrypt sid -- TODO: executed twice
@ -201,7 +215,7 @@ getSheetListR tid ssh csh = do
return $ CSubmissionR tid ssh csh sheetName cid' SubShowR return $ CSubmissionR tid ssh csh sheetName cid' SubShowR
in anchorCellM mkRoute (mkCid >>= \cid2 -> [whamlet|#{display cid2}|]) in anchorCellM mkRoute (mkCid >>= \cid2 -> [whamlet|#{display cid2}|])
, sortable (Just "rating") (i18nCell MsgRating) , sortable (Just "rating") (i18nCell MsgRating)
$ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, mbSub)} -> $ \DBRow{dbrOutput=(Entity _ Sheet{..}, _, mbSub,_)} ->
let stats = sheetTypeSum sheetType in -- for statistics over all shown rows let stats = sheetTypeSum sheetType in -- for statistics over all shown rows
case mbSub of case mbSub of
Nothing -> cellTell mempty $ stats Nothing Nothing -> cellTell mempty $ stats Nothing
@ -216,7 +230,7 @@ getSheetListR tid ssh csh = do
, sortable Nothing -- (Just "percent") , sortable Nothing -- (Just "percent")
(i18nCell MsgRatingPercent) (i18nCell MsgRatingPercent)
$ \DBRow{dbrOutput=(Entity _ Sheet{sheetType=sType}, _, mbSub)} -> case mbSub of $ \DBRow{dbrOutput=(Entity _ Sheet{sheetType=sType}, _, mbSub,_)} -> case mbSub of
(Just (Entity _ Submission{submissionRatingPoints=Just sPoints})) -> (Just (Entity _ Submission{submissionRatingPoints=Just sPoints})) ->
case preview (_grading . _maxPoints) sType of case preview (_grading . _maxPoints) sType of
Just maxPoints Just maxPoints
@ -228,14 +242,21 @@ getSheetListR tid ssh csh = do
] ]
psValidator = def psValidator = def
& defaultSorting [SortDescBy "submission-since"] & defaultSorting [SortDescBy "submission-until", SortDescBy "submission-since"]
(raw_statistics,table) <- runDB $ dbTableWidget psValidator DBTable (raw_statistics,table) <- runDB $ dbTableWidget psValidator DBTable
{ dbtColonnade = sheetCol { dbtColonnade = sheetCol
, dbtSQLQuery = \dt@(sheet `E.LeftOuterJoin` (submission `E.InnerJoin` _submissionUser)) , dbtSQLQuery = \dt@(sheet `E.LeftOuterJoin` (submission `E.InnerJoin` _submissionUser)) -> do
-> sheetData dt *> return (sheet, lastSheetEdit sheet, submission) sheetData dt
let existFiles = -- check whether files exist for given type
( hasSheetFileQuery sheet SheetExercise
, hasSheetFileQuery sheet SheetHint
, hasSheetFileQuery sheet SheetSolution
, hasSheetFileQuery sheet SheetMarking
)
return (sheet, lastSheetEdit sheet, submission, existFiles)
, dbtRowKey = \(sheet `E.LeftOuterJoin` _) -> sheet E.^. SheetId , dbtRowKey = \(sheet `E.LeftOuterJoin` _) -> sheet E.^. SheetId
, dbtProj = \dbr@DBRow{ dbrOutput=(Entity _ Sheet{..}, _, _) } , dbtProj = \dbr@DBRow{ dbrOutput=(Entity _ Sheet{..}, _, _, _) }
-> dbr <$ guardM (lift $ sheetFilter sheetName) -> dbr <$ guardM (lift $ sheetFilter sheetName)
, dbtSorting = Map.fromList , dbtSorting = Map.fromList
[ ( "name" [ ( "name"
@ -412,24 +433,12 @@ postSPseudonymR tid ssh csh shn = do
getSFileR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> SheetFileType -> FilePath -> Handler TypedContent getSFileR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> SheetFileType -> FilePath -> Handler TypedContent
getSFileR tid ssh csh shn typ title = serveOneFile fileQuery getSFileR tid ssh csh shn sft file = serveOneFile $ sheetFileQuery tid ssh csh shn sft file
where
fileQuery = E.select $ E.from $ getSZipR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> ZIPArchiveName SheetFileType -> Handler TypedContent
\(course `E.InnerJoin` sheet `E.InnerJoin` sheetFile `E.InnerJoin` file) -> do getSZipR tid ssh csh shn filename@(ZIPArchiveName sft)
-- Restrict to consistent rows that correspond to each other = serveSomeFiles (toPathPiece filename) $ sheetFilesAllQuery tid ssh csh shn sft
E.on (file E.^. FileId E.==. sheetFile E.^. SheetFileFile)
E.on (sheetFile E.^. SheetFileSheet E.==. sheet E.^. SheetId)
E.on (sheet E.^. SheetCourse E.==. course E.^. CourseId)
-- filter to requested file
E.where_ ((file E.^. FileTitle E.==. E.val title)
E.&&. (sheetFile E.^. SheetFileType E.==. E.val typ )
E.&&. (sheet E.^. SheetName E.==. E.val shn )
E.&&. (course E.^. CourseShorthand E.==. E.val csh )
E.&&. (course E.^. CourseSchool E.==. E.val ssh )
E.&&. (course E.^. CourseTerm E.==. E.val tid )
)
-- return file entity
return file
getSheetNewR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getSheetNewR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getSheetNewR tid ssh csh = do getSheetNewR tid ssh csh = do

View File

@ -7,10 +7,12 @@ import Import
import Utils.Lens import Utils.Lens
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as T
-- import qualified Data.Set (Set) -- import qualified Data.Set (Set)
import qualified Data.Set as Set import qualified Data.Set as Set
import Data.CaseInsensitive (original) import Data.CaseInsensitive (original)
-- import qualified Data.CaseInsensitive as CI -- import qualified Data.CaseInsensitive as CI
import qualified Data.Conduit.List as Conduit
import Language.Haskell.TH import Language.Haskell.TH
import Language.Haskell.TH.Syntax (qRunIO) import Language.Haskell.TH.Syntax (qRunIO)
@ -63,6 +65,26 @@ serveOneFile query = do
$logErrorS "SFileR" $ "Multiple matching files: " <> tshow other $logErrorS "SFileR" $ "Multiple matching files: " <> tshow other
error "Multiple matching files found." error "Multiple matching files found."
-- | Serve one file directly or a zip-archive of files, identified through a given DB query
-- Like `serveOneFile`, but sends a zip-archive if multiple results are returned
serveSomeFiles :: Text -> DB [Entity File] -> Handler TypedContent
serveSomeFiles archiveName query = do
results <- runDB query
case results of
[] -> notFound
[Entity _fileId File{fileTitle, fileContent}]
| Just fileContent' <- fileContent -> do
whenM downloadFiles $
addHeader "Content-Disposition" [st|attachment; filename="#{takeFileName fileTitle}"|]
return $ TypedContent (defaultMimeLookup (pack fileTitle) <> "; charset=utf-8") (toContent fileContent')
| otherwise -> sendResponseStatus noContent204 ()
files -> do
addHeader "Content-Disposition" [st|attachment; filename="#{toPathPiece archiveName}"|]
respondSourceDB "application/zip" $ do
let zipComment = T.encodeUtf8 archiveName
yieldMany files .| Conduit.map entityVal .| produceZip ZipInfo{..} .| Conduit.map toFlushBuilder
tidFromText :: Text -> Maybe TermId tidFromText :: Text -> Maybe TermId
tidFromText = fmap TermKey . maybeRight . termFromText tidFromText = fmap TermKey . maybeRight . termFromText

View File

@ -23,6 +23,7 @@ module Handler.Utils.Table.Pagination
, widgetColonnade, formColonnade, dbColonnade , widgetColonnade, formColonnade, dbColonnade
, cell, textCell, stringCell, i18nCell , cell, textCell, stringCell, i18nCell
, anchorCell, anchorCell', anchorCellM, anchorCellM' , anchorCell, anchorCell', anchorCellM, anchorCellM'
, linkEmptyCell, linkEmptyCellM, linkEmptyCellM'
, cellTooltip , cellTooltip
, listCell , listCell
, formCell, DBFormResult, getDBFormResult , formCell, DBFormResult, getDBFormResult
@ -863,6 +864,8 @@ cellTooltip msg = cellContents.mapped %~ (<> tipWdgt)
<div .tooltip__content>_{msg} <div .tooltip__content>_{msg}
|] |]
-- | Always display widget; maybe a link if user is Authorized.
-- Also see variant `linkEmptyCell`
anchorCell :: IsDBTable m a => Route UniWorX -> Widget -> DBCell m a anchorCell :: IsDBTable m a => Route UniWorX -> Widget -> DBCell m a
anchorCell = anchorCellM . return anchorCell = anchorCellM . return
@ -886,6 +889,24 @@ anchorCellM' xM x2route x2widget = cell $ do
Authorized -> $(widgetFile "table/cell/link") -- show allowed link Authorized -> $(widgetFile "table/cell/link") -- show allowed link
_otherwise -> widget -- don't show prohibited link _otherwise -> widget -- don't show prohibited link
-- | Variant of `anchorCell` that returns `mempty` for unauthorized links
linkEmptyCell :: IsDBTable m a => Route UniWorX -> Widget -> DBCell m a
linkEmptyCell = linkEmptyCellM . return
linkEmptyCellM :: IsDBTable m a => WidgetT UniWorX IO (Route UniWorX) -> Widget -> DBCell m a
linkEmptyCellM routeM widget = linkEmptyCellM' routeM id (const widget)
linkEmptyCellM' :: IsDBTable m a => WidgetT UniWorX IO x -> (x -> Route UniWorX) -> (x -> Widget) -> DBCell m a
linkEmptyCellM' xM x2route x2widget = cell $ do
x <- xM
let route = x2route x
widget = x2widget x
authResult <- liftHandlerT $ isAuthorized route False
case authResult of
Authorized -> $(widgetFile "table/cell/link") -- show allowed link
_otherwise -> mempty -- don't show anything for prohibited links
listCell :: (IsDBTable m a, Traversable f) => f r' -> (r' -> DBCell m a) -> DBCell m a listCell :: (IsDBTable m a, Traversable f) => f r' -> (r' -> DBCell m a) -> DBCell m a
listCell xs mkCell = review dbCell . ([], ) $ do listCell xs mkCell = review dbCell . ([], ) $ do

View File

@ -42,6 +42,7 @@ import Database.Persist.Sql
import Web.HttpApiData import Web.HttpApiData
import Web.PathPieces import Web.PathPieces
import Text.Blaze (Markup)
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as Text import qualified Data.Text as Text
import qualified Data.Text.Lens as Text import qualified Data.Text.Lens as Text
@ -250,6 +251,12 @@ instance PathPiece SheetFileType where
toPathPiece SheetMarking = "marking" toPathPiece SheetMarking = "marking"
fromPathPiece = finiteFromPathPiece fromPathPiece = finiteFromPathPiece
sheetFile2markup :: SheetFileType -> Markup
sheetFile2markup SheetExercise = iconQuestion
sheetFile2markup SheetHint = iconHint
sheetFile2markup SheetSolution = iconSolution
sheetFile2markup SheetMarking = iconMarking
-- $(deriveSimpleWith ''DisplayAble 'display (drop 17) ''SheetFileType) -- $(deriveSimpleWith ''DisplayAble 'display (drop 17) ''SheetFileType)
instance DisplayAble SheetFileType where -- deprecated, see RenderMessage instance in Foundation instance DisplayAble SheetFileType where -- deprecated, see RenderMessage instance in Foundation
display SheetExercise = "Aufgabenstellung" display SheetExercise = "Aufgabenstellung"

View File

@ -123,6 +123,29 @@ type WidgetSiteless = forall site. forall m. (MonadIO m, MonadThrow m, MonadBase
-- Icons -- -- Icons --
----------- -----------
-- We collect all used icons here for an overview.
-- For consistency, some conditional icons are also provided, e.g. `isIvisble`
iconQuestion :: Markup
iconQuestion = [shamlet|<i .fas .fa-question-circle>|]
iconHint :: Markup
iconHint = [shamlet|<i .fas .fa-life-ring>|]
iconSolution :: Markup
iconSolution = [shamlet|<i .fas .fa-exclamation-circle>|]
iconMarking :: Markup
iconMarking = [shamlet|<i .fas .fa-check-circle>|]
fileDownload :: Markup
fileDownload = [shamlet|<i .fas .fa-file-download>|]
zipDownload :: Markup
zipDownload = [shamlet|<i .fas .fa-file-archive>|]
-- Conditional icons
isVisible :: Bool -> Markup isVisible :: Bool -> Markup
-- ^ Display an icon that denotes that something™ is visible or invisible -- ^ Display an icon that denotes that something™ is visible or invisible
isVisible True = [shamlet|<i .fas .fa-eye>|] isVisible True = [shamlet|<i .fas .fa-eye>|]
@ -163,11 +186,6 @@ boolSymbol :: Bool -> Markup
boolSymbol True = [shamlet|<i .fas .fa-check>|] boolSymbol True = [shamlet|<i .fas .fa-check>|]
boolSymbol False = [shamlet|<i .fas .fa-times>|] boolSymbol False = [shamlet|<i .fas .fa-times>|]
fileDownload :: Markup
fileDownload = [shamlet|<i .fas .fa-file-download>|]
zipDownload :: Markup
zipDownload = [shamlet|<i .fas .fa-file-archive>|]
--------------------- ---------------------

View File

@ -3,6 +3,7 @@ module Utils.Sheet where
import Import.NoFoundation import Import.NoFoundation
import qualified Database.Esqueleto as E import qualified Database.Esqueleto as E
import Database.Esqueleto.Internal.Language (From) -- How to avoid this import?
-- DB Queries for Sheets that are used in several places -- DB Queries for Sheets that are used in several places
@ -44,3 +45,49 @@ sheetOldUnassigned tid ssh csh = do
[] -> Nothing [] -> Nothing
[E.Value shn] -> Just shn [E.Value shn] -> Just shn
_ -> error "SQL Query with limit 1 returned more than one result" _ -> error "SQL Query with limit 1 returned more than one result"
-- | Return a specfic file from a `Sheet`
sheetFileQuery :: MonadIO m => TermId -> SchoolId -> CourseShorthand -> SheetName -> SheetFileType -> FilePath -> SqlReadT m [Entity File]
sheetFileQuery tid ssh csh shn sft title = E.select $ E.from $
\(course `E.InnerJoin` sheet `E.InnerJoin` sFile `E.InnerJoin` file) -> do
-- Restrict to consistent rows that correspond to each other
E.on (file E.^. FileId E.==. sFile E.^. SheetFileFile)
E.on (sFile E.^. SheetFileSheet E.==. sheet E.^. SheetId)
E.on (sheet E.^. SheetCourse E.==. course E.^. CourseId)
-- filter to requested file
E.where_ ((file E.^. FileTitle E.==. E.val title)
E.&&. (sFile E.^. SheetFileType E.==. E.val sft )
E.&&. (sheet E.^. SheetName E.==. E.val shn )
E.&&. (course E.^. CourseShorthand E.==. E.val csh )
E.&&. (course E.^. CourseSchool E.==. E.val ssh )
E.&&. (course E.^. CourseTerm E.==. E.val tid )
)
-- return file entity
return file
-- | Return all files of a certain `SheetFileType` for a `Sheet`
sheetFilesAllQuery :: MonadIO m => TermId -> SchoolId -> CourseShorthand -> SheetName -> SheetFileType -> SqlReadT m [Entity File]
sheetFilesAllQuery tid ssh csh shn sft = E.select $ E.from $
\(course `E.InnerJoin` sheet `E.InnerJoin` sFile `E.InnerJoin` file) -> do
-- Restrict to consistent rows that correspond to each other
E.on (file E.^. FileId E.==. sFile E.^. SheetFileFile)
E.on (sFile E.^. SheetFileSheet E.==. sheet E.^. SheetId)
E.on (sheet E.^. SheetCourse E.==. course E.^. CourseId)
-- filter to requested file
E.where_ ((sFile E.^. SheetFileType E.==. E.val sft )
E.&&. (sheet E.^. SheetName E.==. E.val shn )
E.&&. (course E.^. CourseShorthand E.==. E.val csh )
E.&&. (course E.^. CourseSchool E.==. E.val ssh )
E.&&. (course E.^. CourseTerm E.==. E.val tid )
)
-- return file entity
return file
-- | Check whether a sheet has any files for a given file type
hasSheetFileQuery :: Database.Esqueleto.Internal.Language.From query expr backend (expr (Entity SheetFile))
-- hasSheetFileQuery :: (E.Esqueleto query expr backend)
=> expr (Entity Sheet) -> SheetFileType -> expr (E.Value Bool)
hasSheetFileQuery sheet sft =
E.exists $ E.from $ \sFile ->
E.where_ ((sFile E.^. SheetFileSheet E.==. sheet E.^. SheetId)
E.&&. (sFile E.^. SheetFileType E.==. E.val sft ))