From f92a5e7217a9dccbc7d8ed8d284f6fc760bfed4e Mon Sep 17 00:00:00 2001 From: Eden Xu <130119691+FlyingPufferFish@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:07:21 +1100 Subject: [PATCH] Revert "KAN-80 feat(timetables): implement backend logic for timetable update" --- .github/workflows/main.yml | 2 + backend/og-pom.txt | 124 -- backend/pom.xml | 6 +- .../main/java/org/acme/TimetableResource.java | 230 --- .../src/main/java/org/acme/domain/Room.java | 27 +- .../main/java/org/acme/domain/Student.java | 24 +- .../main/java/org/acme/domain/Timetable.java | 27 +- .../src/main/java/org/acme/domain/Unit.java | 21 +- .../acme/{domain => rest}/RoomResource.java | 15 +- .../{domain => rest}/StudentResource.java | 13 +- .../acme/{domain => rest}/UnitResource.java | 15 +- .../java/org/acme/security/jpa/Startup.java | 24 + .../main/java/org/acme/security/jpa/User.java | 37 + .../org/acme/security/jpa/UserResource.java | 17 + .../src/main/resources/application.properties | 10 +- .../security/jpa/JpaSecurityRealmTest.java | 79 + .../TimetableConstraintProviderTest.java | 16 +- frontend/index.html | 4 +- frontend/package-lock.json | 1557 ++++++----------- frontend/package.json | 2 +- frontend/src/App.tsx | 2 +- frontend/src/assets/cropped-vit-logo.png | Bin 0 -> 18508 bytes frontend/src/components/BackButton.tsx | 3 +- frontend/src/components/LoadingButton.tsx | 43 + frontend/src/components/NextButton.tsx | 5 +- frontend/src/components/ProceedButton.tsx | 2 +- frontend/src/components/Sidebar.tsx | 9 - frontend/src/components/SkipButton.tsx | 35 + frontend/src/components/UploadButton.tsx | 56 +- frontend/src/main.tsx | 5 +- frontend/src/pages/Enrolment.tsx | 25 + frontend/src/pages/LoginPage.tsx | 93 + frontend/src/pages/SemesterInfo.tsx | 2 +- frontend/src/pages/SendData.tsx | 33 +- frontend/src/pages/TimetableMod.tsx | 12 +- frontend/src/pages/spreadsheets/Building.tsx | 19 - frontend/src/routes.tsx | 23 +- frontend/src/scripts/api.ts | 16 +- frontend/src/scripts/handleInput.ts | 6 +- frontend/src/security/AuthContext.tsx | 28 + frontend/src/security/PrivateRoute.tsx | 13 + frontend/src/styles/login.css | 79 + frontend/src/tests/api.test.ts | 36 +- 43 files changed, 1182 insertions(+), 1613 deletions(-) delete mode 100644 backend/og-pom.txt delete mode 100644 backend/src/main/java/org/acme/TimetableResource.java rename backend/src/main/java/org/acme/{domain => rest}/RoomResource.java (76%) rename backend/src/main/java/org/acme/{domain => rest}/StudentResource.java (76%) rename backend/src/main/java/org/acme/{domain => rest}/UnitResource.java (76%) create mode 100644 backend/src/main/java/org/acme/security/jpa/Startup.java create mode 100644 backend/src/main/java/org/acme/security/jpa/User.java create mode 100644 backend/src/main/java/org/acme/security/jpa/UserResource.java create mode 100644 backend/src/test/java/org/acme/security/jpa/JpaSecurityRealmTest.java create mode 100644 frontend/src/assets/cropped-vit-logo.png create mode 100644 frontend/src/components/LoadingButton.tsx create mode 100644 frontend/src/components/SkipButton.tsx create mode 100644 frontend/src/pages/LoginPage.tsx delete mode 100644 frontend/src/pages/spreadsheets/Building.tsx create mode 100644 frontend/src/security/AuthContext.tsx create mode 100644 frontend/src/security/PrivateRoute.tsx create mode 100644 frontend/src/styles/login.css 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 0000000000000000000000000000000000000000..761450490756ae74a7187de8828094ed3e67810d GIT binary patch literal 18508 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 pf_v5wkfQi9s+X4Cn`Rd-`VeKg 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 + disabled={disabled} sx={{ color: 'white', borderColor: 'white', diff --git a/frontend/src/components/LoadingButton.tsx b/frontend/src/components/LoadingButton.tsx new file mode 100644 index 0000000..7e7e423 --- /dev/null +++ b/frontend/src/components/LoadingButton.tsx @@ -0,0 +1,43 @@ +import { Theme } from '@emotion/react'; +import { SxProps } from '@mui/material'; +import Button from '@mui/material/Button'; +import CircularProgress from '@mui/material/CircularProgress'; + +interface LoadingButtonProps { + loading: boolean; + onClick: () => void; + text: string; + disabled?: boolean; + sx?: SxProps; + type?: 'button' | 'submit' | 'reset'; +} + +export default function LoadingButton({ loading, onClick, text, disabled=false, sx, type = 'button' }: LoadingButtonProps) { + + 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();