From e32b65d9a66cb3565d2ff070852caf2716f0a043 Mon Sep 17 00:00:00 2001 From: William Le Date: Sun, 20 Jun 2021 10:01:21 -0700 Subject: [PATCH] More uploading features for included files. --- Controllers/AdminController.cs | 4 +- Controllers/AdminSystemController.cs | 4 +- Controllers/AdminTestController.cs | 32 +++++--- Controllers/GoogleAuth.cs | 4 +- Controllers/{MySqlQuery.cs => SqlQuery.cs} | 48 +++++------- Controllers/TestController.cs | 17 ++-- Controllers/UserController.cs | 4 +- CreateDatabase.mssql.sql | 77 +++++++++++++++++++ ...teDatabase.sql => CreateDatabase.mysql.sql | 2 +- Models/AdminStipulatable.cs | 26 ++++++- Models/Stipulatable.cs | 13 +--- Models/UpdateTesterFiles.cs | 6 +- RestrictedViews/js/admin.js | 58 +++++++++++--- RestrictedViews/views/admin.html | 22 ++++-- Startup.cs | 2 +- StaticViews/css/main.css | 21 ++++- StaticViews/js/tester.js | 19 +++-- StaticViews/views/student-test.html | 6 +- Tester/ControllerExtensions.cs | 2 +- 19 files changed, 263 insertions(+), 104 deletions(-) rename Controllers/{MySqlQuery.cs => SqlQuery.cs} (85%) create mode 100644 CreateDatabase.mssql.sql rename CreateDatabase.sql => CreateDatabase.mysql.sql (95%) diff --git a/Controllers/AdminController.cs b/Controllers/AdminController.cs index fbdfeb4..fa97fa5 100644 --- a/Controllers/AdminController.cs +++ b/Controllers/AdminController.cs @@ -11,9 +11,9 @@ namespace Modulr.Controllers [Route("/Admin/{**page}")] public class AdminController : HTMLController { - private readonly MySqlQuery _query; + private readonly SqlQuery _query; - public AdminController(ILogger logger, MySqlQuery query, ModulrConfig config) : base(logger, config) + public AdminController(ILogger logger, SqlQuery query, ModulrConfig config) : base(logger, config) { _query = query; } diff --git a/Controllers/AdminSystemController.cs b/Controllers/AdminSystemController.cs index 3115880..53f906b 100644 --- a/Controllers/AdminSystemController.cs +++ b/Controllers/AdminSystemController.cs @@ -13,11 +13,11 @@ namespace Modulr.Controllers [Route("/Admin/System")] public class AdminSystemController : ControllerBase { - private readonly MySqlQuery _query; + private readonly SqlQuery _query; private readonly GoogleAuth _auth; private readonly IHostApplicationLifetime _app; - public AdminSystemController(MySqlQuery query, ModulrConfig config, GoogleAuth auth, IHostApplicationLifetime app) + public AdminSystemController(SqlQuery query, ModulrConfig config, GoogleAuth auth, IHostApplicationLifetime app) { _query = query; ModulrJail.Config = config; diff --git a/Controllers/AdminTestController.cs b/Controllers/AdminTestController.cs index efae50f..29e2be8 100644 --- a/Controllers/AdminTestController.cs +++ b/Controllers/AdminTestController.cs @@ -15,11 +15,11 @@ namespace Modulr.Controllers [Route("/Admin/Tester")] public class AdminTestController : ControllerBase { - private readonly MySqlQuery _query; + private readonly SqlQuery _query; private readonly ModulrConfig _config; private readonly GoogleAuth _auth; - public AdminTestController(MySqlQuery query, ModulrConfig config, GoogleAuth auth) + public AdminTestController(SqlQuery query, ModulrConfig config, GoogleAuth auth) { _query = query; _config = config; @@ -41,7 +41,7 @@ public async Task OnAdd([FromBody] UpdateTesterFiles input) return -1; } - return await _query.AddTest(input.TestName, input.Testers, input.Required); + return await _query.AddTest(input.TestName, input.TestDescription, input.Included, input.Testers, input.Required); } [HttpPost("GetAll")] @@ -114,7 +114,7 @@ public async Task UpdateTest([FromBody] UpdateTesterFiles input) return false; } - return await _query.UpdateTest(input.TestID, input.TestName, input.Testers, input.Required); + return await _query.UpdateTest(input.TestID, input.TestName, input.TestDescription, input.Included, input.Testers, input.Required); } [HttpDelete("Delete")] @@ -128,10 +128,18 @@ public async Task DeleteTest([FromBody] int id) return await _query.DeleteTest(id); } - - [HttpPost("Upload")] + + [HttpPost("UploadSource")] + [RequestSizeLimit(33554432)] // 32 MiB max + public async Task FileUploadSource([FromForm] TesterFiles input) + => await FileUpload(input, _config.SourceLocation, true); + + [HttpPost("UploadInclude")] [RequestSizeLimit(33554432)] // 32 MiB max - public async Task FileUpload([FromForm] TesterFiles input) + public async Task FileUploadInclude([FromForm] TesterFiles input) + => await FileUpload(input, _config.IncludeLocation); + + private async Task FileUpload(TesterFiles input, string baseLocation, bool verify = false) { if (input.IsEmpty()) return "WARNING: No files were found, nothing was uploaded!"; @@ -154,9 +162,11 @@ public async Task FileUpload([FromForm] TesterFiles input) for (var i = 0; i < input.Files.Count; i++) { var file = input.Files[i]; - if (file.Length > 8 * 1024 * 1024) continue; + var fileName = input.FileNames[i] ?? Path.GetFileName(file.FileName); - var outputPath = Path.Join(_config.SourceLocation, fileName); + var sourcePath = Path.Join(baseLocation, "" + input.TestID); + Directory.CreateDirectory(sourcePath); + var outputPath = Path.Combine(sourcePath, fileName); var backupPath = outputPath; if (System.IO.File.Exists(backupPath)) { @@ -164,13 +174,13 @@ public async Task FileUpload([FromForm] TesterFiles input) do { counter++; - backupPath = Path.Join(_config.SourceLocation, $"old-{counter}-{fileName}"); + backupPath = Path.Combine(sourcePath, $"old-{counter}-{fileName}"); } while (System.IO.File.Exists(backupPath)); System.IO.File.Move(outputPath, backupPath); output.AppendLine($"WARNING: {fileName} existed already, made a copy at {backupPath}."); } - if (test != null && test.RequiredFiles.Contains(fileName)) + if (verify && test != null && test.RequiredFiles.Contains(fileName)) output.AppendLine( $"WARNING: {fileName} is a required file; it will not be copied during stipulation!"); await using var stream = new FileStream(outputPath, FileMode.Create); diff --git a/Controllers/GoogleAuth.cs b/Controllers/GoogleAuth.cs index 23013c0..1bf879c 100644 --- a/Controllers/GoogleAuth.cs +++ b/Controllers/GoogleAuth.cs @@ -10,9 +10,9 @@ namespace Modulr.Controllers public class GoogleAuth { private readonly ModulrConfig _config; - private readonly MySqlQuery _query; + private readonly SqlQuery _query; - public GoogleAuth(ModulrConfig config, MySqlQuery query) + public GoogleAuth(ModulrConfig config, SqlQuery query) { _config = config; _query = query; diff --git a/Controllers/MySqlQuery.cs b/Controllers/SqlQuery.cs similarity index 85% rename from Controllers/MySqlQuery.cs rename to Controllers/SqlQuery.cs index 441874e..0d633e4 100644 --- a/Controllers/MySqlQuery.cs +++ b/Controllers/SqlQuery.cs @@ -15,7 +15,7 @@ namespace Modulr.Controllers /// /// A class that manages database connections to the Modulr backend. /// - public class MySqlQuery : IDisposable + public class SqlQuery : IDisposable { private IDbConnection Connection { get; } private readonly ModulrConfig _config; @@ -27,17 +27,17 @@ public void Dispose() GC.SuppressFinalize(this); } - ~MySqlQuery() + ~SqlQuery() { Dispose(); } - static MySqlQuery() + static SqlQuery() { DefaultTypeMap.MatchNamesWithUnderscores = true; } - public MySqlQuery(ModulrConfig config) + public SqlQuery(ModulrConfig config) { _config = config; if (config.UseMySql) @@ -58,26 +58,11 @@ public async Task GetTest(int id) if (results == null) return null; var description = results.description; + var included = JsonConvert.DeserializeObject>(results.included); var testers = JsonConvert.DeserializeObject>(results.testers); var required = JsonConvert.DeserializeObject>(results.required); - var included = JsonConvert.DeserializeObject>(results.provided); - return new Stipulatable(results.id, results.name, testers, required, description, included); - } - - /// - /// Add a test to the database with the following parameters. - /// - /// The name of the test. - /// The order of compilation, as well as files that should be included. - /// Files that are requested from the user on the interface. - /// An integer, representing the ID of the Stipulatable. - public async Task AddTest(string name, IEnumerable testers, IEnumerable required) - { - const string commandMySql = "INSERT INTO Modulr.Stipulatables (`name`, testers, required) VALUES (@Name, @Testers, @Required); SELECT LAST_INSERT_ID();"; - var results = await Connection.QuerySingleOrDefaultAsync(ConvertSql(commandMySql), - new {Name = name, Testers = JsonConvert.SerializeObject(testers), Required = JsonConvert.SerializeObject(required)}); - return results; + return new Stipulatable(results.id, results.name, description, included, testers, required); } /// @@ -89,27 +74,31 @@ public async Task AddTest(string name, IEnumerable testers, IEnumer /// A short description of what the test is about. /// Files that are displayed for the user to download. /// An integer, representing the ID of the Stipulatable. - public async Task AddTest(string name, IEnumerable testers, IEnumerable required, string description, IEnumerable included) + public async Task AddTest(string name, string description, IEnumerable included, IEnumerable testers, IEnumerable required) { - const string commandMySql = "INSERT INTO Modulr.Stipulatables (`name`, testers, required, provided, description) VALUES (@Name, @Testers, @Required, @Provided, @Description); SELECT LAST_INSERT_ID();"; + const string commandMySql = "INSERT INTO Modulr.Stipulatables (`name`, testers, required, included, description) VALUES (@Name, @Testers, @Required, @Included, @Description); SELECT LAST_INSERT_ID();"; var results = await Connection.QuerySingleOrDefaultAsync(ConvertSql(commandMySql), - new { Name = name, Testers = JsonConvert.SerializeObject(testers), Required = JsonConvert.SerializeObject(required), Provided = JsonConvert.SerializeObject(included), Description = description }); + new { Name = name, Testers = JsonConvert.SerializeObject(testers), Required = JsonConvert.SerializeObject(required), Included = JsonConvert.SerializeObject(included), Description = description }); return results; } - + /// /// Update a test's information by its ID. /// /// The ID of the test. /// The new name of the test. + /// The description associated with the test + /// The files that are available for the user to download. /// The new compile/include order for the test. - /// The new files + /// The files that are requested from the user. /// Whether a test was updated or not. - public async Task UpdateTest(int id, string name, IEnumerable testers, IEnumerable required) + public async Task UpdateTest(int id, string name, string description, IEnumerable included, IEnumerable testers, IEnumerable required) { - const string commandMySql = "UPDATE Modulr.Stipulatables SET `name` = @Name, testers = @Testers, required = @Required WHERE id = @ID"; + const string commandMySql = "UPDATE Modulr.Stipulatables SET `name` = @Name, `description` = @Description, included = @Included, testers = @Testers, required = @Required WHERE id = @ID"; return await Connection.ExecuteAsync(ConvertSql(commandMySql), new { Name = name, + Description = description, + Included = JsonConvert.SerializeObject(included), Testers = JsonConvert.SerializeObject(testers), Required = JsonConvert.SerializeObject(required), ID = id }) != 0; @@ -207,9 +196,10 @@ public async Task> GetAllTests() const string commandMySql = "SELECT * FROM Modulr.Stipulatables"; return (await Connection.QueryAsync(ConvertSql(commandMySql))).ToList().Select(o => { + var included = JsonConvert.DeserializeObject>(o.included); var testers = JsonConvert.DeserializeObject>(o.testers); var required = JsonConvert.DeserializeObject>(o.required); - return new Stipulatable(o.id, o.name, testers, required); + return new Stipulatable(o.id, o.name, o.description, included, testers, required); }).ToList(); } diff --git a/Controllers/TestController.cs b/Controllers/TestController.cs index ea1b405..e85b699 100644 --- a/Controllers/TestController.cs +++ b/Controllers/TestController.cs @@ -13,11 +13,11 @@ namespace Modulr.Controllers public class TestReceivedController : ControllerBase { private readonly JavaUtils _java; - private readonly MySqlQuery _query; + private readonly SqlQuery _query; private readonly GoogleAuth _auth; private readonly ModulrConfig _config; - public TestReceivedController(JavaUtils java, MySqlQuery query, GoogleAuth auth, ModulrConfig config) + public TestReceivedController(JavaUtils java, SqlQuery query, GoogleAuth auth, ModulrConfig config) { _java = java; _query = query; @@ -90,22 +90,23 @@ public async Task FileUpload([FromForm] TesterFiles input) return Fail(404, "Failed to find Test ID!"); var path = _java.GetDummyFolder(); - var srcPath = Path.Join(path, "source"); - Directory.CreateDirectory(srcPath); + var inputPath = Path.Join(path, "source"); + Directory.CreateDirectory(inputPath); for (var i = 0; i < input.Files.Count; i++) { var file = input.Files[i]; if (file.Length > 8 * 1024 * 1024) continue; var fileName = input.FileNames[i]; - var outputPath = Path.Join(srcPath, fileName); + var outputPath = Path.Combine(inputPath, fileName); await using var stream = new FileStream(outputPath, FileMode.Create); await file.CopyToAsync(stream); } foreach (var file in test.TesterFiles) { - var tester = Path.Join(_config.SourceLocation, file); - var testerOut = Path.Join(srcPath, file); + var sourcePath = Path.Join(_config.SourceLocation, "" + input.TestID); + var tester = Path.Combine(sourcePath, file); + var testerOut = Path.Combine(inputPath, file); if (!System.IO.File.Exists(tester) || System.IO.File.Exists(testerOut)) continue; System.IO.File.Copy(tester, testerOut); @@ -118,7 +119,7 @@ public async Task FileUpload([FromForm] TesterFiles input) } /// - /// Allow a user to download a provided file. + /// Allow a user to download an included file. /// /// A file requested from the user. /// Data representing the file, or an error message. diff --git a/Controllers/UserController.cs b/Controllers/UserController.cs index adc9185..649e397 100644 --- a/Controllers/UserController.cs +++ b/Controllers/UserController.cs @@ -9,10 +9,10 @@ namespace Modulr.Controllers [Route("/Users")] public class UserController : ControllerBase { - private readonly MySqlQuery _query; + private readonly SqlQuery _query; private readonly GoogleAuth _auth; - public UserController(MySqlQuery query, GoogleAuth auth) + public UserController(SqlQuery query, GoogleAuth auth) { _query = query; _auth = auth; diff --git a/CreateDatabase.mssql.sql b/CreateDatabase.mssql.sql new file mode 100644 index 0000000..4ac3aae --- /dev/null +++ b/CreateDatabase.mssql.sql @@ -0,0 +1,77 @@ +/* I just used the scripting option in SSMS, so some of this might be wrong. */ + +USE [master] +GO + +SET ANSI_NULLS ON +GO + +SET QUOTED_IDENTIFIER ON +GO + +CREATE DATABASE [Modulr] + CONTAINMENT = NONE +GO + +USE [Modulr] +GO + +CREATE TABLE [Tester].[Users] +( + [id] [int] IDENTITY (1,1) NOT NULL, + [google_id] [nvarchar](64) NOT NULL, + [username] [nvarchar](32) NOT NULL, + [name] [nvarchar](32) NOT NULL, + [email] [nvarchar](128) NOT NULL, + [tests_remaining] [int] NOT NULL, + [tests_timeout] [datetimeoffset](7) NOT NULL, + [role] [tinyint] NOT NULL, + CONSTRAINT [Users_pk] PRIMARY KEY NONCLUSTERED + ( + [id] ASC + ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] +) ON [PRIMARY] +GO + +ALTER TABLE [Tester].[Users] + ADD DEFAULT ((3)) FOR [tests_remaining] +GO + +ALTER TABLE [Tester].[Users] + ADD DEFAULT (sysdatetimeoffset()) FOR [tests_timeout] +GO + +ALTER TABLE [Tester].[Users] + ADD DEFAULT ((0)) FOR [role] +GO + +CREATE TABLE [Tester].[Stipulatables] +( + [id] [int] IDENTITY (1,1) NOT NULL, + [name] [nvarchar](255) NOT NULL, + [testers] [nvarchar](4000) NOT NULL, + [required] [nvarchar](4000) NULL, + [included] [nvarchar](4000) NULL, + [description] [nvarchar](2048) NOT NULL, + CONSTRAINT [Stipulatables_pk] PRIMARY KEY NONCLUSTERED + ( + [id] ASC + ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] +) ON [PRIMARY] +GO + +ALTER TABLE [Tester].[Stipulatables] + ADD DEFAULT ('[]') FOR [testers] +GO + +ALTER TABLE [Tester].[Stipulatables] + ADD DEFAULT ('[]') FOR [required] +GO + +ALTER TABLE [Tester].[Stipulatables] + ADD DEFAULT ('[]') FOR [included] +GO + +ALTER TABLE [Tester].[Stipulatables] + ADD DEFAULT ('') FOR [description] +GO diff --git a/CreateDatabase.sql b/CreateDatabase.mysql.sql similarity index 95% rename from CreateDatabase.sql rename to CreateDatabase.mysql.sql index b407259..509e53c 100644 --- a/CreateDatabase.sql +++ b/CreateDatabase.mysql.sql @@ -6,7 +6,7 @@ CREATE TABLE IF NOT EXISTS Modulr.Stipulatables name VARCHAR(255) NOT NULL, testers JSON DEFAULT '[]' NOT NULL, required JSON DEFAULT '[]' NOT NULL, - provided JSON default '[]' null, + included JSON default '[]' null, description VARCHAR(2048) default '' not null, PRIMARY KEY (id) diff --git a/Models/AdminStipulatable.cs b/Models/AdminStipulatable.cs index 947de06..b9d4726 100644 --- a/Models/AdminStipulatable.cs +++ b/Models/AdminStipulatable.cs @@ -12,18 +12,25 @@ public class TesterFile public class AdminStipulatable { - public int ID { get; set; } - public string Name { get; set; } - public bool Valid { get; set; } + public int ID { get; } + public string Name { get; } + public string Description { get; } + public bool Valid { get; private set; } + public IReadOnlyCollection IncludedFiles => _includedFiles; public IReadOnlyCollection TesterFiles => _testerFiles; public IReadOnlyCollection RequiredFiles => _requiredFiles; private readonly List _testerFiles = new(); + private readonly List _includedFiles = new(); private readonly List _requiredFiles = new(); public AdminStipulatable(Stipulatable sp) { ID = sp.ID; + Description = sp.Description; Name = sp.Name; + + foreach(var included in sp.IncludedFiles) + _includedFiles.Add(new TesterFile {File = included}); foreach(var tester in sp.TesterFiles) _testerFiles.Add(new TesterFile {File = tester}); foreach(var required in sp.RequiredFiles) @@ -33,14 +40,25 @@ public AdminStipulatable(Stipulatable sp) public bool Validate(ModulrConfig config) { var success = true; + + var includePath = Path.Join(config.IncludeLocation, "" + ID); + var sourcePath = Path.Join(config.SourceLocation, "" + ID); foreach (var tester in _testerFiles) { - if (!_requiredFiles.Contains(tester.File) && !File.Exists(Path.Join(config.SourceLocation, tester.File))) + if (!_requiredFiles.Contains(tester.File) && !File.Exists(Path.Join(sourcePath, tester.File))) success = false; else tester.Exists = true; } + + foreach (var include in _includedFiles) + { + if (!File.Exists(Path.Join(includePath, include.File))) + success = false; + else + include.Exists = true; + } Valid = success; return success; diff --git a/Models/Stipulatable.cs b/Models/Stipulatable.cs index 7fbd977..d9da173 100644 --- a/Models/Stipulatable.cs +++ b/Models/Stipulatable.cs @@ -17,18 +17,7 @@ public class Stipulatable private readonly List _includedFiles = new(); [JsonConstructor] - public Stipulatable(int id, string name, IEnumerable testerFiles, IEnumerable requiredFiles) - { - ID = id; - Name = name; - foreach(var file in testerFiles) - _testerFiles.Add(Path.GetFileName(file)); - foreach(var file in requiredFiles) - _requiredFiles.Add(Path.GetFileName(file)); - } - - [JsonConstructor] - public Stipulatable(int id, string name, IEnumerable testerFiles, IEnumerable requiredFiles, string description, IEnumerable includedFiles) + public Stipulatable(int id, string name, string description, IEnumerable includedFiles, IEnumerable testerFiles, IEnumerable requiredFiles) { ID = id; Name = name; diff --git a/Models/UpdateTesterFiles.cs b/Models/UpdateTesterFiles.cs index 9105606..bbcbf58 100644 --- a/Models/UpdateTesterFiles.cs +++ b/Models/UpdateTesterFiles.cs @@ -8,13 +8,17 @@ public class UpdateTesterFiles public string AuthToken { get; set; } public int TestID { get; set; } public string TestName { get; set; } + public string TestDescription { get; set; } + public List Included { get; set; } public List Testers { get; set; } public List Required { get; set; } public bool IsLikelyValid() { - if (TestName == null || Testers == null || Required == null) + if (TestName == null || Included == null || Testers == null || Required == null) return false; + TestDescription ??= ""; + Included.ForEach(o => Path.GetFileName(o)); Testers.ForEach(o => Path.GetFileName(o)); Required.ForEach(o => Path.GetFileName(o)); return true; diff --git a/RestrictedViews/js/admin.js b/RestrictedViews/js/admin.js index a208f99..5d8c156 100644 --- a/RestrictedViews/js/admin.js +++ b/RestrictedViews/js/admin.js @@ -58,13 +58,16 @@ function bindUploads() { function clearInputs() { modified = false; document.getElementById("manager").classList.add("hidden"); + document.getElementById("included").innerHTML = ""; document.getElementById("testers").innerHTML = ""; document.getElementById("required").innerHTML = ""; } function generateFileExplorer(formatted) { let fileManager = document.getElementById("manager"); - fileManager.getElementsByTagName("input")[0].value = formatted.name; + fileManager.querySelector("input").value = formatted.name; + fileManager.querySelector("textarea").value = formatted.description; + generateInputs(formatted.includedFiles, document.getElementById("included")); generateInputs(formatted.testerFiles, document.getElementById("testers")); generateInputs(formatted.requiredFiles, document.getElementById("required")); fileManager.classList.remove("hidden"); @@ -95,7 +98,7 @@ function generateInputs(names, inputArea) { let statusChar = document.createElement("span"); if(!file.exists) { label.classList.add("danger"); - statusChar.innerHTML = "❌"; + statusChar.innerHTML = "✘"; } else { label.classList.add("success"); @@ -164,7 +167,7 @@ function generateList(tests) { testBtn.name = test.id; if(!test.valid) { testBtn.classList.add("danger"); - testBtn.innerHTML += " ❌"; + testBtn.innerHTML += " ✘"; } else { testBtn.classList.add("normal"); @@ -273,9 +276,12 @@ async function submit() { await updateStipulatable(message); else await addStipulatable(message); - message.push("---\nNow uploading files, if any..."); + message.push("---\nNow uploading source files, if any..."); triggerPopup("Updating...", message.join('\n')); - await uploadFiles(message); + await uploadSourceFiles(message); + message.push("---\nNow uploading included files, if any..."); + triggerPopup("Updating...", message.join('\n')); + await uploadIncludeFiles(message); triggerPopup("Finished updating!", message.join('\n')); await getAllTestsAdmin(); await getTestAdmin(currentTest); @@ -286,7 +292,37 @@ async function submit() { } } -async function uploadFiles(message) { +async function uploadSourceFiles(message) { + let data = new FormData(); + + data.append('AuthToken', getLoginToken()); + data.append('ConnectionID', "no"); + if (currentTest == null) + currentTest = 0; + data.append('TestID', JSON.stringify(currentTest)); + let fileInputs = document.querySelectorAll("input[type='file']"); + for (let input of fileInputs) { + if (input.files.length === 0) + continue; + data.append('FileNames', input.parentElement.querySelector("input:not([type])").value); + data.append('Files', input.files[0]); + data.append('IsTester', JSON.stringify(false)); + } + + let response = await fetch("/Admin/Tester/UploadSource", { + method: "POST", + body: data + }); + if (response.status >= 400 && response.status < 600) + handleErrors(response.status, null) + else { + let data = await response.text(); + let text = data.toString(); + message.push(text); + } +} + +async function uploadIncludeFiles(message) { let data = new FormData(); data.append('AuthToken', getLoginToken()); @@ -303,7 +339,7 @@ async function uploadFiles(message) { data.append('IsTester', JSON.stringify(false)); } - let response = await fetch("/Admin/Tester/Upload", { + let response = await fetch("/Admin/Tester/UploadInclude", { method: "POST", body: data }); @@ -327,10 +363,12 @@ async function updateStipulatable(message) { "AuthToken": getLoginToken(), "TestID": currentTest, "TestName": document.querySelector("input[autocorrect]").value, + "TestDescription": document.querySelector("textarea").value, // William, what the heck? (Gets all children, extracts name from each input element) "Required": [...document.getElementById("required").children].map(o => o.getElementsByTagName("input")[0].value), // ... (Get all children under the testers, sort by flexbox order for draggables, then grab the names of each tester) - "Testers": [...document.getElementById("testers").children].sort((p, q) => parseInt(p.style.order) - parseInt(q.style.order)).map(o => o.getElementsByTagName("input")[1].value) + "Testers": [...document.getElementById("testers").children].sort((p, q) => parseInt(p.style.order) - parseInt(q.style.order)).map(o => o.getElementsByTagName("input")[1].value), + "Included": [...document.getElementById("included").children].sort((p, q) => parseInt(p.style.order) - parseInt(q.style.order)).map(o => o.getElementsByTagName("input")[1].value) }) }); if (response.status >= 400 && response.status < 600) @@ -360,8 +398,10 @@ async function addStipulatable(message) { body: JSON.stringify({ "AuthToken": getLoginToken(), "TestName": document.querySelector("input[autocorrect]").value, + "TestDescription": document.querySelector("textarea").value, "Required": [...document.getElementById("required").children].map(o => o.getElementsByTagName("input")[0].value), - "Testers": [...document.getElementById("testers").children].sort((p, q) => parseInt(p.style.order) - parseInt(q.style.order)).map(o => o.getElementsByTagName("input")[1].value) + "Testers": [...document.getElementById("testers").children].sort((p, q) => parseInt(p.style.order) - parseInt(q.style.order)).map(o => o.getElementsByTagName("input")[1].value), + "Included": [...document.getElementById("included").children].sort((p, q) => parseInt(p.style.order) - parseInt(q.style.order)).map(o => o.getElementsByTagName("input")[1].value) }) }) if (response.status >= 400 && response.status < 600) diff --git a/RestrictedViews/views/admin.html b/RestrictedViews/views/admin.html index 960b095..df12ef4 100644 --- a/RestrictedViews/views/admin.html +++ b/RestrictedViews/views/admin.html @@ -20,14 +20,26 @@

Tests Available

File Manager