diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 528cc73..796413d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,6 +44,8 @@ jobs: QUARKUS_DATASOURCE_PASSWORD: ${{secrets.QUARKUS_DATASOURCE_PASSWORD}} QUARKUS_DATASOURCE_JDBC_URL: ${{secrets.QUARKUS_DATASOURCE_JDBC_URL}} + FRONTEND_USERNAME: ${{secrets.FRONTEND_USERNAME}} + FRONTEND_PASSWORD: ${{secrets.FRONTEND_PASSWORD}} deploy: needs: build diff --git a/backend/og-pom.txt b/backend/og-pom.txt deleted file mode 100644 index 80c2bf8..0000000 --- a/backend/og-pom.txt +++ /dev/null @@ -1,124 +0,0 @@ - - - 4.0.0 - org.acme - backend - 1.0.0-SNAPSHOT - - - 3.13.0 - 21 - UTF-8 - UTF-8 - quarkus-bom - io.quarkus.platform - 3.13.3 - true - 3.2.5 - - - - - - ${quarkus.platform.group-id} - ${quarkus.platform.artifact-id} - ${quarkus.platform.version} - pom - import - - - - - - - io.quarkus - quarkus-arc - - - io.quarkus - quarkus-rest - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test - - - - - - - ${quarkus.platform.group-id} - quarkus-maven-plugin - ${quarkus.platform.version} - true - - - - build - generate-code - generate-code-tests - native-image-agent - - - - - - maven-compiler-plugin - ${compiler-plugin.version} - - true - - - - maven-surefire-plugin - ${surefire-plugin.version} - - - org.jboss.logmanager.LogManager - ${maven.home} - - - - - maven-failsafe-plugin - ${surefire-plugin.version} - - - - integration-test - verify - - - - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - ${maven.home} - - - - - - - - - native - - - native - - - - false - true - - - - diff --git a/backend/pom.xml b/backend/pom.xml index e7a8180..0dc9d4c 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -98,11 +98,7 @@ io.quarkus - quarkus-smallrye-metrics - - - io.quarkus - quarkus-narayana-jta + quarkus-security-jpa diff --git a/backend/src/main/java/org/acme/TimetableResource.java b/backend/src/main/java/org/acme/TimetableResource.java deleted file mode 100644 index 2d73625..0000000 --- a/backend/src/main/java/org/acme/TimetableResource.java +++ /dev/null @@ -1,230 +0,0 @@ -package org.acme; - -import ai.timefold.solver.core.api.solver.SolverManager; -import io.smallrye.mutiny.Uni; -import jakarta.inject.Inject; -import jakarta.transaction.Transactional; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; - -import org.acme.domain.Room; -import org.acme.domain.Student; -import org.acme.domain.Timetable; -import org.acme.domain.Unit; - -import java.time.DayOfWeek; -import java.time.Duration; -import java.time.LocalTime; -import java.util.List; -import java.util.concurrent.ExecutionException; - -import java.util.UUID; - -/** - * Entry to the timetabling program. - * Receives a timetabling problem and outputs the solution - * with the best optimised scores according to the provided constraints. - * - * @author Jet Edge - */ -@Path("/timetabling") -public class TimetableResource { - - @Inject - SolverManager solverManager; - - @POST - @Transactional - public Timetable handleRequest(Timetable problem) throws ExecutionException, InterruptedException { - UUID uuid = UUID.randomUUID(); - String uuidAsString = uuid.toString(); - - System.out.println("Your UUID is: " + uuidAsString); - String name = "Job" + uuidAsString; - - findByCampusAndDelete(problem.campusName); - - // generate solution timetable with TimeFold Solver - Timetable solution = solverManager.solve(name, problem).getFinalBestSolution(); - - // store the solution timetable to the database - solution.persist(); - - return solution; - } - - @Path("/view") - @GET - @Produces(MediaType.APPLICATION_JSON) - public List view() { - return Timetable.listAll(); - } - - - /** - * Takes a list of Units as input to updates exisiting timetable once users - * make drag-and-drop modifications in frontend - * - * @param updatedUnits List of all Unit updates - * @return A Response object indicating status - */ - @Path("/update") - @PUT - @Transactional - @Consumes(MediaType.APPLICATION_JSON) - public Response timetableUpdate(List updatedUnits) { - List dbUnits = Unit.listAll(); - for (Unit updatedUnit : updatedUnits) { - if (unitUpdate(updatedUnit, dbUnits) == null) { - return Response.serverError().build(); - } - } - return Response.ok().build(); - } - - - /** - * Update the time/location of a unit - * - * @param updatedUnit Unit object with updated time/location - * @param dbUnits List of all Unit objects in the database - * @return The updated Unit, null otherwise - */ - @PUT - @Transactional - @Consumes(MediaType.APPLICATION_JSON) - public Unit unitUpdate(Unit updatedUnit, List dbUnits) { - - // find existing Unit obj in db by unitId - for (Unit unit : dbUnits) { - if (updatedUnit.unitId == unit.unitId) { - assert(unit.isPersistent()); - // update day of week - unit.dayOfWeek = updatedUnit.dayOfWeek; - // update startTime - unit.startTime = updatedUnit.startTime; - // if room is different - if (unit.room.roomCode != updatedUnit.room.roomCode - || !unit.room.buildingId.equals(updatedUnit.room.buildingId)) { - // update room - Room newRoom; - if ((newRoom = findRoom(updatedUnit.room)) == null) { - throw new WebApplicationException("Room with building ID " + updatedUnit.room.buildingId + ", and room code " + updatedUnit.room.roomCode + " not found", 404); - } - unit.room = newRoom; - } - return unit; - } - } - return null; - } - - /** - * Find existing Room object inside the database with - * desired buildingId and roomCode - * - * @param inputRoom Room object with buildingId and roomCode of desired Room - * @return Room object meeting the input criteria, null otherwise - */ - public Room findRoom(Room inputRoom) { - List rooms = Room.listAll(); - for (Room room : rooms) { - if (room.roomCode.equals(inputRoom.roomCode) - && room.buildingId.equals(inputRoom.buildingId)) { - assert(room.isPersistent()); - return room; - } - } - return null; - } - - public void findByCampusAndDelete(String campusName) { - List timetables = Timetable.listAll(); - for (Timetable timetable : timetables) { - if (campusName.equals(timetable.campusName)) { - timetable.delete(); - } - } - } - - @GET - @Transactional - @Produces(MediaType.APPLICATION_JSON) - public Timetable solveExample() throws ExecutionException, InterruptedException { - - Student a = new Student("a"); - Student b = new Student("b"); - Student c = new Student("c"); - Student d = new Student("d"); - Student e = new Student("e"); - Student f = new Student("f"); - Student g = new Student("g"); - Student h = new Student("h"); - Student i = new Student("i"); - - Room r1 = new Room("Room1", "building A", "campus A", 2, true); - Room r2 = new Room("Room2", "building B", "campus A", 4, false); - Room r3 = new Room("Room3", "building A", "campus B", 4, false); - - Unit u1 = new Unit(1, "1", "Course A", Duration.ofHours(2), List.of(a, b), true); - Unit u2 = new Unit(2, "2", "Course A", Duration.ofHours(2), List.of(a, c, d, e), true); - Unit u3 = new Unit(3, "3", "Course B", Duration.ofHours(2), List.of(f, g, h, i), false); - Unit u4 = new Unit(4, "4", "Course C", Duration.ofHours(2), List.of(a, b), false); - - var problem = new Timetable("Campus A", - List.of( - u1, u2, u3, u4 -// new Unit(5, "5", Duration.ofHours(2), List.of(c, d, e)), -// new Unit(6, "6", Duration.ofHours(2), List.of(f, g, h, i)) - ), - - List.of( - DayOfWeek.MONDAY, - DayOfWeek.TUESDAY, - DayOfWeek.WEDNESDAY -// DayOfWeek.THURSDAY, -// DayOfWeek.FRIDAY - ), - - List.of( - LocalTime.of(15, 0) -// LocalTime.of(17, 0) -// LocalTime.of(16,0), -// LocalTime.of(23,0) - ), - List.of(r1, r2, r3) - ); - - /* - * During this solving phase, new Unit objects will be created with the - * alloted date and Room assignment. - * - * Currently, the 'old' Unit objects in the 'problem' variable and the - * 'new' Unit objects in the 'solution' variable are stored as different - * Units in the database due to our inability to control the behaviour - * of solverManager.solve - * - * i.e. after solving, there will be 2 copies of each Unit in the - * database, where the 'old' Unit has the list of students but no - * timetable assignment, while the 'new' Unit does not have the list - * of students enrolled, but does have the assigned date and room - */ - - findByCampusAndDelete(problem.campusName); - - Timetable solution = solverManager.solve("job 1", problem).getFinalBestSolution(); - - solution.persist(); - // saves the solution timetable and all related entities to database - - return solution; - } - -} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/domain/Room.java b/backend/src/main/java/org/acme/domain/Room.java index 883e39e..1d063ee 100644 --- a/backend/src/main/java/org/acme/domain/Room.java +++ b/backend/src/main/java/org/acme/domain/Room.java @@ -1,18 +1,13 @@ package org.acme.domain; -import java.util.ArrayList; -import java.util.List; - +import ai.timefold.solver.core.api.domain.lookup.PlanningId; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -import ai.timefold.solver.core.api.domain.lookup.PlanningId; import io.quarkus.hibernate.orm.panache.PanacheEntity; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.OneToMany; +import jakarta.persistence.*; + +import java.util.ArrayList; +import java.util.List; /** * Represents a room. @@ -21,7 +16,7 @@ */ @Entity public class Room extends PanacheEntity { - + @PlanningId public String roomCode; @@ -55,11 +50,11 @@ public Room() { /** * Creates a room with its ID and capacity. * - * @param id The room’s id. - * @param buildingId The building that the room belongs to. - * @param buildingId The campus that the room belongs to. - * @param capacity The room's capacity. - * @param isLab Whether the room is a laboratory. + * @param id The room’s id. + * @param buildingId The building that the room belongs to. + * @param campus The campus that the room belongs to. + * @param capacity The room's capacity. + * @param isLab Whether the room is a laboratory. */ public Room(String id, String buildingId, String campus, int capacity, boolean isLab) { this.roomCode = id; diff --git a/backend/src/main/java/org/acme/domain/Student.java b/backend/src/main/java/org/acme/domain/Student.java index 614513a..7804ba7 100644 --- a/backend/src/main/java/org/acme/domain/Student.java +++ b/backend/src/main/java/org/acme/domain/Student.java @@ -1,19 +1,13 @@ package org.acme.domain; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - import io.quarkus.hibernate.orm.panache.PanacheEntity; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; +import jakarta.persistence.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; /** * Represents a student. @@ -21,16 +15,16 @@ * @author Jet Edge */ @Entity -public class Student extends PanacheEntity{ +public class Student extends PanacheEntity { public String name; @JsonIgnoreProperties("students") @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable( - name = "student_unit", - joinColumns = @JoinColumn(name = "student_id"), - inverseJoinColumns = @JoinColumn(name = "unit_id") + name = "student_unit", + joinColumns = @JoinColumn(name = "student_id"), + inverseJoinColumns = @JoinColumn(name = "unit_id") ) @JsonIgnore public List units = new ArrayList(); diff --git a/backend/src/main/java/org/acme/domain/Timetable.java b/backend/src/main/java/org/acme/domain/Timetable.java index 07a139d..325b0ea 100644 --- a/backend/src/main/java/org/acme/domain/Timetable.java +++ b/backend/src/main/java/org/acme/domain/Timetable.java @@ -6,23 +6,16 @@ import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import io.quarkus.hibernate.orm.panache.PanacheEntity; -import jakarta.persistence.CascadeType; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; +import jakarta.persistence.*; import java.time.DayOfWeek; import java.time.LocalTime; import java.util.ArrayList; import java.util.List; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * Represents a timetable, the solution from the program. * @@ -44,16 +37,16 @@ public class Timetable extends PanacheEntity { /* * Rooms can belong to multiple timetables because timetables are generated - * on a per-campus basis, and although each room can only belong to one + * on a per-campus basis, and although each room can only belong to one * campus, the user may choose to generate multiple timetables for each * campus, hence the many-to-many relationship */ @JsonIgnoreProperties("timetables") @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable( - name = "room_timetable", - joinColumns = @JoinColumn(name = "timetable_id"), - inverseJoinColumns = @JoinColumn(name = "room_id") + name = "room_timetable", + joinColumns = @JoinColumn(name = "timetable_id"), + inverseJoinColumns = @JoinColumn(name = "room_id") ) @ProblemFactCollectionProperty @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) @@ -68,9 +61,9 @@ public class Timetable extends PanacheEntity { @JsonIgnoreProperties("timetables") @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable( - name = "unit_timetable", - joinColumns = @JoinColumn(name = "timetable_id"), - inverseJoinColumns = @JoinColumn(name = "unit_id") + name = "unit_timetable", + joinColumns = @JoinColumn(name = "timetable_id"), + inverseJoinColumns = @JoinColumn(name = "unit_id") ) @PlanningEntityCollectionProperty public List units; diff --git a/backend/src/main/java/org/acme/domain/Unit.java b/backend/src/main/java/org/acme/domain/Unit.java index 8bc6d00..eb4cef6 100644 --- a/backend/src/main/java/org/acme/domain/Unit.java +++ b/backend/src/main/java/org/acme/domain/Unit.java @@ -3,14 +3,10 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.lookup.PlanningId; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import io.quarkus.hibernate.orm.panache.PanacheEntity; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Transient; +import jakarta.persistence.*; import java.time.DayOfWeek; import java.time.Duration; @@ -18,9 +14,6 @@ import java.util.ArrayList; import java.util.List; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - /** * Represents a unit. * @@ -55,8 +48,8 @@ public class Unit extends PanacheEntity { /* * currently each unit only has 1 'slot' on the timetable, so it can only - * be associated with one room, but in the final product, we would most - * likely have to change this to a many-to-many relationship + * be associated with one room, but in the final product, we would most + * likely have to change this to a many-to-many relationship * i.e. list of Rooms, because we might want to separate lecture/tutorial * etc. */ @@ -128,7 +121,7 @@ public Unit(int unitID, String name, String course, Duration duration, List students, boolean wantsLab, Room room) { this.unitId = unitID; @@ -194,7 +187,7 @@ public List getStudents() { public void setStudents(List students) { this.students = students; - this.setStudentsUnits();; + this.setStudentsUnits(); } /** diff --git a/backend/src/main/java/org/acme/domain/RoomResource.java b/backend/src/main/java/org/acme/rest/RoomResource.java similarity index 76% rename from backend/src/main/java/org/acme/domain/RoomResource.java rename to backend/src/main/java/org/acme/rest/RoomResource.java index c86a1a9..f31b2af 100644 --- a/backend/src/main/java/org/acme/domain/RoomResource.java +++ b/backend/src/main/java/org/acme/rest/RoomResource.java @@ -1,17 +1,16 @@ -package org.acme.domain; - -import java.util.List; +package org.acme.rest; +import jakarta.annotation.security.RolesAllowed; import jakarta.transaction.Transactional; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.acme.domain.Room; + +import java.util.List; @Path("/rooms") +@RolesAllowed({"user"}) public class RoomResource { @GET diff --git a/backend/src/main/java/org/acme/domain/StudentResource.java b/backend/src/main/java/org/acme/rest/StudentResource.java similarity index 76% rename from backend/src/main/java/org/acme/domain/StudentResource.java rename to backend/src/main/java/org/acme/rest/StudentResource.java index 996d015..7c75d27 100644 --- a/backend/src/main/java/org/acme/domain/StudentResource.java +++ b/backend/src/main/java/org/acme/rest/StudentResource.java @@ -1,15 +1,12 @@ -package org.acme.domain; - -import java.util.List; +package org.acme.rest; import jakarta.transaction.Transactional; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.acme.domain.Student; + +import java.util.List; @Path("/students") public class StudentResource { diff --git a/backend/src/main/java/org/acme/domain/UnitResource.java b/backend/src/main/java/org/acme/rest/UnitResource.java similarity index 76% rename from backend/src/main/java/org/acme/domain/UnitResource.java rename to backend/src/main/java/org/acme/rest/UnitResource.java index 8455d46..80b269b 100644 --- a/backend/src/main/java/org/acme/domain/UnitResource.java +++ b/backend/src/main/java/org/acme/rest/UnitResource.java @@ -1,17 +1,16 @@ -package org.acme.domain; - -import java.util.List; +package org.acme.rest; +import jakarta.annotation.security.RolesAllowed; import jakarta.transaction.Transactional; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; +import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import org.acme.domain.Unit; + +import java.util.List; @Path("/units") +@RolesAllowed({"user"}) public class UnitResource { @GET @Produces(MediaType.APPLICATION_JSON) diff --git a/backend/src/main/java/org/acme/security/jpa/Startup.java b/backend/src/main/java/org/acme/security/jpa/Startup.java new file mode 100644 index 0000000..53d4fa3 --- /dev/null +++ b/backend/src/main/java/org/acme/security/jpa/Startup.java @@ -0,0 +1,24 @@ +package org.acme.security.jpa; + +import io.quarkus.runtime.StartupEvent; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.transaction.Transactional; +import org.eclipse.microprofile.config.Config; + +@Singleton +public class Startup { + @Inject + Config config; + + @Transactional + public void loadUsers(@Observes StartupEvent evt) { + String username = config.getValue("frontend.username", String.class); + String password = config.getValue("frontend.password", String.class); + + // reset and load user + User.deleteAll(); + User.add(username, password, "user"); + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/security/jpa/User.java b/backend/src/main/java/org/acme/security/jpa/User.java new file mode 100644 index 0000000..d2f384b --- /dev/null +++ b/backend/src/main/java/org/acme/security/jpa/User.java @@ -0,0 +1,37 @@ +package org.acme.security.jpa; + +import io.quarkus.elytron.security.common.BcryptUtil; +import io.quarkus.hibernate.orm.panache.PanacheEntity; +import io.quarkus.security.jpa.Password; +import io.quarkus.security.jpa.Roles; +import io.quarkus.security.jpa.UserDefinition; +import io.quarkus.security.jpa.Username; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +@Entity +@Table(name = "frontend_user") +@UserDefinition +public class User extends PanacheEntity { + @Username + public String username; + @Password + public String password; + @Roles + public String role; + + /** + * Adds a new user to the database + * + * @param username the username + * @param password the unencrypted password (it is encrypted with bcrypt) + * @param role the comma-separated roles + */ + public static void add(String username, String password, String role) { + User user = new User(); + user.username = username; + user.password = BcryptUtil.bcryptHash(password); + user.role = role; + user.persist(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/org/acme/security/jpa/UserResource.java b/backend/src/main/java/org/acme/security/jpa/UserResource.java new file mode 100644 index 0000000..d22b125 --- /dev/null +++ b/backend/src/main/java/org/acme/security/jpa/UserResource.java @@ -0,0 +1,17 @@ +package org.acme.security.jpa; + +import jakarta.annotation.security.RolesAllowed; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.SecurityContext; + +@Path("/login") +public class UserResource { + + @GET + @RolesAllowed({"user"}) + public String me(@Context SecurityContext securityContext) { + return securityContext.getUserPrincipal().getName(); + } +} \ No newline at end of file diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index cf27fd9..ea01c7f 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -2,7 +2,9 @@ quarkus.http.port=${PORT:8080} # The solver runs only for 5 seconds to avoid a HTTP timeout in this simple implementation. # It's recommended to run for at least 5 minutes ("5m") otherwise. -quarkus.timefold.solver.termination.spent-limit=60s + +quarkus.timefold.solver.termination.spent-limit=15s + quarkus.http.cors=true quarkus.http.cors.origins=* quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS @@ -15,7 +17,7 @@ quarkus.datasource.password = ${QUARKUS_DATASOURCE_PASSWORD} quarkus.datasource.jdbc.url = ${QUARKUS_DATASOURCE_JDBC_URL} # drop and create the database at startup (use `update` to only update the schema) -quarkus.hibernate-orm.database.generation = create-drop +quarkus.hibernate-orm.database.generation = update # transaction timeout quarkus.transaction-manager.default-transaction-timeout=320s @@ -72,3 +74,7 @@ quarkus.log.file.enable=true quarkus.log.file.path=logs/quarkus.log quarkus.log.file.rotation.max-file-size=10M quarkus.log.file.rotation.max-backup-index=10 + +# log in details for frontend +frontend.username=${FRONTEND_USERNAME} +frontend.password=${FRONTEND_PASSWORD} \ No newline at end of file diff --git a/backend/src/test/java/org/acme/security/jpa/JpaSecurityRealmTest.java b/backend/src/test/java/org/acme/security/jpa/JpaSecurityRealmTest.java new file mode 100644 index 0000000..bc0dfd6 --- /dev/null +++ b/backend/src/test/java/org/acme/security/jpa/JpaSecurityRealmTest.java @@ -0,0 +1,79 @@ +package org.acme.security.jpa; + +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.apache.http.HttpStatus; +import org.eclipse.microprofile.config.Config; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.get; +import static io.restassured.RestAssured.given; +import static org.hamcrest.core.Is.is; + +@QuarkusTest +public class JpaSecurityRealmTest { + @Inject + Config config; + + @Test + void shouldNotAccessLoginWhenAnonymous() { + get("/login") + .then() + .statusCode(HttpStatus.SC_UNAUTHORIZED); + } + + @Test + void shouldNotAccessRoomsWhenAnonymous() { + get("/rooms") + .then() + .statusCode(HttpStatus.SC_UNAUTHORIZED); + } + + @Test + void shouldNotAccessUnitsWhenAnonymous() { + get("/units") + .then() + .statusCode(HttpStatus.SC_UNAUTHORIZED); + } + + @Test + void shouldAccessLoginWhenUserAuthenticated() { + String username = config.getValue("frontend.username", String.class); + String password = config.getValue("frontend.password", String.class); + + given() + .auth().preemptive().basic(username, password) + .when() + .get("/login") + .then() + .statusCode(HttpStatus.SC_OK) + .body(is(username)); + } + + @Test + void shouldAccessRoomsWhenUserAuthenticated() { + String username = config.getValue("frontend.username", String.class); + String password = config.getValue("frontend.password", String.class); + + given() + .auth().preemptive().basic(username, password) + .when() + .get("/rooms") + .then() + .statusCode(HttpStatus.SC_OK); + } + + @Test + void shouldAccessUnitsWhenUserAuthenticated() { + String username = config.getValue("frontend.username", String.class); + String password = config.getValue("frontend.password", String.class); + + given() + .auth().preemptive().basic(username, password) + .when() + .get("/units") + .then() + .statusCode(HttpStatus.SC_OK); + } + +} \ No newline at end of file diff --git a/backend/src/test/java/org/acme/solver/TimetableConstraintProviderTest.java b/backend/src/test/java/org/acme/solver/TimetableConstraintProviderTest.java index 979e96e..4d29887 100644 --- a/backend/src/test/java/org/acme/solver/TimetableConstraintProviderTest.java +++ b/backend/src/test/java/org/acme/solver/TimetableConstraintProviderTest.java @@ -1,18 +1,16 @@ package org.acme.solver; +import ai.timefold.solver.test.api.score.stream.ConstraintVerifier; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import org.acme.domain.*; +import org.junit.jupiter.api.Test; + import java.time.DayOfWeek; import java.time.Duration; import java.time.LocalTime; import java.util.List; -import org.acme.domain.*; - -import jakarta.inject.Inject; -import ai.timefold.solver.test.api.score.stream.ConstraintVerifier; -import org.junit.jupiter.api.Test; - -import io.quarkus.test.junit.QuarkusTest; - @QuarkusTest public class TimetableConstraintProviderTest { private static final Room ROOM1 = new Room("1", "building A", "campus A", 2, true); @@ -37,7 +35,7 @@ public class TimetableConstraintProviderTest { void studentConflict() { Unit firstUnit = new Unit(1, "unit1", "Course A", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT1), true, ROOM1); Unit conflictingUnit = new Unit(2, "unit2", "Course A", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT1), false, ROOM2); - Unit nonConflictingUnit = new Unit(3, "unit3","Course B", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT2), true, ROOM3); + Unit nonConflictingUnit = new Unit(3, "unit3", "Course B", DAY_OF_WEEK, START_TIME, DURATION, List.of(STUDENT2), true, ROOM3); ConflictingUnit conflictingUnitPair = new ConflictingUnit(firstUnit, conflictingUnit, 1); constraintVerifier.verifyThat(TimetableConstraintProvider::studentConflict) diff --git a/frontend/index.html b/frontend/index.html index 8076bf4..07dd708 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,10 +2,10 @@ - + - Vite + React + TS + Timetabling for VIT
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8c492fb..f58f72d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22,7 +22,7 @@ "react-dom": "^18.3.1", "react-router-dom": "^6.26.1", "read-excel-file": "^5.8.5", - "serve": "^6.5.8", + "serve": "^14.2.3", "vis-timeline": "^7.7.3" }, "devDependencies": { @@ -2185,6 +2185,12 @@ "node": ">=10.0.0" } }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "license": "MIT" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -2198,27 +2204,6 @@ "node": ">= 0.6" } }, - "node_modules/accepts/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -2242,15 +2227,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2268,44 +2244,39 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", - "license": "MIT", - "optional": true, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", "dependencies": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "engines": { - "node": ">=0.10.0" + "string-width": "^4.1.0" } }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "license": "BSD-3-Clause OR MIT", - "engines": { - "node": ">=0.4.2" - } + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, - "node_modules/ansi-align": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha512-TdlOggdA/zURfMYa7ABC66j+oqfMew58KpJMbUlH3bcZP1b+cBHIHDDn5uH9INsxrHBPjsqM0tDB4jPTF/vgJA==", - "license": "ISC", + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { - "string-width": "^2.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2344,9 +2315,9 @@ "license": "MIT" }, "node_modules/arg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz", - "integrity": "sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, "node_modules/argparse": { @@ -2356,44 +2327,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/args": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/args/-/args-4.0.0.tgz", - "integrity": "sha512-4b7lVF58nlo7sNtq8s2OueroOY/UHn0Nt/NVjsx9zn28u6yDVb9bQ/uy/5jKtHCbUDil4MlMyDLF5+OHEgnTug==", - "license": "MIT", - "dependencies": { - "camelcase": "5.0.0", - "chalk": "2.3.2", - "leven": "2.1.0", - "mri": "1.1.0" - }, - "engines": { - "node": "> 4.0.0" - } - }, - "node_modules/args/node_modules/chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/args/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2414,12 +2347,6 @@ "node": ">=12" } }, - "node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "license": "MIT" - }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -2439,25 +2366,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/basic-auth": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.0.tgz", - "integrity": "sha512-BNzMKPNNIV3+C02TSO9oILCAPS/htf/upL+TRV2gOnPwSQbFKuL249fwVozDNbSq/AeyoRoVeQgJcK4sN77aHg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/basic-auth/node_modules/safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "license": "MIT" }, "node_modules/bluebird": { @@ -2467,37 +2375,43 @@ "license": "MIT" }, "node_modules/boxen": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", - "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", "license": "MIT", "dependencies": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" }, "engines": { - "node": ">=4" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boxen/node_modules/camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==", + "node_modules/boxen/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "license": "MIT", "engines": { - "node": ">=4" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2579,12 +2493,15 @@ } }, "node_modules/camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", "license": "MIT", "engines": { - "node": ">=6" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/caniuse-lite": { @@ -2608,20 +2525,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/chai": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", @@ -2653,6 +2556,91 @@ "node": ">=4" } }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk-template/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/chalk-template/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk-template/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/chalk/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2673,47 +2661,32 @@ } }, "node_modules/cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha512-3Fo5wu8Ytle8q9iCzS4D2MWVL2X7JVWRiS1BnXbTFDhS9c/REkM9vd1AmabsoZoY5/dGi5TT9iKL8Kb6DeBRQg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/clipboardy": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-1.2.3.tgz", - "integrity": "sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", "license": "MIT", "dependencies": { - "arch": "^2.1.0", - "execa": "^0.8.0" + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", - "license": "ISC", - "optional": true, - "dependencies": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - } - }, - "node_modules/cliui/node_modules/wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q==", - "license": "MIT/X11", - "optional": true, - "engines": { - "node": ">=0.4.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/clsx": { @@ -2806,13 +2779,12 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, "license": "MIT" }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -2850,7 +2822,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2874,15 +2845,6 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, - "node_modules/dargs": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-5.1.0.tgz", - "integrity": "sha512-Mr5OxT76pdJv7BbLq3hF1gSP8zxlCyDA1afj2Iab2MPKmdAKV+aKGC8YJv6cT8ItdFXAf798JJQD7jmbWZcYTQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", @@ -2900,16 +2862,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -2936,53 +2888,6 @@ "dev": true, "license": "MIT" }, - "node_modules/depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha512-Jlk9xvkTDGXwZiIDyoM7+3AsuvJVoyOpRupvEVy9nX3YO3/ieZxhlgh8GpLNZ8AY7HjO6y2YwpMSh1ejhu3uIw==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", - "license": "MIT" - }, - "node_modules/detect-port": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.2.3.tgz", - "integrity": "sha512-IDbrX6PxqnYy8jV4wSHBaJlErYKTJvW8OQb9F7xivl1iQLqiUYHGa+nZ61Do6+N5uuOn/pReXKNqI9rUn04vug==", - "license": "MIT", - "dependencies": { - "address": "^1.0.1", - "debug": "^2.6.0" - }, - "bin": { - "detect": "bin/detect-port", - "detect-port": "bin/detect-port" - }, - "engines": { - "node": ">= 4.2.1" - } - }, - "node_modules/detect-port/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/detect-port/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/dexie": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.8.tgz", @@ -3032,10 +2937,10 @@ "readable-stream": "^2.0.2" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, "node_modules/electron-to-chromium": { @@ -3045,14 +2950,11 @@ "dev": true, "license": "ISC" }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" }, "node_modules/error-ex": { "version": "1.3.2", @@ -3112,12 +3014,6 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3393,98 +3289,33 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/execa": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", - "integrity": "sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", - "license": "MIT", - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/execa/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "license": "ISC", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "license": "MIT", "dependencies": { - "shebang-regex": "^1.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" + "node": ">=10" }, - "bin": { - "which": "bin/which" + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "license": "ISC" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -3531,6 +3362,21 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-url-parser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", + "integrity": "sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==", + "license": "MIT", + "dependencies": { + "punycode": "^1.3.2" + } + }, + "node_modules/fast-url-parser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, "node_modules/fastq": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", @@ -3566,15 +3412,6 @@ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==", "license": "MIT" }, - "node_modules/filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3632,15 +3469,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -3700,12 +3528,15 @@ } }, "node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/glob-parent": { @@ -3768,38 +3599,6 @@ "dev": true, "license": "MIT" }, - "node_modules/handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha512-yrsPfXULrvTP3Aw+zLpett0LUyv2PS/k1jj2UCpYkFElPhMEhspxrCAl4hKGt+vyDjvpxleyYUPF4AMSjCdnhQ==", - "license": "MIT", - "dependencies": { - "async": "^1.4.0", - "optimist": "^0.6.1", - "source-map": "^0.4.4" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^2.6" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==", - "license": "BSD-3-Clause", - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -3836,34 +3635,13 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha512-STnYGcKMXL9CGdtpeTFnLmgMSHTTNQJSHxiC4DETHKf934Q160Ht5pljrNeH24S0O9xUN+9vsDJZdZtk5js6Ww==", - "license": "MIT", - "dependencies": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "license": "ISC" - }, - "node_modules/iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "license": "MIT", + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", "engines": { - "node": ">=0.10.0" + "node": ">=10.17.0" } }, "node_modules/ignore": { @@ -3920,25 +3698,12 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, - "node_modules/ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha512-rBtCAQAJm8A110nbwn6YdveUnuZH3WrC36IwkRXxDnq53JvXA2NVQvB7IHyKomxK1MJ4VDNw3UtFDdXQ+AvLYA==", - "license": "MIT" - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "license": "MIT" }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "license": "MIT", - "optional": true - }, "node_modules/is-core-module": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", @@ -3954,6 +3719,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3965,12 +3745,12 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/is-glob": { @@ -4006,22 +3786,40 @@ "node": ">=8" } }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/isarray": { @@ -4162,38 +3960,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4246,16 +4012,6 @@ "dev": true, "license": "MIT" }, - "node_modules/longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4298,6 +4054,12 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4308,54 +4070,6 @@ "node": ">= 8" } }, - "node_modules/micro": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/micro/-/micro-9.3.1.tgz", - "integrity": "sha512-83uimpPJqfwkfKvJl2WWontBlV3hmzrIgyJ+L2uhDXKNk7Ll+/ezK3zBz7TljubpKPqjM0JdT2Ker4MTPmhjgA==", - "license": "MIT", - "dependencies": { - "arg": "2.0.0", - "chalk": "2.4.0", - "content-type": "1.0.4", - "is-stream": "1.1.0", - "raw-body": "2.3.2" - }, - "bin": { - "micro": "bin/micro.js" - } - }, - "node_modules/micro-compress": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/micro-compress/-/micro-compress-1.0.0.tgz", - "integrity": "sha512-1XM/LoxpfCq9mOgtuT5vwumSGXgCziaT+pZPT1o5Jx9CDc0o6YPma6ACyPuLjchnyi8o80AON7EAw5ZnKQLQyQ==", - "license": "MIT", - "dependencies": { - "compression": "^1.6.2" - } - }, - "node_modules/micro/node_modules/chalk": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", - "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/micro/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -4370,15 +4084,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "license": "MIT", - "bin": { - "mime": "cli.js" - } - }, "node_modules/mime-db": { "version": "1.53.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz", @@ -4389,31 +4094,39 @@ } }, "node_modules/mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { - "mime-db": "~1.33.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/mime-types/node_modules/mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -4423,10 +4136,13 @@ } }, "node_modules/minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==", - "license": "MIT" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/moment": { "version": "2.30.1", @@ -4437,15 +4153,6 @@ "node": "*" } }, - "node_modules/mri": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.0.tgz", - "integrity": "sha512-NbJtWIE2QEVbr9xQHXBY92fxX0Tu8EsS9NBwz7Qn3zoeuvcbP3LzBJw3EUJDpfb9IY8qnZvFSWIepeEFQga28w==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4500,34 +4207,16 @@ "dev": true, "license": "MIT" }, - "node_modules/node-version": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/node-version/-/node-version-1.1.3.tgz", - "integrity": "sha512-rEwE51JWn0yN3Wl5BXeGn5d52OGbSXzWiiXRjAQeuyvcGKyvuSILW2rb3G7Xh+nexzLwhTpek6Ehxd6IjvHePg==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "license": "MIT", "dependencies": { - "path-key": "^2.0.0" + "path-key": "^3.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/object-assign": { @@ -4539,18 +4228,6 @@ "node": ">=0.10.0" } }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/on-headers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", @@ -4560,32 +4237,19 @@ "node": ">= 0.8" } }, - "node_modules/openssl-self-signed-certificate": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/openssl-self-signed-certificate/-/openssl-self-signed-certificate-1.1.6.tgz", - "integrity": "sha512-OJpqrdIYir5DznRSChodZqvpOx2fsT6sNA4PD9kYOMkIPgjfNYTWDzgac0drm+ZR0yI5Un7tE8bsmP1nZpx23Q==", - "license": "MIT" - }, - "node_modules/opn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", - "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "license": "MIT", "dependencies": { - "is-wsl": "^1.1.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==", - "license": "MIT/X11", - "dependencies": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/optionator": { @@ -4606,15 +4270,6 @@ "node": ">= 0.8.0" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4703,7 +4358,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4715,6 +4369,12 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-to-regexp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz", + "integrity": "sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==", + "license": "MIT" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -4760,15 +4420,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/postcss": { "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", @@ -4841,17 +4492,10 @@ "@egjs/hammerjs": "^2.0.17" } }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "license": "ISC" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4879,29 +4523,14 @@ "license": "MIT" }, "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha512-Ss0DsBxqLxCmQkfG5yazYhtbVVTJqS9jTsZG2lhrNwqzOk2SUC7O/NB/M//CkEBqsrtmlNgJCPccJGuYSFr6Vg==", - "license": "MIT", - "dependencies": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -4917,15 +4546,6 @@ "rc": "cli.js" } }, - "node_modules/rc/node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -5078,14 +4698,13 @@ "node": ">=0.10.0" } }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", - "optional": true, "engines": { - "node": ">=0.10" + "node": ">=0.10.0" } }, "node_modules/resolve": { @@ -5121,20 +4740,7 @@ "dev": true, "license": "MIT", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", - "license": "MIT", - "optional": true, - "dependencies": { - "align-text": "^0.1.1" - }, - "engines": { + "iojs": ">=1.0.0", "node": ">=0.10.0" } }, @@ -5223,168 +4829,101 @@ "semver": "bin/semver.js" } }, - "node_modules/send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" + "node_modules/serve": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.3.tgz", + "integrity": "sha512-VqUFMC7K3LDGeGnJM9h56D3XGKb6KGgOw0cVNtA26yYXHCcpxf3xwCTUaQoWlVS7i8Jdh3GjQkOB23qsXyjoyQ==", + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.12.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.7.4", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.5", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 14" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/serve-handler": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", + "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", "license": "MIT", "dependencies": { - "ms": "2.0.0" + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "fast-url-parser": "1.1.3", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "2.2.1", + "range-parser": "1.2.0" } }, - "node_modules/send/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "node_modules/serve-handler/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/send/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "node_modules/serve-handler/node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve": { - "version": "6.5.8", - "resolved": "https://registry.npmjs.org/serve/-/serve-6.5.8.tgz", - "integrity": "sha512-GZYlJz7f6E7Xq6xbg1rTSvQQV9x4v/yYB/sum6egzSBLa/mdk1PViDSX2JvL0Me83sxu3JpEpQELfakDKbGcrw==", - "license": "MIT", - "dependencies": { - "args": "4.0.0", - "basic-auth": "2.0.0", - "bluebird": "3.5.1", - "boxen": "1.3.0", - "chalk": "2.4.1", - "clipboardy": "1.2.3", - "dargs": "5.1.0", - "detect-port": "1.2.3", - "filesize": "3.6.1", - "fs-extra": "6.0.1", - "handlebars": "4.0.11", - "ip": "1.1.5", - "micro": "9.3.1", - "micro-compress": "1.0.0", - "mime-types": "2.1.18", - "node-version": "1.1.3", - "openssl-self-signed-certificate": "1.1.6", - "opn": "5.3.0", - "path-is-inside": "1.0.2", - "path-type": "3.0.0", - "send": "0.16.2", - "update-check": "1.5.1" - }, - "bin": { - "serve": "bin/serve.js" + "dependencies": { + "mime-db": "~1.33.0" }, "engines": { - "node": ">=7.6.0" + "node": ">= 0.6" } }, - "node_modules/serve/node_modules/bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", - "license": "MIT" - }, - "node_modules/serve/node_modules/chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "node_modules/serve/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=4" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/serve/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/serve/node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", "license": "MIT", "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/serve/node_modules/fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "node_modules/serve/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/serve/node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "license": "MIT", - "dependencies": { - "pify": "^3.0.0" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/serve/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } + "node_modules/serve/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/setimmediate": { "version": "1.0.5", @@ -5392,17 +4931,10 @@ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "license": "MIT" }, - "node_modules/setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha512-9jphSf3UbIgpOX/RKvX02iw/rN2TKdusnsPpGfO/rkcsrd+IRqgHZb4VGnmL0Cynps8Nj2hN45wsi30BzrHDIw==", - "license": "ISC" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -5415,7 +4947,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5470,15 +5001,6 @@ "dev": true, "license": "MIT" }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/std-env": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", @@ -5496,44 +5018,53 @@ } }, "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string-width/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "license": "MIT", "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/string-width/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "license": "MIT", "dependencies": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -5542,13 +5073,13 @@ "node": ">=8" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, "node_modules/strip-json-comments": { @@ -5594,96 +5125,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/term-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", - "integrity": "sha512-7dPUZQGy/+m3/wjVz3ZW5dobSoD/02NxJpoXUX0WIyjfVS3l0c+b/+9phIDFA7FHzkYtwtMFgeGZ/Y8jVTeqQQ==", - "license": "MIT", - "dependencies": { - "execa": "^0.7.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/term-size/node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", - "license": "MIT", - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "node_modules/term-size/node_modules/execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/term-size/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "license": "ISC", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/term-size/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/term-size/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/term-size/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/term-size/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "license": "ISC" - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5783,6 +5224,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", @@ -5821,33 +5274,6 @@ } } }, - "node_modules/uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha512-qLq/4y2pjcU3vhlhseXGGJ7VbFO4pBANu0kwl8VCa9KEI0V8VfZIx2Fy3w01iSTA/pGwKZSmu/+I4etLNDdt5w==", - "license": "BSD-2-Clause", - "optional": true, - "dependencies": { - "source-map": "~0.5.1", - "yargs": "~3.10.0" - }, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - }, - "optionalDependencies": { - "uglify-to-browserify": "~1.0.0" - } - }, - "node_modules/uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha512-vb2s1lYx2xBtUgy+ta+b2J/GLVUR+wmpINwHePmPRhOsIVCG2wDzKJ0n14GslH1BifsqVzSOwQhRaCAsZ/nI4Q==", - "license": "MIT", - "optional": true - }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -5857,15 +5283,6 @@ "node": ">= 10.0.0" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/unzipper": { "version": "0.12.3", "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.3.tgz", @@ -5911,9 +5328,9 @@ } }, "node_modules/update-check": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.1.tgz", - "integrity": "sha512-M3rjq5KwSrWZrm2GVPIQIF+NXpIn5I9mIV67gGoydptQvzRjLp9ZbM6ctFJeNuaWSm5+mNP7aInELjSiLcIw6A==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", "license": "MIT", "dependencies": { "registry-auth-token": "3.3.2", @@ -5924,7 +5341,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -6164,7 +5580,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -6194,24 +5609,18 @@ } }, "node_modules/widest-line": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", - "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", "license": "MIT", "dependencies": { - "string-width": "^2.1.1" + "string-width": "^5.0.1" }, "engines": { - "node": ">=4" - } - }, - "node_modules/window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha512-1pTPQDKTdd61ozlKGNCjhNRd+KPmgLSGa3mZTHoOliaGcESD8G1PXhh7c1fgiPjVbNVfgy2Faw4BI8/m0cC8Mg==", - "optional": true, - "engines": { - "node": ">= 0.8.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/word-wrap": { @@ -6224,13 +5633,60 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=0.4.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/xss": { @@ -6266,29 +5722,6 @@ "node": ">= 6" } }, - "node_modules/yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha512-QFzUah88GAGy9lyDKGBqZdkYApt63rCXYBGYnEP4xDJPXNqXXnBDACnbrXnViV6jRSqAePwrATi2i8mfYm4L1A==", - "license": "MIT", - "optional": true, - "dependencies": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - }, - "node_modules/yargs/node_modules/camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index ce733a4..bfc6b7a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,7 +26,7 @@ "react-dom": "^18.3.1", "react-router-dom": "^6.26.1", "read-excel-file": "^5.8.5", - "serve": "^6.5.8", + "serve": "^14.2.3", "vis-timeline": "^7.7.3" }, "devDependencies": { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 73cb4cb..aa46501 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,6 +5,6 @@ const router = createBrowserRouter(routes); export default function App() { return ( - + ) } \ No newline at end of file diff --git a/frontend/src/assets/cropped-vit-logo.png b/frontend/src/assets/cropped-vit-logo.png new file mode 100644 index 0000000..7614504 Binary files /dev/null and b/frontend/src/assets/cropped-vit-logo.png differ diff --git a/frontend/src/components/BackButton.tsx b/frontend/src/components/BackButton.tsx index b30dedf..3a24823 100644 --- a/frontend/src/components/BackButton.tsx +++ b/frontend/src/components/BackButton.tsx @@ -6,11 +6,12 @@ import { Button } from '@mui/material'; * Displays a button with an arrow icon and text 'Back'. * @returns JSX element representing the back button */ -export default function BackButton() { +export default function BackButton({ disabled=false } : {disabled?: boolean}) { return ( + ); +}; \ No newline at end of file diff --git a/frontend/src/components/NextButton.tsx b/frontend/src/components/NextButton.tsx index 2ab09c0..3e3d244 100644 --- a/frontend/src/components/NextButton.tsx +++ b/frontend/src/components/NextButton.tsx @@ -5,11 +5,12 @@ import { Button } from '@mui/material'; * Component for rendering a button with an arrow icon indicating next action. * @returns JSX element representing the NextButton component */ -export default function NextButton() { +export default function NextButton({ disabled=false } : {disabled?: boolean}) { return ( } else { - return ; + return ; } } \ No newline at end of file diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index fd86d43..777a4cd 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -42,15 +42,6 @@ export default function Sidebar({ marginTop, width }: SidebarProps) { > - - - - - - - - - diff --git a/frontend/src/components/SkipButton.tsx b/frontend/src/components/SkipButton.tsx new file mode 100644 index 0000000..3e41ffa --- /dev/null +++ b/frontend/src/components/SkipButton.tsx @@ -0,0 +1,35 @@ +import Button from "@mui/material/Button"; +import { useNavigate } from "react-router-dom"; + +export default function SkipButton() { + const navigate = useNavigate(); + if (sessionStorage.getItem("campusSolutions") === undefined) { + return ( + + ); + } else { + return ( + + ); + } +} diff --git a/frontend/src/components/UploadButton.tsx b/frontend/src/components/UploadButton.tsx index b5a82dc..98da557 100644 --- a/frontend/src/components/UploadButton.tsx +++ b/frontend/src/components/UploadButton.tsx @@ -3,6 +3,8 @@ import Button from "@mui/material/Button"; import UploadFileIcon from "@mui/icons-material/UploadFile"; import { getFile, storeFile } from "../scripts/persistence"; import { prefillUnitSpreadsheet } from "../scripts/handleInput"; +import { useState } from "react"; +import { CircularProgress } from "@mui/material"; interface InputFileUploadProps { setFileChosen: (file: File | null) => void; @@ -27,25 +29,30 @@ const VisuallyHiddenInput = styled("input")({ * @param {InputFileUploadProps.setFileChosen} setFileChosen - Callback function to set the chosen file. * @returns Upload button component with file selection and upload functionality. */ -export default function UploadButton ({ setFileChosen }: InputFileUploadProps) { +export default function UploadButton({ setFileChosen }: InputFileUploadProps) { + + const [loading, setLoading] = useState(false); // Handler for file selection const handleFileChange = (event: React.ChangeEvent) => { if (event.target.files && event.target.files.length > 0) { + setLoading(true); storeFile(event.target.files[0]) - .then(() => { - return getFile(); - }) - .then((file) => { - return prefillUnitSpreadsheet(file); - // return getUnitsList(file); - }) - .then((file) => { - setFileChosen(file); - }) - .catch((error) => { - alert("Upload failed. Please try again. " + error); - }) + .then(() => { + return getFile(); + }) + .then((file) => { + return prefillUnitSpreadsheet(file); + // return getUnitsList(file); + }) + .then((file) => { + setFileChosen(file); + setLoading(false); + }) + .catch((error) => { + alert("Upload failed. Please try again. " + error); + setLoading(false); + }) console.log("File selected:", event.target.files[0]); } }; @@ -56,10 +63,27 @@ export default function UploadButton ({ setFileChosen }: InputFileUploadProps) { role={undefined} variant="contained" tabIndex={-1} - startIcon={} + sx={{ + backgroundColor: '#f05a22', + color: 'white', + '&:hover': { + backgroundColor: '#d1491a', + }, + position: 'relative', + display: 'flex', + alignItems: 'center', + height: 35+"%", + }} + disabled={loading} + startIcon={!loading && } > - Upload file + {loading ? ( + + ) : ( + "Upload File" + )} ); + } diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 4e01bfe..fa2e5a3 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,9 +2,12 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import App from './App.tsx' import './styles/global.css' +import AuthContextProvider from './security/AuthContext.tsx' createRoot(document.getElementById('root')!).render( - + + + ) diff --git a/frontend/src/pages/Enrolment.tsx b/frontend/src/pages/Enrolment.tsx index 995336e..1d1cf76 100644 --- a/frontend/src/pages/Enrolment.tsx +++ b/frontend/src/pages/Enrolment.tsx @@ -4,6 +4,10 @@ import UploadPopUp from "../components/UploadPopUp.tsx"; import Header from "../components/Header.tsx"; import Footer from "../components/Footer.tsx"; import Photo from "../assets/frontpage.jpg"; +import { useEffect } from "react"; +import { REMOTE_API_URL, TimetableSolution } from "../scripts/api.ts"; +import { useAuthContext } from "../security/AuthContext.tsx"; +import SkipButton from "../components/SkipButton.tsx"; /** * Renders the Starter Page component with specific time and tabler styles. @@ -20,6 +24,26 @@ export default function StarterPage() { const tablerStyle = { color: "black", }; + const { authHeader } = useAuthContext(); + useEffect(() => { + fetch(REMOTE_API_URL + "/timetabling/view", { + headers: { Authorization: authHeader }, + }) + .then((response) => { + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); + }) + .then((data) => { + const timetableSolutions: TimetableSolution[] = + data as TimetableSolution[]; + sessionStorage.setItem( + "campusSolutions", + JSON.stringify(timetableSolutions) + ); + }); + }, []); return (
@@ -31,6 +55,7 @@ export default function StarterPage() {

A timetabling website for the Victorian Institute of Technology

-Team JetEdge

+ logo.exe diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx new file mode 100644 index 0000000..195e094 --- /dev/null +++ b/frontend/src/pages/LoginPage.tsx @@ -0,0 +1,93 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { AuthHeader, useAuthContext } from '../security/AuthContext'; +import '../styles/login.css'; +import VIT_Logo from '../assets/logo.png'; +import { REMOTE_API_URL } from '../scripts/api'; +import LoadingButton from '../components/LoadingButton'; + +export default function LoginPage() { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + const { setAuthHeader } = useAuthContext(); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + + const encodedHeader: AuthHeader = `Basic ${btoa(`${username}:${password}`)}`; + + try { + // Send a request to the backend to validate credentials + const response = await fetch(REMOTE_API_URL + "/login", { + method: 'GET', + headers: { + 'Authorization': encodedHeader, + }, + }); + + if (response.status != 401) { + setAuthHeader(encodedHeader); + navigate("/enrolment"); + } + else { + alert("Unauthorised"); + } + } + catch (error) { + alert(error); + } + finally { + setLoading(false); + } + + } + + return ( +
+
+ {/* Logo at the top */} + Logo + +

Login

+ +
+ setUsername(e.target.value)} + className="login-input" + required + /> +
+ +
+ setPassword(e.target.value)} + className="login-input" + required + /> +
+ + {}} + text="Login" + type='submit' + sx={{ + width: '100%', + height: '40px', + marginTop: '2px', + }} + /> + +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/pages/SemesterInfo.tsx b/frontend/src/pages/SemesterInfo.tsx index 4a00ccc..816f87c 100644 --- a/frontend/src/pages/SemesterInfo.tsx +++ b/frontend/src/pages/SemesterInfo.tsx @@ -25,7 +25,7 @@ export default function SemesterInfo() {
- +
diff --git a/frontend/src/pages/SendData.tsx b/frontend/src/pages/SendData.tsx index ec90966..e96a357 100644 --- a/frontend/src/pages/SendData.tsx +++ b/frontend/src/pages/SendData.tsx @@ -7,6 +7,8 @@ import { DB_ROOMS, DB_UNITS, getSpreadsheetData } from "../scripts/persistence"; import { getTimetableProblems } from "../scripts/handleInput"; import { useState } from "react"; import { fetchTimetableSolution } from "../scripts/api"; +import { useAuthContext } from '../security/AuthContext'; +import LoadingButton from "../components/LoadingButton"; /** * Page for containing UI elements that allow user to send input data to backend. @@ -18,10 +20,14 @@ import { fetchTimetableSolution } from "../scripts/api"; */ export default function SendData() { - const [isGenerated, setIsGenerated] = useState(""); + const [loading, setLoading] = useState(false); + const { authHeader } = useAuthContext(); function generateTimetable() { - setIsGenerated(""); + setLoading(true); + setTimeout(() => { + setLoading(false); + }, 120000); Promise.all([getSpreadsheetData(DB_ROOMS), getSpreadsheetData(DB_UNITS)]) .then((responses) => { const [roomData, unitData] = [...responses]; @@ -34,28 +40,37 @@ export default function SendData() { return getTimetableProblems(roomData, unitData); }) .then((problems) => { - return Promise.all(problems.map(p => fetchTimetableSolution(p))); + return Promise.all(problems.map(p => fetchTimetableSolution(p, authHeader))); }) .then((solutions) => { console.log(solutions); - setIsGenerated(JSON.stringify(solutions, null, 2)); + // setLoading(false); }) .catch((error) => { alert(error); + // setLoading(false); }) } return ( <>
-
-
{isGenerated.toString()}
+
+
-
- - + {loading ? ( + <> + + + + ) : ( + <> + + + + )}
diff --git a/frontend/src/pages/TimetableMod.tsx b/frontend/src/pages/TimetableMod.tsx index bcb7fa6..0c321bf 100644 --- a/frontend/src/pages/TimetableMod.tsx +++ b/frontend/src/pages/TimetableMod.tsx @@ -5,7 +5,8 @@ import Footer from "../components/Footer"; import BackButton from "../components/BackButton"; import { Outlet } from "react-router-dom"; import ModSidebar from "../components/ModSiderbar"; -import { TimetableSolution } from "../scripts/api"; +import { REMOTE_API_URL, TimetableSolution } from "../scripts/api"; +import { useAuthContext } from "../security/AuthContext"; /** * Renders the TimetableMod component to display and modify the generated @@ -16,9 +17,10 @@ import { TimetableSolution } from "../scripts/api"; */ export default function TimetableMod() { const [loading, setLoading] = useState(true); + const { authHeader } = useAuthContext(); useEffect(() => { - fetch("http://localhost:8080/timetabling/view") + fetch(REMOTE_API_URL + "/timetabling/view", { headers: { 'Authorization': authHeader } }) .then((response) => { if (!response.ok) { throw new Error("Network response was not ok"); @@ -54,10 +56,8 @@ export default function TimetableMod() {
- - - - + +
diff --git a/frontend/src/pages/spreadsheets/Building.tsx b/frontend/src/pages/spreadsheets/Building.tsx deleted file mode 100644 index be3ccc9..0000000 --- a/frontend/src/pages/spreadsheets/Building.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import Spreadsheet from '../../components/Spreadsheet.tsx' -import { DB_BUILDINGS } from '../../scripts/persistence.ts'; - -/** - * - * @returns Spreadsheet input page for buildings information. - */ -export default function Building() { - - return ( - <> -

Building

- - - ); -}; \ No newline at end of file diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx index 48d05c2..fcdc0c5 100644 --- a/frontend/src/routes.tsx +++ b/frontend/src/routes.tsx @@ -1,12 +1,14 @@ import SemesterInfo from './pages/SemesterInfo.tsx' import TimetableMod from './pages/TimetableMod.tsx' -import Building from './pages/spreadsheets/Building.tsx' import Room from './pages/spreadsheets/Room.tsx' import Unit from './pages/spreadsheets/Unit.tsx' import Download from './pages/Download.tsx' import Enrolment from './pages/Enrolment.tsx' import SendData from './pages/SendData.tsx' import GanttChart from './components/GanttChart.tsx' +import LoginPage from './pages/LoginPage.tsx' +import { Navigate } from 'react-router-dom' +import PrivateRoute from './security/PrivateRoute.tsx' /** * Defines the routes configuration for the application. * Each route specifies a path and the corresponding component to render. @@ -16,31 +18,38 @@ import GanttChart from './components/GanttChart.tsx' const routes = [ { path: "/", - element: , + element: , + }, + { + path: "/login", + element: , + }, + { + path: "enrolment", + element: } />, }, { path: "seminfo", - element: , + element: } />, children: [ - { path: "building", element: }, { path: "room", element: }, { path: "unit", element: }, ], }, { path: "senddata", - element: , + element: } />, }, { path: "timetablemod/*", - element: , + element: } />, children: [ {path: ":location", element: } ], }, { path: "download", - element: , + element: } />, }, ]; diff --git a/frontend/src/scripts/api.ts b/frontend/src/scripts/api.ts index c49eff6..ceb1357 100644 --- a/frontend/src/scripts/api.ts +++ b/frontend/src/scripts/api.ts @@ -1,5 +1,8 @@ +import { AuthHeader } from "../security/AuthContext"; + /* Timetable solver backend endpoint URL */ -const API_URL = "http://localhost:8080/timetabling"; +export const REMOTE_API_URL = "https://jetedge-backend-e1eeff4b0c04.herokuapp.com"; +export const LOCAL_API_URL = "http://localhost:8080"; /* =========================================== Defining types =========================================== */ @@ -59,19 +62,24 @@ export type Time = string; * @param problem A TimetableProblem is a list of units with no allocated time and room. * @returns A TimetableSolution with all units allocated a time and a room. */ -export async function fetchTimetableSolution(problem: TimetableProblem): Promise { +export async function fetchTimetableSolution(problem: TimetableProblem, authHeader: AuthHeader, url?: string): Promise { try { - const response = await fetch(API_URL, { + let api_url = REMOTE_API_URL; + if (url !== undefined) { + api_url = url; + } + const response = await fetch(api_url+"/timetabling", { method: 'POST', headers: { 'Content-Type': 'application/json', + 'Authorization': authHeader, }, body: JSON.stringify(problem) }); if (!response.ok) { if (response.status === 500) { - alert(response.statusText + " " + response.status + ": server was not able to solve the problem. Please check for missing input (i.e. make sure there are at least 1 available room and no rooms with duplicate ID)."); + alert(response.statusText + " " + response.status + ": server was not able to solve the problem."); } throw new Error(`HTTP error! Status: ${response.status} ${response.statusText}`); } diff --git a/frontend/src/scripts/handleInput.ts b/frontend/src/scripts/handleInput.ts index 51bf38a..81ada5f 100644 --- a/frontend/src/scripts/handleInput.ts +++ b/frontend/src/scripts/handleInput.ts @@ -12,7 +12,6 @@ import { DB_UNITS, storeSpreadsheetData } from './persistence'; function isExcelFile(file: File) { const fileExtension = file.name.split('.').pop(); if (fileExtension === undefined || !['xlsx', 'xls'].includes(fileExtension)) { - alert("Wrong file type, file type must be .xlsx or .xls"); return false; } return true; @@ -33,7 +32,6 @@ function validateEnrolmentHeader(inputHeader: Row) { return true; } else { - alert("Enrolment data header row is invalid"); return false; } } @@ -134,7 +132,9 @@ export async function prefillUnitSpreadsheet(enrolmentExcel: File) { const units = unitsMaps.map(m => { const unitsData = Array.from(m.values()); - const transformed = unitsData.map(ud => { return { ...ud, enrolment: JSON.stringify(ud.enrolment) } }); + const transformed = unitsData + .filter((ud) => ud.enrolment.length > 0) + .map(ud => { return { ...ud, enrolment: JSON.stringify(ud.enrolment) } }); return transformed; }).flat(); diff --git a/frontend/src/security/AuthContext.tsx b/frontend/src/security/AuthContext.tsx new file mode 100644 index 0000000..89286ca --- /dev/null +++ b/frontend/src/security/AuthContext.tsx @@ -0,0 +1,28 @@ +import { createContext, useState, ReactNode, useContext } from 'react'; + +export type AuthHeader = `Basic ${string}` | ''; + +type AuthContext = { + authHeader: AuthHeader; + setAuthHeader: React.Dispatch>; +}; + +const AuthContext = createContext(undefined); + +export default function AuthContextProvider({ children }: { children: ReactNode }) { + const [authHeader, setAuthHeader] = useState(''); + + return ( + + {children} + + ); +}; + +export const useAuthContext = () => { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuthContext must be used within an AuthContextProvider'); + } + return context; +}; \ No newline at end of file diff --git a/frontend/src/security/PrivateRoute.tsx b/frontend/src/security/PrivateRoute.tsx new file mode 100644 index 0000000..14464ef --- /dev/null +++ b/frontend/src/security/PrivateRoute.tsx @@ -0,0 +1,13 @@ +import { useAuthContext } from './AuthContext'; +import { Navigate } from 'react-router-dom'; + +export default function PrivateRoute({ element }: { element: JSX.Element }) { + const { authHeader } = useAuthContext(); + + // If no credentials are set, redirect to the login page + if (authHeader === '') { + return ; + } + + return element; +}; \ No newline at end of file diff --git a/frontend/src/styles/login.css b/frontend/src/styles/login.css new file mode 100644 index 0000000..721ad43 --- /dev/null +++ b/frontend/src/styles/login.css @@ -0,0 +1,79 @@ +/* Container */ +.login-container { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + background-color: #f9f9f9; +} + +/* Form */ +.login-form { + background-color: #fff; + padding: 2rem; + border-radius: 10px; + box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 400px; + display: flex; + flex-direction: column; + align-items: center; +} + +/* Logo */ +.login-logo { + width: 30%; /* Adjust the size of the logo */ + height: auto; + margin-bottom: 0.5rem; /* Space between logo and title */ +} + +/* Title */ +.login-title { + color: #333; + margin-bottom: 1.5rem; + font-size: 24px; + text-align: center; +} + +/* Input Wrapper */ +.input-group { + width: 100%; + margin-bottom: 1rem; +} + +/* Input */ +.login-input { + box-sizing: border-box; + width: 100%; + padding: 12px; + border-radius: 5px; + border: 1px solid #ccc; + font-size: 16px; + transition: border-color 0.3s ease; +} + +.login-input:focus { + outline: none; + border-color: #f05a22; +} + +/* Button */ +.login-button { + background-color: #f05a22; + color: white; + border: none; + padding: 12px; + border-radius: 5px; + font-size: 16px; + cursor: pointer; + width: 100%; + transition: background-color 0.3s ease; +} + +.login-button:hover { + background-color: #e0481f; +} + +.login-button:active { + background-color: #cc3d1b; +} diff --git a/frontend/src/tests/api.test.ts b/frontend/src/tests/api.test.ts index fe9d6fe..0f26572 100644 --- a/frontend/src/tests/api.test.ts +++ b/frontend/src/tests/api.test.ts @@ -1,26 +1,29 @@ import { describe, it, expect } from 'vitest'; -import { fetchTimetableSolution, TimetableProblem } from '../scripts/api'; +import { fetchTimetableSolution, LOCAL_API_URL, TimetableProblem } from '../scripts/api'; import moment from 'moment'; +import { AuthHeader } from '../security/AuthContext'; /** * Test fetchTimetableSolution API method. * Check if connection to backend is working. * Check that output matches expected output. */ -describe('fetchTimetableSolution', { timeout: 60000 }, () => { +describe('fetchTimetableSolution', { timeout: 200000 }, () => { /** * Validate end-to-end scheduling and data consistency of 1 API method call. */ it('return TimetableSolution', async () => { const problem: TimetableProblem = { - campusName: "A", - units: [{ campus: "A", course: "B", unitId: 0, name: "Unit0", duration: 1200, students: [], wantsLab: true }], + campusName: "Geelong", + units: [{ campus: "Geelong", course: "B", unitId: 0, name: "Unit0", duration: 1200, students: [], wantsLab: true }], daysOfWeek: ["MONDAY"], startTimes: ["11:00:00"], - rooms: [{ campus: "A", buildingId: "01", roomCode: "Room A", capacity: 10, lab: true }] + rooms: [{ campus: "Geelong", buildingId: "01", roomCode: "Room A", capacity: 10, lab: true }] }; - - const solution = await fetchTimetableSolution(problem); + + const authHeader: AuthHeader = `Basic ${btoa(`${import.meta.env.VITE_FRONTEND_USERNAME}:${import.meta.env.VITE_FRONTEND_PASSWORD}`)}`; + + const solution = await fetchTimetableSolution(problem, authHeader, LOCAL_API_URL); expect(solution).not.toBeNull(); expect(solution?.units[0].dayOfWeek).toEqual(problem.daysOfWeek[0]); expect(solution?.units[0].startTime).toEqual(problem.startTimes[0]); @@ -35,15 +38,24 @@ describe('fetchTimetableSolution', { timeout: 60000 }, () => { * Validate that backend server can handle multiple solve requests concurrently. */ it ('can be called multiple times', async () => { - const problem: TimetableProblem = { - campusName: "B", - units: [{ campus: "B", course: "C", unitId: 0, name: "Unit0", duration: 1200, students: [], wantsLab: true }], + const problem0: TimetableProblem = { + campusName: "Adelaide", + units: [{ campus: "Adelaide", course: "C", unitId: 0, name: "Unit0", duration: 1200, students: [], wantsLab: true }], + daysOfWeek: ["MONDAY"], + startTimes: ["11:00:00"], + rooms: [{ campus: "Adelaide", buildingId: "02", roomCode: "Room A", capacity: 10, lab: true }] + }; + + const problem1: TimetableProblem = { + campusName: "Melbourne", + units: [{ campus: "Melbourne", course: "C", unitId: 0, name: "Unit0", duration: 1200, students: [], wantsLab: true }], daysOfWeek: ["MONDAY"], startTimes: ["11:00:00"], - rooms: [{ campus: "B", buildingId: "02", roomCode: "Room A", capacity: 10, lab: true }] + rooms: [{ campus: "Melbourne", buildingId: "02", roomCode: "Room A", capacity: 10, lab: true }] }; - const solutions = await Promise.all([fetchTimetableSolution(problem), fetchTimetableSolution(problem), fetchTimetableSolution(problem)]); + const authHeader: AuthHeader = `Basic ${btoa(`${import.meta.env.VITE_FRONTEND_USERNAME}:${import.meta.env.VITE_FRONTEND_PASSWORD}`)}`; + const solutions = await Promise.all([fetchTimetableSolution(problem0, authHeader, LOCAL_API_URL), fetchTimetableSolution(problem1, authHeader, LOCAL_API_URL)]); for (let i = 0; i < solutions.length; i++) { expect(solutions[i]).not.toBeNull();