From 9c70b9a0d5df4ba09aa2c249cfb7def1e03fc016 Mon Sep 17 00:00:00 2001
From: mewilker
Date: Fri, 22 May 2026 09:27:12 -0600
Subject: [PATCH 30/34] add coverage to ConfigurationDaoTest
---
.../java/edu/byu/cs/dataAccess/base/ConfigurationDaoTest.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/test/java/edu/byu/cs/dataAccess/base/ConfigurationDaoTest.java b/src/test/java/edu/byu/cs/dataAccess/base/ConfigurationDaoTest.java
index 8c6fe4526..195f2cf8f 100644
--- a/src/test/java/edu/byu/cs/dataAccess/base/ConfigurationDaoTest.java
+++ b/src/test/java/edu/byu/cs/dataAccess/base/ConfigurationDaoTest.java
@@ -96,8 +96,10 @@ Object generateDummyDataForKey(ConfigurationDao.Configuration key){
-> random.nextInt(1000000,2000000);
case MAX_ERROR_OUTPUT_CHARS -> random.nextInt(1, 10000);
case GRADER_SHUTDOWN_WARNING_MILLISECONDS -> 86400000; //24 hours in milliseconds
- case GIT_COMMIT_PENALTY, PER_DAY_LATE_PENALTY -> random.nextFloat(0, 1);
+ case GIT_COMMIT_PENALTY, PER_DAY_LATE_PENALTY, COVERAGE_PERCENT, EXTRA_COVERAGE_PERCENT
+ -> random.nextFloat(0, 1);
case SLACK_LINK, BANNER_LINK, BANNER_COLOR, BANNER_MESSAGE-> "https://slack.com";
+ case COVERAGE_TYPE -> "LINE";
case STUDENT_SUBMISSIONS_ENABLED -> random.nextBoolean();
case GRADER_SHUTDOWN_DATE, HOLIDAY_LIST, BANNER_EXPIRATION-> Instant.now();
};
From 3d9b0f29e3eb43d2a86080e9a56c50326064a2db Mon Sep 17 00:00:00 2001
From: mewilker
Date: Fri, 22 May 2026 15:35:17 +0000
Subject: [PATCH 31/34] update line endings from crlf to lf
---
.../cs/autograder/test/CoverageAnalyzer.java | 202 ++++----
.../java/edu/byu/cs/model/PrivateConfig.java | 120 ++---
.../request/ConfigPenaltyUpdateRequest.java | 62 +--
.../components/config/PenaltyConfigEditor.vue | 290 +++++------
.../src/network/ServerCommunicator.ts | 462 +++++++++---------
.../frontend/src/services/configService.ts | 176 +++----
.../resources/frontend/src/stores/config.ts | 256 +++++-----
.../src/views/AdminView/ConfigView.vue | 456 ++++++++---------
8 files changed, 1012 insertions(+), 1012 deletions(-)
diff --git a/src/main/java/edu/byu/cs/autograder/test/CoverageAnalyzer.java b/src/main/java/edu/byu/cs/autograder/test/CoverageAnalyzer.java
index 92420fe50..7c1c72073 100644
--- a/src/main/java/edu/byu/cs/autograder/test/CoverageAnalyzer.java
+++ b/src/main/java/edu/byu/cs/autograder/test/CoverageAnalyzer.java
@@ -1,101 +1,101 @@
-package edu.byu.cs.autograder.test;
-
-import edu.byu.cs.autograder.GradingException;
-import edu.byu.cs.dataAccess.DaoService;
-import edu.byu.cs.dataAccess.DataAccessException;
-import edu.byu.cs.dataAccess.daoInterface.ConfigurationDao;
-import edu.byu.cs.model.ClassCoverageAnalysis;
-import edu.byu.cs.model.CoverageAnalysis;
-import edu.byu.cs.util.FileUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.HashSet;
-
-/**
- * Parses the code coverage output stored in a JaCoCo CSV file into a
- * {@link CoverageAnalysis} containing the appropriate coverage results.
- *
- * Supports Branch and Line coverage. Default coverage is Line Coverage.
- */
-public class CoverageAnalyzer {
- private final String coverageMissedHeader;
- private final String coverageCoveredHeader;
- private static final String PACKAGE_HEADER = "PACKAGE";
- private static final String CLASS_HEADER = "CLASS";
-
- private static final Logger LOGGER = LoggerFactory.getLogger(CoverageAnalyzer.class);
-
- public CoverageAnalyzer(){
- String coverageTested = "LINE";
- try{
- String config = DaoService.getConfigurationDao().getConfiguration(ConfigurationDao.Configuration.COVERAGE_TYPE, String.class);
- if (config.equals("BRANCH")){ //ensure the value is valid
- coverageTested = config;
- }
- } catch (DataAccessException e) {
- LOGGER.error("Could not get coverage type, using line coverage", e);
- }
- coverageMissedHeader = coverageTested + "_MISSED";
- coverageCoveredHeader = coverageTested + "_COVERED";
- }
-
- /**
- * Parses the output of a JaCoCo CSV file, if it exists,
- * and returns the coverage results as a {@link CoverageAnalysis}
- *
- * @param jacocoCsvOutput the file storing the coverage results from running the tests
- * @return a {@link CoverageAnalysis} containing the code coverage results
- * @throws GradingException if an issue arose parsing the coverage results
- */
- public CoverageAnalysis parse(File jacocoCsvOutput) throws GradingException {
- Collection classAnalyses = new HashSet<>();
- if(!jacocoCsvOutput.exists()) {
- return new CoverageAnalysis(classAnalyses);
- }
-
- String csv = FileUtils.readStringFromFile(jacocoCsvOutput);
- String[] lines = csv.split("\n");
-
- Integer classHeader = null;
- Integer packageHeader = null;
- Integer coverageMissedHeader = null;
- Integer coverageCoveredHeader = null;
-
- String[] headers = lines[0].split(",");
- for(int i = 0; i < headers.length; i++) {
- String s = headers[i].trim();
- switch (s) {
- case CLASS_HEADER -> classHeader = i;
- case PACKAGE_HEADER -> packageHeader = i;
- default -> {
- if (s.equals(this.coverageMissedHeader)){
- coverageMissedHeader = i;
- }
- else if (s.equals(this.coverageCoveredHeader)) {
- coverageCoveredHeader = i;
- }
- }
- }
- }
-
- if(classHeader == null || packageHeader == null || coverageMissedHeader == null || coverageCoveredHeader == null) {
- throw new GradingException("Error parsing coverage results");
- }
-
- for(int i = 1; i < lines.length; i++) {
- String[] values = lines[i].split(",");
-
- String className = values[classHeader].trim();
- String packageName = values[packageHeader].trim();
- int coverageMissed = Integer.parseInt(values[coverageMissedHeader].trim());
- int coverageCovered = Integer.parseInt(values[coverageCoveredHeader].trim());
-
- classAnalyses.add(new ClassCoverageAnalysis(className, packageName, coverageCovered, coverageMissed));
- }
-
- return new CoverageAnalysis(classAnalyses);
- }
-}
+package edu.byu.cs.autograder.test;
+
+import edu.byu.cs.autograder.GradingException;
+import edu.byu.cs.dataAccess.DaoService;
+import edu.byu.cs.dataAccess.DataAccessException;
+import edu.byu.cs.dataAccess.daoInterface.ConfigurationDao;
+import edu.byu.cs.model.ClassCoverageAnalysis;
+import edu.byu.cs.model.CoverageAnalysis;
+import edu.byu.cs.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * Parses the code coverage output stored in a JaCoCo CSV file into a
+ * {@link CoverageAnalysis} containing the appropriate coverage results.
+ *
+ * Supports Branch and Line coverage. Default coverage is Line Coverage.
+ */
+public class CoverageAnalyzer {
+ private final String coverageMissedHeader;
+ private final String coverageCoveredHeader;
+ private static final String PACKAGE_HEADER = "PACKAGE";
+ private static final String CLASS_HEADER = "CLASS";
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(CoverageAnalyzer.class);
+
+ public CoverageAnalyzer(){
+ String coverageTested = "LINE";
+ try{
+ String config = DaoService.getConfigurationDao().getConfiguration(ConfigurationDao.Configuration.COVERAGE_TYPE, String.class);
+ if (config.equals("BRANCH")){ //ensure the value is valid
+ coverageTested = config;
+ }
+ } catch (DataAccessException e) {
+ LOGGER.error("Could not get coverage type, using line coverage", e);
+ }
+ coverageMissedHeader = coverageTested + "_MISSED";
+ coverageCoveredHeader = coverageTested + "_COVERED";
+ }
+
+ /**
+ * Parses the output of a JaCoCo CSV file, if it exists,
+ * and returns the coverage results as a {@link CoverageAnalysis}
+ *
+ * @param jacocoCsvOutput the file storing the coverage results from running the tests
+ * @return a {@link CoverageAnalysis} containing the code coverage results
+ * @throws GradingException if an issue arose parsing the coverage results
+ */
+ public CoverageAnalysis parse(File jacocoCsvOutput) throws GradingException {
+ Collection classAnalyses = new HashSet<>();
+ if(!jacocoCsvOutput.exists()) {
+ return new CoverageAnalysis(classAnalyses);
+ }
+
+ String csv = FileUtils.readStringFromFile(jacocoCsvOutput);
+ String[] lines = csv.split("\n");
+
+ Integer classHeader = null;
+ Integer packageHeader = null;
+ Integer coverageMissedHeader = null;
+ Integer coverageCoveredHeader = null;
+
+ String[] headers = lines[0].split(",");
+ for(int i = 0; i < headers.length; i++) {
+ String s = headers[i].trim();
+ switch (s) {
+ case CLASS_HEADER -> classHeader = i;
+ case PACKAGE_HEADER -> packageHeader = i;
+ default -> {
+ if (s.equals(this.coverageMissedHeader)){
+ coverageMissedHeader = i;
+ }
+ else if (s.equals(this.coverageCoveredHeader)) {
+ coverageCoveredHeader = i;
+ }
+ }
+ }
+ }
+
+ if(classHeader == null || packageHeader == null || coverageMissedHeader == null || coverageCoveredHeader == null) {
+ throw new GradingException("Error parsing coverage results");
+ }
+
+ for(int i = 1; i < lines.length; i++) {
+ String[] values = lines[i].split(",");
+
+ String className = values[classHeader].trim();
+ String packageName = values[packageHeader].trim();
+ int coverageMissed = Integer.parseInt(values[coverageMissedHeader].trim());
+ int coverageCovered = Integer.parseInt(values[coverageCoveredHeader].trim());
+
+ classAnalyses.add(new ClassCoverageAnalysis(className, packageName, coverageCovered, coverageMissed));
+ }
+
+ return new CoverageAnalysis(classAnalyses);
+ }
+}
diff --git a/src/main/java/edu/byu/cs/model/PrivateConfig.java b/src/main/java/edu/byu/cs/model/PrivateConfig.java
index f3b8c1375..114cdd9d5 100644
--- a/src/main/java/edu/byu/cs/model/PrivateConfig.java
+++ b/src/main/java/edu/byu/cs/model/PrivateConfig.java
@@ -1,60 +1,60 @@
-package edu.byu.cs.model;
-
-import edu.byu.cs.model.request.ConfigPenaltyUpdateRequest;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * Represents the configuration values that only admins should see
- *
- * @param penalty A {@link PenaltyConfig} containing information about penalties
- * students may receive
- * @param courseNumber The number assigned to the course on Canvas
- * @param assignments A list containing information about an assignment in
- * Canvas for each {@link AssignmentConfig}.
- * @param holidays A list of holidays the AutoGrader doesn't count towards the late penalty
- */
-public record PrivateConfig(
- PenaltyConfig penalty,
- int courseNumber,
- List assignments,
- String[] holidays
-) {
- /**
- * Represents the configuration information needed for penalties students may receive
- *
- * @param perDayLatePenalty The penalty to apply on a submission for everyday late
- * @param gitCommitPenalty the penalty to apply on a submission for missing commits
- * @param maxLateDaysPenalized the maximum number of days the late penalty should apply
- * @param linesChangedPerCommit the minimum number of lines needed for a commit to count
- * @param clockForgivenessMinutes the number of minutes a commit can be authored
- * past the time of submission
- * @param coveragePercent the percentage of code coverage expected for a submission to receive full credit
- * @param extraCoveragePercent the percentage of code coverage expected for a submission to receive extra credit
- * @param coverageType the type of code coverage to use when calculating penalties (branch or line)
- */
- public record PenaltyConfig(
- float perDayLatePenalty,
- float gitCommitPenalty,
- int maxLateDaysPenalized,
- int linesChangedPerCommit,
- int clockForgivenessMinutes,
- float coveragePercent,
- float extraCoveragePercent,
- ConfigPenaltyUpdateRequest.CoverageType coverageType
- ){ }
-
- /**
- * Represents the configuration information for a Canvas assignment
- *
- * @param phase the phase associated with the Canvas assignment
- * @param assignmentId the id of the assignment in Canvas
- * @param rubricItems the rubric items for the Canvas assignment
- */
- public record AssignmentConfig(
- Phase phase,
- int assignmentId,
- Map rubricItems
- ){ }
-}
+package edu.byu.cs.model;
+
+import edu.byu.cs.model.request.ConfigPenaltyUpdateRequest;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents the configuration values that only admins should see
+ *
+ * @param penalty A {@link PenaltyConfig} containing information about penalties
+ * students may receive
+ * @param courseNumber The number assigned to the course on Canvas
+ * @param assignments A list containing information about an assignment in
+ * Canvas for each {@link AssignmentConfig}.
+ * @param holidays A list of holidays the AutoGrader doesn't count towards the late penalty
+ */
+public record PrivateConfig(
+ PenaltyConfig penalty,
+ int courseNumber,
+ List assignments,
+ String[] holidays
+) {
+ /**
+ * Represents the configuration information needed for penalties students may receive
+ *
+ * @param perDayLatePenalty The penalty to apply on a submission for everyday late
+ * @param gitCommitPenalty the penalty to apply on a submission for missing commits
+ * @param maxLateDaysPenalized the maximum number of days the late penalty should apply
+ * @param linesChangedPerCommit the minimum number of lines needed for a commit to count
+ * @param clockForgivenessMinutes the number of minutes a commit can be authored
+ * past the time of submission
+ * @param coveragePercent the percentage of code coverage expected for a submission to receive full credit
+ * @param extraCoveragePercent the percentage of code coverage expected for a submission to receive extra credit
+ * @param coverageType the type of code coverage to use when calculating penalties (branch or line)
+ */
+ public record PenaltyConfig(
+ float perDayLatePenalty,
+ float gitCommitPenalty,
+ int maxLateDaysPenalized,
+ int linesChangedPerCommit,
+ int clockForgivenessMinutes,
+ float coveragePercent,
+ float extraCoveragePercent,
+ ConfigPenaltyUpdateRequest.CoverageType coverageType
+ ){ }
+
+ /**
+ * Represents the configuration information for a Canvas assignment
+ *
+ * @param phase the phase associated with the Canvas assignment
+ * @param assignmentId the id of the assignment in Canvas
+ * @param rubricItems the rubric items for the Canvas assignment
+ */
+ public record AssignmentConfig(
+ Phase phase,
+ int assignmentId,
+ Map rubricItems
+ ){ }
+}
diff --git a/src/main/java/edu/byu/cs/model/request/ConfigPenaltyUpdateRequest.java b/src/main/java/edu/byu/cs/model/request/ConfigPenaltyUpdateRequest.java
index dc87d2e46..a5b0f2387 100644
--- a/src/main/java/edu/byu/cs/model/request/ConfigPenaltyUpdateRequest.java
+++ b/src/main/java/edu/byu/cs/model/request/ConfigPenaltyUpdateRequest.java
@@ -1,31 +1,31 @@
-package edu.byu.cs.model.request;
-
-/**
- * Represents a request for updating the values used for calculating penalties
- *
- * @param perDayLatePenalty the percentage taken off submission for everyday late
- * @param maxLateDaysPenalized the maximum number of days the late penalty should apply
- * @param gitCommitPenalty the penalty to apply for missing commits
- * @param linesChangedPerCommit the minimum number of lines needed for a commit to count
- * @param clockForgivenessMinutes the number of minutes a commit can be authored
- * past the time of submission
- * @param coveragePercent the percentage of code coverage expected for a submission to receive full credit
- * @param extraCoveragePercent the percentage of code coverage expected for a submission to receive extra credit
- * @param coverageType the type of code coverage to use when calculating penalties (branch or line)
- */
-public record ConfigPenaltyUpdateRequest(
- float perDayLatePenalty,
- int maxLateDaysPenalized,
- float gitCommitPenalty,
- int linesChangedPerCommit,
- int clockForgivenessMinutes,
- float coveragePercent,
- float extraCoveragePercent,
- CoverageType coverageType
-) {
- public enum CoverageType {
- LINE,
- BRANCH
- }
-}
-
+package edu.byu.cs.model.request;
+
+/**
+ * Represents a request for updating the values used for calculating penalties
+ *
+ * @param perDayLatePenalty the percentage taken off submission for everyday late
+ * @param maxLateDaysPenalized the maximum number of days the late penalty should apply
+ * @param gitCommitPenalty the penalty to apply for missing commits
+ * @param linesChangedPerCommit the minimum number of lines needed for a commit to count
+ * @param clockForgivenessMinutes the number of minutes a commit can be authored
+ * past the time of submission
+ * @param coveragePercent the percentage of code coverage expected for a submission to receive full credit
+ * @param extraCoveragePercent the percentage of code coverage expected for a submission to receive extra credit
+ * @param coverageType the type of code coverage to use when calculating penalties (branch or line)
+ */
+public record ConfigPenaltyUpdateRequest(
+ float perDayLatePenalty,
+ int maxLateDaysPenalized,
+ float gitCommitPenalty,
+ int linesChangedPerCommit,
+ int clockForgivenessMinutes,
+ float coveragePercent,
+ float extraCoveragePercent,
+ CoverageType coverageType
+) {
+ public enum CoverageType {
+ LINE,
+ BRANCH
+ }
+}
+
diff --git a/src/main/resources/frontend/src/components/config/PenaltyConfigEditor.vue b/src/main/resources/frontend/src/components/config/PenaltyConfigEditor.vue
index 3f527507a..7874e092b 100644
--- a/src/main/resources/frontend/src/components/config/PenaltyConfigEditor.vue
+++ b/src/main/resources/frontend/src/components/config/PenaltyConfigEditor.vue
@@ -1,145 +1,145 @@
-
-
-
-
-
-
Late Penalty
-
Applied per day the submission is late.
-
%
-
-
-
Max Late Days
-
Days after which the late penalty caps out.
-
days
-
-
-
-
-
-
-
-
Git Commit Penalty
-
- Applied when students don't have enough commits and a TA determines they should receive a
- penalty.
-
-
%
-
-
-
Lines Changed Per Commit
-
- The minimum number of lines that must change for a commit to count.
-
-
lines
-
-
-
Clock Forgiveness
-
- The number of minutes in the future we will tolerate local clock non-synchronization in Git
- Commit Verification.
-
-
minutes
-
-
-
Coverage Percent
-
- The percentage of code coverage expected for unit tests to receive full credit.
-
-
%
-
-
-
Extra Coverage Percent
-
- The percentage of code coverage expected for unit tests to receive extra credit.
-
-
%
-
-
-
Coverage Type
-
- The type of coverage to be measured.
-
-
Line
-
Branch
-
-
-
-
-
- All values must be non-negative, and penalties must be equal to or less than 100%
-
-
None of these values affect admin submissions
-
-
-
+
+
+
+
+
+
Late Penalty
+
Applied per day the submission is late.
+
%
+
+
+
Max Late Days
+
Days after which the late penalty caps out.
+
days
+
+
+
+
+
+
+
+
Git Commit Penalty
+
+ Applied when students don't have enough commits and a TA determines they should receive a
+ penalty.
+
+
%
+
+
+
Lines Changed Per Commit
+
+ The minimum number of lines that must change for a commit to count.
+
+
lines
+
+
+
Clock Forgiveness
+
+ The number of minutes in the future we will tolerate local clock non-synchronization in Git
+ Commit Verification.
+
+
minutes
+
+
+
Coverage Percent
+
+ The percentage of code coverage expected for unit tests to receive full credit.
+
+
%
+
+
+
Extra Coverage Percent
+
+ The percentage of code coverage expected for unit tests to receive extra credit.
+
+
%
+
+
+
Coverage Type
+
+ The type of coverage to be measured.
+
+
Line
+
Branch
+
+
+
+
+
+ All values must be non-negative, and penalties must be equal to or less than 100%
+
+
None of these values affect admin submissions
+
+
+
diff --git a/src/main/resources/frontend/src/network/ServerCommunicator.ts b/src/main/resources/frontend/src/network/ServerCommunicator.ts
index f13070770..72fda3443 100644
--- a/src/main/resources/frontend/src/network/ServerCommunicator.ts
+++ b/src/main/resources/frontend/src/network/ServerCommunicator.ts
@@ -1,231 +1,231 @@
-import { useAuthStore } from "@/stores/auth";
-import { ServerError } from "@/network/ServerError";
-import { useConfigStore } from "@/stores/config";
-
-/**
- * Utility for making authenticated HTTP requests to the server with automatic error handling
- * and response parsing.
- *
- * @example
- * // GET request expecting a User response
- * const user = await ServerCommunicator.getRequest('/api/user');
- *
- * // POST request with body, not expecting response
- * await ServerCommunicator.postRequest('/api/logs', { event: 'action' }, false);
- */
-export const ServerCommunicator = {
- getRequest: getRequest,
- getRequestGuaranteed: getRequestGuaranteed,
- postRequest: postRequest,
- doUnprocessedRequest: doUnprocessedRequest,
-};
-
-/**
- * Makes a GET request to the specified endpoint with a guaranteed response.
- * This will not throw an error if the server returns an error, but will print it
- * to the console.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} endpoint - The API endpoint to call
- * @param {T} errorResponse - The object the method call should return if the server
- * returns nothing or responds with a non-2XX code
- * @returns {Promise} Promise that resolves to the response data of type T
- */
-function getRequestGuaranteed(endpoint: string, errorResponse: T): Promise {
- return getRequest(endpoint, true).catch((_error) => Promise.resolve(errorResponse));
-}
-
-/**
- * Makes a GET request to the specified endpoint.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} endpoint - The API endpoint to call
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} when expectResponse is true but no response is received
- */
-function getRequest(endpoint: string, expectResponse: false): Promise;
-/**
- * Makes a GET request to the specified endpoint.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} endpoint - The API endpoint to call
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} when expectResponse is true but no response is received
- */
-function getRequest(endpoint: string, expectResponse?: boolean): Promise;
-function getRequest(endpoint: string, expectResponse: boolean = true): Promise {
- return doRequest("GET", endpoint, null, expectResponse);
-}
-
-/**
- * Makes a POST request to the specified endpoint.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} when expectResponse is true but no response is received
- *
- * @example
- * // With response
- * const user = await postRequest('/api/users', { name: 'John' });
- *
- * // Without response
- * await postRequest('/api/logs', { event: 'action' }, false);
- */
-function postRequest(
- endpoint: string,
- bodyObject: Object | null,
- expectResponse: false,
-): Promise;
-/**
- * Makes a POST request to the specified endpoint.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} when expectResponse is true but no response is received
- *
- * @example
- * // With response
- * const user = await postRequest('/api/users', { name: 'John' });
- *
- * // Without response
- * await postRequest('/api/logs', { event: 'action' }, false);
- */
-function postRequest(
- endpoint: string,
- bodyObject?: Object | null,
- expectResponse?: boolean,
-): Promise;
-function postRequest(
- endpoint: string,
- bodyObject: Object | null = null,
- expectResponse: boolean = true,
-): Promise {
- return doRequest("POST", endpoint, bodyObject, expectResponse);
-}
-/**
- * Internal method to make an HTTP request.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} method - The HTTP method to use
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} When expectResponse is true but no response is received
- * @internal
- */
-function doRequest(
- method: string,
- endpoint: string,
- bodyObject: Object | null,
- expectResponse: false,
-): Promise;
-/**
- * Internal method to make an HTTP request.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} method - The HTTP method to use
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} When expectResponse is true but no response is received
- * @internal
- */
-function doRequest(
- method: string,
- endpoint: string,
- bodyObject?: Object | null,
- expectResponse?: boolean,
-): Promise;
-/**
- * Internal method to make an HTTP request.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} method - The HTTP method to use
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} When expectResponse is true but no response is received
- * @internal
- */
-async function doRequest(
- method: string,
- endpoint: string,
- bodyObject: Object | null = null,
- expectResponse: boolean = true,
-): Promise {
- const response = await doUnprocessedRequest(method, endpoint, bodyObject);
-
- if (!expectResponse) {
- return null;
- }
-
- const text = await response.text();
- if (text) {
- return JSON.parse(text) as T;
- }
-
- if (bodyObject) {
- console.error("Body request:", bodyObject);
- }
- console.error("Response: ", response);
- throw new Error(`Expected a response from ${method} call to ${endpoint} but got none`);
-}
-
-/**
- * Makes a raw HTTP request to the server with authentication.
- * @param {string} method - The HTTP method to use
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @returns {Promise} A promise that resolves to the raw fetch response object
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- */
-async function doUnprocessedRequest(
- method: string,
- endpoint: string,
- bodyObject: Object | null = null,
-): Promise {
- const authToken = useAuthStore().token ?? "";
-
- const response = await fetch(useConfigStore().backendUrl + endpoint, {
- method: method,
- credentials: "include",
- headers: {
- "Content-Type": "application/json",
- Authorization: authToken,
- },
- body: bodyObject ? JSON.stringify(bodyObject) : null,
- });
-
- if (!response.ok) {
- console.error(
- `A ${response.status} error occurred while making a ${method} request to ${endpoint}`,
- );
- console.error(response);
- throw new ServerError(endpoint, await response.text(), response.status, response.statusText);
- }
- return response;
-}
+import { useAuthStore } from "@/stores/auth";
+import { ServerError } from "@/network/ServerError";
+import { useConfigStore } from "@/stores/config";
+
+/**
+ * Utility for making authenticated HTTP requests to the server with automatic error handling
+ * and response parsing.
+ *
+ * @example
+ * // GET request expecting a User response
+ * const user = await ServerCommunicator.getRequest('/api/user');
+ *
+ * // POST request with body, not expecting response
+ * await ServerCommunicator.postRequest('/api/logs', { event: 'action' }, false);
+ */
+export const ServerCommunicator = {
+ getRequest: getRequest,
+ getRequestGuaranteed: getRequestGuaranteed,
+ postRequest: postRequest,
+ doUnprocessedRequest: doUnprocessedRequest,
+};
+
+/**
+ * Makes a GET request to the specified endpoint with a guaranteed response.
+ * This will not throw an error if the server returns an error, but will print it
+ * to the console.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} endpoint - The API endpoint to call
+ * @param {T} errorResponse - The object the method call should return if the server
+ * returns nothing or responds with a non-2XX code
+ * @returns {Promise} Promise that resolves to the response data of type T
+ */
+function getRequestGuaranteed(endpoint: string, errorResponse: T): Promise {
+ return getRequest(endpoint, true).catch((_error) => Promise.resolve(errorResponse));
+}
+
+/**
+ * Makes a GET request to the specified endpoint.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} endpoint - The API endpoint to call
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} when expectResponse is true but no response is received
+ */
+function getRequest(endpoint: string, expectResponse: false): Promise;
+/**
+ * Makes a GET request to the specified endpoint.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} endpoint - The API endpoint to call
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} when expectResponse is true but no response is received
+ */
+function getRequest(endpoint: string, expectResponse?: boolean): Promise;
+function getRequest(endpoint: string, expectResponse: boolean = true): Promise {
+ return doRequest("GET", endpoint, null, expectResponse);
+}
+
+/**
+ * Makes a POST request to the specified endpoint.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} when expectResponse is true but no response is received
+ *
+ * @example
+ * // With response
+ * const user = await postRequest('/api/users', { name: 'John' });
+ *
+ * // Without response
+ * await postRequest('/api/logs', { event: 'action' }, false);
+ */
+function postRequest(
+ endpoint: string,
+ bodyObject: Object | null,
+ expectResponse: false,
+): Promise;
+/**
+ * Makes a POST request to the specified endpoint.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} when expectResponse is true but no response is received
+ *
+ * @example
+ * // With response
+ * const user = await postRequest('/api/users', { name: 'John' });
+ *
+ * // Without response
+ * await postRequest('/api/logs', { event: 'action' }, false);
+ */
+function postRequest(
+ endpoint: string,
+ bodyObject?: Object | null,
+ expectResponse?: boolean,
+): Promise;
+function postRequest(
+ endpoint: string,
+ bodyObject: Object | null = null,
+ expectResponse: boolean = true,
+): Promise {
+ return doRequest("POST", endpoint, bodyObject, expectResponse);
+}
+/**
+ * Internal method to make an HTTP request.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} method - The HTTP method to use
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} When expectResponse is true but no response is received
+ * @internal
+ */
+function doRequest(
+ method: string,
+ endpoint: string,
+ bodyObject: Object | null,
+ expectResponse: false,
+): Promise;
+/**
+ * Internal method to make an HTTP request.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} method - The HTTP method to use
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} When expectResponse is true but no response is received
+ * @internal
+ */
+function doRequest(
+ method: string,
+ endpoint: string,
+ bodyObject?: Object | null,
+ expectResponse?: boolean,
+): Promise;
+/**
+ * Internal method to make an HTTP request.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} method - The HTTP method to use
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} When expectResponse is true but no response is received
+ * @internal
+ */
+async function doRequest(
+ method: string,
+ endpoint: string,
+ bodyObject: Object | null = null,
+ expectResponse: boolean = true,
+): Promise {
+ const response = await doUnprocessedRequest(method, endpoint, bodyObject);
+
+ if (!expectResponse) {
+ return null;
+ }
+
+ const text = await response.text();
+ if (text) {
+ return JSON.parse(text) as T;
+ }
+
+ if (bodyObject) {
+ console.error("Body request:", bodyObject);
+ }
+ console.error("Response: ", response);
+ throw new Error(`Expected a response from ${method} call to ${endpoint} but got none`);
+}
+
+/**
+ * Makes a raw HTTP request to the server with authentication.
+ * @param {string} method - The HTTP method to use
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @returns {Promise} A promise that resolves to the raw fetch response object
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ */
+async function doUnprocessedRequest(
+ method: string,
+ endpoint: string,
+ bodyObject: Object | null = null,
+): Promise {
+ const authToken = useAuthStore().token ?? "";
+
+ const response = await fetch(useConfigStore().backendUrl + endpoint, {
+ method: method,
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: authToken,
+ },
+ body: bodyObject ? JSON.stringify(bodyObject) : null,
+ });
+
+ if (!response.ok) {
+ console.error(
+ `A ${response.status} error occurred while making a ${method} request to ${endpoint}`,
+ );
+ console.error(response);
+ throw new ServerError(endpoint, await response.text(), response.status, response.statusText);
+ }
+ return response;
+}
diff --git a/src/main/resources/frontend/src/services/configService.ts b/src/main/resources/frontend/src/services/configService.ts
index de79afeac..157f1071e 100644
--- a/src/main/resources/frontend/src/services/configService.ts
+++ b/src/main/resources/frontend/src/services/configService.ts
@@ -1,88 +1,88 @@
-import { type PrivateConfig, type PublicConfig, useConfigStore } from "@/stores/config";
-import { Phase } from "@/types/types";
-import { ServerCommunicator } from "@/network/ServerCommunicator";
-
-export const getPublicConfig = async (): Promise => {
- return await ServerCommunicator.getRequest("/api/config");
-};
-
-export const getAdminConfig = async (): Promise => {
- return await ServerCommunicator.getRequest("/api/admin/config");
-};
-
-export const setPenalties = async (
- maxLateDaysPenalized: number,
- gitCommitPenalty: number,
- perDayLatePenalty: number,
- linesChangedPerCommit: number,
- clockForgivenessMinutes: number,
- coveragePercent: number,
- extraCoveragePercent: number,
- coverageType: 'LINE' | 'BRANCH'
-) => {
- await doSetConfigItem("/api/admin/config/penalties", {
- maxLateDaysPenalized: maxLateDaysPenalized,
- gitCommitPenalty: gitCommitPenalty,
- perDayLatePenalty: perDayLatePenalty,
- linesChangedPerCommit: linesChangedPerCommit,
- clockForgivenessMinutes: clockForgivenessMinutes,
- coveragePercent: coveragePercent,
- extraCoveragePercent: extraCoveragePercent,
- coverageType: coverageType,
- });
-};
-
-export const setBanner = async (
- message: String,
- link: String,
- color: String,
- expirationTimestamp: String,
-): Promise => {
- await doSetConfigItem("/api/admin/config/banner", {
- bannerMessage: message,
- bannerLink: link,
- bannerColor: color,
- bannerExpiration: expirationTimestamp,
- });
-};
-
-export const setLivePhases = async (phases: Array): Promise => {
- await doSetConfigItem("/api/admin/config/phases", { phases: phases });
-};
-
-export const setGraderShutdown = async (
- shutdownTimestamp: string,
- shutdownWarningHours: number,
-): Promise => {
- if (shutdownWarningHours < 0) shutdownWarningHours = 0;
-
- await doSetConfigItem("/api/admin/config/phases/shutdown", {
- shutdownTimestamp: shutdownTimestamp,
- shutdownWarningMilliseconds: Math.trunc(shutdownWarningHours * 60 * 60 * 1000), // convert to milliseconds
- });
-};
-
-export const reloadCourseIds = async (): Promise => {
- await doSetConfigItem("/api/admin/config/reloadCourseIds", {});
-};
-
-export const setCourseId = async (courseNumber: number) => {
- await doSetConfigItem("/api/admin/config/courseId", { courseId: courseNumber });
-};
-
-export const updateHolidays = async (dates: string[]) => {
- await doSetConfigItem("/api/admin/config/holidays", {
- holidays: dates,
- });
-};
-
-export const setSlackLink = async (slackLink: string) => {
- await doSetConfigItem("/api/admin/config/slackLink", {
- slackLink: slackLink,
- });
-};
-
-const doSetConfigItem = async (path: string, body: Object): Promise => {
- await ServerCommunicator.postRequest(path, body, false);
- await useConfigStore().updateConfig();
-};
+import { type PrivateConfig, type PublicConfig, useConfigStore } from "@/stores/config";
+import { Phase } from "@/types/types";
+import { ServerCommunicator } from "@/network/ServerCommunicator";
+
+export const getPublicConfig = async (): Promise => {
+ return await ServerCommunicator.getRequest("/api/config");
+};
+
+export const getAdminConfig = async (): Promise => {
+ return await ServerCommunicator.getRequest("/api/admin/config");
+};
+
+export const setPenalties = async (
+ maxLateDaysPenalized: number,
+ gitCommitPenalty: number,
+ perDayLatePenalty: number,
+ linesChangedPerCommit: number,
+ clockForgivenessMinutes: number,
+ coveragePercent: number,
+ extraCoveragePercent: number,
+ coverageType: 'LINE' | 'BRANCH'
+) => {
+ await doSetConfigItem("/api/admin/config/penalties", {
+ maxLateDaysPenalized: maxLateDaysPenalized,
+ gitCommitPenalty: gitCommitPenalty,
+ perDayLatePenalty: perDayLatePenalty,
+ linesChangedPerCommit: linesChangedPerCommit,
+ clockForgivenessMinutes: clockForgivenessMinutes,
+ coveragePercent: coveragePercent,
+ extraCoveragePercent: extraCoveragePercent,
+ coverageType: coverageType,
+ });
+};
+
+export const setBanner = async (
+ message: String,
+ link: String,
+ color: String,
+ expirationTimestamp: String,
+): Promise => {
+ await doSetConfigItem("/api/admin/config/banner", {
+ bannerMessage: message,
+ bannerLink: link,
+ bannerColor: color,
+ bannerExpiration: expirationTimestamp,
+ });
+};
+
+export const setLivePhases = async (phases: Array): Promise => {
+ await doSetConfigItem("/api/admin/config/phases", { phases: phases });
+};
+
+export const setGraderShutdown = async (
+ shutdownTimestamp: string,
+ shutdownWarningHours: number,
+): Promise => {
+ if (shutdownWarningHours < 0) shutdownWarningHours = 0;
+
+ await doSetConfigItem("/api/admin/config/phases/shutdown", {
+ shutdownTimestamp: shutdownTimestamp,
+ shutdownWarningMilliseconds: Math.trunc(shutdownWarningHours * 60 * 60 * 1000), // convert to milliseconds
+ });
+};
+
+export const reloadCourseIds = async (): Promise => {
+ await doSetConfigItem("/api/admin/config/reloadCourseIds", {});
+};
+
+export const setCourseId = async (courseNumber: number) => {
+ await doSetConfigItem("/api/admin/config/courseId", { courseId: courseNumber });
+};
+
+export const updateHolidays = async (dates: string[]) => {
+ await doSetConfigItem("/api/admin/config/holidays", {
+ holidays: dates,
+ });
+};
+
+export const setSlackLink = async (slackLink: string) => {
+ await doSetConfigItem("/api/admin/config/slackLink", {
+ slackLink: slackLink,
+ });
+};
+
+const doSetConfigItem = async (path: string, body: Object): Promise => {
+ await ServerCommunicator.postRequest(path, body, false);
+ await useConfigStore().updateConfig();
+};
diff --git a/src/main/resources/frontend/src/stores/config.ts b/src/main/resources/frontend/src/stores/config.ts
index 556f55363..c290ea74e 100644
--- a/src/main/resources/frontend/src/stores/config.ts
+++ b/src/main/resources/frontend/src/stores/config.ts
@@ -1,128 +1,128 @@
-import { reactive, readonly, ref } from "vue";
-import { defineStore } from "pinia";
-import { Phase, type RubricInfo, type RubricType } from "@/types/types";
-import { getAdminConfig, getPublicConfig } from "@/services/configService";
-import { useAuthStore } from "@/stores/auth";
-
-type ImportMeta = {
- VITE_APP_BACKEND_URL: string;
-};
-
-/**
- * Config available to be read by any user
- */
-export type PublicConfig = {
- banner: {
- message: string;
- link: string;
- color: string;
- expiration: string;
- };
-
- shutdown: {
- timestamp: string;
- warningMilliseconds: number;
- };
-
- livePhases: string[];
-
- slackLink: string;
-};
-
-/**
- * Config available to be read only by admins
- */
-export type PrivateConfig = {
- penalty: {
- perDayLatePenalty: number;
- gitCommitPenalty: number;
- maxLateDaysPenalized: number;
- linesChangedPerCommit: number;
- clockForgivenessMinutes: number;
- coveragePercent: number;
- extraCoveragePercent: number;
- coverageType: 'LINE' | 'BRANCH';
- };
-
- courseNumber: number;
- assignments: {
- phase: Phase;
- assignmentId: number;
- rubricItems: Map;
- }[];
-
- holidays: Date[];
-};
-
-// @ts-ignore
-const env: ImportMeta = import.meta.env;
-export const useConfigStore = defineStore("config", () => {
- const publicConfig = reactive({
- banner: {
- message: "",
- link: "",
- color: "",
- expiration: "",
- },
-
- shutdown: {
- timestamp: "",
- warningMilliseconds: 0,
- },
-
- livePhases: [],
-
- slackLink: "",
- });
-
- const privateConfig = reactive({
- penalty: {
- perDayLatePenalty: -1,
- gitCommitPenalty: -1,
- maxLateDaysPenalized: -1,
- linesChangedPerCommit: -1,
- clockForgivenessMinutes: -1,
- coveragePercent: -1,
- extraCoveragePercent: -1,
- coverageType: 'LINE',
- },
- courseNumber: -1,
- assignments: [],
- holidays: [],
- });
-
- const updateConfig = async () => {
- if (useAuthStore().isLoggedIn) await updateAdminConfig();
- await updatePublicConfig();
- };
-
- const updatePublicConfig = async () => {
- const latestPublicConfig: PublicConfig = await getPublicConfig();
-
- Object.assign(publicConfig, latestPublicConfig);
-
- // Backend lets the front end choose the default banner color
- if (!publicConfig.banner.color) publicConfig.banner.color = "#4fa0ff";
- };
-
- const updateAdminConfig = async () => {
- const latestAdminConfig = await getAdminConfig();
-
- console.log(latestAdminConfig);
-
- Object.assign(privateConfig, latestAdminConfig);
-
- console.log(privateConfig);
- };
-
- const backendUrl = ref(env.VITE_APP_BACKEND_URL);
-
- return {
- updateConfig,
- updatePublicConfig,
- updateAdminConfig,
- backendUrl: readonly(backendUrl),
- public: readonly(publicConfig),
- admin: readonly(privateConfig),
- };
-});
+import { reactive, readonly, ref } from "vue";
+import { defineStore } from "pinia";
+import { Phase, type RubricInfo, type RubricType } from "@/types/types";
+import { getAdminConfig, getPublicConfig } from "@/services/configService";
+import { useAuthStore } from "@/stores/auth";
+
+type ImportMeta = {
+ VITE_APP_BACKEND_URL: string;
+};
+
+/**
+ * Config available to be read by any user
+ */
+export type PublicConfig = {
+ banner: {
+ message: string;
+ link: string;
+ color: string;
+ expiration: string;
+ };
+
+ shutdown: {
+ timestamp: string;
+ warningMilliseconds: number;
+ };
+
+ livePhases: string[];
+
+ slackLink: string;
+};
+
+/**
+ * Config available to be read only by admins
+ */
+export type PrivateConfig = {
+ penalty: {
+ perDayLatePenalty: number;
+ gitCommitPenalty: number;
+ maxLateDaysPenalized: number;
+ linesChangedPerCommit: number;
+ clockForgivenessMinutes: number;
+ coveragePercent: number;
+ extraCoveragePercent: number;
+ coverageType: 'LINE' | 'BRANCH';
+ };
+
+ courseNumber: number;
+ assignments: {
+ phase: Phase;
+ assignmentId: number;
+ rubricItems: Map;
+ }[];
+
+ holidays: Date[];
+};
+
+// @ts-ignore
+const env: ImportMeta = import.meta.env;
+export const useConfigStore = defineStore("config", () => {
+ const publicConfig = reactive({
+ banner: {
+ message: "",
+ link: "",
+ color: "",
+ expiration: "",
+ },
+
+ shutdown: {
+ timestamp: "",
+ warningMilliseconds: 0,
+ },
+
+ livePhases: [],
+
+ slackLink: "",
+ });
+
+ const privateConfig = reactive({
+ penalty: {
+ perDayLatePenalty: -1,
+ gitCommitPenalty: -1,
+ maxLateDaysPenalized: -1,
+ linesChangedPerCommit: -1,
+ clockForgivenessMinutes: -1,
+ coveragePercent: -1,
+ extraCoveragePercent: -1,
+ coverageType: 'LINE',
+ },
+ courseNumber: -1,
+ assignments: [],
+ holidays: [],
+ });
+
+ const updateConfig = async () => {
+ if (useAuthStore().isLoggedIn) await updateAdminConfig();
+ await updatePublicConfig();
+ };
+
+ const updatePublicConfig = async () => {
+ const latestPublicConfig: PublicConfig = await getPublicConfig();
+
+ Object.assign(publicConfig, latestPublicConfig);
+
+ // Backend lets the front end choose the default banner color
+ if (!publicConfig.banner.color) publicConfig.banner.color = "#4fa0ff";
+ };
+
+ const updateAdminConfig = async () => {
+ const latestAdminConfig = await getAdminConfig();
+
+ console.log(latestAdminConfig);
+
+ Object.assign(privateConfig, latestAdminConfig);
+
+ console.log(privateConfig);
+ };
+
+ const backendUrl = ref(env.VITE_APP_BACKEND_URL);
+
+ return {
+ updateConfig,
+ updatePublicConfig,
+ updateAdminConfig,
+ backendUrl: readonly(backendUrl),
+ public: readonly(publicConfig),
+ admin: readonly(privateConfig),
+ };
+});
diff --git a/src/main/resources/frontend/src/views/AdminView/ConfigView.vue b/src/main/resources/frontend/src/views/AdminView/ConfigView.vue
index dc839a655..b4bf946f2 100644
--- a/src/main/resources/frontend/src/views/AdminView/ConfigView.vue
+++ b/src/main/resources/frontend/src/views/AdminView/ConfigView.vue
@@ -1,228 +1,228 @@
-
-
-
-
-
-
-
-
-
-
-
- Message:
-
-
- Link:
-
- none
-
-
- Expires:
-
-
-
There is currently no banner message
-
-
-
-
-
-
-
-
-
-
-
-
- {{ phase }}
-
-
-
-
-
-
-
-
-
-
-
- Scheduled to shutdown:
- {{ readableTimestamp(config.public.shutdown.timestamp) }}
-
- Applied when students don't have enough commits and a TA determines they should receive a
- penalty.
-
-
%
-
-
-
Lines Changed Per Commit
-
- The minimum number of lines that must change for a commit to count.
-
-
lines
-
-
-
Clock Forgiveness
-
- The number of minutes in the future we will tolerate local clock non-synchronization in Git
- Commit Verification.
-
-
minutes
-
-
-
Coverage Percent
-
- The percentage of code coverage expected for unit tests to receive full credit.
-
-
%
-
-
-
Extra Coverage Percent
-
- The percentage of code coverage expected for unit tests to receive extra credit.
-
-
%
-
-
-
Coverage Type
-
The type of coverage to be measured.
-
Line
-
Branch
-
-
-
-
-
- All values must be non-negative, and penalties must be equal to or less than 100%
-
-
None of these values affect admin submissions
-
-
-
+
+
+
+
+
+
Late Penalty
+
Applied per day the submission is late.
+
%
+
+
+
Max Late Days
+
Days after which the late penalty caps out.
+
days
+
+
+
+
+
+
+
+
Git Commit Penalty
+
+ Applied when students don't have enough commits and a TA determines they should receive a
+ penalty.
+
+
%
+
+
+
Lines Changed Per Commit
+
+ The minimum number of lines that must change for a commit to count.
+
+
lines
+
+
+
Clock Forgiveness
+
+ The number of minutes in the future we will tolerate local clock non-synchronization in Git
+ Commit Verification.
+
+
minutes
+
+
+
Coverage Percent
+
+ The percentage of code coverage expected for unit tests to receive full credit.
+
+
%
+
+
+
Extra Coverage Percent
+
+ The percentage of code coverage expected for unit tests to receive extra credit.
+
+
%
+
+
+
Coverage Type
+
The type of coverage to be measured.
+
Line
+
Branch
+
+
+
+
+
+ All values must be non-negative, and penalties must be equal to or less than 100%
+
+
None of these values affect admin submissions
+
+
+
diff --git a/src/main/resources/frontend/src/network/ServerCommunicator.ts b/src/main/resources/frontend/src/network/ServerCommunicator.ts
index 72fda3443..f13070770 100644
--- a/src/main/resources/frontend/src/network/ServerCommunicator.ts
+++ b/src/main/resources/frontend/src/network/ServerCommunicator.ts
@@ -1,231 +1,231 @@
-import { useAuthStore } from "@/stores/auth";
-import { ServerError } from "@/network/ServerError";
-import { useConfigStore } from "@/stores/config";
-
-/**
- * Utility for making authenticated HTTP requests to the server with automatic error handling
- * and response parsing.
- *
- * @example
- * // GET request expecting a User response
- * const user = await ServerCommunicator.getRequest('/api/user');
- *
- * // POST request with body, not expecting response
- * await ServerCommunicator.postRequest('/api/logs', { event: 'action' }, false);
- */
-export const ServerCommunicator = {
- getRequest: getRequest,
- getRequestGuaranteed: getRequestGuaranteed,
- postRequest: postRequest,
- doUnprocessedRequest: doUnprocessedRequest,
-};
-
-/**
- * Makes a GET request to the specified endpoint with a guaranteed response.
- * This will not throw an error if the server returns an error, but will print it
- * to the console.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} endpoint - The API endpoint to call
- * @param {T} errorResponse - The object the method call should return if the server
- * returns nothing or responds with a non-2XX code
- * @returns {Promise} Promise that resolves to the response data of type T
- */
-function getRequestGuaranteed(endpoint: string, errorResponse: T): Promise {
- return getRequest(endpoint, true).catch((_error) => Promise.resolve(errorResponse));
-}
-
-/**
- * Makes a GET request to the specified endpoint.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} endpoint - The API endpoint to call
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} when expectResponse is true but no response is received
- */
-function getRequest(endpoint: string, expectResponse: false): Promise;
-/**
- * Makes a GET request to the specified endpoint.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} endpoint - The API endpoint to call
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} when expectResponse is true but no response is received
- */
-function getRequest(endpoint: string, expectResponse?: boolean): Promise;
-function getRequest(endpoint: string, expectResponse: boolean = true): Promise {
- return doRequest("GET", endpoint, null, expectResponse);
-}
-
-/**
- * Makes a POST request to the specified endpoint.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} when expectResponse is true but no response is received
- *
- * @example
- * // With response
- * const user = await postRequest('/api/users', { name: 'John' });
- *
- * // Without response
- * await postRequest('/api/logs', { event: 'action' }, false);
- */
-function postRequest(
- endpoint: string,
- bodyObject: Object | null,
- expectResponse: false,
-): Promise;
-/**
- * Makes a POST request to the specified endpoint.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} when expectResponse is true but no response is received
- *
- * @example
- * // With response
- * const user = await postRequest('/api/users', { name: 'John' });
- *
- * // Without response
- * await postRequest('/api/logs', { event: 'action' }, false);
- */
-function postRequest(
- endpoint: string,
- bodyObject?: Object | null,
- expectResponse?: boolean,
-): Promise;
-function postRequest(
- endpoint: string,
- bodyObject: Object | null = null,
- expectResponse: boolean = true,
-): Promise {
- return doRequest("POST", endpoint, bodyObject, expectResponse);
-}
-/**
- * Internal method to make an HTTP request.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} method - The HTTP method to use
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} When expectResponse is true but no response is received
- * @internal
- */
-function doRequest(
- method: string,
- endpoint: string,
- bodyObject: Object | null,
- expectResponse: false,
-): Promise;
-/**
- * Internal method to make an HTTP request.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} method - The HTTP method to use
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} When expectResponse is true but no response is received
- * @internal
- */
-function doRequest(
- method: string,
- endpoint: string,
- bodyObject?: Object | null,
- expectResponse?: boolean,
-): Promise;
-/**
- * Internal method to make an HTTP request.
- * @template T - The type of the expected response (when expectResponse is true)
- * @param {string} method - The HTTP method to use
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
- * @returns {Promise} Promise that resolves to:
- * - The response data of type T when expectResponse is true
- * - null when expectResponse is false
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- * @throws {Error} When expectResponse is true but no response is received
- * @internal
- */
-async function doRequest(
- method: string,
- endpoint: string,
- bodyObject: Object | null = null,
- expectResponse: boolean = true,
-): Promise {
- const response = await doUnprocessedRequest(method, endpoint, bodyObject);
-
- if (!expectResponse) {
- return null;
- }
-
- const text = await response.text();
- if (text) {
- return JSON.parse(text) as T;
- }
-
- if (bodyObject) {
- console.error("Body request:", bodyObject);
- }
- console.error("Response: ", response);
- throw new Error(`Expected a response from ${method} call to ${endpoint} but got none`);
-}
-
-/**
- * Makes a raw HTTP request to the server with authentication.
- * @param {string} method - The HTTP method to use
- * @param {string} endpoint - The API endpoint to call
- * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
- * @returns {Promise} A promise that resolves to the raw fetch response object
- * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
- */
-async function doUnprocessedRequest(
- method: string,
- endpoint: string,
- bodyObject: Object | null = null,
-): Promise {
- const authToken = useAuthStore().token ?? "";
-
- const response = await fetch(useConfigStore().backendUrl + endpoint, {
- method: method,
- credentials: "include",
- headers: {
- "Content-Type": "application/json",
- Authorization: authToken,
- },
- body: bodyObject ? JSON.stringify(bodyObject) : null,
- });
-
- if (!response.ok) {
- console.error(
- `A ${response.status} error occurred while making a ${method} request to ${endpoint}`,
- );
- console.error(response);
- throw new ServerError(endpoint, await response.text(), response.status, response.statusText);
- }
- return response;
-}
+import { useAuthStore } from "@/stores/auth";
+import { ServerError } from "@/network/ServerError";
+import { useConfigStore } from "@/stores/config";
+
+/**
+ * Utility for making authenticated HTTP requests to the server with automatic error handling
+ * and response parsing.
+ *
+ * @example
+ * // GET request expecting a User response
+ * const user = await ServerCommunicator.getRequest('/api/user');
+ *
+ * // POST request with body, not expecting response
+ * await ServerCommunicator.postRequest('/api/logs', { event: 'action' }, false);
+ */
+export const ServerCommunicator = {
+ getRequest: getRequest,
+ getRequestGuaranteed: getRequestGuaranteed,
+ postRequest: postRequest,
+ doUnprocessedRequest: doUnprocessedRequest,
+};
+
+/**
+ * Makes a GET request to the specified endpoint with a guaranteed response.
+ * This will not throw an error if the server returns an error, but will print it
+ * to the console.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} endpoint - The API endpoint to call
+ * @param {T} errorResponse - The object the method call should return if the server
+ * returns nothing or responds with a non-2XX code
+ * @returns {Promise} Promise that resolves to the response data of type T
+ */
+function getRequestGuaranteed(endpoint: string, errorResponse: T): Promise {
+ return getRequest(endpoint, true).catch((_error) => Promise.resolve(errorResponse));
+}
+
+/**
+ * Makes a GET request to the specified endpoint.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} endpoint - The API endpoint to call
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} when expectResponse is true but no response is received
+ */
+function getRequest(endpoint: string, expectResponse: false): Promise;
+/**
+ * Makes a GET request to the specified endpoint.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} endpoint - The API endpoint to call
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} when expectResponse is true but no response is received
+ */
+function getRequest(endpoint: string, expectResponse?: boolean): Promise;
+function getRequest(endpoint: string, expectResponse: boolean = true): Promise {
+ return doRequest("GET", endpoint, null, expectResponse);
+}
+
+/**
+ * Makes a POST request to the specified endpoint.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} when expectResponse is true but no response is received
+ *
+ * @example
+ * // With response
+ * const user = await postRequest('/api/users', { name: 'John' });
+ *
+ * // Without response
+ * await postRequest('/api/logs', { event: 'action' }, false);
+ */
+function postRequest(
+ endpoint: string,
+ bodyObject: Object | null,
+ expectResponse: false,
+): Promise;
+/**
+ * Makes a POST request to the specified endpoint.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} when expectResponse is true but no response is received
+ *
+ * @example
+ * // With response
+ * const user = await postRequest('/api/users', { name: 'John' });
+ *
+ * // Without response
+ * await postRequest('/api/logs', { event: 'action' }, false);
+ */
+function postRequest(
+ endpoint: string,
+ bodyObject?: Object | null,
+ expectResponse?: boolean,
+): Promise;
+function postRequest(
+ endpoint: string,
+ bodyObject: Object | null = null,
+ expectResponse: boolean = true,
+): Promise {
+ return doRequest("POST", endpoint, bodyObject, expectResponse);
+}
+/**
+ * Internal method to make an HTTP request.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} method - The HTTP method to use
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} When expectResponse is true but no response is received
+ * @internal
+ */
+function doRequest(
+ method: string,
+ endpoint: string,
+ bodyObject: Object | null,
+ expectResponse: false,
+): Promise;
+/**
+ * Internal method to make an HTTP request.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} method - The HTTP method to use
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} When expectResponse is true but no response is received
+ * @internal
+ */
+function doRequest(
+ method: string,
+ endpoint: string,
+ bodyObject?: Object | null,
+ expectResponse?: boolean,
+): Promise;
+/**
+ * Internal method to make an HTTP request.
+ * @template T - The type of the expected response (when expectResponse is true)
+ * @param {string} method - The HTTP method to use
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @param {boolean} [expectResponse=true] - Whether to expect and parse a response
+ * @returns {Promise} Promise that resolves to:
+ * - The response data of type T when expectResponse is true
+ * - null when expectResponse is false
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ * @throws {Error} When expectResponse is true but no response is received
+ * @internal
+ */
+async function doRequest(
+ method: string,
+ endpoint: string,
+ bodyObject: Object | null = null,
+ expectResponse: boolean = true,
+): Promise {
+ const response = await doUnprocessedRequest(method, endpoint, bodyObject);
+
+ if (!expectResponse) {
+ return null;
+ }
+
+ const text = await response.text();
+ if (text) {
+ return JSON.parse(text) as T;
+ }
+
+ if (bodyObject) {
+ console.error("Body request:", bodyObject);
+ }
+ console.error("Response: ", response);
+ throw new Error(`Expected a response from ${method} call to ${endpoint} but got none`);
+}
+
+/**
+ * Makes a raw HTTP request to the server with authentication.
+ * @param {string} method - The HTTP method to use
+ * @param {string} endpoint - The API endpoint to call
+ * @param {Object | null} [bodyObject=null] - The request body object to send (will be sent as JSON)
+ * @returns {Promise} A promise that resolves to the raw fetch response object
+ * @throws {ServerError} When the request fails (meaning the server returned a code other than 2XX)
+ */
+async function doUnprocessedRequest(
+ method: string,
+ endpoint: string,
+ bodyObject: Object | null = null,
+): Promise {
+ const authToken = useAuthStore().token ?? "";
+
+ const response = await fetch(useConfigStore().backendUrl + endpoint, {
+ method: method,
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: authToken,
+ },
+ body: bodyObject ? JSON.stringify(bodyObject) : null,
+ });
+
+ if (!response.ok) {
+ console.error(
+ `A ${response.status} error occurred while making a ${method} request to ${endpoint}`,
+ );
+ console.error(response);
+ throw new ServerError(endpoint, await response.text(), response.status, response.statusText);
+ }
+ return response;
+}
diff --git a/src/main/resources/frontend/src/services/configService.ts b/src/main/resources/frontend/src/services/configService.ts
index a7f6044ee..53be99266 100644
--- a/src/main/resources/frontend/src/services/configService.ts
+++ b/src/main/resources/frontend/src/services/configService.ts
@@ -1,88 +1,88 @@
-import { type PrivateConfig, type PublicConfig, useConfigStore } from "@/stores/config";
-import { Phase } from "@/types/types";
-import { ServerCommunicator } from "@/network/ServerCommunicator";
-
-export const getPublicConfig = async (): Promise => {
- return await ServerCommunicator.getRequest("/api/config");
-};
-
-export const getAdminConfig = async (): Promise => {
- return await ServerCommunicator.getRequest("/api/admin/config");
-};
-
-export const setPenalties = async (
- maxLateDaysPenalized: number,
- gitCommitPenalty: number,
- perDayLatePenalty: number,
- linesChangedPerCommit: number,
- clockForgivenessMinutes: number,
- coveragePercent: number,
- extraCoveragePercent: number,
- coverageType: "LINE" | "BRANCH",
-) => {
- await doSetConfigItem("/api/admin/config/penalties", {
- maxLateDaysPenalized: maxLateDaysPenalized,
- gitCommitPenalty: gitCommitPenalty,
- perDayLatePenalty: perDayLatePenalty,
- linesChangedPerCommit: linesChangedPerCommit,
- clockForgivenessMinutes: clockForgivenessMinutes,
- coveragePercent: coveragePercent,
- extraCoveragePercent: extraCoveragePercent,
- coverageType: coverageType,
- });
-};
-
-export const setBanner = async (
- message: String,
- link: String,
- color: String,
- expirationTimestamp: String,
-): Promise => {
- await doSetConfigItem("/api/admin/config/banner", {
- bannerMessage: message,
- bannerLink: link,
- bannerColor: color,
- bannerExpiration: expirationTimestamp,
- });
-};
-
-export const setLivePhases = async (phases: Array): Promise => {
- await doSetConfigItem("/api/admin/config/phases", { phases: phases });
-};
-
-export const setGraderShutdown = async (
- shutdownTimestamp: string,
- shutdownWarningHours: number,
-): Promise => {
- if (shutdownWarningHours < 0) shutdownWarningHours = 0;
-
- await doSetConfigItem("/api/admin/config/phases/shutdown", {
- shutdownTimestamp: shutdownTimestamp,
- shutdownWarningMilliseconds: Math.trunc(shutdownWarningHours * 60 * 60 * 1000), // convert to milliseconds
- });
-};
-
-export const reloadCourseIds = async (): Promise => {
- await doSetConfigItem("/api/admin/config/reloadCourseIds", {});
-};
-
-export const setCourseId = async (courseNumber: number) => {
- await doSetConfigItem("/api/admin/config/courseId", { courseId: courseNumber });
-};
-
-export const updateHolidays = async (dates: string[]) => {
- await doSetConfigItem("/api/admin/config/holidays", {
- holidays: dates,
- });
-};
-
-export const setSlackLink = async (slackLink: string) => {
- await doSetConfigItem("/api/admin/config/slackLink", {
- slackLink: slackLink,
- });
-};
-
-const doSetConfigItem = async (path: string, body: Object): Promise => {
- await ServerCommunicator.postRequest(path, body, false);
- await useConfigStore().updateConfig();
-};
+import { type PrivateConfig, type PublicConfig, useConfigStore } from "@/stores/config";
+import { Phase } from "@/types/types";
+import { ServerCommunicator } from "@/network/ServerCommunicator";
+
+export const getPublicConfig = async (): Promise => {
+ return await ServerCommunicator.getRequest("/api/config");
+};
+
+export const getAdminConfig = async (): Promise => {
+ return await ServerCommunicator.getRequest("/api/admin/config");
+};
+
+export const setPenalties = async (
+ maxLateDaysPenalized: number,
+ gitCommitPenalty: number,
+ perDayLatePenalty: number,
+ linesChangedPerCommit: number,
+ clockForgivenessMinutes: number,
+ coveragePercent: number,
+ extraCoveragePercent: number,
+ coverageType: "LINE" | "BRANCH",
+) => {
+ await doSetConfigItem("/api/admin/config/penalties", {
+ maxLateDaysPenalized: maxLateDaysPenalized,
+ gitCommitPenalty: gitCommitPenalty,
+ perDayLatePenalty: perDayLatePenalty,
+ linesChangedPerCommit: linesChangedPerCommit,
+ clockForgivenessMinutes: clockForgivenessMinutes,
+ coveragePercent: coveragePercent,
+ extraCoveragePercent: extraCoveragePercent,
+ coverageType: coverageType,
+ });
+};
+
+export const setBanner = async (
+ message: String,
+ link: String,
+ color: String,
+ expirationTimestamp: String,
+): Promise => {
+ await doSetConfigItem("/api/admin/config/banner", {
+ bannerMessage: message,
+ bannerLink: link,
+ bannerColor: color,
+ bannerExpiration: expirationTimestamp,
+ });
+};
+
+export const setLivePhases = async (phases: Array): Promise => {
+ await doSetConfigItem("/api/admin/config/phases", { phases: phases });
+};
+
+export const setGraderShutdown = async (
+ shutdownTimestamp: string,
+ shutdownWarningHours: number,
+): Promise => {
+ if (shutdownWarningHours < 0) shutdownWarningHours = 0;
+
+ await doSetConfigItem("/api/admin/config/phases/shutdown", {
+ shutdownTimestamp: shutdownTimestamp,
+ shutdownWarningMilliseconds: Math.trunc(shutdownWarningHours * 60 * 60 * 1000), // convert to milliseconds
+ });
+};
+
+export const reloadCourseIds = async (): Promise => {
+ await doSetConfigItem("/api/admin/config/reloadCourseIds", {});
+};
+
+export const setCourseId = async (courseNumber: number) => {
+ await doSetConfigItem("/api/admin/config/courseId", { courseId: courseNumber });
+};
+
+export const updateHolidays = async (dates: string[]) => {
+ await doSetConfigItem("/api/admin/config/holidays", {
+ holidays: dates,
+ });
+};
+
+export const setSlackLink = async (slackLink: string) => {
+ await doSetConfigItem("/api/admin/config/slackLink", {
+ slackLink: slackLink,
+ });
+};
+
+const doSetConfigItem = async (path: string, body: Object): Promise => {
+ await ServerCommunicator.postRequest(path, body, false);
+ await useConfigStore().updateConfig();
+};
diff --git a/src/main/resources/frontend/src/stores/config.ts b/src/main/resources/frontend/src/stores/config.ts
index 5a5807843..67813aabe 100644
--- a/src/main/resources/frontend/src/stores/config.ts
+++ b/src/main/resources/frontend/src/stores/config.ts
@@ -1,128 +1,128 @@
-import { reactive, readonly, ref } from "vue";
-import { defineStore } from "pinia";
-import { Phase, type RubricInfo, type RubricType } from "@/types/types";
-import { getAdminConfig, getPublicConfig } from "@/services/configService";
-import { useAuthStore } from "@/stores/auth";
-
-type ImportMeta = {
- VITE_APP_BACKEND_URL: string;
-};
-
-/**
- * Config available to be read by any user
- */
-export type PublicConfig = {
- banner: {
- message: string;
- link: string;
- color: string;
- expiration: string;
- };
-
- shutdown: {
- timestamp: string;
- warningMilliseconds: number;
- };
-
- livePhases: string[];
-
- slackLink: string;
-};
-
-/**
- * Config available to be read only by admins
- */
-export type PrivateConfig = {
- penalty: {
- perDayLatePenalty: number;
- gitCommitPenalty: number;
- maxLateDaysPenalized: number;
- linesChangedPerCommit: number;
- clockForgivenessMinutes: number;
- coveragePercent: number;
- extraCoveragePercent: number;
- coverageType: "LINE" | "BRANCH";
- };
-
- courseNumber: number;
- assignments: {
- phase: Phase;
- assignmentId: number;
- rubricItems: Map;
- }[];
-
- holidays: Date[];
-};
-
-// @ts-ignore
-const env: ImportMeta = import.meta.env;
-export const useConfigStore = defineStore("config", () => {
- const publicConfig = reactive({
- banner: {
- message: "",
- link: "",
- color: "",
- expiration: "",
- },
-
- shutdown: {
- timestamp: "",
- warningMilliseconds: 0,
- },
-
- livePhases: [],
-
- slackLink: "",
- });
-
- const privateConfig = reactive({
- penalty: {
- perDayLatePenalty: -1,
- gitCommitPenalty: -1,
- maxLateDaysPenalized: -1,
- linesChangedPerCommit: -1,
- clockForgivenessMinutes: -1,
- coveragePercent: -1,
- extraCoveragePercent: -1,
- coverageType: "LINE",
- },
- courseNumber: -1,
- assignments: [],
- holidays: [],
- });
-
- const updateConfig = async () => {
- if (useAuthStore().isLoggedIn) await updateAdminConfig();
- await updatePublicConfig();
- };
-
- const updatePublicConfig = async () => {
- const latestPublicConfig: PublicConfig = await getPublicConfig();
-
- Object.assign(publicConfig, latestPublicConfig);
-
- // Backend lets the front end choose the default banner color
- if (!publicConfig.banner.color) publicConfig.banner.color = "#4fa0ff";
- };
-
- const updateAdminConfig = async () => {
- const latestAdminConfig = await getAdminConfig();
-
- console.log(latestAdminConfig);
-
- Object.assign(privateConfig, latestAdminConfig);
-
- console.log(privateConfig);
- };
-
- const backendUrl = ref(env.VITE_APP_BACKEND_URL);
-
- return {
- updateConfig,
- updatePublicConfig,
- updateAdminConfig,
- backendUrl: readonly(backendUrl),
- public: readonly(publicConfig),
- admin: readonly(privateConfig),
- };
-});
+import { reactive, readonly, ref } from "vue";
+import { defineStore } from "pinia";
+import { Phase, type RubricInfo, type RubricType } from "@/types/types";
+import { getAdminConfig, getPublicConfig } from "@/services/configService";
+import { useAuthStore } from "@/stores/auth";
+
+type ImportMeta = {
+ VITE_APP_BACKEND_URL: string;
+};
+
+/**
+ * Config available to be read by any user
+ */
+export type PublicConfig = {
+ banner: {
+ message: string;
+ link: string;
+ color: string;
+ expiration: string;
+ };
+
+ shutdown: {
+ timestamp: string;
+ warningMilliseconds: number;
+ };
+
+ livePhases: string[];
+
+ slackLink: string;
+};
+
+/**
+ * Config available to be read only by admins
+ */
+export type PrivateConfig = {
+ penalty: {
+ perDayLatePenalty: number;
+ gitCommitPenalty: number;
+ maxLateDaysPenalized: number;
+ linesChangedPerCommit: number;
+ clockForgivenessMinutes: number;
+ coveragePercent: number;
+ extraCoveragePercent: number;
+ coverageType: "LINE" | "BRANCH";
+ };
+
+ courseNumber: number;
+ assignments: {
+ phase: Phase;
+ assignmentId: number;
+ rubricItems: Map;
+ }[];
+
+ holidays: Date[];
+};
+
+// @ts-ignore
+const env: ImportMeta = import.meta.env;
+export const useConfigStore = defineStore("config", () => {
+ const publicConfig = reactive({
+ banner: {
+ message: "",
+ link: "",
+ color: "",
+ expiration: "",
+ },
+
+ shutdown: {
+ timestamp: "",
+ warningMilliseconds: 0,
+ },
+
+ livePhases: [],
+
+ slackLink: "",
+ });
+
+ const privateConfig = reactive({
+ penalty: {
+ perDayLatePenalty: -1,
+ gitCommitPenalty: -1,
+ maxLateDaysPenalized: -1,
+ linesChangedPerCommit: -1,
+ clockForgivenessMinutes: -1,
+ coveragePercent: -1,
+ extraCoveragePercent: -1,
+ coverageType: "LINE",
+ },
+ courseNumber: -1,
+ assignments: [],
+ holidays: [],
+ });
+
+ const updateConfig = async () => {
+ if (useAuthStore().isLoggedIn) await updateAdminConfig();
+ await updatePublicConfig();
+ };
+
+ const updatePublicConfig = async () => {
+ const latestPublicConfig: PublicConfig = await getPublicConfig();
+
+ Object.assign(publicConfig, latestPublicConfig);
+
+ // Backend lets the front end choose the default banner color
+ if (!publicConfig.banner.color) publicConfig.banner.color = "#4fa0ff";
+ };
+
+ const updateAdminConfig = async () => {
+ const latestAdminConfig = await getAdminConfig();
+
+ console.log(latestAdminConfig);
+
+ Object.assign(privateConfig, latestAdminConfig);
+
+ console.log(privateConfig);
+ };
+
+ const backendUrl = ref(env.VITE_APP_BACKEND_URL);
+
+ return {
+ updateConfig,
+ updatePublicConfig,
+ updateAdminConfig,
+ backendUrl: readonly(backendUrl),
+ public: readonly(publicConfig),
+ admin: readonly(privateConfig),
+ };
+});
diff --git a/src/main/resources/frontend/src/views/AdminView/ConfigView.vue b/src/main/resources/frontend/src/views/AdminView/ConfigView.vue
index c4075b25f..bd1bbcad4 100644
--- a/src/main/resources/frontend/src/views/AdminView/ConfigView.vue
+++ b/src/main/resources/frontend/src/views/AdminView/ConfigView.vue
@@ -1,228 +1,228 @@
-
-
-
-
-
-
-
-
-
-
-
- Message:
-
-
- Link:
-
- none
-
-
- Expires:
-
-
-
There is currently no banner message
-
-
-
-
-
-
-
-
-
-
-
-
- {{ phase }}
-
-
-
-
-
-
-
-
-
-
-
- Scheduled to shutdown:
- {{ readableTimestamp(config.public.shutdown.timestamp) }}
-