diff --git a/src/main/java/edu/byu/cs/controller/ConfigController.java b/src/main/java/edu/byu/cs/controller/ConfigController.java new file mode 100644 index 000000000..b1adea4eb --- /dev/null +++ b/src/main/java/edu/byu/cs/controller/ConfigController.java @@ -0,0 +1,80 @@ +package edu.byu.cs.controller; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import edu.byu.cs.dataAccess.ConfigurationDao; +import edu.byu.cs.dataAccess.DaoService; +import edu.byu.cs.dataAccess.DataAccessException; +import edu.byu.cs.model.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spark.Route; + +import java.util.ArrayList; + +public class ConfigController { + + private static final Logger LOGGER = LoggerFactory.getLogger(SubmissionController.class); + + private static void logConfigChange(String changeMessage, String adminNetId) { + LOGGER.info("[CONFIG] Admin %s has %s".formatted(adminNetId, changeMessage)); + } + + public static final Route getConfigAdmin = (req, res) -> { + JsonObject response = getPublicConfig(); + + res.status(200); + return response.toString(); + }; + + public static final Route getConfigStudent = (req, res) -> { + String response = getPublicConfig().toString(); + + res.status(200); + return response; + }; + + private static JsonObject getPublicConfig() throws DataAccessException { + ConfigurationDao dao = DaoService.getConfigurationDao(); + + JsonObject response = new JsonObject(); + + response.addProperty("bannerMessage", dao.getConfiguration(ConfigurationDao.Configuration.BANNER_MESSAGE, String.class)); + response.addProperty("phases", dao.getConfiguration(ConfigurationDao.Configuration.STUDENT_SUBMISSIONS_ENABLED, String.class)); + + return response; + } + + public static final Route updateLivePhases = (req, res) -> { + ConfigurationDao dao = DaoService.getConfigurationDao(); + + JsonObject jsonObject = new Gson().fromJson(req.body(), JsonObject.class); + ArrayList phasesArray = new Gson().fromJson(jsonObject.get("phases"), ArrayList.class); + + dao.setConfiguration(ConfigurationDao.Configuration.STUDENT_SUBMISSIONS_ENABLED, phasesArray, ArrayList.class); + + User user = req.session().attribute("user"); + logConfigChange("set the following phases as live: %s".formatted(phasesArray), user.netId()); + + res.status(200); + return ""; + }; + + public static final Route updateBannerMessage = (req, res) -> { + ConfigurationDao dao = DaoService.getConfigurationDao(); + + JsonObject jsonObject = new Gson().fromJson(req.body(), JsonObject.class); + String message = new Gson().fromJson(jsonObject.get("bannerMessage"), String.class); + dao.setConfiguration(ConfigurationDao.Configuration.BANNER_MESSAGE, message, String.class); + + User user = req.session().attribute("user"); + if (message.isEmpty()) { + logConfigChange("cleared the banner message", user.netId()); + } else { + logConfigChange("set the banner message to: '%s'".formatted(message), user.netId()); + } + + res.status(200); + return ""; + }; +} diff --git a/src/main/java/edu/byu/cs/controller/SubmissionController.java b/src/main/java/edu/byu/cs/controller/SubmissionController.java index 226362c44..03fe29375 100644 --- a/src/main/java/edu/byu/cs/controller/SubmissionController.java +++ b/src/main/java/edu/byu/cs/controller/SubmissionController.java @@ -36,12 +36,8 @@ public class SubmissionController { User user = req.session().attribute("user"); - Boolean submissionsEnabled = getSubmissionsEnabledConfig(); - if (submissionsEnabled == null) return null; - - if (!submissionsEnabled) { - halt(400, "Student submission is disabled"); - return null; + if (!phaseIsEnabled(request.phase())) { + halt(400, "Student submission is disabled for " + request.phase()); } updateRepoFromCanvas(user, req); @@ -56,11 +52,27 @@ public class SubmissionController { return ""; }; + private static boolean phaseIsEnabled(Phase phase) { + boolean phaseEnabled; + + try { + phaseEnabled = DaoService.getConfigurationDao() + .getConfiguration(ConfigurationDao.Configuration.STUDENT_SUBMISSIONS_ENABLED, String.class) + .contains(phase.toString()); + } catch (DataAccessException e) { + LOGGER.error("Error getting configuration for live phase", e); + halt(500); + return false; + } + + return phaseEnabled; + } + private static Boolean getSubmissionsEnabledConfig() { boolean submissionsEnabled; try { submissionsEnabled = DaoService.getConfigurationDao().getConfiguration( - ConfigurationDao.Configuration.STUDENT_SUBMISSION_ENABLED, + ConfigurationDao.Configuration.STUDENT_SUBMISSIONS_ENABLED, Boolean.class); } catch (Exception e) { LOGGER.error("Error getting configuration", e); diff --git a/src/main/java/edu/byu/cs/dataAccess/ConfigREADME.md b/src/main/java/edu/byu/cs/dataAccess/ConfigREADME.md new file mode 100644 index 000000000..0aa809b0e --- /dev/null +++ b/src/main/java/edu/byu/cs/dataAccess/ConfigREADME.md @@ -0,0 +1,8 @@ +# Autograder Config Info + +The autograder has a bunch of settings that change from time to time, but need to persist across reboots. For instance, whether a phase is enabled for student submissions, or the Canvas Course ID. Thats where the Autograder Config system comes in. + +The `Configuration` table in the database is a set of key value pairs. This gives us a lot of flexibility, but also the need to define how it will be used. That is what this file is for. + +### Live Phases +The `STUDENT_SUBMISSION_ENABLED` Configuration enum has a value of an array of phases. Each phase in the array is enabled, the rest are not. \ No newline at end of file diff --git a/src/main/java/edu/byu/cs/dataAccess/ConfigurationDao.java b/src/main/java/edu/byu/cs/dataAccess/ConfigurationDao.java index b215464f9..883f53076 100644 --- a/src/main/java/edu/byu/cs/dataAccess/ConfigurationDao.java +++ b/src/main/java/edu/byu/cs/dataAccess/ConfigurationDao.java @@ -5,6 +5,7 @@ public interface ConfigurationDao { T getConfiguration(Configuration key, Class type) throws DataAccessException; enum Configuration { - STUDENT_SUBMISSION_ENABLED + STUDENT_SUBMISSIONS_ENABLED, + BANNER_MESSAGE } } diff --git a/src/main/java/edu/byu/cs/dataAccess/sql/ConfigurationSqlDao.java b/src/main/java/edu/byu/cs/dataAccess/sql/ConfigurationSqlDao.java index 4451a9a99..6d36e628d 100644 --- a/src/main/java/edu/byu/cs/dataAccess/sql/ConfigurationSqlDao.java +++ b/src/main/java/edu/byu/cs/dataAccess/sql/ConfigurationSqlDao.java @@ -29,7 +29,10 @@ public ConfigurationSqlDao() { @Override public void setConfiguration(Configuration key, T value, Class type) throws DataAccessException { try (var connection = SqlDb.getConnection()) { - var statement = connection.prepareStatement("INSERT INTO configuration (config_key, value) VALUES (?, ?)"); + var statement = connection.prepareStatement(""" + INSERT INTO configuration (config_key, value) VALUES (?, ?) + ON DUPLICATE KEY UPDATE value = VALUES(value); + """); statement.setString(1, key.toString()); statement.setString(2, value.toString()); statement.executeUpdate(); diff --git a/src/main/java/edu/byu/cs/server/Server.java b/src/main/java/edu/byu/cs/server/Server.java index 7bf3df684..dbc03757b 100644 --- a/src/main/java/edu/byu/cs/server/Server.java +++ b/src/main/java/edu/byu/cs/server/Server.java @@ -19,6 +19,7 @@ import static edu.byu.cs.controller.AdminController.*; import static edu.byu.cs.controller.AuthController.*; import static edu.byu.cs.controller.CasController.*; +import static edu.byu.cs.controller.ConfigController.*; import static edu.byu.cs.controller.SubmissionController.*; import static spark.Spark.*; @@ -65,6 +66,8 @@ public static int setupEndpoints(int port) { get("/me", meGet); + get("/config", getConfigStudent); + path("/admin", () -> { before("/*", (req, res) -> { if (!req.requestMethod().equals("OPTIONS")) @@ -100,6 +103,13 @@ public static int setupEndpoints(int port) { get("/honorChecker/zip/:section", honorCheckerZipGet); get("/sections", sectionsGet); + + path("/config", () -> { + get("", getConfigAdmin); + + post("/phases", updateLivePhases); + post("/banner", updateBannerMessage); + }); }); }); diff --git a/src/main/resources/frontend/src/App.vue b/src/main/resources/frontend/src/App.vue index 56c34f5be..ccbe7b4f3 100644 --- a/src/main/resources/frontend/src/App.vue +++ b/src/main/resources/frontend/src/App.vue @@ -1,11 +1,12 @@ \ No newline at end of file diff --git a/src/main/resources/frontend/src/views/AdminView/QueueStatus.vue b/src/main/resources/frontend/src/views/AdminView/QueueStatus.vue index 7dfacb643..c799e2198 100644 --- a/src/main/resources/frontend/src/views/AdminView/QueueStatus.vue +++ b/src/main/resources/frontend/src/views/AdminView/QueueStatus.vue @@ -66,14 +66,17 @@ const reRunQueue = async () => {