diff --git a/frontend/src/utils/form/form-error-remover.js b/frontend/src/utils/form/form-error-remover.js index 235dd8ade..1b1509c2d 100644 --- a/frontend/src/utils/form/form-error-remover.js +++ b/frontend/src/utils/form/form-error-remover.js @@ -36,7 +36,9 @@ export class FormErrorRemover { inputElements.forEach((inputElement) => { inputElement.addEventListener('input', () => { - FORM_GROUP_WITH_ERRORS_CLASSES.forEach(c => { this._element.classList.remove(c); }); + if (!inputElement.willValidate || inputElement.validity.vaild) { + FORM_GROUP_WITH_ERRORS_CLASSES.forEach(c => { this._element.classList.remove(c); }); + } }); }); } diff --git a/frontend/src/utils/form/form-error-reporter.js b/frontend/src/utils/form/form-error-reporter.js new file mode 100644 index 000000000..dff3f00d2 --- /dev/null +++ b/frontend/src/utils/form/form-error-reporter.js @@ -0,0 +1,68 @@ +import { Utility } from '../../core/utility'; +import * as defer from 'lodash.defer'; + +const FORM_ERROR_REPORTER_INITIALIZED_CLASS = 'form-error-remover--initialized'; + +@Utility({ + selector: 'input, textarea, select', +}) +export class FormErrorReporter { + _element; + _err; + + constructor(element) { + if (!element) + throw new Error('Form Error Reporter utility needs to be passed an element!'); + + this._element = element; + + if (this._element.classList.contains(FORM_ERROR_REPORTER_INITIALIZED_CLASS)) + return; + + this._element.classList.add(FORM_ERROR_REPORTER_INITIALIZED_CLASS); + } + + start() { + if (this._element.willValidate) { + this._element.addEventListener('invalid', this.report.bind(this)); + this._element.addEventListener('change', () => { defer(this.report.bind(this)); } ); + } + } + + report() { + const msg = this._element.validity.valid ? null : this._element.validationMessage; + + const target = this._element.closest('.standalone-field, .form-group'); + + if (!target) + return; + + if (this._err && this._err.parentNode) { + this._err.parentNode.removeChild(this._err); + this._err = undefined; + } + + if (!msg) { + target.classList.remove('standalone-field--has-error', 'form-group--has-error'); + } else { + if (target.classList.contains('form-group')) { + target.classList.add('form-group--has-error'); + + const container = target.querySelector('.form-group__input'); + if (container) { + this._err = document.createElement('div'); + this._err.classList.add('form-error'); + this._err.innerText = msg; + container.appendChild(this._err); + } + } else { + target.classList.add('standalone-field--has-error'); + + this._err = document.createElement('div'); + this._err.classList.add('standalone-field__error'); + this._err.innerText = msg; + target.appendChild(this._err); + } + } + } +} diff --git a/frontend/src/utils/form/form.js b/frontend/src/utils/form/form.js index c541de367..2327ca1db 100644 --- a/frontend/src/utils/form/form.js +++ b/frontend/src/utils/form/form.js @@ -3,6 +3,7 @@ import { AutoSubmitButton } from './auto-submit-button'; import { AutoSubmitInput } from './auto-submit-input'; import { Datepicker } from './datepicker'; import { FormErrorRemover } from './form-error-remover'; +import { FormErrorReporter } from './form-error-reporter'; import { InteractiveFieldset } from './interactive-fieldset'; import { NavigateAwayPrompt } from './navigate-away-prompt'; import { CommunicationRecipients } from './communication-recipients'; @@ -12,6 +13,7 @@ export const FormUtils = [ AutoSubmitInput, Datepicker, FormErrorRemover, + FormErrorReporter, InteractiveFieldset, NavigateAwayPrompt, CommunicationRecipients, diff --git a/frontend/src/utils/inputs/file-max-size.js b/frontend/src/utils/inputs/file-max-size.js new file mode 100644 index 000000000..653cca287 --- /dev/null +++ b/frontend/src/utils/inputs/file-max-size.js @@ -0,0 +1,44 @@ +import { Utility } from '../../core/utility'; + +const FILE_MAX_SIZE_INITIALIZED_CLASS = 'file-max-size--initialized'; + +@Utility({ + selector: 'input[type="file"][data-max-size]', +}) +export class FileMaxSize { + _element; + _app; + + constructor(element, app) { + if (!element) + throw new Error('FileMaxSize utility cannot be setup without an element!'); + + this._element = element; + this._app = app; + + if (this._element.classList.contains(FILE_MAX_SIZE_INITIALIZED_CLASS)) { + throw new Error('FileMaxSize utility already initialized!'); + } + + this._element.classList.add(FILE_MAX_SIZE_INITIALIZED_CLASS); + } + + start() { + this._element.addEventListener('change', this._change.bind(this)); + } + + _change() { + const hasOversized = Array.from(this._element.files).some(file => file.size > this._element.dataset.maxSize); + if (hasOversized) { + if (this._element.files.length > 1) { + this._element.setCustomValidity(this._app.i18n.get('fileTooLargeMultiple')); + } else { + this._element.setCustomValidity(this._app.i18n.get('fileTooLarge')); + } + } else { + this._element.setCustomValidity(''); + } + + this._element.reportValidity(); + } +} diff --git a/frontend/src/utils/inputs/inputs.js b/frontend/src/utils/inputs/inputs.js index 39b484759..9b438bf6e 100644 --- a/frontend/src/utils/inputs/inputs.js +++ b/frontend/src/utils/inputs/inputs.js @@ -1,5 +1,6 @@ import { Checkbox } from './checkbox'; import { FileInput } from './file-input'; +import { FileMaxSize } from './file-max-size'; import './inputs.sass'; import './radio-group.sass'; @@ -7,4 +8,5 @@ import './radio-group.sass'; export const InputUtils = [ Checkbox, FileInput, + FileMaxSize, ]; diff --git a/messages/frontend/de-de-formal.msg b/messages/frontend/de-de-formal.msg index a17c4540c..bb88f4cee 100644 --- a/messages/frontend/de-de-formal.msg +++ b/messages/frontend/de-de-formal.msg @@ -1,4 +1,6 @@ FilesSelected: Dateien ausgewählt SelectFile: Datei auswählen SelectFiles: Datei(en) auswählen -AsyncFormFailure: Da ist etwas schief gelaufen, das tut uns Leid. Falls das erneut passiert schicken Sie uns bitte eine kurze Beschreibung dieses Ereignisses über das Hilfe-Widget rechts oben. Vielen Dank für Ihre Hilfe! \ No newline at end of file +AsyncFormFailure: Da ist etwas schief gelaufen, das tut uns Leid. Falls das erneut passiert schicken Sie uns bitte eine kurze Beschreibung dieses Ereignisses über das Hilfe-Widget rechts oben. Vielen Dank für Ihre Hilfe! +FileTooLarge: Die ausgewählte Datei ist zu groß +FileTooLargeMultiple: Mindestens eine der ausgewählten Dateien ist zu groß \ No newline at end of file diff --git a/messages/frontend/en.msg b/messages/frontend/en.msg index 743eb91be..8a7ca474e 100644 --- a/messages/frontend/en.msg +++ b/messages/frontend/en.msg @@ -2,3 +2,5 @@ FilesSelected: Files selected SelectFile: Select file SelectFiles: Select file(s) AsyncFormFailure: Something went wrong, we are sorry. If this error occurs again, please let us know by clicking the Support button in the upper right corner. Thank you very much! +FileTooLarge: The selected file is too large +FileTooLargeMultiple: At least one of the selected files is too large \ No newline at end of file diff --git a/messages/uniworx/de-de-formal.msg b/messages/uniworx/de-de-formal.msg index 608e43266..8a185e148 100644 --- a/messages/uniworx/de-de-formal.msg +++ b/messages/uniworx/de-de-formal.msg @@ -823,8 +823,11 @@ UploadModeExtensionRestrictionTip: Komma-separiert. Wenn keine Dateiendungen ang UploadModeExtensionRestrictionEmpty: Liste von zulässigen Dateiendungen darf nicht leer sein UploadModeExtensionRestrictionMultipleTip: Einschränkung von Dateiendungen erfolgt für alle hochgeladenen Dateien, auch innerhalb von ZIP-Archiven. +GenericFileFieldFileTooLarge file@FilePath: „#{file}“ ist zu groß GenericFileFieldInvalidExtension file@FilePath: „#{file}” hat keine zulässige Dateiendung FileUploadOnlySessionTip: Sie haben diese Datei in der aktuellen Session bereits hochgeladen, sie ist allerdings noch nicht gespeichert. Sie müssen zunächst noch das Formular „Senden“, damit die Datei ordnungsgemäß gespeichert wird. +FileUploadMaxSize maxSize@Text: Datei darf maximal #{maxSize} groß sein +FileUploadMaxSizeMultiple maxSize@Text: Dateien dürfen jeweils maximal #{maxSize} groß sein UploadSpecificFiles: Vorgegebene Dateinamen NoUploadSpecificFilesConfigured: Wenn der Abgabemodus vorgegebene Dateinamen vorsieht, muss mindestens ein vorgegebener Dateiname konfiguriert werden. @@ -833,6 +836,8 @@ UploadSpecificFilesDuplicateLabels: Bezeichner für vorgegebene Dateinamen müss UploadSpecificFileLabel: Bezeichnung UploadSpecificFileName: Dateiname UploadSpecificFileRequired: Zur Abgabe erforderlich +UploadSpecificFileMaxSize: Maximale Dateigröße (Bytes) +UploadSpecificFileMaxSizeNegative: Maximale Dateigröße darf nicht negativ sein NoSubmissions: Keine Abgabe CorrectorSubmissions: Abgabe extern mit Pseudonym diff --git a/messages/uniworx/en-eu.msg b/messages/uniworx/en-eu.msg index f98e3f169..36dff2046 100644 --- a/messages/uniworx/en-eu.msg +++ b/messages/uniworx/en-eu.msg @@ -820,8 +820,11 @@ UploadModeExtensionRestrictionTip: Comma-separated. If no file extensions are sp UploadModeExtensionRestrictionEmpty: List of permitted file extensions may not be empty UploadModeExtensionRestrictionMultipleTip: Checks for valid file extension are performed for all uploaded files, including those packed within zip-archives. +GenericFileFieldFileTooLarge file: “#{file}” is too large GenericFileFieldInvalidExtension file: “#{file}” does not have an acceptable file extension FileUploadOnlySessionTip: You have uploaded this file during your current session. It has not yet been saved permanently. The file will be saved permanently if you “Send” as part of this Form. +FileUploadMaxSize maxSize: File may be up to #{maxSize} in size +FileUploadMaxSizeMultiple maxSize: Files may each be up to #{maxSize} in size UploadSpecificFiles: Pre-defined files NoUploadSpecificFilesConfigured: If pre-defined files are selected, at least one file needs to be configured. @@ -830,6 +833,8 @@ UploadSpecificFilesDuplicateLabels: Labels of pre-defined files must be unique UploadSpecificFileLabel: Label UploadSpecificFileName: Filename UploadSpecificFileRequired: Required for submission +UploadSpecificFileMaxSize: Maximum filesize (bytes) +UploadSpecificFileMaxSizeNegative: Maximum filesize may not be negative NoSubmissions: No submission CorrectorSubmissions: External submission via pseudonym diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index 7c43e6aef..842c3b4a5 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -30,7 +30,8 @@ import qualified Data.Text as T import Yesod.Form.Bootstrap3 import Handler.Utils.Zip -import qualified Data.Conduit.List as C +import qualified Data.Conduit.Combinators as C +import qualified Data.Conduit.List as C (mapMaybe, mapMaybeM) import qualified Database.Esqueleto as E import qualified Database.Esqueleto.Utils as E @@ -637,9 +638,10 @@ uploadModeForm prev = multiActionA actions (fslI MsgSheetUploadMode) (classifyUp sFileForm nudge mPrevUF csrf = do (labelRes, labelView) <- mpreq textField (fslI MsgUploadSpecificFileLabel & addName (nudge "label")) $ specificFileLabel <$> mPrevUF (nameRes, nameView) <- mpreq textField (fslI MsgUploadSpecificFileName & addName (nudge "name")) $ specificFileName <$> mPrevUF + (maxSizeRes, maxSizeView) <- mopt (natFieldI MsgUploadSpecificFileMaxSizeNegative) (fslI MsgUploadSpecificFileMaxSize & addName (nudge "max-size")) $ specificFileMaxSize <$> mPrevUF (reqRes, reqView) <- mpreq checkBoxField (fslI MsgUploadSpecificFileRequired & addName (nudge "required")) $ specificFileRequired <$> mPrevUF - return ( UploadSpecificFile <$> labelRes <*> nameRes <*> reqRes + return ( UploadSpecificFile <$> labelRes <*> nameRes <*> reqRes <*> maxSizeRes , $(widgetFile "widgets/massinput/uploadSpecificFiles/form") ) @@ -846,6 +848,7 @@ data FileField = FileField , fieldMultiple :: Bool , fieldRestrictExtensions :: Maybe (NonNull (Set Extension)) , fieldAdditionalFiles :: Map FileId (FileFieldUserOption Bool) + , fieldMaxFileSize :: Maybe Natural -- ^ Applied to each file separately } deriving (Eq, Ord, Read, Show, Generic, Typeable) genericFileField :: forall m. @@ -893,23 +896,26 @@ genericFileField mkOpts = Field{..} $logDebugS "genericFileField.getPermittedFiles" $ "Session: " <> tshow sessionFiles' return $ fieldAdditionalFiles <> sessionFiles' - handleUpload :: Maybe Text -> File -> DB (Maybe FileId) - handleUpload mIdent file = do - for mIdent $ \ident -> do - now <- liftIO getCurrentTime - oldSFIds <- fmap (Set.fromList . map E.unValue) . E.select . E.from $ \sessionFile -> do - E.where_ $ E.subSelectForeign sessionFile SessionFileFile (E.^. FileTitle) E.==. E.val (fileTitle file) - E.&&. sessionFile E.^. SessionFileTouched E.<=. E.val now - return $ sessionFile E.^. SessionFileId - fId <- insert file - sfId <- insert $ SessionFile fId now - modifySessionJson SessionFiles $ \(fromMaybe mempty -> MergeHashMap old) -> - Just . MergeHashMap $ HashMap.insert ident (Set.insert sfId . maybe Set.empty (`Set.difference` oldSFIds) $ HashMap.lookup ident old) old - return fId + handleUpload :: FileField -> Maybe Text -> File -> DB (Maybe FileId) + handleUpload FileField{fieldMaxFileSize} mIdent file + | maybe (const False) (<) fieldMaxFileSize $ maybe 0 (fromIntegral . olength) (fileContent file) + = return Nothing -- Don't save files that are too large + | otherwise = do + for mIdent $ \ident -> do + now <- liftIO getCurrentTime + oldSFIds <- fmap (Set.fromList . map E.unValue) . E.select . E.from $ \sessionFile -> do + E.where_ $ E.subSelectForeign sessionFile SessionFileFile (E.^. FileTitle) E.==. E.val (fileTitle file) + E.&&. sessionFile E.^. SessionFileTouched E.<=. E.val now + return $ sessionFile E.^. SessionFileId + fId <- insert file + sfId <- insert $ SessionFile fId now + modifySessionJson SessionFiles $ \(fromMaybe mempty -> MergeHashMap old) -> + Just . MergeHashMap $ HashMap.insert ident (Set.insert sfId . maybe Set.empty (`Set.difference` oldSFIds) $ HashMap.lookup ident old) old + return fId fieldEnctype = Multipart fieldParse :: [Text] -> [FileInfo] -> m (Either (SomeMessage (HandlerSite m)) (Maybe FileUploads)) - fieldParse vals files = do + fieldParse vals files = runExceptT $ do opts@FileField{..} <- liftHandler mkOpts mIdent <- fmap getFirst . flip foldMapM vals $ \v -> @@ -933,12 +939,20 @@ genericFileField mkOpts = Field{..} = not (permittedExtension opts fName) && (not doUnpack || ((/=) `on` simpleContentType) (mimeLookup fName) typeZip) + whenIsJust fieldMaxFileSize $ \maxSize -> forM_ files $ \fInfo -> do + fLength <- runConduit $ fileSource fInfo .| C.takeE (fromIntegral $ succ maxSize) .| C.lengthE + when (fLength > maxSize) $ do + liftHandler . runDB . runConduit $ + mapM_ (transPipe lift . handleFile) files + .| C.mapM_ (void . handleUpload opts mIdent) + throwE . SomeMessage . MsgGenericFileFieldFileTooLarge . unpack $ fileName fInfo + if | invExt : _ <- filter invalidUploadExtension uploadedFilenames -> do liftHandler . runDB . runConduit $ mapM_ (transPipe lift . handleFile) files - .| C.mapM_ (void . handleUpload mIdent) - return . Left . SomeMessage . MsgGenericFileFieldInvalidExtension $ unpack invExt + .| C.mapM_ (void . handleUpload opts mIdent) + throwE . SomeMessage . MsgGenericFileFieldInvalidExtension $ unpack invExt | otherwise -> do let fSrc = do @@ -961,14 +975,14 @@ genericFileField mkOpts = Field{..} (unsealConduitT -> fSrc', length -> nFiles) <- liftHandler $ fSrc $$+ peekN 2 $logDebugS "genericFileField.fieldParse" $ tshow nFiles if - | nFiles <= 0 -> return $ Right Nothing - | nFiles <= 1 -> return . Right $ Just fSrc' + | nFiles <= 0 -> return Nothing + | nFiles <= 1 -> return $ Just fSrc' | not fieldMultiple -> do liftHandler . runDB . runConduit $ mapM_ (transPipe lift . handleFile) files - .| C.mapM_ (void . handleUpload mIdent) - return . Left $ SomeMessage MsgOnlyUploadOneFile - | otherwise -> return . Right $ Just fSrc' + .| C.mapM_ (void . handleUpload opts mIdent) + throwE $ SomeMessage MsgOnlyUploadOneFile + | otherwise -> return $ Just fSrc' fieldView :: FieldViewFunc m FileUploads fieldView fieldId fieldName _attrs val req = do @@ -980,7 +994,7 @@ genericFileField mkOpts = Field{..} (uploads, references) <- runWriterT . for val $ \src -> do fmap Set.fromList . sourceToList $ transPipe (lift . lift) src - .| C.mapMaybeM (either (\fId -> Nothing <$ tell (Set.singleton fId)) $ lift . handleUpload mIdent) + .| C.mapMaybeM (either (\fId -> Nothing <$ tell (Set.singleton fId)) $ lift . handleUpload opts mIdent) permittedFiles <- getPermittedFiles mIdent opts @@ -1035,6 +1049,7 @@ fileFieldMultiple = genericFileField $ return FileField , fieldMultiple = True , fieldRestrictExtensions = Nothing , fieldAdditionalFiles = Map.empty + , fieldMaxFileSize = Nothing } fileField :: (MonadHandler m, HandlerSite m ~ UniWorX) => Field m FileUploads @@ -1044,6 +1059,7 @@ fileField = genericFileField $ return FileField , fieldMultiple = False , fieldRestrictExtensions = Nothing , fieldAdditionalFiles = Map.empty + , fieldMaxFileSize = Nothing } specificFileField :: UploadSpecificFile -> Field Handler FileUploads @@ -1053,6 +1069,7 @@ specificFileField UploadSpecificFile{..} = convertField fixupFileTitles id . gen , fieldMultiple = False , fieldRestrictExtensions = fromNullable . maybe Set.empty (Set.singleton . view _2) . Map.lookupMin . Map.fromList . map (length &&& id) $ fileNameExtensions specificFileName , fieldAdditionalFiles = Map.empty + , fieldMaxFileSize = specificFileMaxSize } where fixupFileTitles = flip (.|) . C.mapM $ either (fmap Left . updateFileReference) (fmap Right . updateFile) @@ -1084,6 +1101,7 @@ zipFileField doUnpack permittedExtensions = genericFileField $ return FileField , fieldMultiple = doUnpack , fieldRestrictExtensions = permittedExtensions , fieldAdditionalFiles = Map.empty + , fieldMaxFileSize = Nothing } fileUploadForm :: Bool -- ^ Required? @@ -1119,6 +1137,7 @@ multiFileField mkPermitted = genericFileField $ mkField <$> mkPermitted , fieldMultiple = True , fieldRestrictExtensions = Nothing , fieldAdditionalFiles = Map.fromSet (const $ FileFieldUserOption False True) permitted + , fieldMaxFileSize = Nothing } data SheetGrading' = Points' | PassPoints' | PassBinary' | PassAlways' diff --git a/src/Model/Types/Sheet.hs b/src/Model/Types/Sheet.hs index a35553fa9..e596d64c6 100644 --- a/src/Model/Types/Sheet.hs +++ b/src/Model/Types/Sheet.hs @@ -186,10 +186,12 @@ data UploadSpecificFile = UploadSpecificFile { specificFileLabel :: Text , specificFileName :: FileName , specificFileRequired :: Bool + , specificFileMaxSize :: Maybe Natural } deriving (Show, Read, Eq, Ord, Generic) deriveJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 2 + , omitNothingFields = True } ''UploadSpecificFile derivePersistFieldJSON ''UploadSpecificFile diff --git a/src/Utils.hs b/src/Utils.hs index 8624748ab..2548aea5e 100644 --- a/src/Utils.hs +++ b/src/Utils.hs @@ -258,10 +258,10 @@ textPercent' trailZero precision part whole -- | Convert number of bytes to human readable format textBytes :: forall a. Integral a => a -> Text textBytes x - | v < kb = rshow v <> "B" - | v < mb = rshow (v/kb) <> "KB" - | v < gb = rshow (v/mb) <> "MB" - | otherwise = rshow (v/gb) <> "GB" + | v < kb = rshow v <> "B" + | v < mb = rshow (v/kb) <> "KiB" + | v < gb = rshow (v/mb) <> "MiB" + | otherwise = rshow (v/gb) <> "GiB" where v = fromIntegral x kb :: Double diff --git a/src/Utils/Frontend/I18n.hs b/src/Utils/Frontend/I18n.hs index 1d1c24652..7ec1d9ef2 100644 --- a/src/Utils/Frontend/I18n.hs +++ b/src/Utils/Frontend/I18n.hs @@ -28,6 +28,8 @@ data FrontendMessage = MsgFilesSelected | MsgSelectFile | MsgSelectFiles | MsgAsyncFormFailure + | MsgFileTooLarge + | MsgFileTooLargeMultiple deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) instance Universe FrontendMessage instance Finite FrontendMessage diff --git a/templates/widgets/genericFileField.hamlet b/templates/widgets/genericFileField.hamlet index f257bfeb0..356bbdc57 100644 --- a/templates/widgets/genericFileField.hamlet +++ b/templates/widgets/genericFileField.hamlet @@ -21,7 +21,7 @@ $if not (null fileInfos)
_{MsgAddMoreFiles} $# new files - + $if fieldMultiple
@@ -37,6 +37,13 @@ $maybe exts <- fmap toNullable fieldRestrictExtensions
_{MsgUploadModeExtensionRestrictionMultipleTip} +$maybe maxSize <- fieldMaxFileSize +
+ $if fieldMultiple + _{MsgFileUploadMaxSizeMultiple (textBytes maxSize)} + $else + _{MsgFileUploadMaxSize (textBytes maxSize)} + $if not (fieldOptionForce fieldUnpackZips)
^{iconTooltip (i18n MsgAutoUnzipInfo) Nothing False} diff --git a/templates/widgets/massinput/uploadSpecificFiles/form.hamlet b/templates/widgets/massinput/uploadSpecificFiles/form.hamlet index 3ae5d7f21..8ce4629c7 100644 --- a/templates/widgets/massinput/uploadSpecificFiles/form.hamlet +++ b/templates/widgets/massinput/uploadSpecificFiles/form.hamlet @@ -1,4 +1,5 @@ $newline never #{csrf}^{fvWidget labelView} ^{fvWidget nameView} +^{fvWidget maxSizeView} ^{fvWidget reqView} diff --git a/templates/widgets/massinput/uploadSpecificFiles/layout.hamlet b/templates/widgets/massinput/uploadSpecificFiles/layout.hamlet index b525b28c4..16639a8dd 100644 --- a/templates/widgets/massinput/uploadSpecificFiles/layout.hamlet +++ b/templates/widgets/massinput/uploadSpecificFiles/layout.hamlet @@ -4,6 +4,7 @@ $newline never _{MsgUploadSpecificFileLabel} _{MsgUploadSpecificFileName} _{MsgUploadSpecificFileRequired} + _{MsgUploadSpecificFileMaxSize} $forall coord <- review liveCoords lLength diff --git a/test/Database/Fill.hs b/test/Database/Fill.hs index e752ed049..029579b6d 100644 --- a/test/Database/Fill.hs +++ b/test/Database/Fill.hs @@ -757,9 +757,9 @@ fillDb = do , SubmissionMode corrector $ Just NoUpload , SubmissionMode corrector $ Just UploadSpecific { specificFiles = impureNonNull $ Set.fromList - [ UploadSpecificFile "Aufgabe 1" "exercise_2.1.hs" False - , UploadSpecificFile "Aufgabe 2" "exercise_2.2.hs" False - , UploadSpecificFile "Erklärung der Eigenständigkeit" "erklärung.txt" True + [ UploadSpecificFile "Aufgabe 1" "exercise_2.1.hs" False Nothing + , UploadSpecificFile "Aufgabe 2" "exercise_2.2.hs" False Nothing + , UploadSpecificFile "Erklärung der Eigenständigkeit" "erklärung.txt" True (Just 42) ] } ] ++ [ SubmissionMode corrector $ Just UploadAny{..}