diff --git a/.gitignore b/.gitignore index c50fe24..3b1c6d3 100644 --- a/.gitignore +++ b/.gitignore @@ -14,11 +14,9 @@ diagram.puml backend/*.png backend/*.jpg backend/*.svg -__pycache__ /go.work.sum /package-lock.json -/backend/docs/swagger.json # Test binary, built with `go test -c` *.test diff --git a/backend/Makefile b/backend/Makefile index 15a550c..3443e94 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -34,7 +34,6 @@ clean: rm -f plantuml.jar rm -f erd.png rm -f config.toml - rm -f database.txt # Test target test: db.sqlite3 @@ -47,7 +46,7 @@ itest: make build ./bin/$(PROC_NAME) >/dev/null 2>&1 & sleep 1 # Adjust if needed - python ../testing/testing.py || pkill $(PROC_NAME) + python ../testing.py pkill $(PROC_NAME) # Get dependencies target @@ -105,17 +104,6 @@ default: build docs: swag init -outputTypes go -api: ./docs/swagger.json - rm ../frontend/src/API/GenApi.ts - npx swagger-typescript-api \ - --api-class-name GenApi \ - --path ./docs/swagger.json \ - --output ../frontend/src/API \ - --name GenApi.ts \ - -./docs/swagger.json: - swag init -outputTypes json - .PHONY: docfmt docfmt: swag fmt diff --git a/backend/docs/docs.go b/backend/docs/docs.go index c8b020d..322c812 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -21,21 +21,21 @@ const docTemplate = `{ "paths": { "/login": { "post": { - "description": "Logs in a user and returns a JWT token", + "description": "logs the user in and returns a jwt token", "consumes": [ "application/json" ], "produces": [ - "application/json" + "text/plain" ], "tags": [ - "Auth" + "User" ], - "summary": "Login", + "summary": "login", "parameters": [ { - "description": "User credentials", - "name": "body", + "description": "login info", + "name": "NewUser", "in": "body", "required": true, "schema": { @@ -45,9 +45,9 @@ const docTemplate = `{ ], "responses": { "200": { - "description": "JWT token", + "description": "Successfully signed token for user", "schema": { - "$ref": "#/definitions/types.Token" + "type": "Token" } }, "400": { @@ -71,26 +71,29 @@ const docTemplate = `{ } } }, - "/loginrenew": { + "/loginerenew": { "post": { "security": [ { - "JWT": [] + "bererToken": [] } ], - "description": "Renews the users token.", - "produces": [ + "description": "renews the users token", + "consumes": [ "application/json" ], + "produces": [ + "text/plain" + ], "tags": [ - "Auth" + "User" ], "summary": "LoginRenews", "responses": { "200": { "description": "Successfully signed token for user", "schema": { - "$ref": "#/definitions/types.Token" + "type": "Token" } }, "401": { @@ -108,64 +111,9 @@ const docTemplate = `{ } } }, - "/promote/{projectName}": { - "put": { - "security": [ - { - "JWT": [] - } - ], - "description": "Promote a user to project manager", - "consumes": [ - "text/plain" - ], - "produces": [ - "text/plain" - ], - "tags": [ - "Auth" - ], - "summary": "Promote to project manager", - "parameters": [ - { - "type": "string", - "description": "Project name", - "name": "projectName", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "User name", - "name": "userName", - "in": "query", - "required": true - } - ], - "responses": { - "403": { - "description": "Forbidden", - "schema": { - "type": "string" - } - }, - "500": { - "description": "Internal server error", - "schema": { - "type": "string" - } - } - } - } - }, "/promoteToAdmin": { "post": { - "security": [ - { - "JWT": [] - } - ], - "description": "Promote chosen user to site admin", + "description": "promote chosen user to admin", "consumes": [ "application/json" ], @@ -189,13 +137,13 @@ const docTemplate = `{ ], "responses": { "200": { - "description": "Successfully promoted user", + "description": "Successfully prometed user", "schema": { - "$ref": "#/definitions/types.Token" + "type": "json" } }, "400": { - "description": "Bad request", + "description": "bad request", "schema": { "type": "string" } @@ -225,7 +173,7 @@ const docTemplate = `{ "text/plain" ], "tags": [ - "Auth" + "User" ], "summary": "Register", "parameters": [ @@ -263,11 +211,6 @@ const docTemplate = `{ }, "/userdelete/{username}": { "delete": { - "security": [ - { - "JWT": [] - } - ], "description": "UserDelete deletes a user from the database", "consumes": [ "application/json" @@ -309,27 +252,22 @@ const docTemplate = `{ }, "/users/all": { "get": { - "security": [ - { - "JWT": [] - } - ], "description": "lists all users", - "produces": [ + "consumes": [ "application/json" ], + "produces": [ + "text/plain" + ], "tags": [ "User" ], "summary": "ListsAllUsers", "responses": { "200": { - "description": "Successfully returned all users", + "description": "Successfully signed token for user", "schema": { - "type": "array", - "items": { - "type": "string" - } + "type": "json" } }, "401": { @@ -353,27 +291,16 @@ const docTemplate = `{ "type": "object", "properties": { "password": { - "type": "string", - "example": "password123" + "type": "string" }, "username": { - "type": "string", - "example": "username123" - } - } - }, - "types.Token": { - "type": "object", - "properties": { - "token": { "type": "string" } } } }, "securityDefinitions": { - "JWT": { - "description": "Use the JWT token provided by the login endpoint to authenticate requests. **Prefix the token with \"Bearer \".**", + "bererToken": { "type": "apiKey", "name": "Authorization", "in": "header" diff --git a/backend/go.mod b/backend/go.mod index 4f71031..98c606d 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -13,10 +13,10 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/spec v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/jsonpointer v0.20.3 // indirect + github.com/go-openapi/jsonreference v0.20.5 // indirect + github.com/go-openapi/spec v0.20.15 // indirect + github.com/go-openapi/swag v0.22.10 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -25,10 +25,10 @@ require ( github.com/swaggo/files/v2 v2.0.0 // indirect golang.org/x/tools v0.19.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect - modernc.org/libc v1.49.1 // indirect + modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect + modernc.org/libc v1.41.0 // indirect modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.8.0 // indirect + modernc.org/memory v1.7.2 // indirect modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect ) @@ -42,7 +42,7 @@ require ( // These are all for fiber require ( github.com/andybalholm/brotli v1.1.0 // indirect - github.com/gofiber/fiber/v2 v2.52.4 + github.com/gofiber/fiber/v2 v2.52.2 github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -52,5 +52,5 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.52.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/sys v0.18.0 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index aa8ee65..3c07b9a 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -10,20 +10,20 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= -github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/jsonpointer v0.20.3 h1:jykzYWS/kyGtsHfRt6aV8JTB9pcQAXPIA7qlZ5aRlyk= +github.com/go-openapi/jsonpointer v0.20.3/go.mod h1:c7l0rjoouAuIxCm8v/JWKRgMjDG/+/7UBWsXMrv6PsM= +github.com/go-openapi/jsonreference v0.20.5 h1:hutI+cQI+HbSQaIGSfsBsYI0pHk+CATf8Fk5gCSj0yI= +github.com/go-openapi/jsonreference v0.20.5/go.mod h1:thAqAp31UABtI+FQGKAQfmv7DbFpKNUlva2UPCxKu2Y= +github.com/go-openapi/spec v0.20.15 h1:8bDcVxF607pTh9NpPwgsH4J5Uhh5mV5XoWnkurdiY+U= +github.com/go-openapi/spec v0.20.15/go.mod h1:o0upgqg5uYFG7O5mADrDVmSG3Wa6y6OLhwiCqQ+sTv4= +github.com/go-openapi/swag v0.22.10 h1:4y86NVn7Z2yYd6pfS4Z+Nyh3aAUL3Nul+LMbhFKy0gA= +github.com/go-openapi/swag v0.22.10/go.mod h1:Cnn8BYtRlx6BNE3DPN86f/xkapGIcLWzh3CLEb4C1jI= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gofiber/contrib/jwt v1.0.8 h1:/GeOsm/Mr1OGr0GTy+RIVSz5VgNNyP3ZgK4wdqxF/WY= github.com/gofiber/contrib/jwt v1.0.8/go.mod h1:gWWBtBiLmKXRN7xy6a96QO0KGvPEyxdh8x496Ujtg84= -github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= -github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/fiber/v2 v2.52.2 h1:b0rYH6b06Df+4NyrbdptQL8ifuxw/Tf2DgfkZkDaxEo= +github.com/gofiber/fiber/v2 v2.52.2/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/swagger v1.0.0 h1:BzUzDS9ZT6fDUa692kxmfOjc1DZiloLiPK/W5z1H1tc= github.com/gofiber/swagger v1.0.0/go.mod h1:QrYNF1Yrc7ggGK6ATsJ6yfH/8Zi5bu9lA7wB8TmCecg= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= @@ -85,8 +85,8 @@ golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -94,26 +94,16 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk= -modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.15.0 h1:uwfCZOkKhaNNotgYW7kxkJwrkQC1HfGitt/7ousudJE= -modernc.org/ccgo/v4 v4.15.0/go.mod h1:XVITcYGiI+O97UNDLMsnZ9ZjJOhC+ACX+TfxpsWWyRc= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= -modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8= -modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= -modernc.org/libc v1.49.1 h1:r4UaWllkYXRPA7Mq/KzmassZBvNJiH9egF4O/KV/gdE= -modernc.org/libc v1.49.1/go.mod h1:Hx2rWfza47GSzCluTU7Vf0Qx3z9rWCVORL6RNgq+Xog= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= +modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE= modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= diff --git a/backend/internal/database/db.go b/backend/internal/database/db.go index 12b0ee1..3aae28d 100644 --- a/backend/internal/database/db.go +++ b/backend/internal/database/db.go @@ -2,12 +2,11 @@ package database import ( "embed" - "encoding/json" "errors" + "fmt" "path/filepath" "ttime/internal/types" - "github.com/gofiber/fiber/v2/log" "github.com/jmoiron/sqlx" _ "modernc.org/sqlite" ) @@ -18,11 +17,12 @@ type Database interface { AddUser(username string, password string) error CheckUser(username string, password string) bool RemoveUser(username string) error - RemoveUserFromProject(username string, projectname string) error PromoteToAdmin(username string) error GetUserId(username string) (int, error) AddProject(name string, description string, username string) error DeleteProject(name string, username string) error + Migrate() error + MigrateSampleData() error GetProjectId(projectname string) (int, error) AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error AddUserToProject(username string, projectname string, role string) error @@ -35,26 +35,19 @@ type Database interface { GetProject(projectId int) (types.Project, error) GetUserRole(username string, projectname string) (string, error) GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error) - GetAllWeeklyReports(username string, projectname string) ([]types.WeeklyReportList, error) - GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) + GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, error) SignWeeklyReport(reportId int, projectManagerId int) error IsSiteAdmin(username string) (bool, error) IsProjectManager(username string, projectname string) (bool, error) - ReportStatistics(username string, projectName string) (*types.Statistics, error) GetProjectTimes(projectName string) (map[string]int, error) UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error RemoveProject(projectname string) error - GetUserName(id int) (string, error) - UnsignWeeklyReport(reportId int, projectManagerId int) error - DeleteReport(reportID int) error - ChangeProjectName(projectName string, newProjectName string) error - ChangeUserPassword(username string, password string) error } // This struct is a wrapper type that holds the database connection // Internally DB holds a connection pool, so it's safe for concurrent use type Db struct { - *sqlx.Tx + *sqlx.DB } type UserProjectMember struct { @@ -92,23 +85,8 @@ const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles JOIN projects ON user_roles.project_id = projects.id WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'` -const removeUserFromProjectQuery = `DELETE FROM user_roles - WHERE user_id = (SELECT id FROM users WHERE username = ?) - AND project_id = (SELECT id FROM projects WHERE name = ?)` - -const reportStatistics = `SELECT SUM(development_time) AS total_development_time, - SUM(meeting_time) AS total_meeting_time, - SUM(admin_time) AS total_admin_time, - SUM(own_work_time) AS total_own_work_time, - SUM(study_time) AS total_study_time, - SUM(testing_time) AS total_testing_time - FROM weekly_reports - WHERE user_id = (SELECT id FROM users WHERE username = ?) - AND project_id = (SELECT id FROM projects WHERE name = ?) - GROUP BY user_id, project_id` - // DbConnect connects to the database -func DbConnect(dbpath string) sqlx.DB { +func DbConnect(dbpath string) Database { // Open the database db, err := sqlx.Connect("sqlite", dbpath) if err != nil { @@ -121,25 +99,7 @@ func DbConnect(dbpath string) sqlx.DB { panic(err) } - return *db -} - -func (d *Db) ReportStatistics(username string, projectName string) (*types.Statistics, error) { - var result types.Statistics - - err := d.Get(&result, reportStatistics, username, projectName) - if err != nil { - return nil, err - } - - serialized, err := json.Marshal(result) - if err != nil { - return nil, err - } - - log.Info(string(serialized)) - - return &result, nil + return &Db{db} } func (d *Db) CheckUser(username string, password string) bool { @@ -186,11 +146,6 @@ func (d *Db) AddUserToProject(username string, projectname string, role string) return err } -func (d *Db) RemoveUserFromProject(username string, projectname string) error { - _, err := d.Exec(removeUserFromProjectQuery, username, projectname) - return err -} - // ChangeUserRole changes the role of a user within a project. func (d *Db) ChangeUserRole(username string, projectname string, role string) error { // Execute the SQL query to change the user's role @@ -243,15 +198,25 @@ func (d *Db) GetProjectId(projectname string) (int, error) { // Creates a new project in the database, associated with a user func (d *Db) AddProject(name string, description string, username string) error { + tx := d.MustBegin() // Insert the project into the database - _, err := d.Exec(projectInsert, name, description, username) + _, err := tx.Exec(projectInsert, name, description, username) if err != nil { + if err := tx.Rollback(); err != nil { + return err + } return err } // Add creator to project as project manager - _, err = d.Exec(addUserToProject, username, name, "project_manager") + _, err = tx.Exec(addUserToProject, username, name, "project_manager") if err != nil { + if err := tx.Rollback(); err != nil { + return err + } + return err + } + if err := tx.Commit(); err != nil { return err } @@ -259,7 +224,16 @@ func (d *Db) AddProject(name string, description string, username string) error } func (d *Db) DeleteProject(projectID string, username string) error { - _, err := d.Exec(deleteProject, projectID, username) + tx := d.MustBegin() + + _, err := tx.Exec(deleteProject, projectID, username) + + if err != nil { + if rollbackErr := tx.Rollback(); rollbackErr != nil { + return fmt.Errorf("error rolling back transaction: %v, delete error: %v", rollbackErr, err) + } + panic(err) + } return err } @@ -364,14 +338,9 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error { return err } - managerQuery := `SELECT project_id FROM user_roles - WHERE user_id = ? - AND project_id = (SELECT project_id FROM weekly_reports WHERE report_id = ?) - AND p_role = 'project_manager'` - // Retrieve the project ID associated with the project manager var managerProjectID int - err = d.Get(&managerProjectID, managerQuery, projectManagerId, reportId) + err = d.Get(&managerProjectID, "SELECT project_id FROM user_roles WHERE user_id = ? AND p_role = 'project_manager'", projectManagerId) if err != nil { return err } @@ -386,81 +355,6 @@ func (d *Db) SignWeeklyReport(reportId int, projectManagerId int) error { return err } -func (d *Db) UnsignWeeklyReport(reportId int, projectManagerId int) error { - // Retrieve the project ID associated with the report - var reportProjectID int - err := d.Get(&reportProjectID, "SELECT project_id FROM weekly_reports WHERE report_id = ?", reportId) - if err != nil { - return err - } - - managerQuery := `SELECT project_id FROM user_roles - WHERE user_id = ? - AND project_id = (SELECT project_id FROM weekly_reports WHERE report_id = ?) - AND p_role = 'project_manager'` - - // Retrieve the project ID associated with the project manager - var managerProjectID int - err = d.Get(&managerProjectID, managerQuery, projectManagerId, reportId) - if err != nil { - return err - } - - // Check if the project manager is in the same project as the report - if reportProjectID != managerProjectID { - return errors.New("project manager doesn't have permission to unsign the report") - } - - // Update the signed_by field of the specified report - _, err = d.Exec("UPDATE weekly_reports SET signed_by = NULL WHERE report_id = ?;", reportId) - return err -} - -func (d *Db) GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error) { - // Define the SQL query to fetch unsigned reports for a given user - query := ` - SELECT - report_id, - user_id, - project_id, - week, - development_time, - meeting_time, - admin_time, - own_work_time, - study_time, - testing_time, - signed_by - FROM - weekly_reports - WHERE - signed_by IS NULL - AND project_id = (SELECT id FROM projects WHERE name = ?) - ` - - // Execute the query - rows, err := d.Queryx(query, projectName) - if err != nil { - return nil, err - } - defer rows.Close() - - // Iterate over the rows and populate the result slice - var reports []types.WeeklyReport - for rows.Next() { - var report types.WeeklyReport - if err := rows.StructScan(&report); err != nil { - return nil, err - } - reports = append(reports, report) - } - if err := rows.Err(); err != nil { - return nil, err - } - - return reports, nil -} - // IsSiteAdmin checks if a given username is a site admin func (d *Db) IsSiteAdmin(username string) (bool, error) { // Define the SQL query to check if the user is a site admin @@ -483,7 +377,7 @@ func (d *Db) IsSiteAdmin(username string) (bool, error) { // Reads a directory of migration files and applies them to the database. // This will eventually be used on an embedded directory -func Migrate(db sqlx.DB) error { +func (d *Db) Migrate() error { // Read the embedded scripts directory files, err := scripts.ReadDir("migrations") if err != nil { @@ -495,7 +389,7 @@ func Migrate(db sqlx.DB) error { return nil } - tr := db.MustBegin() + tr := d.MustBegin() // Iterate over each SQL file and execute it for _, file := range files { @@ -523,8 +417,8 @@ func Migrate(db sqlx.DB) error { return nil } -// GetAllWeeklyReports retrieves weekly reports for a specific user and project. -func (d *Db) GetAllWeeklyReports(username string, projectName string) ([]types.WeeklyReportList, error) { +// GetWeeklyReportsUser retrieves weekly reports for a specific user and project. +func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types.WeeklyReportList, error) { query := ` SELECT wr.week, @@ -581,7 +475,7 @@ func (d *Db) UpdateWeeklyReport(projectName string, userName string, week int, d } // MigrateSampleData applies sample data to the database. -func MigrateSampleData(db sqlx.DB) error { +func (d *Db) MigrateSampleData() error { // Insert sample data files, err := sampleData.ReadDir("sample_data") if err != nil { @@ -591,7 +485,7 @@ func MigrateSampleData(db sqlx.DB) error { if len(files) == 0 { println("No sample data files found") } - tr := db.MustBegin() + tr := d.MustBegin() // Iterate over each SQL file and execute it for _, file := range files { @@ -628,7 +522,7 @@ func (d *Db) GetProjectTimes(projectName string) (map[string]int, error) { WHERE projects.name = ? ` - rows, err := d.Query(query, projectName) + rows, err := d.DB.Query(query, projectName) if err != nil { return nil, err } @@ -661,25 +555,3 @@ func (d *Db) RemoveProject(projectname string) error { _, err := d.Exec("DELETE FROM projects WHERE name = ?", projectname) return err } - -func (d *Db) GetUserName(id int) (string, error) { - var username string - err := d.Get(&username, "SELECT username FROM users WHERE id = ?", id) - return username, err -} - -func (d *Db) DeleteReport(reportID int) error { - _, err := d.Exec("DELETE FROM weekly_reports WHERE report_id = ?", reportID) - return err -} - -// ChangeProjectName is a handler that changes the name of a project -func (d *Db) ChangeProjectName(projectName string, newProjectName string) error { - _, err := d.Exec("UPDATE projects SET name = ? WHERE name = ?", newProjectName, projectName) - return err -} - -func (d *Db) ChangeUserPassword(username string, password string) error { - _, err := d.Exec("UPDATE users SET password = ? WHERE username = ?", password, username) - return err -} diff --git a/backend/internal/database/db_test.go b/backend/internal/database/db_test.go index 7b599f2..90ef221 100644 --- a/backend/internal/database/db_test.go +++ b/backend/internal/database/db_test.go @@ -9,13 +9,11 @@ import ( // setupState initializes a database instance with necessary setup for testing func setupState() (Database, error) { db := DbConnect(":memory:") - err := Migrate(db) + err := db.Migrate() if err != nil { return nil, err } - - db_iface := Db{db.MustBegin()} - return &db_iface, nil + return db, nil } // This is a more advanced setup that includes more data in the database. @@ -472,47 +470,6 @@ func TestGetWeeklyReport(t *testing.T) { // Check other fields similarly } -func TestGetUnsignedWeeklyReports(t *testing.T) { - db, err := setupAdvancedState() - if err != nil { - t.Error("setupState failed:", err) - } - - err = db.AddUser("testuser", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - err = db.AddUser("testuser1", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - err = db.AddProject("testproject", "description", "testuser") - if err != nil { - t.Error("AddProject failed:", err) - } - - err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1) - if err != nil { - t.Error("AddWeeklyReport failed:", err) - } - - err = db.AddWeeklyReport("testproject", "testuser1", 1, 1, 1, 1, 1, 1, 1) - if err != nil { - t.Error("AddWeeklyReport failed:", err) - } - - reports, err := db.GetUnsignedWeeklyReports("testproject") - if err != nil { - t.Error("GetUnsignedWeeklyReports failed:", err) - } - - if reports == nil { - t.Error("Expected non-nil reports, got nil") - } -} - // TestSignWeeklyReport tests SignWeeklyReport function of the database func TestSignWeeklyReport(t *testing.T) { db, err := setupState() @@ -585,94 +542,6 @@ func TestSignWeeklyReport(t *testing.T) { } } -func TestUnsignWeeklyReport(t *testing.T) { - db, err := setupState() - if err != nil { - t.Error("setupState failed:", err) - } - - // Add project manager - err = db.AddUser("projectManager", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - // Add a regular user - err = db.AddUser("testuser", "password") - if err != nil { - t.Error("AddUser failed:", err) - } - - // Add project - err = db.AddProject("testproject", "description", "projectManager") - if err != nil { - t.Error("AddProject failed:", err) - } - - // Add both regular users as members to the project - err = db.AddUserToProject("testuser", "testproject", "member") - if err != nil { - t.Error("AddUserToProject failed:", err) - } - - err = db.AddUserToProject("projectManager", "testproject", "project_manager") - if err != nil { - t.Error("AddUserToProject failed:", err) - } - - // Add a weekly report for one of the regular users - err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1) - if err != nil { - t.Error("AddWeeklyReport failed:", err) - } - - // Retrieve the added report - report, err := db.GetWeeklyReport("testuser", "testproject", 1) - if err != nil { - t.Error("GetWeeklyReport failed:", err) - } - - // Print project manager's ID - projectManagerID, err := db.GetUserId("projectManager") - if err != nil { - t.Error("GetUserId failed:", err) - } - - // Sign the report with the project manager - err = db.SignWeeklyReport(report.ReportId, projectManagerID) - if err != nil { - t.Error("SignWeeklyReport failed:", err) - } - - // Retrieve the report again to check if it's signed - signedReport, err := db.GetWeeklyReport("testuser", "testproject", 1) - if err != nil { - t.Error("GetWeeklyReport failed:", err) - } - - // Ensure the report is signed by the project manager - if *signedReport.SignedBy != projectManagerID { - t.Errorf("Expected SignedBy to be %d, got %d", projectManagerID, *signedReport.SignedBy) - } - - // Unsign the report - err = db.UnsignWeeklyReport(report.ReportId, projectManagerID) - if err != nil { - t.Error("UnsignWeeklyReport failed:", err) - } - - // Retrieve the report again to check if it's unsigned - unsignedReport, err := db.GetWeeklyReport("testuser", "testproject", 1) - if err != nil { - t.Error("GetWeeklyReport failed:", err) - } - - // Ensure the report is unsigned - if unsignedReport.SignedBy != nil { - t.Error("Expected SignedBy to be nil, got", unsignedReport.SignedBy) - } -} - // TestSignWeeklyReportByAnotherProjectManager tests the scenario where a project manager attempts to sign a weekly report for a user who is not assigned to their project func TestSignWeeklyReportByAnotherProjectManager(t *testing.T) { db, err := setupState() @@ -795,7 +664,7 @@ func TestGetWeeklyReportsUser(t *testing.T) { t.Error("AddWeeklyReport failed:", err) } - reports, err := db.GetAllWeeklyReports("testuser", "testproject") + reports, err := db.GetWeeklyReportsUser("testuser", "testproject") if err != nil { t.Error("GetWeeklyReportsUser failed:", err) } @@ -1052,93 +921,6 @@ func TestRemoveProject(t *testing.T) { if len(projects) != 0 { t.Error("RemoveProject failed: expected 0, got", len(projects)) } - -} - -func TestDeleteReport(t *testing.T) { - db, err := setupAdvancedState() - if err != nil { - t.Error("setupState failed:", err) - } - - // Promote user to Admin - err = db.PromoteToAdmin("demouser") - if err != nil { - t.Error("PromoteToAdmin failed:", err) - } - - // create a weekly report - err = db.AddWeeklyReport("projecttest", "demouser", 16, 1, 1, 1, 1, 1, 1) - if err != nil { - t.Error("AddWeeklyReport failed:", err) - } - - // Check if the report was added - report, err := db.GetWeeklyReport("demouser", "projecttest", 16) - if err != nil { - t.Error("GetWeeklyReport failed:", err) - } - - // Remove report - err = db.DeleteReport(report.ReportId) - if err != nil { - t.Error("RemoveReport failed:", err) - } - - // Check if the report was removed - report, err = db.GetWeeklyReport("demouser", "projecttest", 16) - if err == nil { - t.Error("RemoveReport failed: report not removed") - } - -} - -func TestChangeProjectName(t *testing.T) { - db, err := setupAdvancedState() - if err != nil { - t.Error("setupState failed:", err) - } - - // Promote user to Admin - err = db.PromoteToAdmin("demouser") - if err != nil { - t.Error("PromoteToAdmin failed:", err) - } - - // Change project name - err = db.ChangeProjectName("projecttest", "newprojectname") - if err != nil { - t.Error("ChangeProjectName failed:", err) - } - - // Check if the project name was changed - projects, err := db.GetAllProjects() - if err != nil { - t.Error("GetAllProjects failed:", err) - } - if projects[0].Name != "newprojectname" { - t.Error("ChangeProjectName failed: expected newprojectname, got", projects[0].Name) - } -} - -func TestChangeUserPassword(t *testing.T) { - db, err := setupState() - if err != nil { - t.Error("setupState failed:", err) - } - - // Add a user - _ = db.AddUser("testuser", "password") - - // Change user password - err = db.ChangeUserPassword("testuser", "newpassword") - if err != nil { - t.Error("ChangeUserPassword failed:", err) - } - - // Check if the password was changed - if !db.CheckUser("testuser", "newpassword") { - t.Error("ChangeUserPassword failed: password not changed") - } - + } + \ No newline at end of file diff --git a/backend/internal/database/middleware.go b/backend/internal/database/middleware.go deleted file mode 100644 index b73a42f..0000000 --- a/backend/internal/database/middleware.go +++ /dev/null @@ -1,34 +0,0 @@ -package database - -import ( - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/jmoiron/sqlx" -) - -// Simple middleware that provides a transaction as a local key "db" -func DbMiddleware(db *sqlx.DB) func(c *fiber.Ctx) error { - return func(c *fiber.Ctx) error { - tx := db.MustBegin() - - defer func() { - if err := tx.Commit(); err != nil { - if err = tx.Rollback(); err != nil { - log.Error("Failed to rollback transaction: ", err) - } - return - } - }() - - var db_iface Database = &Db{tx} - - c.Locals("db", &db_iface) - return c.Next() - } -} - -// Helper function to get the database from the context, without fiddling with casts -func GetDb(c *fiber.Ctx) Database { - // Dereference a pointer to a local, casted to a pointer to a Database - return *c.Locals("db").(*Database) -} diff --git a/backend/internal/database/sample_data/0010_sample_data.sql b/backend/internal/database/sample_data/0010_sample_data.sql index f519608..092fbb0 100644 --- a/backend/internal/database/sample_data/0010_sample_data.sql +++ b/backend/internal/database/sample_data/0010_sample_data.sql @@ -1,220 +1,50 @@ INSERT OR IGNORE INTO users(username, password) -VALUES ("admin", "123"), - ("user", "123"), - ("user2", "123"), - ("John", "123"), - ("Emma", "123"), - ("Michael", "123"), - ("Liam", "123"), - ("Oliver", "123"), - ("Amelia", "123"), - ("Benjamin", "123"), - ("Mia", "123"), - ("Elijah", "123"), - ("Charlotte", "123"), - ("Henry", "123"), - ("Harper", "123"), - ("Lucas", "123"), - ("Emily", "123"), - ("Alexander", "123"), - ("Daniel", "123"), - ("Ella", "123"), - ("Matthew", "123"), - ("Madison", "123"), - ("Samuel", "123"), - ("Avery", "123"), - ("Sofia", "123"), - ("David", "123"), - ("Victoria", "123"), - ("Jackson", "123"), - ("Abigail", "123"), - ("Gabriel", "123"), - ("Luna", "123"), - ("Wyatt", "123"), - ("Chloe", "123"), - ("Nora", "123"), - ("Joshua", "123"), - ("Hazel", "123"), - ("Riley", "123"), - ("Scarlett", "123"), - ("Aria", "123"), - ("Carter", "123"), - ("Grace", "123"), - ("Jayden", "123"), - ("Hannah", "123"), - ("Zoe", "123"), - ("Luke", "123"), - ("Sophia", "123"), - ("Jack", "123"), - ("Isabella", "123"), - ("William", "123"), - ("Mason", "123"), - ("Evelyn", "123"), - ("James", "123"), - ("Cynthia", "123"), - ("Abraham", "123"), - ("Ava", "123"), - ("Aiden", "123"), - ("Natalie", "123"), - ("Lily", "123"), - ("Olivia", "123"), - ("Alexander", "123"), - ("Ethan", "123"), - ("Mila", "123"), - ("Evelyn", "123"), - ("Logan", "123"), - ("Riley", "123"), - ("Grace", "123"), - ("Arnold", "123"), - ("Connor", "123"), - ("Samantha", "123"), - ("Emma", "123"), - ("Sarah", "123"), - ("Nathan", "123"), - ("Layla", "123"), - ("Ryan", "123"), - ("Zoey", "123"), - ("Megan", "123"), - ("Christian", "123"), - ("Eva", "123"), - ("Isaac", "123"), - ("Michaela", "123"), - ("Caroline", "123"), - ("Elijah", "123"), - ("Elena", "123"), - ("Julian", "123"), - ("Sophie", "123"), - ("Gabriella", "123"), - ("Cole", "123"), - ("Hannah", "123"), - ("Lucy", "123"), - ("Katherine", "123"), - ("Benjamin", "123"), - ("Ella", "123"), - ("Evan", "123"); +VALUES ("admin", "123"); -INSERT OR IGNORE INTO projects(name, description, owner_user_id) -VALUES ("projecttest1", "Description for projecttest1", 1), - ("projecttest2", "Description for projecttest2", 1), - ("projecttest3", "Description for projecttest3", 1), - ("projecttest4", "Description for projecttest4", 1), - ("projecttest5", "Description for projecttest5", 1), - ("projecttest6", "Description for projecttest6", 1), - ("projecttest7", "Description for projecttest7", 1), - ("projecttest8", "Description for projecttest8", 1), - ("projecttest9", "Description for projecttest9", 1), - ("projecttest10", "Description for projecttest10", 1), - ("projecttest11", "Description for projecttest11", 1), - ("projecttest12", "Description for projecttest12", 1), - ("projecttest13", "Description for projecttest13", 1), - ("projecttest14", "Description for projecttest14", 1), - ("projecttest15", "Description for projecttest15", 1); +INSERT OR IGNORE INTO users(username, password) +VALUES ("user", "123"); + +INSERT OR IGNORE INTO users(username, password) +VALUES ("user2", "123"); + +INSERT OR IGNORE INTO projects(name,description,owner_user_id) +VALUES ("projecttest","test project", 1); + +INSERT OR IGNORE INTO projects(name,description,owner_user_id) +VALUES ("projecttest2","test project2", 1); + +INSERT OR IGNORE INTO projects(name,description,owner_user_id) +VALUES ("projecttest3","test project3", 1); INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) -VALUES (1,1,"project_manager"), - (1,2,"project_manager"), - (1,3,"project_manager"), - (1,4,"project_manager"), - (1,5,"project_manager"), - (1,6,"project_manager"), - (1,7,"project_manager"), - (1,8,"project_manager"), - (1,9,"project_manager"), - (1,10,"project_manager"), - (1,11,"project_manager"), - (1,12,"project_manager"), - (1,13,"project_manager"), - (1,14,"project_manager"), - (1,15,"project_manager"), - (2,1,"project_manager"), - (2,2,"member"), - (2,3,"member"), - (2,4,"member"), - (2,5,"member"), - (2,6,"member"), - (2,7,"member"), - (2,8,"member"), - (2,9,"member"), - (2,10,"member"), - (2,11,"member"), - (2,12,"member"), - (2,13,"member"), - (2,14,"member"), - (2,15,"member"), - (3,1,"member"), - (3,2,"member"), - (3,3,"member"), - (3,4,"member"), - (3,5,"member"), - (3,6,"member"), - (3,7,"member"), - (3,8,"member"), - (3,9,"member"), - (3,10,"member"), - (3,11,"member"), - (3,12,"member"), - (3,13,"member"), - (3,14,"member"), - (3,15,"member"), - (4,1,"member"), - (4,2,"member"), - (4,3,"member"), - (4,4,"member"), - (4,5,"member"), - (4,6,"member"), - (4,7,"member"), - (4,8,"member"), - (4,9,"member"), - (4,10,"member"), - (4,11,"member"), - (4,12,"member"), - (4,13,"member"), - (4,14,"member"), - (4,15,"member"), - (5,1,"member"), - (5,2,"member"), - (5,3,"member"), - (5,4,"member"), - (5,5,"member"), - (5,6,"member"), - (5,7,"member"), - (5,8,"member"), - (5,9,"member"), - (5,10,"member"), - (5,11,"member"), - (5,12,"member"), - (5,13,"member"), - (5,14,"member"), - (5,15,"member"); +VALUES (1,1,"project_manager"); + +INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) +VALUES (2,1,"member"); + +INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) +VALUES (3,1,"member"); + +INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) +VALUES (3,2,"member"); + +INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) +VALUES (3,3,"member"); + +INSERT OR IGNORE INTO user_roles(user_id,project_id,p_role) +VALUES (2,1,"project_manager"); INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) -VALUES (2, 1, 12, 100, 50, 30, 150, 80, 20, NULL), - (3, 1, 12, 200, 80, 20, 200, 100, 30, NULL), - (3, 1, 14, 150, 70, 40, 180, 90, 25, NULL), - (3, 2, 12, 120, 60, 35, 160, 85, 15, NULL), - (3, 3, 12, 180, 90, 25, 190, 110, 40, NULL), - (2, 1, 13, 130, 70, 40, 170, 95, 35, NULL), - (3, 1, 15, 140, 60, 50, 200, 120, 30, NULL), - (2, 2, 11, 110, 50, 45, 140, 70, 25, NULL), - (3, 3, 14, 170, 80, 30, 180, 100, 35, NULL), - (3, 3, 15, 200, 100, 20, 220, 130, 45, NULL), - (2, 4, 12, 120, 60, 40, 160, 80, 30, NULL), - (3, 5, 14, 150, 70, 30, 180, 90, 25, NULL), - (3, 5, 15, 180, 90, 20, 190, 110, 35, NULL), - (2, 6, 11, 100, 50, 35, 130, 60, 20, NULL), - (3, 7, 14, 170, 80, 25, 180, 100, 30, NULL), - (2, 8, 12, 130, 70, 30, 170, 90, 25, NULL), - (2, 8, 13, 150, 80, 20, 180, 110, 35, NULL), - (3, 9, 12, 140, 60, 40, 180, 100, 30, NULL), - (3, 10, 11, 120, 50, 45, 150, 70, 25, NULL), - (2, 11, 13, 110, 60, 35, 140, 80, 30, NULL), - (3, 12, 12, 160, 70, 30, 180, 100, 35, NULL), - (3, 12, 13, 180, 90, 25, 190, 110, 40, NULL), - (3, 12, 14, 200, 100, 20, 220, 130, 45, NULL), - (2, 13, 11, 100, 50, 45, 130, 60, 20, NULL), - (2, 13, 12, 120, 60, 40, 160, 80, 30, NULL), - (3, 14, 13, 140, 70, 30, 160, 90, 35, NULL), - (3, 15, 12, 150, 80, 25, 180, 100, 30, NULL), - (3, 15, 13, 170, 90, 20, 190, 110, 35, NULL); +VALUES (2, 1, 12, 20, 10, 5, 30, 15, 10, NULL); -INSERT OR IGNORE INTO site_admin VALUES (1); \ No newline at end of file +INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) +VALUES (3, 1, 12, 20, 10, 5, 30, 15, 10, NULL); + +INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) +VALUES (3, 1, 14, 20, 10, 5, 30, 15, 10, NULL); + +INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) +VALUES (3, 2, 12, 20, 10, 5, 30, 15, 10, NULL); + +INSERT OR IGNORE INTO weekly_reports (user_id, project_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time, signed_by) +VALUES (3, 3, 12, 20, 10, 5, 30, 15, 10, NULL); diff --git a/backend/internal/handlers/global_state.go b/backend/internal/handlers/global_state.go new file mode 100644 index 0000000..b832f92 --- /dev/null +++ b/backend/internal/handlers/global_state.go @@ -0,0 +1,43 @@ +package handlers + +import ( + "ttime/internal/database" + + "github.com/gofiber/fiber/v2" +) + +// The actual interface that we will use +type GlobalState interface { + Register(c *fiber.Ctx) error // To register a new user + UserDelete(c *fiber.Ctx) error // To delete a user + Login(c *fiber.Ctx) error // To get the token + LoginRenew(c *fiber.Ctx) error // To renew the token + CreateProject(c *fiber.Ctx) error // To create a new project + GetUserProjects(c *fiber.Ctx) error // To get all projects + SubmitWeeklyReport(c *fiber.Ctx) error + GetWeeklyReport(c *fiber.Ctx) error + SignReport(c *fiber.Ctx) error + GetProject(c *fiber.Ctx) error + AddUserToProjectHandler(c *fiber.Ctx) error + PromoteToAdmin(c *fiber.Ctx) error + GetWeeklyReportsUserHandler(c *fiber.Ctx) error + IsProjectManagerHandler(c *fiber.Ctx) error + DeleteProject(c *fiber.Ctx) error // To delete a project // WIP + ListAllUsers(c *fiber.Ctx) error // To get a list of all users in the application database + ListAllUsersProject(c *fiber.Ctx) error // To get a list of all users for a specific project + ProjectRoleChange(c *fiber.Ctx) error // To change a users role in a project + ChangeUserName(c *fiber.Ctx) error // WIP + GetAllUsersProject(c *fiber.Ctx) error // WIP + UpdateWeeklyReport(c *fiber.Ctx) error + RemoveProject(c *fiber.Ctx) error +} + +// "Constructor" +func NewGlobalState(db database.Database) GlobalState { + return &GState{Db: db} +} + +// The global state, which implements all the handlers +type GState struct { + Db database.Database +} diff --git a/backend/internal/handlers/global_state_test.go b/backend/internal/handlers/global_state_test.go new file mode 100644 index 0000000..c0b64f7 --- /dev/null +++ b/backend/internal/handlers/global_state_test.go @@ -0,0 +1,15 @@ +package handlers + +import ( + "testing" + "ttime/internal/database" +) + +// The actual interface that we will use +func TestGlobalState(t *testing.T) { + db := database.DbConnect(":memory:") + gs := NewGlobalState(db) + if gs == nil { + t.Error("NewGlobalState returned nil") + } +} diff --git a/backend/internal/handlers/handlers_project_related.go b/backend/internal/handlers/handlers_project_related.go new file mode 100644 index 0000000..bdcabd7 --- /dev/null +++ b/backend/internal/handlers/handlers_project_related.go @@ -0,0 +1,315 @@ +package handlers + +import ( + "strconv" + "ttime/internal/types" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" + "github.com/golang-jwt/jwt/v5" +) + +// CreateProject is a simple handler that creates a new project +func (gs *GState) CreateProject(c *fiber.Ctx) error { + user := c.Locals("user").(*jwt.Token) + + p := new(types.NewProject) + if err := c.BodyParser(p); err != nil { + return c.Status(400).SendString(err.Error()) + } + + // Get the username from the token and set it as the owner of the project + // This is ugly but + claims := user.Claims.(jwt.MapClaims) + owner := claims["name"].(string) + + if err := gs.Db.AddProject(p.Name, p.Description, owner); err != nil { + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("Project added") +} + +func (gs *GState) DeleteProject(c *fiber.Ctx) error { + + projectID := c.Params("projectID") + username := c.Params("username") + + if err := gs.Db.DeleteProject(projectID, username); err != nil { + return c.Status(500).SendString((err.Error())) + } + + return c.Status(200).SendString("Project deleted") +} + +// GetUserProjects returns all projects that the user is a member of +func (gs *GState) GetUserProjects(c *fiber.Ctx) error { + // First we get the username from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Then dip into the database to get the projects + projects, err := gs.Db.GetProjectsForUser(username) + if err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return a json serialized list of projects + return c.JSON(projects) +} + +// ProjectRoleChange is a handler that changes a user's role within a project +func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error { + + //check token and get username of current user + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Extract the necessary parameters from the request + data := new(types.RoleChange) + if err := c.BodyParser(data); err != nil { + log.Info("error parsing username, project or role") + return c.Status(400).SendString(err.Error()) + } + + log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role) + + // Dubble diping and checcking if current user is + if ismanager, err := gs.Db.IsProjectManager(username, data.Projectname); err != nil { + log.Warn("Error checking if projectmanager:", err) + return c.Status(500).SendString(err.Error()) + } else if !ismanager { + log.Warn("User is not projectmanager") + return c.Status(401).SendString("User is not projectmanager") + } + + // Change the user's role within the project in the database + if err := gs.Db.ChangeUserRole(username, data.Projectname, data.Role); err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return a success message + return c.SendStatus(fiber.StatusOK) +} + +// GetProject retrieves a specific project by its ID +func (gs *GState) GetProject(c *fiber.Ctx) error { + // Extract the project ID from the request parameters or body + projectID := c.Params("projectID") + if projectID == "" { + log.Info("No project ID provided") + return c.Status(400).SendString("No project ID provided") + } + log.Info("Getting project with ID: ", projectID) + + // Parse the project ID into an integer + projectIDInt, err := strconv.Atoi(projectID) + if err != nil { + log.Info("Invalid project ID") + return c.Status(400).SendString("Invalid project ID") + } + + // Get the project from the database by its ID + project, err := gs.Db.GetProject(projectIDInt) + if err != nil { + log.Info("Error getting project:", err) + return c.Status(500).SendString(err.Error()) + } + + // Return the project as JSON + log.Info("Returning project: ", project.Name) + return c.JSON(project) +} + +func (gs *GState) ListAllUsersProject(c *fiber.Ctx) error { + // Extract the project name from the request parameters or body + projectName := c.Params("projectName") + if projectName == "" { + log.Info("No project name provided") + return c.Status(400).SendString("No project name provided") + } + + // Get the user token + userToken := c.Locals("user").(*jwt.Token) + claims := userToken.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Check if the user is a project manager for the specified project + isManager, err := gs.Db.IsProjectManager(username, projectName) + if err != nil { + log.Info("Error checking project manager status:", err) + return c.Status(500).SendString(err.Error()) + } + + // If the user is not a project manager, check if the user is a site admin + if !isManager { + isAdmin, err := gs.Db.IsSiteAdmin(username) + if err != nil { + log.Info("Error checking admin status:", err) + return c.Status(500).SendString(err.Error()) + } + if !isAdmin { + log.Info("User is neither a project manager nor a site admin:", username) + return c.Status(403).SendString("User is neither a project manager nor a site admin") + } + } + + // Get all users associated with the project from the database + users, err := gs.Db.GetAllUsersProject(projectName) + if err != nil { + log.Info("Error getting users for project:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning users for project: ", projectName) + + // Return the list of users as JSON + return c.JSON(users) +} + +// AddUserToProjectHandler is a handler that adds a user to a project with a specified role +func (gs *GState) AddUserToProjectHandler(c *fiber.Ctx) error { + // Extract necessary parameters from the request + var requestData struct { + Username string `json:"username"` + ProjectName string `json:"projectName"` + Role string `json:"role"` + } + if err := c.BodyParser(&requestData); err != nil { + log.Info("Error parsing request body:", err) + return c.Status(400).SendString("Bad request") + } + + // Check if the user adding another user to the project is a site admin + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + adminUsername := claims["name"].(string) + log.Info("Admin username from claims:", adminUsername) + + isAdmin, err := gs.Db.IsSiteAdmin(adminUsername) + if err != nil { + log.Info("Error checking admin status:", err) + return c.Status(500).SendString(err.Error()) + } + + if !isAdmin { + log.Info("User is not a site admin:", adminUsername) + return c.Status(403).SendString("User is not a site admin") + } + + // Add the user to the project with the specified role + err = gs.Db.AddUserToProject(requestData.Username, requestData.ProjectName, requestData.Role) + if err != nil { + log.Info("Error adding user to project:", err) + return c.Status(500).SendString(err.Error()) + } + + // Return success message + log.Info("User added to project successfully:", requestData.Username) + return c.SendStatus(fiber.StatusOK) +} + +// IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project +func (gs *GState) IsProjectManagerHandler(c *fiber.Ctx) error { + // Get the username from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Extract necessary parameters from the request query string + projectName := c.Query("projectName") + + log.Info("Checking if user ", username, " is a project manager for project ", projectName) + + // Check if the user is a project manager for the specified project + isManager, err := gs.Db.IsProjectManager(username, projectName) + if err != nil { + log.Info("Error checking project manager status:", err) + return c.Status(500).SendString(err.Error()) + } + + // Return the result as JSON + return c.JSON(map[string]bool{"isProjectManager": isManager}) +} + +func (gs *GState) GetProjectTimesHandler(c *fiber.Ctx) error { + // Get the username from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Get project + projectName := c.Params("projectName") + if projectName == "" { + log.Info("No project name provided") + return c.Status(400).SendString("No project name provided") + } + + // Get all users in the project and roles + userProjects, err := gs.Db.GetAllUsersProject(projectName) + if err != nil { + log.Info("Error getting users in project:", err) + return c.Status(500).SendString(err.Error()) + } + + // If the user is member + isMember := false + for _, userProject := range userProjects { + if userProject.Username == username { + isMember = true + break + } + } + + // If the user is admin + if !isMember { + isAdmin, err := gs.Db.IsSiteAdmin(username) + if err != nil { + log.Info("Error checking admin status:", err) + return c.Status(500).SendString(err.Error()) + } + if !isAdmin { + log.Info("User is neither a project member nor a site admin:", username) + return c.Status(403).SendString("User is neither a project member nor a site admin") + } + } + + // Get project times + projectTimes, err := gs.Db.GetProjectTimes(projectName) + if err != nil { + log.Info("Error getting project times:", err) + return c.Status(500).SendString(err.Error()) + } + + // Return project times as JSON + log.Info("Returning project times for project:", projectName) + return c.JSON(projectTimes) +} + +func (gs *GState) RemoveProject(c *fiber.Ctx) error { + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Check if the user is a site admin + isAdmin, err := gs.Db.IsSiteAdmin(username) + if err != nil { + log.Info("Error checking admin status:", err) + return c.Status(500).SendString(err.Error()) + } + + if !isAdmin { + log.Info("User is not a site admin:", username) + return c.Status(403).SendString("User is not a site admin") + } + + projectName := c.Params("projectName") + + if err := gs.Db.RemoveProject(projectName); err != nil { + return c.Status(500).SendString((err.Error())) + } + + return c.Status(200).SendString("Project deleted") +} diff --git a/backend/internal/handlers/handlers_report_related.go b/backend/internal/handlers/handlers_report_related.go new file mode 100644 index 0000000..0e72ead --- /dev/null +++ b/backend/internal/handlers/handlers_report_related.go @@ -0,0 +1,177 @@ +package handlers + +import ( + "strconv" + "ttime/internal/types" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" + "github.com/golang-jwt/jwt/v5" +) + +func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error { + // Extract the necessary parameters from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + report := new(types.NewWeeklyReport) + if err := c.BodyParser(report); err != nil { + log.Info("Error parsing weekly report") + return c.Status(400).SendString(err.Error()) + } + + // Make sure all the fields of the report are valid + if report.Week < 1 || report.Week > 52 { + log.Info("Invalid week number") + return c.Status(400).SendString("Invalid week number") + } + if report.DevelopmentTime < 0 || report.MeetingTime < 0 || report.AdminTime < 0 || report.OwnWorkTime < 0 || report.StudyTime < 0 || report.TestingTime < 0 { + log.Info("Invalid time report") + return c.Status(400).SendString("Invalid time report") + } + + if err := gs.Db.AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil { + log.Info("Error adding weekly report to db:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Weekly report added") + return c.Status(200).SendString("Time report added") +} + +// Handler for retrieving weekly report +func (gs *GState) GetWeeklyReport(c *fiber.Ctx) error { + // Extract the necessary parameters from the request + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + log.Info("Getting weekly report for: ", username) + + // Extract project name and week from query parameters + projectName := c.Query("projectName") + week := c.Query("week") + + if projectName == "" || week == "" { + log.Info("Missing project name or week number") + return c.Status(400).SendString("Missing project name or week number") + } + + // Convert week to integer + weekInt, err := strconv.Atoi(week) + if err != nil { + log.Info("Invalid week number") + return c.Status(400).SendString("Invalid week number") + } + + // Call the database function to get the weekly report + report, err := gs.Db.GetWeeklyReport(username, projectName, weekInt) + if err != nil { + log.Info("Error getting weekly report from db:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning weekly report") + // Return the retrieved weekly report + return c.JSON(report) +} + +type ReportId struct { + ReportId int +} + +func (gs *GState) SignReport(c *fiber.Ctx) error { + // Extract the necessary parameters from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + projectManagerUsername := claims["name"].(string) + + log.Info("Signing report for: ", projectManagerUsername) + + // Extract report ID from the request query parameters + // reportID := c.Query("reportId") + rid := new(ReportId) + if err := c.BodyParser(rid); err != nil { + return err + } + log.Info("Signing report for: ", rid.ReportId) + + // Get the project manager's ID + projectManagerID, err := gs.Db.GetUserId(projectManagerUsername) + if err != nil { + log.Info("Failed to get project manager ID") + return c.Status(500).SendString("Failed to get project manager ID") + } + log.Info("Project manager ID: ", projectManagerID) + + // Call the database function to sign the weekly report + err = gs.Db.SignWeeklyReport(rid.ReportId, projectManagerID) + if err != nil { + log.Info("Error signing weekly report:", err) + return c.Status(500).SendString(err.Error()) + } + + return c.Status(200).SendString("Weekly report signed successfully") +} + +// GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project +func (gs *GState) GetWeeklyReportsUserHandler(c *fiber.Ctx) error { + // Extract the necessary parameters from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Extract necessary (path) parameters from the request + projectName := c.Params("projectName") + + // TODO: Here we need to check whether the user is a member of the project + // If not, we should return an error. On the other hand, if the user not a member, + // the returned list of reports will (should) allways be empty. + + // Retrieve weekly reports for the user in the project from the database + reports, err := gs.Db.GetWeeklyReportsUser(username, projectName) + if err != nil { + log.Error("Error getting weekly reports for user:", username, "in project:", projectName, ":", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning weekly reports for user:", username, "in project:", projectName) + + // Return the list of reports as JSON + return c.JSON(reports) +} + +func (gs *GState) UpdateWeeklyReport(c *fiber.Ctx) error { + // Extract the necessary parameters from the token + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + username := claims["name"].(string) + + // Parse the request body into an UpdateWeeklyReport struct + var updateReport types.UpdateWeeklyReport + if err := c.BodyParser(&updateReport); err != nil { + log.Info("Error parsing weekly report") + return c.Status(400).SendString(err.Error()) + } + + // Make sure all the fields of the report are valid + if updateReport.Week < 1 || updateReport.Week > 52 { + log.Info("Invalid week number") + return c.Status(400).SendString("Invalid week number") + } + + if updateReport.DevelopmentTime < 0 || updateReport.MeetingTime < 0 || updateReport.AdminTime < 0 || updateReport.OwnWorkTime < 0 || updateReport.StudyTime < 0 || updateReport.TestingTime < 0 { + log.Info("Invalid time report") + return c.Status(400).SendString("Invalid time report") + } + + // Update the weekly report in the database + if err := gs.Db.UpdateWeeklyReport(updateReport.ProjectName, username, updateReport.Week, updateReport.DevelopmentTime, updateReport.MeetingTime, updateReport.AdminTime, updateReport.OwnWorkTime, updateReport.StudyTime, updateReport.TestingTime); err != nil { + log.Info("Error updating weekly report in db:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("Weekly report updated") + return c.Status(200).SendString("Weekly report updated") +} diff --git a/backend/internal/handlers/handlers_user_related.go b/backend/internal/handlers/handlers_user_related.go new file mode 100644 index 0000000..39788ae --- /dev/null +++ b/backend/internal/handlers/handlers_user_related.go @@ -0,0 +1,269 @@ +package handlers + +import ( + "time" + "ttime/internal/types" + + "github.com/gofiber/fiber/v2/log" + + "github.com/gofiber/fiber/v2" + "github.com/golang-jwt/jwt/v5" +) + +// Register is a simple handler that registers a new user +// +// @Summary Register +// @Description Register a new user +// @Tags User +// @Accept json +// @Produce plain +// @Param NewUser body types.NewUser true "User to register" +// @Success 200 {string} string "User added" +// @Failure 400 {string} string "Bad request" +// @Failure 500 {string} string "Internal server error" +// @Router /register [post] +func (gs *GState) Register(c *fiber.Ctx) error { + u := new(types.NewUser) + if err := c.BodyParser(u); err != nil { + log.Warn("Error parsing body") + return c.Status(400).SendString(err.Error()) + } + + log.Info("Adding user:", u.Username) + if err := gs.Db.AddUser(u.Username, u.Password); err != nil { + log.Warn("Error adding user:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("User added:", u.Username) + return c.Status(200).SendString("User added") +} + +// This path should obviously be protected in the future +// UserDelete deletes a user from the database +// +// @Summary UserDelete +// @Description UserDelete deletes a user from the database +// @Tags User +// @Accept json +// @Produce plain +// @Success 200 {string} string "User deleted" +// @Failure 403 {string} string "You can only delete yourself" +// @Failure 500 {string} string "Internal server error" +// @Failure 401 {string} string "Unauthorized" +// @Router /userdelete/{username} [delete] +func (gs *GState) UserDelete(c *fiber.Ctx) error { + // Read from path parameters + username := c.Params("username") + + // Read username from Locals + auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string) + + if username != auth_username { + log.Info("User tried to delete another user") + return c.Status(403).SendString("You can only delete yourself") + } + + if err := gs.Db.RemoveUser(username); err != nil { + log.Warn("Error deleting user:", err) + return c.Status(500).SendString(err.Error()) + } + + log.Info("User deleted:", username) + return c.Status(200).SendString("User deleted") +} + +// Login is a simple login handler that returns a JWT token +// +// @Summary login +// @Description logs the user in and returns a jwt token +// @Tags User +// @Accept json +// @Param NewUser body types.NewUser true "login info" +// @Produce plain +// @Success 200 Token types.Token "Successfully signed token for user" +// @Failure 400 {string} string "Bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 500 {string} string "Internal server error" +// @Router /login [post] +func (gs *GState) Login(c *fiber.Ctx) error { + // The body type is identical to a NewUser + + u := new(types.NewUser) + if err := c.BodyParser(u); err != nil { + log.Warn("Error parsing body") + return c.Status(400).SendString(err.Error()) + } + + log.Info("Username logging in:", u.Username) + if !gs.Db.CheckUser(u.Username, u.Password) { + log.Info("User not found") + return c.SendStatus(fiber.StatusUnauthorized) + } + + isAdmin, err := gs.Db.IsSiteAdmin(u.Username) + if err != nil { + log.Info("Error checking admin status:", err) + return c.Status(500).SendString(err.Error()) + } + // Create the Claims + claims := jwt.MapClaims{ + "name": u.Username, + "admin": isAdmin, + "exp": time.Now().Add(time.Hour * 72).Unix(), + } + + // Create token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + log.Info("Token created for user:", u.Username) + + // Generate encoded token and send it as response. + t, err := token.SignedString([]byte("secret")) + if err != nil { + log.Warn("Error signing token") + return c.SendStatus(fiber.StatusInternalServerError) + } + + println("Successfully signed token for user:", u.Username) + return c.JSON(types.Token{Token: t}) +} + +// LoginRenew is a simple handler that renews the token +// +// @Summary LoginRenews +// @Description renews the users token +// @Security bererToken +// @Tags User +// @Accept json +// @Produce plain +// @Success 200 Token types.Token "Successfully signed token for user" +// @Failure 401 {string} string "Unauthorized" +// @Failure 500 {string} string "Internal server error" +// @Router /loginerenew [post] +func (gs *GState) LoginRenew(c *fiber.Ctx) error { + user := c.Locals("user").(*jwt.Token) + + log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"]) + + claims := user.Claims.(jwt.MapClaims) + claims["exp"] = time.Now().Add(time.Hour * 72).Unix() + renewed := jwt.MapClaims{ + "name": claims["name"], + "admin": claims["admin"], + "exp": claims["exp"], + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed) + t, err := token.SignedString([]byte("secret")) + if err != nil { + log.Warn("Error signing token") + return c.SendStatus(fiber.StatusInternalServerError) + } + + log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"]) + return c.JSON(types.Token{Token: t}) +} + +// ListAllUsers is a handler that returns a list of all users in the application database +// +// @Summary ListsAllUsers +// @Description lists all users +// @Tags User +// @Accept json +// @Produce plain +// @Success 200 {json} json "Successfully signed token for user" +// @Failure 401 {string} string "Unauthorized" +// @Failure 500 {string} string "Internal server error" +// @Router /users/all [get] +func (gs *GState) ListAllUsers(c *fiber.Ctx) error { + // Get all users from the database + users, err := gs.Db.GetAllUsersApplication() + if err != nil { + log.Info("Error getting users from db:", err) // Debug print + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning all users") + // Return the list of users as JSON + return c.JSON(users) +} + +func (gs *GState) GetAllUsersProject(c *fiber.Ctx) error { + // Get all users from a project + projectName := c.Params("projectName") + users, err := gs.Db.GetAllUsersProject(projectName) + if err != nil { + log.Info("Error getting users from project:", err) // Debug print + return c.Status(500).SendString(err.Error()) + } + + log.Info("Returning all users") + // Return the list of users as JSON + return c.JSON(users) +} + +// @Summary PromoteToAdmin +// @Description promote chosen user to admin +// @Tags User +// @Accept json +// @Produce plain +// @Param NewUser body types.NewUser true "user info" +// @Success 200 {json} json "Successfully promoted user" +// @Failure 400 {string} string "Bad request" +// @Failure 401 {string} string "Unauthorized" +// @Failure 500 {string} string "Internal server error" +// @Router /promoteToAdmin [post] +func (gs *GState) PromoteToAdmin(c *fiber.Ctx) error { + // Extract the username from the request body + var newUser types.NewUser + if err := c.BodyParser(&newUser); err != nil { + return c.Status(400).SendString("Bad request") + } + username := newUser.Username + + log.Info("Promoting user to admin:", username) // Debug print + + // Promote the user to a site admin in the database + if err := gs.Db.PromoteToAdmin(username); err != nil { + log.Info("Error promoting user to admin:", err) // Debug print + return c.Status(500).SendString(err.Error()) + } + + log.Info("User promoted to admin successfully:", username) // Debug print + + // Return a success message + return c.SendStatus(fiber.StatusOK) +} + +// ChangeUserName changes a user's username in the database +func (gs *GState) ChangeUserName(c *fiber.Ctx) error { + // Check token and get username of current user + user := c.Locals("user").(*jwt.Token) + claims := user.Claims.(jwt.MapClaims) + adminUsername := claims["name"].(string) + log.Info(adminUsername) + + // Extract the necessary parameters from the request + data := new(types.StrNameChange) + if err := c.BodyParser(data); err != nil { + log.Info("Error parsing username") + return c.Status(400).SendString(err.Error()) + } + + // Check if the current user is an admin + isAdmin, err := gs.Db.IsSiteAdmin(adminUsername) + if err != nil { + log.Warn("Error checking if admin:", err) + return c.Status(500).SendString(err.Error()) + } else if !isAdmin { + log.Warn("Tried changing name when not admin") + return c.Status(401).SendString("You cannot change name unless you are an admin") + } + + // Change the user's name in the database + if err := gs.Db.ChangeUserName(data.PrevName, data.NewName); err != nil { + return c.Status(500).SendString(err.Error()) + } + + // Return a success message + return c.SendStatus(fiber.StatusOK) +} diff --git a/backend/internal/handlers/projects/AddUserToProject.go b/backend/internal/handlers/projects/AddUserToProject.go deleted file mode 100644 index 3195314..0000000 --- a/backend/internal/handlers/projects/AddUserToProject.go +++ /dev/null @@ -1,42 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// AddUserToProjectHandler is a handler that adds a user to a project with a specified role -func AddUserToProjectHandler(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - pm_name := claims["name"].(string) - - project := c.Params("projectName") - username := c.Query("userName") - - // Check if the user is a project manager - isPM, err := db.GetDb(c).IsProjectManager(pm_name, project) - if err != nil { - log.Info("Error checking if user is project manager:", err) - return c.Status(500).SendString(err.Error()) - } - - if !isPM { - log.Info("User: ", pm_name, " is not a project manager in project: ", project) - return c.Status(403).SendString("User is not a project manager") - } - - // Add the user to the project with the specified role - err = db.GetDb(c).AddUserToProject(username, project, "member") - if err != nil { - log.Info("Error adding user to project:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return success message - log.Info("User : ", username, " added to project: ", project) - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/internal/handlers/projects/ChangeProjectName.go b/backend/internal/handlers/projects/ChangeProjectName.go deleted file mode 100644 index f6831db..0000000 --- a/backend/internal/handlers/projects/ChangeProjectName.go +++ /dev/null @@ -1,43 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// ChangeProjectName is a handler that changes the name of a project -func ChangeProjectName(c *fiber.Ctx) error { - - //check token and get username of current user - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Extract the necessary parameters from the request - projectName := c.Params("projectName") - newProjectName := c.Query("newProjectName") - - // Check if user is site admin - issiteadmin, err := db.GetDb(c).IsSiteAdmin(username) - if err != nil { - log.Warn("Error checking if siteadmin:", err) - return c.Status(500).SendString(err.Error()) - } else if !issiteadmin { - log.Warn("User is not siteadmin") - return c.Status(401).SendString("User is not siteadmin") - } - - - // Perform the project name change - err = db.GetDb(c).ChangeProjectName(projectName, newProjectName) - if err != nil { - log.Warn("Error changing project name:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return a success message - return c.Status(200).SendString("Project name changed successfully") -} diff --git a/backend/internal/handlers/projects/CreateProject.go b/backend/internal/handlers/projects/CreateProject.go deleted file mode 100644 index cef2f2b..0000000 --- a/backend/internal/handlers/projects/CreateProject.go +++ /dev/null @@ -1,30 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/golang-jwt/jwt/v5" -) - -// CreateProject is a simple handler that creates a new project -func CreateProject(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - - p := new(types.NewProject) - if err := c.BodyParser(p); err != nil { - return c.Status(400).SendString(err.Error()) - } - - // Get the username from the token and set it as the owner of the project - // This is ugly but - claims := user.Claims.(jwt.MapClaims) - owner := claims["name"].(string) - - if err := db.GetDb(c).AddProject(p.Name, p.Description, owner); err != nil { - return c.Status(500).SendString(err.Error()) - } - - return c.Status(200).SendString("Project added") -} diff --git a/backend/internal/handlers/projects/DeleteProject.go b/backend/internal/handlers/projects/DeleteProject.go deleted file mode 100644 index 415424a..0000000 --- a/backend/internal/handlers/projects/DeleteProject.go +++ /dev/null @@ -1,19 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" -) - -func DeleteProject(c *fiber.Ctx) error { - - projectID := c.Params("projectID") - username := c.Params("username") - - if err := db.GetDb(c).DeleteProject(projectID, username); err != nil { - return c.Status(500).SendString((err.Error())) - } - - return c.Status(200).SendString("Project deleted") -} diff --git a/backend/internal/handlers/projects/GetProject.go b/backend/internal/handlers/projects/GetProject.go deleted file mode 100644 index 03333ce..0000000 --- a/backend/internal/handlers/projects/GetProject.go +++ /dev/null @@ -1,38 +0,0 @@ -package projects - -import ( - "strconv" - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" -) - -// GetProject retrieves a specific project by its ID -func GetProject(c *fiber.Ctx) error { - // Extract the project ID from the request parameters or body - projectID := c.Params("projectID") - if projectID == "" { - log.Info("No project ID provided") - return c.Status(400).SendString("No project ID provided") - } - log.Info("Getting project with ID: ", projectID) - - // Parse the project ID into an integer - projectIDInt, err := strconv.Atoi(projectID) - if err != nil { - log.Info("Invalid project ID") - return c.Status(400).SendString("Invalid project ID") - } - - // Get the project from the database by its ID - project, err := db.GetDb(c).GetProject(projectIDInt) - if err != nil { - log.Info("Error getting project:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return the project as JSON - log.Info("Returning project: ", project.Name) - return c.JSON(project) -} diff --git a/backend/internal/handlers/projects/GetProjectTimes.go b/backend/internal/handlers/projects/GetProjectTimes.go deleted file mode 100644 index 573a95e..0000000 --- a/backend/internal/handlers/projects/GetProjectTimes.go +++ /dev/null @@ -1,63 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func GetProjectTimesHandler(c *fiber.Ctx) error { - // Get the username from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Get project - projectName := c.Params("projectName") - if projectName == "" { - log.Info("No project name provided") - return c.Status(400).SendString("No project name provided") - } - - // Get all users in the project and roles - userProjects, err := db.GetDb(c).GetAllUsersProject(projectName) - if err != nil { - log.Info("Error getting users in project:", err) - return c.Status(500).SendString(err.Error()) - } - - // If the user is member - isMember := false - for _, userProject := range userProjects { - if userProject.Username == username { - isMember = true - break - } - } - - // If the user is admin - if !isMember { - isAdmin, err := db.GetDb(c).IsSiteAdmin(username) - if err != nil { - log.Info("Error checking admin status:", err) - return c.Status(500).SendString(err.Error()) - } - if !isAdmin { - log.Info("User is neither a project member nor a site admin:", username) - return c.Status(403).SendString("User is neither a project member nor a site admin") - } - } - - // Get project times - projectTimes, err := db.GetDb(c).GetProjectTimes(projectName) - if err != nil { - log.Info("Error getting project times:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return project times as JSON - log.Info("Returning project times for project:", projectName) - return c.JSON(projectTimes) -} diff --git a/backend/internal/handlers/projects/GetUserProject.go b/backend/internal/handlers/projects/GetUserProject.go deleted file mode 100644 index 6c80515..0000000 --- a/backend/internal/handlers/projects/GetUserProject.go +++ /dev/null @@ -1,26 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" -) - -// GetUserProjects returns all projects that the user is a member of -func GetUserProjects(c *fiber.Ctx) error { - username := c.Params("username") - if username == "" { - log.Info("No username provided") - return c.Status(400).SendString("No username provided") - } - - // Then dip into the database to get the projects - projects, err := db.GetDb(c).GetProjectsForUser(username) - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return a json serialized list of projects - return c.JSON(projects) -} diff --git a/backend/internal/handlers/projects/IsProjectManager.go b/backend/internal/handlers/projects/IsProjectManager.go deleted file mode 100644 index 678fad5..0000000 --- a/backend/internal/handlers/projects/IsProjectManager.go +++ /dev/null @@ -1,32 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// IsProjectManagerHandler is a handler that checks if a user is a project manager for a given project -func IsProjectManagerHandler(c *fiber.Ctx) error { - // Get the username from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Extract necessary parameters from the request query string - projectName := c.Params("projectName") - - log.Info("Checking if user ", username, " is a project manager for project ", projectName) - - // Check if the user is a project manager for the specified project - isManager, err := db.GetDb(c).IsProjectManager(username, projectName) - if err != nil { - log.Info("Error checking project manager status:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return the result as JSON - return c.JSON(fiber.Map{"isProjectManager": isManager}) -} diff --git a/backend/internal/handlers/projects/ListAllUserProjects.go b/backend/internal/handlers/projects/ListAllUserProjects.go deleted file mode 100644 index e0bcaf5..0000000 --- a/backend/internal/handlers/projects/ListAllUserProjects.go +++ /dev/null @@ -1,55 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func ListAllUsersProject(c *fiber.Ctx) error { - // Extract the project name from the request parameters or body - projectName := c.Params("projectName") - if projectName == "" { - log.Info("No project name provided") - return c.Status(400).SendString("No project name provided") - } - - // Get the user token - userToken := c.Locals("user").(*jwt.Token) - claims := userToken.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Check if the user is a project manager for the specified project - isManager, err := db.GetDb(c).IsProjectManager(username, projectName) - if err != nil { - log.Info("Error checking project manager status:", err) - return c.Status(500).SendString(err.Error()) - } - - // If the user is not a project manager, check if the user is a site admin - if !isManager { - isAdmin, err := db.GetDb(c).IsSiteAdmin(username) - if err != nil { - log.Info("Error checking admin status:", err) - return c.Status(500).SendString(err.Error()) - } - if !isAdmin { - log.Info("User is neither a project manager nor a site admin:", username) - return c.Status(403).SendString("User is neither a project manager nor a site admin") - } - } - - // Get all users associated with the project from the database - users, err := db.GetDb(c).GetAllUsersProject(projectName) - if err != nil { - log.Info("Error getting users for project:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning users for project: ", projectName) - - // Return the list of users as JSON - return c.JSON(users) -} diff --git a/backend/internal/handlers/projects/ProjectRoleChange.go b/backend/internal/handlers/projects/ProjectRoleChange.go deleted file mode 100644 index 6c5d455..0000000 --- a/backend/internal/handlers/projects/ProjectRoleChange.go +++ /dev/null @@ -1,51 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// ProjectRoleChange is a handler that changes a user's role within a project -func ProjectRoleChange(c *fiber.Ctx) error { - - //check token and get username of current user - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Extract the necessary parameters from the request - data := new(types.RoleChange) - if err := c.BodyParser(data); err != nil { - log.Info("error parsing username, project or role") - return c.Status(400).SendString(err.Error()) - } - - // Check if user is trying to change its own role - if username == data.UserName { - log.Info("Can't change your own role") - return c.Status(403).SendString("Can't change your own role") - } - - log.Info("Changing role for user: ", data.UserName, " in project: ", data.Projectname, " to: ", data.Role) - - // Dubble diping and checcking if current user is - if ismanager, err := db.GetDb(c).IsProjectManager(username, data.Projectname); err != nil { - log.Warn("Error checking if projectmanager:", err) - return c.Status(500).SendString(err.Error()) - } else if !ismanager { - log.Warn("User is not projectmanager") - return c.Status(401).SendString("User is not projectmanager") - } - - // Change the user's role within the project in the database - if err := db.GetDb(c).ChangeUserRole(data.UserName, data.Projectname, data.Role); err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return a success message - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/internal/handlers/projects/PromoteToPm.go b/backend/internal/handlers/projects/PromoteToPm.go deleted file mode 100644 index c587b65..0000000 --- a/backend/internal/handlers/projects/PromoteToPm.go +++ /dev/null @@ -1,55 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// @Summary Promote to project manager -// @Description Promote a user to project manager -// @Tags Auth -// @Security JWT -// @Accept plain -// @Produce plain -// @Param projectName path string true "Project name" -// @Param userName query string true "User name" -// @Failure 500 {string} string "Internal server error" -// @Failure 403 {string} string "Forbidden" -// @Router /promote/{projectName} [put] -// -// Login logs in a user and returns a JWT token -// Promote to project manager -func PromoteToPm(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - pm_name := claims["name"].(string) - - project := c.Params("projectName") - new_pm_name := c.Query("userName") - - // Check if the user is a project manager - isPM, err := db.GetDb(c).IsProjectManager(pm_name, project) - if err != nil { - log.Info("Error checking if user is project manager:", err) - return c.Status(500).SendString(err.Error()) - } - - if !isPM { - log.Info("User: ", pm_name, " is not a project manager in project: ", project) - return c.Status(403).SendString("User is not a project manager") - } - - // Add the user to the project with the specified role - err = db.GetDb(c).ChangeUserRole(new_pm_name, project, "project_manager") - if err != nil { - log.Info("Error promoting user to project manager:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return success message - log.Info("User : ", new_pm_name, " promoted to project manager in project: ", project) - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/internal/handlers/projects/RemoveProject.go b/backend/internal/handlers/projects/RemoveProject.go deleted file mode 100644 index 7b140dd..0000000 --- a/backend/internal/handlers/projects/RemoveProject.go +++ /dev/null @@ -1,35 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func RemoveProject(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Check if the user is a site admin - isAdmin, err := db.GetDb(c).IsSiteAdmin(username) - if err != nil { - log.Info("Error checking admin status:", err) - return c.Status(500).SendString(err.Error()) - } - - if !isAdmin { - log.Info("User is not a site admin:", username) - return c.Status(403).SendString("User is not a site admin") - } - - projectName := c.Params("projectName") - - if err := db.GetDb(c).RemoveProject(projectName); err != nil { - return c.Status(500).SendString((err.Error())) - } - - return c.Status(200).SendString("Project deleted") -} diff --git a/backend/internal/handlers/projects/RemoveUserFromProject.go b/backend/internal/handlers/projects/RemoveUserFromProject.go deleted file mode 100644 index 7aefcf8..0000000 --- a/backend/internal/handlers/projects/RemoveUserFromProject.go +++ /dev/null @@ -1,40 +0,0 @@ -package projects - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func RemoveUserFromProject(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - pm_name := claims["name"].(string) - - project := c.Params("projectName") - username := c.Query("userName") - - // Check if the user is a project manager - isPM, err := db.GetDb(c).IsProjectManager(pm_name, project) - if err != nil { - log.Info("Error checking if user is project manager:", err) - return c.Status(500).SendString(err.Error()) - } - - if !isPM { - log.Info("User: ", pm_name, " is not a project manager in project: ", project) - return c.Status(403).SendString("User is not a project manager") - } - - // Remove the user from the project - if err = db.GetDb(c).RemoveUserFromProject(username, project); err != nil { - log.Info("Error removing user from project:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return success message - log.Info("User : ", username, " removed from project: ", project) - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/internal/handlers/reports/DeleteReport.go b/backend/internal/handlers/reports/DeleteReport.go deleted file mode 100644 index b3ae7de..0000000 --- a/backend/internal/handlers/reports/DeleteReport.go +++ /dev/null @@ -1,22 +0,0 @@ -package reports - -import ( - "strconv" - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" -) - -func DeleteReport(c *fiber.Ctx) error { - reportID := c.Params("reportID") - reportIDInt, err := strconv.Atoi(reportID) - if err != nil { - return c.Status(400).SendString("Invalid report ID") - } - - if err := db.GetDb(c).DeleteReport(reportIDInt); err != nil { - return c.Status(500).SendString((err.Error())) - } - - return c.Status(200).SendString("Weekly report deleted") -} diff --git a/backend/internal/handlers/reports/GetAllWeeklyReports.go b/backend/internal/handlers/reports/GetAllWeeklyReports.go deleted file mode 100644 index d9778b8..0000000 --- a/backend/internal/handlers/reports/GetAllWeeklyReports.go +++ /dev/null @@ -1,56 +0,0 @@ -package reports - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// GetAllWeeklyReports retrieves all weekly reports for a user in a specific project -func GetAllWeeklyReports(c *fiber.Ctx) error { - // Extract the necessary parameters from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Extract project name and week from query parameters - projectName := c.Params("projectName") - target_user := c.Query("targetUser") // The user whose reports are being requested - - // If the target user is not empty, use it as the username - if target_user == "" { - target_user = username - } - - log.Info(username, " trying to get all weekly reports for: ", target_user) - - if projectName == "" { - log.Info("Missing project name") - return c.Status(400).SendString("Missing project name") - } - - // If the user is not a project manager, they can only view their own reports - pm, err := db.GetDb(c).IsProjectManager(username, projectName) - if err != nil { - log.Info("Error checking if user is project manager:", err) - return c.Status(500).SendString(err.Error()) - } - - if !(pm || target_user == username) { - log.Info("Unauthorized access") - return c.Status(403).SendString("Unauthorized access") - } - - // Retrieve weekly reports for the user in the project from the database - reports, err := db.GetDb(c).GetAllWeeklyReports(target_user, projectName) - if err != nil { - log.Error("Error getting weekly reports for user:", target_user, "in project:", projectName, ":", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning weekly report") - // Return the retrieved weekly report - return c.JSON(reports) -} diff --git a/backend/internal/handlers/reports/GetUnsignedReports.go b/backend/internal/handlers/reports/GetUnsignedReports.go deleted file mode 100644 index 9525f55..0000000 --- a/backend/internal/handlers/reports/GetUnsignedReports.go +++ /dev/null @@ -1,45 +0,0 @@ -package reports - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func GetUnsignedReports(c *fiber.Ctx) error { - // Extract the necessary parameters from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - projectManagerUsername := claims["name"].(string) - - // Extract project name and week from query parameters - projectName := c.Params("projectName") - - log.Info("Getting unsigned reports for") - - if projectName == "" { - log.Info("Missing project name") - return c.Status(400).SendString("Missing project name") - } - - // Get the project manager's ID - isProjectManager, err := db.GetDb(c).IsProjectManager(projectManagerUsername, projectName) - if err != nil { - log.Info("Failed to get project manager ID") - return c.Status(500).SendString("Failed to get project manager ID") - } - log.Info("User is Project Manager: ", isProjectManager) - - // Call the database function to get the unsigned weekly reports - reports, err := db.GetDb(c).GetUnsignedWeeklyReports(projectName) - if err != nil { - log.Info("Error getting unsigned weekly reports:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning unsigned reports") - // Return the list of unsigned reports - return c.JSON(reports) -} diff --git a/backend/internal/handlers/reports/GetWeeklyReport.go b/backend/internal/handlers/reports/GetWeeklyReport.go deleted file mode 100644 index 2b6827e..0000000 --- a/backend/internal/handlers/reports/GetWeeklyReport.go +++ /dev/null @@ -1,65 +0,0 @@ -package reports - -import ( - "strconv" - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// Handler for retrieving weekly report -func GetWeeklyReport(c *fiber.Ctx) error { - // Extract the necessary parameters from the request - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Extract project name and week from query parameters - projectName := c.Query("projectName") - week := c.Query("week") - target_user := c.Query("targetUser") // The user whose report is being requested - - // If the target user is not empty, use it as the username - if target_user == "" { - target_user = username - } - - log.Info(username, " trying to get weekly report for: ", target_user) - - if projectName == "" || week == "" { - log.Info("Missing project name or week number") - return c.Status(400).SendString("Missing project name or week number") - } - - // Convert week to integer - weekInt, err := strconv.Atoi(week) - if err != nil { - log.Info("Invalid week number") - return c.Status(400).SendString("Invalid week number") - } - - // If the token user is not an admin, check if the target user is the same as the token user - pm, err := db.GetDb(c).IsProjectManager(username, projectName) - if err != nil { - log.Info("Error checking if user is project manager:", err) - return c.Status(500).SendString(err.Error()) - } - - if !(pm || target_user == username) { - log.Info("Unauthorized access") - return c.Status(403).SendString("Unauthorized access") - } - - // Call the database function to get the weekly report - report, err := db.GetDb(c).GetWeeklyReport(target_user, projectName, weekInt) - if err != nil { - log.Info("Error getting weekly report from db:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning weekly report") - // Return the retrieved weekly report - return c.JSON(report) -} diff --git a/backend/internal/handlers/reports/SignReport.go b/backend/internal/handlers/reports/SignReport.go deleted file mode 100644 index a486ecc..0000000 --- a/backend/internal/handlers/reports/SignReport.go +++ /dev/null @@ -1,41 +0,0 @@ -package reports - -import ( - "strconv" - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func SignReport(c *fiber.Ctx) error { - // Extract the necessary parameters from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - projectManagerUsername := claims["name"].(string) - - // Extract report ID from the path - reportId, err := strconv.Atoi(c.Params("reportId")) - if err != nil { - log.Info("Invalid report ID") - return c.Status(400).SendString("Invalid report ID") - } - - // Get the project manager's ID - projectManagerID, err := db.GetDb(c).GetUserId(projectManagerUsername) - if err != nil { - log.Info("Failed to get project manager ID for user: ", projectManagerUsername) - return c.Status(500).SendString("Failed to get project manager ID") - } - - // Call the database function to sign the weekly report - err = db.GetDb(c).SignWeeklyReport(reportId, projectManagerID) - if err != nil { - log.Info("Error signing weekly report:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Project manager ID: ", projectManagerID, " signed report ID: ", reportId) - return c.Status(200).SendString("Weekly report signed successfully") -} diff --git a/backend/internal/handlers/reports/Statistics.go b/backend/internal/handlers/reports/Statistics.go deleted file mode 100644 index dac017d..0000000 --- a/backend/internal/handlers/reports/Statistics.go +++ /dev/null @@ -1,56 +0,0 @@ -package reports - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func GetStatistics(c *fiber.Ctx) error { - // Extract the necessary parameters from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Extract project name from query parameters - projectName := c.Query("projectName") - userNameParam := c.Query("userName") - - log.Info(username, " trying to get statistics for project: ", projectName) - - if projectName == "" { - log.Info("Missing project name") - return c.Status(400).SendString("Missing project name") - } - - // Check if the user is a project manager - pm, err := db.GetDb(c).IsProjectManager(username, projectName) - if err != nil { - log.Info("Error checking if user is project manager:", err) - return c.Status(500).SendString(err.Error()) - } - - // Bail if the user is not a PM or checking its own statistics - if !pm && userNameParam != "" && userNameParam != username { - log.Info("Unauthorized access for user: ", username, "trying to access project: ", projectName, "statistics for user: ", userNameParam) - return c.Status(403).SendString("Unauthorized access") - } - - if pm && userNameParam != "" { - username = userNameParam - } - - // Retrieve statistics for the project from the database - statistics, err := db.GetDb(c).ReportStatistics(username, projectName) - if err != nil { - log.Error("Error getting statistics for project:", projectName, ":", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning statistics") - // Return the retrieved statistics - return c.JSON(statistics) - -} diff --git a/backend/internal/handlers/reports/SubmitWeeklyReport.go b/backend/internal/handlers/reports/SubmitWeeklyReport.go deleted file mode 100644 index 900aa03..0000000 --- a/backend/internal/handlers/reports/SubmitWeeklyReport.go +++ /dev/null @@ -1,41 +0,0 @@ -package reports - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func SubmitWeeklyReport(c *fiber.Ctx) error { - // Extract the necessary parameters from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - report := new(types.NewWeeklyReport) - if err := c.BodyParser(report); err != nil { - log.Info("Error parsing weekly report") - return c.Status(400).SendString(err.Error()) - } - - // Make sure all the fields of the report are valid - if report.Week < 1 || report.Week > 52 { - log.Info("Invalid week number") - return c.Status(400).SendString("Invalid week number") - } - if report.DevelopmentTime < 0 || report.MeetingTime < 0 || report.AdminTime < 0 || report.OwnWorkTime < 0 || report.StudyTime < 0 || report.TestingTime < 0 { - log.Info("Invalid time report") - return c.Status(400).SendString("Invalid time report") - } - - if err := db.GetDb(c).AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil { - log.Info("Error adding weekly report to db:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Weekly report added") - return c.Status(200).SendString("Time report added") -} diff --git a/backend/internal/handlers/reports/UnsignReport.go b/backend/internal/handlers/reports/UnsignReport.go deleted file mode 100644 index 45943de..0000000 --- a/backend/internal/handlers/reports/UnsignReport.go +++ /dev/null @@ -1,41 +0,0 @@ -package reports - -import ( - "strconv" - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func UnsignReport(c *fiber.Ctx) error { - // Extract the necessary parameters from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - projectManagerUsername := claims["name"].(string) - - // Extract report ID from the path - reportId, err := strconv.Atoi(c.Params("reportId")) - if err != nil { - log.Info("Invalid report ID") - return c.Status(400).SendString("Invalid report ID") - } - - // Get the project manager's ID - projectManagerID, err := db.GetDb(c).GetUserId(projectManagerUsername) - if err != nil { - log.Info("Failed to get project manager ID for user: ", projectManagerUsername) - return c.Status(500).SendString("Failed to get project manager ID") - } - - // Call the database function to sign the weekly report - err = db.GetDb(c).UnsignWeeklyReport(reportId, projectManagerID) - if err != nil { - log.Info("Error Unsigning weekly report:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Project manager ID: ", projectManagerID, " unsigned report ID: ", reportId) - return c.Status(200).SendString("Weekly report unsigned successfully") -} diff --git a/backend/internal/handlers/reports/UpdateWeeklyReport.go b/backend/internal/handlers/reports/UpdateWeeklyReport.go deleted file mode 100644 index 3ab835d..0000000 --- a/backend/internal/handlers/reports/UpdateWeeklyReport.go +++ /dev/null @@ -1,44 +0,0 @@ -package reports - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -func UpdateWeeklyReport(c *fiber.Ctx) error { - // Extract the necessary parameters from the token - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - username := claims["name"].(string) - - // Parse the request body into an UpdateWeeklyReport struct - var updateReport types.UpdateWeeklyReport - if err := c.BodyParser(&updateReport); err != nil { - log.Info("Error parsing weekly report") - return c.Status(400).SendString(err.Error()) - } - - // Make sure all the fields of the report are valid - if updateReport.Week < 1 || updateReport.Week > 52 { - log.Info("Invalid week number") - return c.Status(400).SendString("Invalid week number") - } - - if updateReport.DevelopmentTime < 0 || updateReport.MeetingTime < 0 || updateReport.AdminTime < 0 || updateReport.OwnWorkTime < 0 || updateReport.StudyTime < 0 || updateReport.TestingTime < 0 { - log.Info("Invalid time report") - return c.Status(400).SendString("Invalid time report") - } - - // Update the weekly report in the database - if err := db.GetDb(c).UpdateWeeklyReport(updateReport.ProjectName, username, updateReport.Week, updateReport.DevelopmentTime, updateReport.MeetingTime, updateReport.AdminTime, updateReport.OwnWorkTime, updateReport.StudyTime, updateReport.TestingTime); err != nil { - log.Info("Error updating weekly report in db:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("Weekly report updated") - return c.Status(200).SendString("Weekly report updated") -} diff --git a/backend/internal/handlers/users/ChangeUserName.go b/backend/internal/handlers/users/ChangeUserName.go deleted file mode 100644 index 75032e4..0000000 --- a/backend/internal/handlers/users/ChangeUserName.go +++ /dev/null @@ -1,44 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// ChangeUserName changes a user's username in the database -func ChangeUserName(c *fiber.Ctx) error { - // Check token and get username of current user - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - adminUsername := claims["name"].(string) - log.Info(adminUsername) - - // Extract the necessary parameters from the request - data := new(types.StrNameChange) - if err := c.BodyParser(data); err != nil { - log.Info("Error parsing username") - return c.Status(400).SendString(err.Error()) - } - - // Check if the current user is an admin - isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername) - if err != nil { - log.Warn("Error checking if admin:", err) - return c.Status(500).SendString(err.Error()) - } else if !isAdmin { - log.Warn("Tried changing name when not admin") - return c.Status(401).SendString("You cannot change name unless you are an admin") - } - - // Change the user's name in the database - if err := db.GetDb(c).ChangeUserName(data.PrevName, data.NewName); err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Return a success message - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/internal/handlers/users/ChangeUserPassword.go b/backend/internal/handlers/users/ChangeUserPassword.go deleted file mode 100644 index 1596247..0000000 --- a/backend/internal/handlers/users/ChangeUserPassword.go +++ /dev/null @@ -1,42 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// ChangeUserPassword is a handler that changes the password of a user -func ChangeUserPassword(c *fiber.Ctx) error { - - //Check token and get username of current user - user := c.Locals("user").(*jwt.Token) - claims := user.Claims.(jwt.MapClaims) - admin := claims["name"].(string) - - // Extract the necessary parameters from the request - username := c.Params("username") - newPassword := c.Query("newPassword") - - // Check if user is site admin - issiteadmin, err := db.GetDb(c).IsSiteAdmin(admin) - if err != nil { - log.Warn("Error checking if siteadmin:", err) - return c.Status(500).SendString(err.Error()) - } else if !issiteadmin { - log.Warn("User is not siteadmin") - return c.Status(401).SendString("User is not siteadmin") - } - - // Perform the password change - err = db.GetDb(c).ChangeUserPassword(username, newPassword) - if err != nil { - log.Warn("Error changing password:", err) - return c.Status(500).SendString(err.Error()) - } - - // Return a success message - return c.Status(200).SendString("Password changed successfully") -} diff --git a/backend/internal/handlers/users/GetUserName.go b/backend/internal/handlers/users/GetUserName.go deleted file mode 100644 index 82b6cc8..0000000 --- a/backend/internal/handlers/users/GetUserName.go +++ /dev/null @@ -1,32 +0,0 @@ -package users - -import ( - "strconv" - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" -) - -// Return the username of a user given their user id -func GetUserName(c *fiber.Ctx) error { - // Check the query params for userId - user_id_string := c.Query("userId") - if user_id_string == "" { - return c.Status(400).SendString("Missing user id") - } - - // Convert to int - user_id, err := strconv.Atoi(user_id_string) - if err != nil { - return c.Status(400).SendString("Invalid user id") - } - - // Get the username from the database - username, err := db.GetDb(c).GetUserName(user_id) - if err != nil { - return c.Status(500).SendString(err.Error()) - } - - // Send the nuclear launch codes to north korea - return c.JSON(fiber.Map{"username": username}) -} diff --git a/backend/internal/handlers/users/GetUsersProjects.go b/backend/internal/handlers/users/GetUsersProjects.go deleted file mode 100644 index 10a6ec6..0000000 --- a/backend/internal/handlers/users/GetUsersProjects.go +++ /dev/null @@ -1,22 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" -) - -func GetAllUsersProject(c *fiber.Ctx) error { - // Get all users from a project - projectName := c.Params("projectName") - users, err := db.GetDb(c).GetAllUsersProject(projectName) - if err != nil { - log.Info("Error getting users from project:", err) // Debug print - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning all users") - // Return the list of users as JSON - return c.JSON(users) -} diff --git a/backend/internal/handlers/users/ListAllUsers.go b/backend/internal/handlers/users/ListAllUsers.go deleted file mode 100644 index 5ac5df0..0000000 --- a/backend/internal/handlers/users/ListAllUsers.go +++ /dev/null @@ -1,32 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" -) - -// @Summary ListsAllUsers -// @Description lists all users -// @Tags User -// @Produce json -// @Security JWT -// @Success 200 {array} string "Successfully returned all users" -// @Failure 401 {string} string "Unauthorized" -// @Failure 500 {string} string "Internal server error" -// @Router /users/all [get] -// -// ListAllUsers returns a list of all users in the application database -func ListAllUsers(c *fiber.Ctx) error { - // Get all users from the database - users, err := db.GetDb(c).GetAllUsersApplication() - if err != nil { - log.Info("Error getting users from db:", err) // Debug print - return c.Status(500).SendString(err.Error()) - } - - log.Info("Returning all users") - // Return the list of users as JSON - return c.JSON(users) -} diff --git a/backend/internal/handlers/users/Login.go b/backend/internal/handlers/users/Login.go deleted file mode 100644 index 42c52a5..0000000 --- a/backend/internal/handlers/users/Login.go +++ /dev/null @@ -1,66 +0,0 @@ -package users - -import ( - "time" - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// @Summary Login -// @Description Logs in a user and returns a JWT token -// @Tags Auth -// @Accept json -// @Produce json -// @Param body body types.NewUser true "User credentials" -// @Success 200 {object} types.Token "JWT token" -// @Failure 400 {string} string "Bad request" -// @Failure 401 {string} string "Unauthorized" -// @Failure 500 {string} string "Internal server error" -// @Router /login [post] -// -// Login logs in a user and returns a JWT token -func Login(c *fiber.Ctx) error { - // The body type is identical to a NewUser - - u := new(types.NewUser) - if err := c.BodyParser(u); err != nil { - log.Warn("Error parsing body") - return c.Status(400).SendString(err.Error()) - } - - log.Info("Username logging in:", u.Username) - if !db.GetDb(c).CheckUser(u.Username, u.Password) { - log.Info("User not found") - return c.SendStatus(fiber.StatusUnauthorized) - } - - isAdmin, err := db.GetDb(c).IsSiteAdmin(u.Username) - if err != nil { - log.Info("Error checking admin status:", err) - return c.Status(500).SendString(err.Error()) - } - // Create the Claims - claims := jwt.MapClaims{ - "name": u.Username, - "admin": isAdmin, - "exp": time.Now().Add(time.Hour * 72).Unix(), - } - - // Create token - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - log.Info("Token created for user:", u.Username) - - // Generate encoded token and send it as response. - t, err := token.SignedString([]byte("secret")) - if err != nil { - log.Warn("Error signing token") - return c.SendStatus(fiber.StatusInternalServerError) - } - - println("Successfully signed token for user:", u.Username) - return c.JSON(types.Token{Token: t}) -} diff --git a/backend/internal/handlers/users/LoginRenew.go b/backend/internal/handlers/users/LoginRenew.go deleted file mode 100644 index 3926ce4..0000000 --- a/backend/internal/handlers/users/LoginRenew.go +++ /dev/null @@ -1,50 +0,0 @@ -package users - -import ( - "time" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// @Summary LoginRenews -// @Description Renews the users token. -// @Tags Auth -// @Produce json -// @Security JWT -// @Success 200 {object} types.Token "Successfully signed token for user" -// @Failure 401 {string} string "Unauthorized" -// @Failure 500 {string} string "Internal server error" -// @Router /loginrenew [post] -// -// LoginRenew renews the users token -func LoginRenew(c *fiber.Ctx) error { - user := c.Locals("user").(*jwt.Token) - - log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"]) - - // Renewing the token means we trust whatever is already in the token - claims := user.Claims.(jwt.MapClaims) - - // 72 hour expiration time - claims["exp"] = time.Now().Add(time.Hour * 72).Unix() - - // Create token with old claims, but new expiration time - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "name": claims["name"], - "admin": claims["admin"], - "exp": claims["exp"], - }) - - // Sign it with top secret key - t, err := token.SignedString([]byte("secret")) - if err != nil { - log.Warn("Error signing token") - return c.SendStatus(fiber.StatusInternalServerError) // 500 - } - - log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"]) - return c.JSON(types.Token{Token: t}) -} diff --git a/backend/internal/handlers/users/PromoteToAdmin.go b/backend/internal/handlers/users/PromoteToAdmin.go deleted file mode 100644 index 3f0a6d3..0000000 --- a/backend/internal/handlers/users/PromoteToAdmin.go +++ /dev/null @@ -1,45 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" -) - -// @Summary PromoteToAdmin -// @Description Promote chosen user to site admin -// @Tags User -// @Accept json -// @Produce plain -// @Security JWT -// @Param NewUser body types.NewUser true "user info" -// @Success 200 {object} types.Token "Successfully promoted user" -// @Failure 400 {string} string "Bad request" -// @Failure 401 {string} string "Unauthorized" -// @Failure 500 {string} string "Internal server error" -// @Router /promoteToAdmin [post] -// -// PromoteToAdmin promotes a user to a site admin -func PromoteToAdmin(c *fiber.Ctx) error { - // Extract the username from the request body - var newUser types.NewUser - if err := c.BodyParser(&newUser); err != nil { - return c.Status(400).SendString("Bad request") - } - username := newUser.Username - - log.Info("Promoting user to admin:", username) // Debug print - - // Promote the user to a site admin in the database - if err := db.GetDb(c).PromoteToAdmin(username); err != nil { - log.Info("Error promoting user to admin:", err) // Debug print - return c.Status(500).SendString(err.Error()) - } - - log.Info("User promoted to admin successfully:", username) // Debug print - - // Return a success message - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/internal/handlers/users/Register.go b/backend/internal/handlers/users/Register.go deleted file mode 100644 index b9e0c78..0000000 --- a/backend/internal/handlers/users/Register.go +++ /dev/null @@ -1,38 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - "ttime/internal/types" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" -) - -// @Summary Register -// @Description Register a new user -// @Tags Auth -// @Accept json -// @Produce plain -// @Param NewUser body types.NewUser true "User to register" -// @Success 200 {string} string "User added" -// @Failure 400 {string} string "Bad request" -// @Failure 500 {string} string "Internal server error" -// @Router /register [post] -// -// Register is a simple handler that registers a new user -func Register(c *fiber.Ctx) error { - u := new(types.NewUser) - if err := c.BodyParser(u); err != nil { - log.Warn("Error parsing body") - return c.Status(400).SendString(err.Error()) - } - - log.Info("Adding user:", u.Username) - if err := db.GetDb(c).AddUser(u.Username, u.Password); err != nil { - log.Warn("Error adding user:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("User added:", u.Username) - return c.Status(200).SendString("User added") -} diff --git a/backend/internal/handlers/users/UserDelete.go b/backend/internal/handlers/users/UserDelete.go deleted file mode 100644 index 491a1b3..0000000 --- a/backend/internal/handlers/users/UserDelete.go +++ /dev/null @@ -1,43 +0,0 @@ -package users - -import ( - db "ttime/internal/database" - - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/log" - "github.com/golang-jwt/jwt/v5" -) - -// @Summary UserDelete -// @Description UserDelete deletes a user from the database -// @Tags User -// @Accept json -// @Produce plain -// @Security JWT -// @Success 200 {string} string "User deleted" -// @Failure 403 {string} string "You can only delete yourself" -// @Failure 500 {string} string "Internal server error" -// @Failure 401 {string} string "Unauthorized" -// @Router /userdelete/{username} [delete] -// -// UserDelete deletes a user from the database -func UserDelete(c *fiber.Ctx) error { - // Read from path parameters - username := c.Params("username") - - // Read username from Locals - auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string) - - if username == auth_username { - log.Info("User tried to delete itself") - return c.Status(403).SendString("You can't delete yourself") - } - - if err := db.GetDb(c).RemoveUser(username); err != nil { - log.Warn("Error deleting user:", err) - return c.Status(500).SendString(err.Error()) - } - - log.Info("User deleted:", username) - return c.Status(200).SendString("User deleted") -} diff --git a/backend/internal/types/WeeklyReport.go b/backend/internal/types/WeeklyReport.go index 5550b3f..234781b 100644 --- a/backend/internal/types/WeeklyReport.go +++ b/backend/internal/types/WeeklyReport.go @@ -66,15 +66,6 @@ type WeeklyReport struct { SignedBy *int `json:"signedBy" db:"signed_by"` } -type Statistics struct { - TotalDevelopmentTime int `json:"totalDevelopmentTime" db:"total_development_time"` - TotalMeetingTime int `json:"totalMeetingTime" db:"total_meeting_time"` - TotalAdminTime int `json:"totalAdminTime" db:"total_admin_time"` - TotalOwnWorkTime int `json:"totalOwnWorkTime" db:"total_own_work_time"` - TotalStudyTime int `json:"totalStudyTime" db:"total_study_time"` - TotalTestingTime int `json:"totalTestingTime" db:"total_testing_time"` -} - type UpdateWeeklyReport struct { // The name of the project, as it appears in the database ProjectName string `json:"projectName"` diff --git a/backend/internal/types/users.go b/backend/internal/types/users.go index 37cc8c2..88b4f06 100644 --- a/backend/internal/types/users.go +++ b/backend/internal/types/users.go @@ -18,8 +18,8 @@ func (u *User) ToPublicUser() (*PublicUser, error) { // Should be used when registering, for example type NewUser struct { - Username string `json:"username" example:"username123"` - Password string `json:"password" example:"password123"` + Username string `json:"username"` + Password string `json:"password"` } // PublicUser represents a user that is safe to send over the API (no password) diff --git a/backend/main.go b/backend/main.go index 0c66369..0ec638b 100644 --- a/backend/main.go +++ b/backend/main.go @@ -6,9 +6,7 @@ import ( _ "ttime/docs" "ttime/internal/config" "ttime/internal/database" - "ttime/internal/handlers/projects" - "ttime/internal/handlers/reports" - "ttime/internal/handlers/users" + "ttime/internal/handlers" "github.com/BurntSushi/toml" "github.com/gofiber/fiber/v2" @@ -25,16 +23,15 @@ import ( // @license.name AGPL // @license.url https://www.gnu.org/licenses/agpl-3.0.html -// @securityDefinitions.apikey JWT -// @in header -// @name Authorization -// @description Use the JWT token provided by the login endpoint to authenticate requests. **Prefix the token with "Bearer ".** +//@securityDefinitions.apikey bererToken +//@in header +//@name Authorization // @host localhost:8080 // @BasePath /api -// @externalDocs.description OpenAPI -// @externalDocs.url https://swagger.io/resources/open-api/ +// @externalDocs.description OpenAPI +// @externalDocs.url https://swagger.io/resources/open-api/ /** Main function for starting the server and initializing configurations. @@ -57,28 +54,24 @@ func main() { // Connect to the database db := database.DbConnect(conf.DbPath) - // Migrate the database - if err = database.Migrate(db); err != nil { + if err = db.Migrate(); err != nil { fmt.Println("Error migrating database: ", err) os.Exit(1) } - // Migrate sample data, should not be used in production - if err = database.MigrateSampleData(db); err != nil { + if err = db.MigrateSampleData(); err != nil { fmt.Println("Error migrating sample data: ", err) os.Exit(1) } + // Get our global state + gs := handlers.NewGlobalState(db) // Create the server server := fiber.New() - // We want some logs server.Use(logger.New()) - // Sets up db middleware, accessed as Local "db" key - server.Use(database.DbMiddleware(&db)) - // Mounts the swagger documentation, this is available at /swagger/index.html server.Get("/swagger/*", swagger.HandlerDefault) @@ -86,59 +79,36 @@ func main() { // This will likely be replaced by an embedded filesystem in the future server.Static("/", "./static") - // Create a group for our API - api := server.Group("/api") - // Register our unprotected routes - api.Post("/register", users.Register) - api.Post("/login", users.Login) + server.Post("/api/register", gs.Register) + server.Post("/api/login", gs.Login) - // Every route from here on will require a valid - // JWT bearer token authentication in the header + // Every route from here on will require a valid JWT server.Use(jwtware.New(jwtware.Config{ SigningKey: jwtware.SigningKey{Key: []byte("secret")}, })) - // All user related routes - // userGroup := api.Group("/user") // Not currently in use - api.Get("/users/all", users.ListAllUsers) - api.Get("/project/getAllUsers", users.GetAllUsersProject) - api.Get("/username", users.GetUserName) - api.Post("/login", users.Login) - api.Post("/register", users.Register) - api.Post("/loginrenew", users.LoginRenew) - api.Post("/promoteToAdmin", users.PromoteToAdmin) - api.Put("/changeUserName", users.ChangeUserName) - api.Delete("/userdelete/:username", users.UserDelete) // Perhaps just use POST to avoid headaches - api.Put("/changeUserPassword/:username", users.ChangeUserPassword) - - // All project related routes - // projectGroup := api.Group("/project") // Not currently in use - api.Get("/getProjectTimes/:projectName", projects.GetProjectTimesHandler) - api.Get("/getUserProjects/:username", projects.GetUserProjects) - api.Get("/project/:projectId", projects.GetProject) - api.Get("/checkIfProjectManager/:projectName", projects.IsProjectManagerHandler) - api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject) - api.Post("/project", projects.CreateProject) - api.Post("/ProjectRoleChange", projects.ProjectRoleChange) - api.Put("/promoteToPm/:projectName", projects.PromoteToPm) - api.Put("/addUserToProject/:projectName", projects.AddUserToProjectHandler) - api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject) - api.Delete("/removeProject/:projectName", projects.RemoveProject) - api.Delete("/project/:projectID", projects.DeleteProject) - api.Put("/changeProjectName/:projectName", projects.ChangeProjectName) - - // All report related routes - // reportGroup := api.Group("/report") // Not currently in use - api.Get("/getWeeklyReport", reports.GetWeeklyReport) - api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports) - api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports) - api.Get("/getStatistics", reports.GetStatistics) - api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport) - api.Put("/signReport/:reportId", reports.SignReport) - api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport) - api.Put("/unsignReport/:reportId", reports.UnsignReport) - api.Delete("/deleteReport/:reportId", reports.DeleteReport) + // Protected routes (require a valid JWT bearer token authentication header) + server.Post("/api/submitWeeklyReport", gs.SubmitWeeklyReport) + server.Get("/api/getUserProjects", gs.GetUserProjects) + server.Post("/api/loginrenew", gs.LoginRenew) + server.Delete("/api/userdelete/:username", gs.UserDelete) // Perhaps just use POST to avoid headaches + server.Delete("api/project/:projectID", gs.DeleteProject) // WIP + server.Post("/api/project", gs.CreateProject) // WIP + server.Get("/api/project/:projectId", gs.GetProject) + server.Get("/api/project/getAllUsers", gs.GetAllUsersProject) + server.Get("/api/getWeeklyReport", gs.GetWeeklyReport) + server.Post("/api/signReport", gs.SignReport) + server.Put("/api/addUserToProject", gs.AddUserToProjectHandler) + server.Put("/api/changeUserName", gs.ChangeUserName) + server.Post("/api/promoteToAdmin", gs.PromoteToAdmin) + server.Get("/api/users/all", gs.ListAllUsers) + server.Get("/api/getWeeklyReportsUser/:projectName", gs.GetWeeklyReportsUserHandler) + server.Get("/api/checkIfProjectManager/:projectName", gs.IsProjectManagerHandler) + server.Post("/api/ProjectRoleChange", gs.ProjectRoleChange) + server.Get("/api/getUsersProject/:projectName", gs.ListAllUsersProject) + server.Put("/api/updateWeeklyReport", gs.UpdateWeeklyReport) + server.Delete("/api/removeProject/:projectName", gs.RemoveProject) // Announce the port we are listening on and start the server err = server.Listen(fmt.Sprintf(":%d", conf.Port)) diff --git a/frontend/.prettierignore b/frontend/.prettierignore deleted file mode 100644 index c49d006..0000000 --- a/frontend/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -goTypes.ts -GenApi.ts \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 323ef11..eecde70 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -75,42 +75,113 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", - "picocolors": "^1.0.0" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/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==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", - "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", + "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.4", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.4", - "@babel/parser": "^7.24.4", + "@babel/helpers": "^7.24.0", + "@babel/parser": "^7.24.0", "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", + "@babel/traverse": "^7.24.0", "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -136,14 +207,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.4.tgz", - "integrity": "sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@babel/types": "^7.24.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { @@ -166,6 +237,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -175,6 +255,12 @@ "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", @@ -210,12 +296,12 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, "dependencies": { - "@babel/types": "^7.24.0" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -274,9 +360,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -301,13 +387,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.4.tgz", - "integrity": "sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", + "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", "dev": true, "dependencies": { "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.1", + "@babel/traverse": "^7.24.0", "@babel/types": "^7.24.0" }, "engines": { @@ -315,15 +401,14 @@ } }, "node_modules/@babel/highlight": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", - "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "js-tokens": "^4.0.0" }, "engines": { "node": ">=6.9.0" @@ -401,9 +486,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.4.tgz", - "integrity": "sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -473,12 +558,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", - "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -575,12 +660,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", - "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -590,9 +675,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz", - "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.0.tgz", + "integrity": "sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -615,18 +700,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", - "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", + "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.24.1", - "@babel/generator": "^7.24.1", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.1", + "@babel/parser": "^7.24.0", "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" @@ -665,9 +750,9 @@ "dev": true }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", "cpu": [ "ppc64" ], @@ -681,9 +766,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", "cpu": [ "arm" ], @@ -697,9 +782,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", "cpu": [ "arm64" ], @@ -713,9 +798,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", "cpu": [ "x64" ], @@ -729,9 +814,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", "cpu": [ "arm64" ], @@ -745,9 +830,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", "cpu": [ "x64" ], @@ -761,9 +846,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", "cpu": [ "arm64" ], @@ -777,9 +862,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", "cpu": [ "x64" ], @@ -793,9 +878,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", "cpu": [ "arm" ], @@ -809,9 +894,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", "cpu": [ "arm64" ], @@ -825,9 +910,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", "cpu": [ "ia32" ], @@ -841,9 +926,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", "cpu": [ "loong64" ], @@ -857,9 +942,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", "cpu": [ "mips64el" ], @@ -873,9 +958,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", "cpu": [ "ppc64" ], @@ -889,9 +974,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", "cpu": [ "riscv64" ], @@ -905,9 +990,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", "cpu": [ "s390x" ], @@ -921,9 +1006,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", "cpu": [ "x64" ], @@ -937,9 +1022,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", "cpu": [ "x64" ], @@ -953,9 +1038,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", "cpu": [ "x64" ], @@ -969,9 +1054,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", "cpu": [ "x64" ], @@ -985,9 +1070,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", "cpu": [ "arm64" ], @@ -1001,9 +1086,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", "cpu": [ "ia32" ], @@ -1017,9 +1102,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", "cpu": [ "x64" ], @@ -1102,9 +1187,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1160,9 +1245,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, "node_modules/@isaacs/cliui": { @@ -1194,41 +1279,6 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@isaacs/cliui/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==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/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==", - "dev": true - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -1244,23 +1294,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@isaacs/cliui/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==", - "dev": true, - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1698,9 +1731,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.24.tgz", + "integrity": "sha512-+VaWXDa6+l6MhflBvVXjIEAzb59nQ2JUK3bwRp2zRpPtU+8TFRy9Gg/5oIcNlkEL5PGlBFGfemUVvIgLnTzq7Q==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1765,17 +1798,17 @@ } }, "node_modules/@remix-run/router": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", - "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.2.tgz", + "integrity": "sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==", "engines": { "node": ">=14.0.0" } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.14.0.tgz", - "integrity": "sha512-jwXtxYbRt1V+CdQSy6Z+uZti7JF5irRKF8hlKfEnF/xJpcNGuuiZMBvuoYM+x9sr9iWGnzrlM0+9hvQ1kgkf1w==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz", + "integrity": "sha512-/MeDQmcD96nVoRumKUljsYOLqfv1YFJps+0pTrb2Z9Nl/w5qNUysMaWQsrd1mvAlNT4yza1iVyIu4Q4AgF6V3A==", "cpu": [ "arm" ], @@ -1786,9 +1819,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.14.0.tgz", - "integrity": "sha512-fI9nduZhCccjzlsA/OuAwtFGWocxA4gqXGTLvOyiF8d+8o0fZUeSztixkYjcGq1fGZY3Tkq4yRvHPFxU+jdZ9Q==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.10.0.tgz", + "integrity": "sha512-lvu0jK97mZDJdpZKDnZI93I0Om8lSDaiPx3OiCk0RXn3E8CMPJNS/wxjAvSJJzhhZpfjXsjLWL8LnS6qET4VNQ==", "cpu": [ "arm64" ], @@ -1799,9 +1832,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.14.0.tgz", - "integrity": "sha512-BcnSPRM76/cD2gQC+rQNGBN6GStBs2pl/FpweW8JYuz5J/IEa0Fr4AtrPv766DB/6b2MZ/AfSIOSGw3nEIP8SA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.10.0.tgz", + "integrity": "sha512-uFpayx8I8tyOvDkD7X6n0PriDRWxcqEjqgtlxnUA/G9oS93ur9aZ8c8BEpzFmsed1TH5WZNG5IONB8IiW90TQg==", "cpu": [ "arm64" ], @@ -1812,9 +1845,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.14.0.tgz", - "integrity": "sha512-LDyFB9GRolGN7XI6955aFeI3wCdCUszFWumWU0deHA8VpR3nWRrjG6GtGjBrQxQKFevnUTHKCfPR4IvrW3kCgQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.10.0.tgz", + "integrity": "sha512-nIdCX03qFKoR/MwQegQBK+qZoSpO3LESurVAC6s6jazLA1Mpmgzo3Nj3H1vydXp/JM29bkCiuF7tDuToj4+U9Q==", "cpu": [ "x64" ], @@ -1825,9 +1858,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.14.0.tgz", - "integrity": "sha512-ygrGVhQP47mRh0AAD0zl6QqCbNsf0eTo+vgwkY6LunBcg0f2Jv365GXlDUECIyoXp1kKwL5WW6rsO429DBY/bA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.10.0.tgz", + "integrity": "sha512-Fz7a+y5sYhYZMQFRkOyCs4PLhICAnxRX/GnWYReaAoruUzuRtcf+Qnw+T0CoAWbHCuz2gBUwmWnUgQ67fb3FYw==", "cpu": [ "arm" ], @@ -1838,9 +1871,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.14.0.tgz", - "integrity": "sha512-x+uJ6MAYRlHGe9wi4HQjxpaKHPM3d3JjqqCkeC5gpnnI6OWovLdXTpfa8trjxPLnWKyBsSi5kne+146GAxFt4A==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.10.0.tgz", + "integrity": "sha512-yPtF9jIix88orwfTi0lJiqINnlWo6p93MtZEoaehZnmCzEmLL0eqjA3eGVeyQhMtxdV+Mlsgfwhh0+M/k1/V7Q==", "cpu": [ "arm64" ], @@ -1851,9 +1884,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.14.0.tgz", - "integrity": "sha512-nrRw8ZTQKg6+Lttwqo6a2VxR9tOroa2m91XbdQ2sUUzHoedXlsyvY1fN4xWdqz8PKmf4orDwejxXHjh7YBGUCA==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.10.0.tgz", + "integrity": "sha512-9GW9yA30ib+vfFiwjX+N7PnjTnCMiUffhWj4vkG4ukYv1kJ4T9gHNg8zw+ChsOccM27G9yXrEtMScf1LaCuoWQ==", "cpu": [ "arm64" ], @@ -1863,23 +1896,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.14.0.tgz", - "integrity": "sha512-xV0d5jDb4aFu84XKr+lcUJ9y3qpIWhttO3Qev97z8DKLXR62LC3cXT/bMZXrjLF9X+P5oSmJTzAhqwUbY96PnA==", - "cpu": [ - "ppc64le" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.14.0.tgz", - "integrity": "sha512-SDDhBQwZX6LPRoPYjAZWyL27LbcBo7WdBFWJi5PI9RPCzU8ijzkQn7tt8NXiXRiFMJCVpkuMkBf4OxSxVMizAw==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.10.0.tgz", + "integrity": "sha512-X1ES+V4bMq2ws5fF4zHornxebNxMXye0ZZjUrzOrf7UMx1d6wMQtfcchZ8SqUnQPPHdOyOLW6fTcUiFgHFadRA==", "cpu": [ "riscv64" ], @@ -1889,23 +1909,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.14.0.tgz", - "integrity": "sha512-RxB/qez8zIDshNJDufYlTT0ZTVut5eCpAZ3bdXDU9yTxBzui3KhbGjROK2OYTTor7alM7XBhssgoO3CZ0XD3qA==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.14.0.tgz", - "integrity": "sha512-C6y6z2eCNCfhZxT9u+jAM2Fup89ZjiG5pIzZIDycs1IwESviLxwkQcFRGLjnDrP+PT+v5i4YFvlcfAs+LnreXg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.10.0.tgz", + "integrity": "sha512-w/5OpT2EnI/Xvypw4FIhV34jmNqU5PZjZue2l2Y3ty1Ootm3SqhI+AmfhlUYGBTd9JnpneZCDnt3uNOiOBkMyw==", "cpu": [ "x64" ], @@ -1916,9 +1923,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.14.0.tgz", - "integrity": "sha512-i0QwbHYfnOMYsBEyjxcwGu5SMIi9sImDVjDg087hpzXqhBSosxkE7gyIYFHgfFl4mr7RrXksIBZ4DoLoP4FhJg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.10.0.tgz", + "integrity": "sha512-q/meftEe3QlwQiGYxD9rWwB21DoKQ9Q8wA40of/of6yGHhZuGfZO0c3WYkN9dNlopHlNT3mf5BPsUSxoPuVQaw==", "cpu": [ "x64" ], @@ -1929,9 +1936,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.14.0.tgz", - "integrity": "sha512-Fq52EYb0riNHLBTAcL0cun+rRwyZ10S9vKzhGKKgeD+XbwunszSY0rVMco5KbOsTlwovP2rTOkiII/fQ4ih/zQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.10.0.tgz", + "integrity": "sha512-NrR6667wlUfP0BHaEIKgYM/2va+Oj+RjZSASbBMnszM9k+1AmliRjHc3lJIiOehtSSjqYiO7R6KLNrWOX+YNSQ==", "cpu": [ "arm64" ], @@ -1942,9 +1949,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.14.0.tgz", - "integrity": "sha512-e/PBHxPdJ00O9p5Ui43+vixSgVf4NlLsmV6QneGERJ3lnjIua/kim6PRFe3iDueT1rQcgSkYP8ZBBXa/h4iPvw==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.10.0.tgz", + "integrity": "sha512-FV0Tpt84LPYDduIDcXvEC7HKtyXxdvhdAOvOeWMWbQNulxViH2O07QXkT/FffX4FqEI02jEbCJbr+YcuKdyyMg==", "cpu": [ "ia32" ], @@ -1955,9 +1962,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.14.0.tgz", - "integrity": "sha512-aGg7iToJjdklmxlUlJh/PaPNa4PmqHfyRMLunbL3eaMO0gp656+q1zOKkpJ/CVe9CryJv6tAN1HDoR8cNGzkag==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.10.0.tgz", + "integrity": "sha512-OZoJd+o5TaTSQeFFQ6WjFCiltiYVjIdsXxwu/XZ8qRpsvMQr4UsVrE5UyT9RIvsnuF47DqkJKhhVZ2Q9YW9IpQ==", "cpu": [ "x64" ], @@ -1992,9 +1999,9 @@ } }, "node_modules/@swc/core": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.12.tgz", - "integrity": "sha512-QljRxTaUajSLB9ui93cZ38/lmThwIw/BPxjn+TphrYN6LPU3vu9/ykjgHtlpmaXDDcngL4K5i396E7iwwEUxYg==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.2.tgz", + "integrity": "sha512-vWgY07R/eqj1/a0vsRKLI9o9klGZfpLNOVEnrv4nrccxBgYPjcf22IWwAoaBJ+wpA7Q4fVjCUM8lP0m01dpxcg==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2009,16 +2016,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.4.12", - "@swc/core-darwin-x64": "1.4.12", - "@swc/core-linux-arm-gnueabihf": "1.4.12", - "@swc/core-linux-arm64-gnu": "1.4.12", - "@swc/core-linux-arm64-musl": "1.4.12", - "@swc/core-linux-x64-gnu": "1.4.12", - "@swc/core-linux-x64-musl": "1.4.12", - "@swc/core-win32-arm64-msvc": "1.4.12", - "@swc/core-win32-ia32-msvc": "1.4.12", - "@swc/core-win32-x64-msvc": "1.4.12" + "@swc/core-darwin-arm64": "1.4.2", + "@swc/core-darwin-x64": "1.4.2", + "@swc/core-linux-arm-gnueabihf": "1.4.2", + "@swc/core-linux-arm64-gnu": "1.4.2", + "@swc/core-linux-arm64-musl": "1.4.2", + "@swc/core-linux-x64-gnu": "1.4.2", + "@swc/core-linux-x64-musl": "1.4.2", + "@swc/core-win32-arm64-msvc": "1.4.2", + "@swc/core-win32-ia32-msvc": "1.4.2", + "@swc/core-win32-x64-msvc": "1.4.2" }, "peerDependencies": { "@swc/helpers": "^0.5.0" @@ -2030,9 +2037,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.12.tgz", - "integrity": "sha512-BZUUq91LGJsLI2BQrhYL3yARkcdN4TS3YGNS6aRYUtyeWrGCTKHL90erF2BMU2rEwZLLkOC/U899R4o4oiSHfA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.2.tgz", + "integrity": "sha512-1uSdAn1MRK5C1m/TvLZ2RDvr0zLvochgrZ2xL+lRzugLlCTlSA+Q4TWtrZaOz+vnnFVliCpw7c7qu0JouhgQIw==", "cpu": [ "arm64" ], @@ -2046,9 +2053,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.12.tgz", - "integrity": "sha512-Wkk8rq1RwCOgg5ybTlfVtOYXLZATZ+QjgiBNM7pIn03A5/zZicokNTYd8L26/mifly2e74Dz34tlIZBT4aTGDA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.2.tgz", + "integrity": "sha512-TYD28+dCQKeuxxcy7gLJUCFLqrwDZnHtC2z7cdeGfZpbI2mbfppfTf2wUPzqZk3gEC96zHd4Yr37V3Tvzar+lQ==", "cpu": [ "x64" ], @@ -2062,9 +2069,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.12.tgz", - "integrity": "sha512-8jb/SN67oTQ5KSThWlKLchhU6xnlAlnmnLCCOKK1xGtFS6vD+By9uL+qeEY2krV98UCRTf68WSmC0SLZhVoz5A==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.2.tgz", + "integrity": "sha512-Eyqipf7ZPGj0vplKHo8JUOoU1un2sg5PjJMpEesX0k+6HKE2T8pdyeyXODN0YTFqzndSa/J43EEPXm+rHAsLFQ==", "cpu": [ "arm" ], @@ -2078,9 +2085,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.12.tgz", - "integrity": "sha512-DhW47DQEZKCdSq92v5F03rqdpjRXdDMqxfu4uAlZ9Uo1wJEGvY23e1SNmhji2sVHsZbBjSvoXoBLk0v00nSG8w==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.2.tgz", + "integrity": "sha512-wZn02DH8VYPv3FC0ub4my52Rttsus/rFw+UUfzdb3tHMHXB66LqN+rR0ssIOZrH6K+VLN6qpTw9VizjyoH0BxA==", "cpu": [ "arm64" ], @@ -2094,9 +2101,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.12.tgz", - "integrity": "sha512-PR57pT3TssnCRvdsaKNsxZy9N8rFg9AKA1U7W+LxbZ/7Z7PHc5PjxF0GgZpE/aLmU6xOn5VyQTlzjoamVkt05g==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.2.tgz", + "integrity": "sha512-3G0D5z9hUj9bXNcwmA1eGiFTwe5rWkuL3DsoviTj73TKLpk7u64ND0XjEfO0huVv4vVu9H1jodrKb7nvln/dlw==", "cpu": [ "arm64" ], @@ -2110,9 +2117,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.12.tgz", - "integrity": "sha512-HLZIWNHWuFIlH+LEmXr1lBiwGQeCshKOGcqbJyz7xpqTh7m2IPAxPWEhr/qmMTMsjluGxeIsLrcsgreTyXtgNA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.2.tgz", + "integrity": "sha512-LFxn9U8cjmYHw3jrdPNqPAkBGglKE3tCZ8rA7hYyp0BFxuo7L2ZcEnPm4RFpmSCCsExFH+LEJWuMGgWERoktvg==", "cpu": [ "x64" ], @@ -2126,9 +2133,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.12.tgz", - "integrity": "sha512-M5fBAtoOcpz2YQAFtNemrPod5BqmzAJc8pYtT3dVTn1MJllhmLHlphU8BQytvoGr1PHgJL8ZJBlBGdt70LQ7Mw==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.2.tgz", + "integrity": "sha512-dp0fAmreeVVYTUcb4u9njTPrYzKnbIH0EhH2qvC9GOYNNREUu2GezSIDgonjOXkHiTCvopG4xU7y56XtXj4VrQ==", "cpu": [ "x64" ], @@ -2142,9 +2149,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.12.tgz", - "integrity": "sha512-K8LjjgZ7VQFtM+eXqjfAJ0z+TKVDng3r59QYn7CL6cyxZI2brLU3lNknZcUFSouZD+gsghZI/Zb8tQjVk7aKDQ==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.2.tgz", + "integrity": "sha512-HlVIiLMQkzthAdqMslQhDkoXJ5+AOLUSTV6fm6shFKZKqc/9cJvr4S8UveNERL9zUficA36yM3bbfo36McwnvQ==", "cpu": [ "arm64" ], @@ -2158,9 +2165,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.12.tgz", - "integrity": "sha512-hflO5LCxozngoOmiQbDPyvt6ODc5Cu9AwTJP9uH/BSMPdEQ6PCnefuUOJLAKew2q9o+NmDORuJk+vgqQz9Uzpg==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.2.tgz", + "integrity": "sha512-WCF8faPGjCl4oIgugkp+kL9nl3nUATlzKXCEGFowMEmVVCFM0GsqlmGdPp1pjZoWc9tpYanoXQDnp5IvlDSLhA==", "cpu": [ "ia32" ], @@ -2174,9 +2181,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.4.12", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.12.tgz", - "integrity": "sha512-3A4qMtddBDbtprV5edTB/SgJn9L+X5TL7RGgS3eWtEgn/NG8gA80X/scjf1v2MMeOsrcxiYhnemI2gXCKuQN2g==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.2.tgz", + "integrity": "sha512-oV71rwiSpA5xre2C5570BhCsg1HF97SNLsZ/12xv7zayGzqr3yvFALFJN8tHKpqUdCB4FGPjoP3JFdV3i+1wUw==", "cpu": [ "x64" ], @@ -2213,13 +2220,10 @@ } }, "node_modules/@swc/types": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz", - "integrity": "sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==", - "dev": true, - "dependencies": { - "@swc/counter": "^0.1.3" - } + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "dev": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -2318,43 +2322,50 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.12.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", - "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", + "version": "20.11.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.24.tgz", + "integrity": "sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", "dev": true }, "node_modules/@types/react": { - "version": "18.2.74", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.74.tgz", - "integrity": "sha512-9AEqNZZyBx8OdZpxzQlaFEVCSFUM2YXJH46yPOiOpm078k6ZLOCcuAzGum/zK8YBwY+dbahVNbHrbgrAwIRlqw==", + "version": "18.2.55", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.55.tgz", + "integrity": "sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==", "dev": true, "dependencies": { "@types/prop-types": "*", + "@types/scheduler": "*", "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "18.2.24", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.24.tgz", - "integrity": "sha512-cN6upcKd8zkGy4HU9F1+/s98Hrp6D4MOcippK4PoE8OZRngohHZpbJn1GsaDLz87MqvHNoT13nHvNqM9ocRHZg==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz", + "integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==", "dev": true, "dependencies": { "@types/react": "*" } }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true + }, "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", + "integrity": "sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==", "dev": true }, "node_modules/@types/stack-utils": { @@ -2722,16 +2733,15 @@ } }, "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", "is-string": "^1.0.7" }, "engines": { @@ -2750,26 +2760,6 @@ "node": ">=8" } }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array.prototype.flat": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", @@ -2806,18 +2796,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.toreversed": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", - "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - } - }, "node_modules/array.prototype.tosorted": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", @@ -2853,10 +2831,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", + "integrity": "sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + } + }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.17", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.17.tgz", + "integrity": "sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==", "dev": true, "funding": [ { @@ -2873,8 +2860,8 @@ } ], "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.22.2", + "caniuse-lite": "^1.0.30001578", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.0", @@ -2891,13 +2878,10 @@ } }, "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz", + "integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==", "dev": true, - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, "engines": { "node": ">= 0.4" }, @@ -3028,15 +3012,12 @@ "dev": true }, "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/brace-expansion": { @@ -3154,9 +3135,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001605", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz", - "integrity": "sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==", + "version": "1.0.30001591", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz", + "integrity": "sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==", "dev": true, "funding": [ { @@ -3269,6 +3250,43 @@ "node": ">=12" } }, + "node_modules/cliui/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==", + "dev": true + }, + "node_modules/cliui/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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -3377,57 +3395,6 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, - "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3475,14 +3442,15 @@ } }, "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.3.tgz", + "integrity": "sha512-h3GBouC+RPtNX2N0hHVLo2ZwPYurq8mLmXpOLTsw71gr7lHt5VaI4vVkDUNOfiWmm48JEXe3VM7PmLX45AMmmg==", "dev": true, "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -3569,9 +3537,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.727", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.727.tgz", - "integrity": "sha512-brpv4KTeC4g0Fx2FeIKytLd4UGn1zBQq5Lauy7zEWT9oqkaj5mgsxblEZIAOf1HHLlXxzr6adGViiBy5Z39/CA==", + "version": "1.4.686", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.686.tgz", + "integrity": "sha512-3avY1B+vUzNxEgkBDpKOP8WarvUAEwpRaiCL0He5OKWEFxzaOFiq4WoZEZe7qh0ReS7DiWoHMnYoQCKxNZNzSg==", "dev": true }, "node_modules/emittery": { @@ -3587,9 +3555,9 @@ } }, "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==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, "node_modules/error-ex": { @@ -3602,57 +3570,50 @@ } }, "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", + "is-typed-array": "^1.1.12", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "which-typed-array": "^1.1.13" }, "engines": { "node": ">= 0.4" @@ -3683,51 +3644,40 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.0.18", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz", - "integrity": "sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.16.tgz", + "integrity": "sha512-CREG2A9Vq7bpDRnldhFcMKuKArvkZtsH6Y0DHOHVg49qhf+LD8uEdUM3OkOAICv0EziGtDEnQtqY2/mfBILpFw==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.6", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", + "es-abstract": "^1.22.3", "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", + "es-set-tostringtag": "^2.0.2", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", + "has-property-descriptors": "^1.0.1", + "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "internal-slot": "^1.0.7", "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0" + "safe-array-concat": "^1.1.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" }, "engines": { "node": ">= 0.4" @@ -3760,9 +3710,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", "dev": true, "hasInstallScript": true, "bin": { @@ -3772,29 +3722,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" } }, "node_modules/escalade": { @@ -3819,16 +3769,16 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -3916,29 +3866,27 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.34.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz", - "integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==", + "version": "7.33.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz", + "integrity": "sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==", "dev": true, "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlast": "^1.2.4", - "array.prototype.flatmap": "^1.3.2", - "array.prototype.toreversed": "^1.1.2", - "array.prototype.tosorted": "^1.1.3", + "array-includes": "^3.1.6", + "array.prototype.flatmap": "^1.3.1", + "array.prototype.tosorted": "^1.1.1", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.17", + "es-iterator-helpers": "^1.0.12", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.7", - "object.fromentries": "^2.0.7", - "object.hasown": "^1.1.3", - "object.values": "^1.1.7", + "object.entries": "^1.1.6", + "object.fromentries": "^2.0.6", + "object.hasown": "^1.1.2", + "object.values": "^1.1.6", "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", + "resolve": "^2.0.0-next.4", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.10" + "string.prototype.matchall": "^4.0.8" }, "engines": { "node": ">=4" @@ -3960,9 +3908,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.6.tgz", - "integrity": "sha512-NjGXdm7zgcKRkKMua34qVO9doI7VOxZ6ancSvBELJSSoX97jyndXcSoa8XBh69JoB31dNz3EEzlMcizZl7LaMA==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.5.tgz", + "integrity": "sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==", "dev": true, "peerDependencies": { "eslint": ">=7" @@ -4156,6 +4104,12 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -4306,9 +4260,9 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, "node_modules/for-each": { @@ -4336,18 +4290,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -4651,9 +4593,9 @@ } }, "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", "dev": true, "engines": { "node": ">= 0.4" @@ -4690,9 +4632,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", "dev": true, "dependencies": { "function-bind": "^1.1.2" @@ -4905,21 +4847,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dev": true, - "dependencies": { - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-date-object": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", @@ -5002,21 +4929,18 @@ } }, "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", "dev": true, - "engines": { - "node": ">= 0.4" - }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "dev": true, "engines": { "node": ">= 0.4" @@ -5075,27 +4999,21 @@ } }, "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", "dev": true, - "engines": { - "node": ">= 0.4" - }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" + "call-bind": "^1.0.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5159,13 +5077,10 @@ } }, "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", "dev": true, - "engines": { - "node": ">= 0.4" - }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5183,16 +5098,13 @@ } }, "node_modules/is-weakset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", - "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6078,12 +5990,15 @@ } }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { - "yallist": "^3.0.2" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" } }, "node_modules/make-dir": { @@ -6318,29 +6233,28 @@ } }, "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz", + "integrity": "sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -6350,31 +6264,27 @@ } }, "node_modules/object.hasown": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", - "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz", + "integrity": "sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==", "dev": true, "dependencies": { - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -6527,12 +6437,12 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", "dev": true, "dependencies": { - "lru-cache": "^10.2.0", + "lru-cache": "^9.1.1 || ^10.0.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -6660,19 +6570,10 @@ "node": ">=8" } }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", + "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", "dev": true, "funding": [ { @@ -6691,7 +6592,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "source-map-js": "^1.0.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -6817,9 +6718,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -6897,6 +6798,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -6921,12 +6828,6 @@ "react-is": "^16.13.1" } }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6937,9 +6838,9 @@ } }, "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", "dev": true, "funding": [ { @@ -6996,17 +6897,17 @@ } }, "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, "node_modules/react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "version": "6.22.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.2.tgz", + "integrity": "sha512-YD3Dzprzpcq+tBMHBS822tCjnWD3iIZbTeSXMY9LPSG541EfoBGyZ3bS25KEnaZjLcmQpw2AVLkFyfgXY8uvcw==", "dependencies": { - "@remix-run/router": "1.15.3" + "@remix-run/router": "1.15.2" }, "engines": { "node": ">=14.0.0" @@ -7016,12 +6917,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", - "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "version": "6.22.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.2.tgz", + "integrity": "sha512-WgqxD2qySEIBPZ3w0sHH+PUAiamDeszls9tzqMPBDA1YYVucTBXLU7+gtRfcSnhe92A3glPnvSxK2dhNoAVOIQ==", "dependencies": { - "@remix-run/router": "1.15.3", - "react-router": "6.22.3" + "@remix-run/router": "1.15.2", + "react-router": "6.22.2" }, "engines": { "node": ">=14.0.0" @@ -7058,6 +6959,12 @@ "react": "^18.2.0" } }, + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -7080,16 +6987,16 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", - "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz", + "integrity": "sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.5", "define-properties": "^1.2.1", - "es-abstract": "^1.23.1", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0", + "get-intrinsic": "^1.2.3", "globalthis": "^1.0.3", "which-builtin-type": "^1.1.3" }, @@ -7219,9 +7126,9 @@ } }, "node_modules/rollup": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.14.0.tgz", - "integrity": "sha512-Qe7w62TyawbDzB4yt32R0+AbIo6m1/sqO7UPzFS8Z/ksL5mrfhA0v4CavfdmFav3D+ub4QeAgsGEe84DoWe/nQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.10.0.tgz", + "integrity": "sha512-t2v9G2AKxcQ8yrG+WGxctBes1AomT0M4ND7jTFBCVPXQ/WFTvNSefIrNSmLKhIKBrvN8SG+CZslimJcT3W2u2g==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -7234,21 +7141,19 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.14.0", - "@rollup/rollup-android-arm64": "4.14.0", - "@rollup/rollup-darwin-arm64": "4.14.0", - "@rollup/rollup-darwin-x64": "4.14.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.14.0", - "@rollup/rollup-linux-arm64-gnu": "4.14.0", - "@rollup/rollup-linux-arm64-musl": "4.14.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.14.0", - "@rollup/rollup-linux-riscv64-gnu": "4.14.0", - "@rollup/rollup-linux-s390x-gnu": "4.14.0", - "@rollup/rollup-linux-x64-gnu": "4.14.0", - "@rollup/rollup-linux-x64-musl": "4.14.0", - "@rollup/rollup-win32-arm64-msvc": "4.14.0", - "@rollup/rollup-win32-ia32-msvc": "4.14.0", - "@rollup/rollup-win32-x64-msvc": "4.14.0", + "@rollup/rollup-android-arm-eabi": "4.10.0", + "@rollup/rollup-android-arm64": "4.10.0", + "@rollup/rollup-darwin-arm64": "4.10.0", + "@rollup/rollup-darwin-x64": "4.10.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.10.0", + "@rollup/rollup-linux-arm64-gnu": "4.10.0", + "@rollup/rollup-linux-arm64-musl": "4.10.0", + "@rollup/rollup-linux-riscv64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-gnu": "4.10.0", + "@rollup/rollup-linux-x64-musl": "4.10.0", + "@rollup/rollup-win32-arm64-msvc": "4.10.0", + "@rollup/rollup-win32-ia32-msvc": "4.10.0", + "@rollup/rollup-win32-x64-msvc": "4.10.0", "fsevents": "~2.3.2" } }, @@ -7276,13 +7181,13 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, @@ -7333,51 +7238,32 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dev": true, "dependencies": { - "define-data-property": "^1.1.4", + "define-data-property": "^1.1.2", "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "has-property-descriptors": "^1.0.1" }, "engines": { "node": ">= 0.4" } }, "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", "dev": true, "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", + "define-data-property": "^1.0.1", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" + "has-property-descriptors": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -7405,12 +7291,12 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", + "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.6", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", "object-inspect": "^1.13.1" @@ -7423,10 +7309,16 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/sisteransi": { "version": "1.0.5", @@ -7458,9 +7350,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true, "engines": { "node": ">=0.10.0" @@ -7517,17 +7409,20 @@ } }, "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==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/string-width-cjs": { @@ -7545,42 +7440,68 @@ "node": ">=8" } }, - "node_modules/string.prototype.matchall": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", - "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "node_modules/string-width-cjs/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==", + "dev": true + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/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==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "regexp.prototype.flags": "^1.5.2", - "set-function-name": "^2.0.2", - "side-channel": "^1.0.6" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz", + "integrity": "sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", + "side-channel": "^1.0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -7590,31 +7511,28 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7698,16 +7616,16 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", + "jackspeak": "^2.3.5", "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -7760,9 +7678,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", - "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -7773,7 +7691,7 @@ "fast-glob": "^3.3.0", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.21.0", + "jiti": "^1.19.1", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", @@ -7904,9 +7822,9 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", + "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==", "dev": true, "engines": { "node": ">=16" @@ -7961,12 +7879,12 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz", + "integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.6", "es-errors": "^1.3.0", "is-typed-array": "^1.1.13" }, @@ -7975,16 +7893,15 @@ } }, "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.2", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" }, "engines": { "node": ">= 0.4" @@ -7994,17 +7911,16 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" }, "engines": { "node": ">= 0.4" @@ -8014,29 +7930,23 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.2", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" + "is-typed-array": "^1.1.9" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/typescript": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", - "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8127,14 +8037,14 @@ } }, "node_modules/vite": { - "version": "5.2.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.8.tgz", - "integrity": "sha512-OyZR+c1CE8yeHw5V5t59aXsUPPVTHMDjEZz8MgguLL/Q7NblxhZUlTu9xSPqlsUO/y+X7dlU05jdhvyycD55DA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.1.tgz", + "integrity": "sha512-wclpAgY3F1tR7t9LL5CcHC41YPkQIpKUGeIuT8MdNwNZr6OqOTLs7JX5vIHAtzqLWXts0T+GDrh9pN2arneKqg==", "dev": true, "dependencies": { - "esbuild": "^0.20.1", - "postcss": "^8.4.38", - "rollup": "^4.13.0" + "esbuild": "^0.19.3", + "postcss": "^8.4.35", + "rollup": "^4.2.0" }, "bin": { "vite": "bin/vite.js" @@ -8248,34 +8158,31 @@ } }, "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", "dev": true, "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" + "has-tostringtag": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -8285,17 +8192,17 @@ } }, "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -8319,6 +8226,65 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/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==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -8338,6 +8304,12 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -8348,15 +8320,15 @@ } }, "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "node_modules/yaml": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", - "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.0.tgz", + "integrity": "sha512-j9iR8g+/t0lArF4V6NE/QCfT+CO7iLqrXAHZbJdo+LfjqP1vR8Fg5bSiaq6Q2lOD1AUEVrEVIgABvBFYojJVYQ==", "dev": true, "bin": { "yaml": "bin.mjs" @@ -8392,6 +8364,26 @@ "node": ">=12" } }, + "node_modules/yargs/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==", + "dev": true + }, + "node_modules/yargs/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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "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/src/API/API.ts b/frontend/src/API/API.ts index 67768c2..e7aab0c 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -1,17 +1,11 @@ -import { AddMemberInfo } from "../Components/AddMember"; -import { ProjectRoleChange } from "../Components/ChangeRole"; -import { projectTimes } from "../Components/GetProjectTimes"; -import { ProjectMember } from "../Components/GetUsersInProject"; import { - UpdateWeeklyReport, NewWeeklyReport, NewUser, User, Project, NewProject, + UserProjectMember, WeeklyReport, - StrNameChange, - Statistics, } from "../Types/goTypes"; /** @@ -77,7 +71,10 @@ interface API { * @param {string} token The authentication token. * @returns {Promise>} A promise resolving to an API response with the created project. */ - createProject(project: NewProject, token: string): Promise>; + createProject( + project: NewProject, + token: string, + ): Promise>; /** Submits a weekly report * @param {NewWeeklyReport} weeklyReport The weekly report object. @@ -89,31 +86,16 @@ interface API { token: string, ): Promise>; - /** - * Updates a weekly report. - * @param {UpdateWeeklyReport} weeklyReport The updated weekly report object. - * @param {string} token The authentication token. - * @returns {Promise>} A promise containing the API response with the updated report. - */ - updateWeeklyReport( - weeklyReport: UpdateWeeklyReport, - token: string, - ): Promise>; - - /** Gets a weekly report for a specific user, project and week. - * Keep in mind that the user within the token needs to be PM - * of the project to get the report, unless the user is the target user. + /** Gets a weekly report for a specific user, project and week * @param {string} projectName The name of the project. * @param {string} week The week number. * @param {string} token The authentication token. - * @param {string} targetUser The username of the target user. Defaults to token user. * @returns {Promise>} A promise resolving to an API response with the retrieved report. */ getWeeklyReport( projectName: string, week: string, token: string, - targetUser?: string, ): Promise>; /** @@ -123,21 +105,16 @@ interface API { * @param {string} token The token of the user * @returns {APIResponse} A list of weekly reports */ - getAllWeeklyReportsForUser( + getWeeklyReportsForUser( projectName: string, token: string, - targetUser?: string, ): Promise>; /** Gets all the projects of a user - * @param {string} username - The authentication token. * @param {string} token - The authentication token. * @returns {Promise>} A promise containing the API response with the user's projects. */ - getUserProjects( - username: string, - token: string, - ): Promise>; + getUserProjects(token: string): Promise>; /** Gets a project by its id. * @param {number} id The id of the project to retrieve. @@ -145,16 +122,6 @@ interface API { */ getProject(id: number): Promise>; - /** Gets a projects reported time - * @param {string} projectName The name of the project. - * @param {string} token The usertoken. - * @returns {Promise>} A promise resolving to an API response containing the project times. - */ - getProjectTimes( - projectName: string, - token: string, - ): Promise>; - /** Gets a list of all users. * @param {string} token The authentication token of the requesting user. * @returns {Promise>} A promise resolving to an API response containing the list of users. @@ -164,137 +131,12 @@ interface API { getAllUsersProject( projectName: string, token: string, - ): Promise>; - - /** Gets all unsigned reports in a project. - * @param {string} projectName The name of the project. - * @param {string} token The authentication token. - * @returns {Promise>} A promise resolving to an API response containing the list of unsigned reports. - */ - getUnsignedReportsInProject( - projectName: string, - token: string, - ): Promise>; - - /** - * Changes the username of a user in the database. - * @param {StrNameChange} data The object containing the previous and new username. - * @param {string} token The authentication token. - * @returns {Promise>} A promise resolving to an API response. - */ - changeUserName( - data: StrNameChange, - token: string, - ): Promise>; - /** - * Changes the role of a user in the database. - * @param {RoleChange} roleInfo The object containing the previous and new username. - * @param {string} token The authentication token. - * @returns {Promise>} A promise resolving to an API response. - */ - changeUserRole( - roleInfo: ProjectRoleChange, - token: string, - ): Promise>; - - addUserToProject( - addMemberInfo: AddMemberInfo, - token: string, - ): Promise>; - - removeUserFromProject( - user: string, - project: string, - token: string, - ): Promise>; + ): Promise>; removeProject( projectName: string, token: string, ): Promise>; - - /** - * Signs a report. Keep in mind that the user which the token belongs to must be - * the project manager of the project the report belongs to. - * - * @param {number} reportId The id of the report to sign - * @param {string} token The authentication token - */ - signReport(reportId: number, token: string): Promise>; - - /** - * Unsigns a report. Keep in mind that the user which the token belongs to must be - * the project manager of the project the report belongs to. - * - * @param {number} reportId The id of the report to sign - * @param {string} token The authentication token - */ - unsignReport(reportId: number, token: string): Promise>; - - /** - * Promotes a user to project manager within a project. - * - * @param {string} userName The username of the user to promote - * @param {string} projectName The name of the project to promote the user in - * @returns {Promise} A promise resolving to an API response. - */ - promoteToPm( - userName: string, - projectName: string, - token: string, - ): Promise>; - /** - * Get the username from the id - * @param {number} id The id of the user - * @param {string} token Your token - */ - getUsername(id: number, token: string): Promise>; - - /** - * Deletes a WeeklyReport from the database - * @param {number} reportId The id of the report to delete - * @param {string} token The authentication token - */ - deleteWeeklyReport( - reportId: number, - token: string, - ): Promise>; - - /** - * Retrieves the total time spent on a project for a particular user (the user is determined by the token) - * - * @param {string} projectName The name of the project - * @param {string} token The authentication token - */ - getStatistics( - projectName: string, - token: string, - userName?: string, - ): Promise>; - - /** - * Changes the name of a project - * @param {string} projectName The name of the project - * @param {string} newProjectName The new name of the project - * @param {string} token The authentication token - */ - changeProjectName( - projectName: string, - newProjectName: string, - token: string, - ): Promise>; - - /** - * Changes the password of a user - * @param {string} username The username of the user - * @param {string} newPassword The new password - * @param {string} token The authentication token - */ - changeUserPassword( - username: string, - newPassword: string, - token: string, - ): Promise>; } /** An instance of the API */ @@ -332,17 +174,19 @@ export const api: API = { ): Promise> { try { const response = await fetch(`/api/userdelete/${username}`, { - method: "DELETE", + method: "POST", headers: { "Content-Type": "application/json", Authorization: "Bearer " + token, }, body: JSON.stringify(username), }); + if (!response.ok) { - return { success: false, message: "Could not remove user" }; + return { success: false, message: "Failed to remove user" }; } else { - return { success: true }; + const data = (await response.json()) as User; + return { success: true, data }; } } catch (e) { return { success: false, message: "Failed to remove user" }; @@ -355,7 +199,7 @@ export const api: API = { ): Promise> { try { const response = await fetch( - `/api/checkIfProjectManager/${projectName}`, + `/api/checkIfProjectManager?projectName=${projectName}`, { method: "GET", headers: { @@ -382,7 +226,7 @@ export const api: API = { async createProject( project: NewProject, token: string, - ): Promise> { + ): Promise> { try { const response = await fetch("/api/project", { method: "POST", @@ -396,64 +240,14 @@ export const api: API = { if (!response.ok) { return { success: false, message: "Failed to create project" }; } else { - return { success: true }; + const data = (await response.json()) as Project; + return { success: true, data }; } } catch (e) { - return { success: false, message: "Failed to create project!" }; + return { success: false, message: "Failed to create project" }; } }, - async addUserToProject( - addMemberInfo: AddMemberInfo, - token: string, - ): Promise> { - try { - const response = await fetch( - `/api/addUserToProject/${addMemberInfo.projectName}/?userName=${addMemberInfo.userName}`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }, - ); - - if (!response.ok) { - return { success: false, message: "Failed to add member" }; - } else { - return { success: true, message: "Added member" }; - } - } catch (e) { - return { success: false, message: "Failed to add member" }; - } - }, - - async removeUserFromProject( - user: string, - project: string, - token: string, - ): Promise> { - try { - const response = await fetch( - `/api/removeUserFromProject/${project}?userName=${user}`, - { - method: "DELETE", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }, - ); - if (!response.ok) { - return { success: false, message: "Failed to remove member" }; - } - } catch (e) { - return { success: false, message: "Failed to remove member" }; - } - return { success: true, message: "Removed member" }; - }, - async renewToken(token: string): Promise> { try { const response = await fetch("/api/loginrenew", { @@ -475,39 +269,9 @@ export const api: API = { } }, - async changeUserRole( - roleInfo: ProjectRoleChange, - token: string, - ): Promise> { + async getUserProjects(token: string): Promise> { try { - const response = await fetch("/api/ProjectRoleChange", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - body: JSON.stringify(roleInfo), - }); - - if (!response.ok) { - if (response.status === 403) { - return { success: false, message: "Cannot change your own role" }; - } - return { success: false, message: "Could not change role" }; - } else { - return { success: true }; - } - } catch (e) { - return { success: false, message: "Could not change role" }; - } - }, - - async getUserProjects( - username: string, - token: string, - ): Promise> { - try { - const response = await fetch(`/api/getUserProjects/${username}`, { + const response = await fetch("/api/getUserProjects", { method: "GET", headers: { "Content-Type": "application/json", @@ -532,37 +296,6 @@ export const api: API = { } }, - async getProjectTimes( - projectName: string, - token: string, - ): Promise> { - try { - const response = await fetch(`/api/getProjectTimes/${projectName}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }); - - if (!response.ok) { - return Promise.resolve({ - success: false, - message: - "Fetch error: " + response.status + ", failed to get project times", - }); - } else { - const data = (await response.json()) as projectTimes; - return Promise.resolve({ success: true, data }); - } - } catch (e) { - return Promise.resolve({ - success: false, - message: "API error! Could not get times.", - }); - } - }, - async submitWeeklyReport( weeklyReport: NewWeeklyReport, token: string, @@ -594,46 +327,14 @@ export const api: API = { } }, - async updateWeeklyReport( - weeklyReport: UpdateWeeklyReport, - token: string, - ): Promise> { - try { - const response = await fetch("/api/updateWeeklyReport", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - body: JSON.stringify(weeklyReport), - }); - - if (!response.ok) { - return { - success: false, - message: "Failed to update weekly report", - }; - } - - const data = await response.text(); - return { success: true, message: data }; - } catch (e) { - return { - success: false, - message: "Failed to update weekly report", - }; - } - }, - async getWeeklyReport( projectName: string, week: string, token: string, - targetUser?: string, ): Promise> { try { const response = await fetch( - `/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser ?? ""}`, + `/api/getWeeklyReport?projectName=${projectName}&week=${week}`, { method: "GET", headers: { @@ -654,22 +355,18 @@ export const api: API = { } }, - async getAllWeeklyReportsForUser( + async getWeeklyReportsForUser( projectName: string, token: string, - targetUser?: string, ): Promise> { try { - const response = await fetch( - `/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser ?? ""}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, + const response = await fetch(`/api/getWeeklyReportsUser/${projectName}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, }, - ); + }); if (!response.ok) { return { @@ -701,11 +398,7 @@ export const api: API = { }); if (!response.ok) { - return { - success: false, - data: `${response.status}`, - message: "Failed to login", - }; + return { success: false, message: "Failed to login" }; } else { const data = (await response.json()) as { token: string }; // Update the type of 'data' return { success: true, data: data.token }; @@ -770,7 +463,7 @@ export const api: API = { async getAllUsersProject( projectName: string, token: string, - ): Promise> { + ): Promise> { try { const response = await fetch(`/api/getUsersProject/${projectName}`, { method: "GET", @@ -786,7 +479,7 @@ export const api: API = { message: "Failed to get users", }); } else { - const data = (await response.json()) as ProjectMember[]; + const data = (await response.json()) as UserProjectMember[]; return Promise.resolve({ success: true, data }); } } catch (e) { @@ -797,68 +490,12 @@ export const api: API = { } }, - async getUnsignedReportsInProject( - projectName: string, - token: string, - ): Promise> { - try { - const response = await fetch(`/api/getUnsignedReports/${projectName}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }); - - if (!response.ok) { - return { - success: false, - message: - "Failed to get unsigned reports for project: Response code " + - response.status, - }; - } else { - const data = (await response.json()) as WeeklyReport[]; - return { success: true, data }; - } - } catch (e) { - return { - success: false, - message: "Failed to get unsigned reports for project, unknown error", - }; - } - }, - - async changeUserName( - data: StrNameChange, - token: string, - ): Promise> { - try { - const response = await fetch("/api/changeUserName", { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - body: JSON.stringify(data), - }); - - if (!response.ok) { - return { success: false, message: "Failed to change username" }; - } else { - return { success: true }; - } - } catch (e) { - return { success: false, message: "Failed to change username" }; - } - }, - async removeProject( projectName: string, token: string, ): Promise> { try { - const response = await fetch(`/api/removeProject/${projectName}`, { + const response = await fetch(`/api/projectdelete/${projectName}`, { method: "DELETE", headers: { "Content-Type": "application/json", @@ -882,206 +519,4 @@ export const api: API = { }); } }, - - async signReport( - reportId: number, - token: string, - ): Promise> { - try { - const response = await fetch(`/api/signReport/${reportId}`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }); - - if (!response.ok) { - return { success: false, message: "Failed to sign report" }; - } else { - return { success: true, message: "Report signed" }; - } - } catch (e) { - return { success: false, message: "Failed to sign report" }; - } - }, - - async unsignReport( - reportId: number, - token: string, - ): Promise> { - try { - const response = await fetch(`/api/unsignReport/${reportId}`, { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }); - - if (!response.ok) { - return { success: false, message: "Failed to unsign report" }; - } else { - return { success: true, message: "Report unsigned" }; - } - } catch (e) { - return { success: false, message: "Failed to unsign report" }; - } - }, - - async promoteToPm( - userName: string, - projectName: string, - token: string, - ): Promise> { - try { - const response = await fetch( - `/api/promoteToPm/${projectName}?userName=${userName}`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }, - ); - if (!response.ok) { - return { - success: false, - message: "Failed to promote user to project manager", - }; - } - } catch (e) { - return { - success: false, - message: "Failed to promote user to project manager", - }; - } - return { success: true, message: "User promoted to project manager" }; - }, - - async getUsername(id: number, token: string): Promise> { - try { - const response = await fetch(`/api/username?userId=${id}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }); - - if (!response.ok) { - return { success: false, message: "Failed to get username" }; - } else { - const data = (await response.json()) as string; - return { success: true, data }; - } - } catch (e) { - return { success: false, message: "Failed to get username" }; - } - }, - - async deleteWeeklyReport( - reportId: number, - token: string, - ): Promise> { - try { - const response = await fetch(`/api/deleteReport/${reportId}`, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }); - - if (!response.ok) { - return { success: false, message: "Failed to delete report" }; - } else { - return { success: true, message: "Report deleted" }; - } - } catch (e) { - return { success: false, message: "Failed to delete report" }; - } - }, - async getStatistics( - projectName: string, - token: string, - userName?: string, - ): Promise> { - try { - const response = await fetch( - `/api/getStatistics/?projectName=${projectName}&userName=${userName ?? ""}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }, - ); - - if (!response.ok) { - return { success: false, message: "Failed to get statistics" }; - } else { - const data = (await response.json()) as Statistics; - return { success: true, data }; - } - } catch (e) { - return { success: false, message: "Failed to get statistics" }; - } - }, - - async changeProjectName( - projectName: string, - newProjectName: string, - token: string, - ): Promise> { - try { - const response = await fetch( - `/api/changeProjectName/${projectName}?newProjectName=${newProjectName}`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }, - ); - - if (!response.ok) { - return { success: false, message: "Failed to change project name" }; - } else { - return { success: true, message: "Project name changed" }; - } - } catch (e) { - return { success: false, message: "Failed to change project name" }; - } - }, - - async changeUserPassword( - username: string, - newPassword: string, - token: string, - ): Promise> { - try { - const response = await fetch( - `/api/changeUserPassword/${username}?newPassword=${newPassword}`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + token, - }, - }, - ); - - if (!response.ok) { - return { success: false, message: "Failed to change password" }; - } else { - return { success: true, message: "Password changed" }; - } - } catch (e) { - return { success: false, message: "Failed to change password" }; - } - }, }; diff --git a/frontend/src/API/GenApi.ts b/frontend/src/API/GenApi.ts deleted file mode 100644 index 8ca851b..0000000 --- a/frontend/src/API/GenApi.ts +++ /dev/null @@ -1,358 +0,0 @@ -/* eslint-disable */ -/* tslint:disable */ -/* - * --------------------------------------------------------------- - * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ## - * ## ## - * ## AUTHOR: acacode ## - * ## SOURCE: https://github.com/acacode/swagger-typescript-api ## - * --------------------------------------------------------------- - */ - -export interface TypesNewUser { - /** @example "password123" */ - password?: string; - /** @example "username123" */ - username?: string; -} - -export interface TypesToken { - token?: string; -} - -export type QueryParamsType = Record; -export type ResponseFormat = keyof Omit; - -export interface FullRequestParams extends Omit { - /** set parameter to `true` for call `securityWorker` for this request */ - secure?: boolean; - /** request path */ - path: string; - /** content type of request body */ - type?: ContentType; - /** query params */ - query?: QueryParamsType; - /** format of response (i.e. response.json() -> format: "json") */ - format?: ResponseFormat; - /** request body */ - body?: unknown; - /** base url */ - baseUrl?: string; - /** request cancellation token */ - cancelToken?: CancelToken; -} - -export type RequestParams = Omit; - -export interface ApiConfig { - baseUrl?: string; - baseApiParams?: Omit; - securityWorker?: (securityData: SecurityDataType | null) => Promise | RequestParams | void; - customFetch?: typeof fetch; -} - -export interface HttpResponse extends Response { - data: D; - error: E; -} - -type CancelToken = Symbol | string | number; - -export enum ContentType { - Json = "application/json", - FormData = "multipart/form-data", - UrlEncoded = "application/x-www-form-urlencoded", - Text = "text/plain", -} - -export class HttpClient { - public baseUrl: string = "//localhost:8080/api"; - private securityData: SecurityDataType | null = null; - private securityWorker?: ApiConfig["securityWorker"]; - private abortControllers = new Map(); - private customFetch = (...fetchParams: Parameters) => fetch(...fetchParams); - - private baseApiParams: RequestParams = { - credentials: "same-origin", - headers: {}, - redirect: "follow", - referrerPolicy: "no-referrer", - }; - - constructor(apiConfig: ApiConfig = {}) { - Object.assign(this, apiConfig); - } - - public setSecurityData = (data: SecurityDataType | null) => { - this.securityData = data; - }; - - protected encodeQueryParam(key: string, value: any) { - const encodedKey = encodeURIComponent(key); - return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`; - } - - protected addQueryParam(query: QueryParamsType, key: string) { - return this.encodeQueryParam(key, query[key]); - } - - protected addArrayQueryParam(query: QueryParamsType, key: string) { - const value = query[key]; - return value.map((v: any) => this.encodeQueryParam(key, v)).join("&"); - } - - protected toQueryString(rawQuery?: QueryParamsType): string { - const query = rawQuery || {}; - const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]); - return keys - .map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key))) - .join("&"); - } - - protected addQueryParams(rawQuery?: QueryParamsType): string { - const queryString = this.toQueryString(rawQuery); - return queryString ? `?${queryString}` : ""; - } - - private contentFormatters: Record any> = { - [ContentType.Json]: (input: any) => - input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input, - [ContentType.Text]: (input: any) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input), - [ContentType.FormData]: (input: any) => - Object.keys(input || {}).reduce((formData, key) => { - const property = input[key]; - formData.append( - key, - property instanceof Blob - ? property - : typeof property === "object" && property !== null - ? JSON.stringify(property) - : `${property}`, - ); - return formData; - }, new FormData()), - [ContentType.UrlEncoded]: (input: any) => this.toQueryString(input), - }; - - protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams { - return { - ...this.baseApiParams, - ...params1, - ...(params2 || {}), - headers: { - ...(this.baseApiParams.headers || {}), - ...(params1.headers || {}), - ...((params2 && params2.headers) || {}), - }, - }; - } - - protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => { - if (this.abortControllers.has(cancelToken)) { - const abortController = this.abortControllers.get(cancelToken); - if (abortController) { - return abortController.signal; - } - return void 0; - } - - const abortController = new AbortController(); - this.abortControllers.set(cancelToken, abortController); - return abortController.signal; - }; - - public abortRequest = (cancelToken: CancelToken) => { - const abortController = this.abortControllers.get(cancelToken); - - if (abortController) { - abortController.abort(); - this.abortControllers.delete(cancelToken); - } - }; - - public request = async ({ - body, - secure, - path, - type, - query, - format, - baseUrl, - cancelToken, - ...params - }: FullRequestParams): Promise> => { - const secureParams = - ((typeof secure === "boolean" ? secure : this.baseApiParams.secure) && - this.securityWorker && - (await this.securityWorker(this.securityData))) || - {}; - const requestParams = this.mergeRequestParams(params, secureParams); - const queryString = query && this.toQueryString(query); - const payloadFormatter = this.contentFormatters[type || ContentType.Json]; - const responseFormat = format || requestParams.format; - - return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, { - ...requestParams, - headers: { - ...(requestParams.headers || {}), - ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}), - }, - signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null, - body: typeof body === "undefined" || body === null ? null : payloadFormatter(body), - }).then(async (response) => { - const r = response as HttpResponse; - r.data = null as unknown as T; - r.error = null as unknown as E; - - const data = !responseFormat - ? r - : await response[responseFormat]() - .then((data) => { - if (r.ok) { - r.data = data; - } else { - r.error = data; - } - return r; - }) - .catch((e) => { - r.error = e; - return r; - }); - - if (cancelToken) { - this.abortControllers.delete(cancelToken); - } - - if (!response.ok) throw data; - return data; - }); - }; -} - -/** - * @title TTime API - * @version 0.0.1 - * @license AGPL (https://www.gnu.org/licenses/agpl-3.0.html) - * @baseUrl //localhost:8080/api - * @externalDocs https://swagger.io/resources/open-api/ - * @contact - * - * This is the API for TTime, a time tracking application. - */ -export class GenApi extends HttpClient { - login = { - /** - * @description Logs in a user and returns a JWT token - * - * @tags Auth - * @name LoginCreate - * @summary Login - * @request POST:/login - */ - loginCreate: (body: TypesNewUser, params: RequestParams = {}) => - this.request({ - path: `/login`, - method: "POST", - body: body, - type: ContentType.Json, - format: "json", - ...params, - }), - }; - loginrenew = { - /** - * @description Renews the users token. - * - * @tags Auth - * @name LoginrenewCreate - * @summary LoginRenews - * @request POST:/loginrenew - * @secure - */ - loginrenewCreate: (params: RequestParams = {}) => - this.request({ - path: `/loginrenew`, - method: "POST", - secure: true, - format: "json", - ...params, - }), - }; - promoteToAdmin = { - /** - * @description Promote chosen user to site admin - * - * @tags User - * @name PromoteToAdminCreate - * @summary PromoteToAdmin - * @request POST:/promoteToAdmin - * @secure - */ - promoteToAdminCreate: (NewUser: TypesNewUser, params: RequestParams = {}) => - this.request({ - path: `/promoteToAdmin`, - method: "POST", - body: NewUser, - secure: true, - type: ContentType.Json, - ...params, - }), - }; - register = { - /** - * @description Register a new user - * - * @tags Auth - * @name RegisterCreate - * @summary Register - * @request POST:/register - */ - registerCreate: (NewUser: TypesNewUser, params: RequestParams = {}) => - this.request({ - path: `/register`, - method: "POST", - body: NewUser, - type: ContentType.Json, - ...params, - }), - }; - userdelete = { - /** - * @description UserDelete deletes a user from the database - * - * @tags User - * @name UserdeleteDelete - * @summary UserDelete - * @request DELETE:/userdelete/{username} - * @secure - */ - userdeleteDelete: (username: string, params: RequestParams = {}) => - this.request({ - path: `/userdelete/${username}`, - method: "DELETE", - secure: true, - type: ContentType.Json, - ...params, - }), - }; - users = { - /** - * @description lists all users - * - * @tags User - * @name GetUsers - * @summary ListsAllUsers - * @request GET:/users/all - * @secure - */ - getUsers: (params: RequestParams = {}) => - this.request({ - path: `/users/all`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - }; -} diff --git a/frontend/src/Components/AddMember.tsx b/frontend/src/Components/AddMember.tsx deleted file mode 100644 index d8036b7..0000000 --- a/frontend/src/Components/AddMember.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { api } from "../API/API"; - -export interface AddMemberInfo { - userName: string; - projectName: string; -} - -/** - * Tries to add a member to a project - * @param {AddMemberInfo} props.membertoAdd - Contains user's name and project's name - * @returns {Promise} - */ -async function AddMember(props: { memberToAdd: AddMemberInfo }): Promise { - if (props.memberToAdd.userName === "") { - alert("You must choose at least one user to add"); - return; - } - try { - const response = await api.addUserToProject( - props.memberToAdd, - localStorage.getItem("accessToken") ?? "", - ); - if (response.success) { - alert(`[${props.memberToAdd.userName}] added`); - } else { - alert(`[${props.memberToAdd.userName}] not added`); - console.error(response.message); - } - } catch (error) { - alert(`[${props.memberToAdd.userName}] not added`); - console.error("An error occurred during member add:", error); - } -} - -export default AddMember; diff --git a/frontend/src/Components/AddProject.tsx b/frontend/src/Components/AddProject.tsx index 8c9c566..f5f4a08 100644 --- a/frontend/src/Components/AddProject.tsx +++ b/frontend/src/Components/AddProject.tsx @@ -1,13 +1,37 @@ import { useState } from "react"; -import { api } from "../API/API"; -import { NewProject } from "../Types/goTypes"; +import { APIResponse, api } from "../API/API"; +import { NewProject, Project } from "../Types/goTypes"; +import InputField from "./InputField"; import Logo from "../assets/Logo.svg"; import Button from "./Button"; -import { useNavigate } from "react-router-dom"; -import ProjectNameInput from "./Inputs/ProjectNameInput"; -import DescriptionInput from "./Inputs/DescriptionInput"; -import { alphanumeric } from "../Data/regex"; -import { projNameHighLimit, projNameLowLimit } from "../Data/constants"; + +/** + * Tries to add a project to the system + * @param {Object} props - Project name and description + * @returns {boolean} True if created, false if not + */ +function CreateProject(props: { name: string; description: string }): boolean { + const project: NewProject = { + name: props.name, + description: props.description, + }; + + let created = false; + + api + .createProject(project, localStorage.getItem("accessToken") ?? "") + .then((response: APIResponse) => { + if (response.success) { + created = true; + } else { + console.error(response.message); + } + }) + .catch((error) => { + console.error("An error occurred during creation:", error); + }); + return created; +} /** * Provides UI for adding a project to the system. @@ -16,85 +40,42 @@ import { projNameHighLimit, projNameLowLimit } from "../Data/constants"; function AddProject(): JSX.Element { const [name, setName] = useState(""); const [description, setDescription] = useState(""); - const navigate = useNavigate(); - - /** - * Tries to add a project to the system - */ - const handleCreateProject = async (): Promise => { - if ( - !alphanumeric.test(name) || - name.length > projNameHighLimit || - name.length < projNameLowLimit - ) { - alert( - "Please provide valid project name: \n-Between 10-99 characters \n-No special characters (.-!?/*)", - ); - return; - } - if (description.length > projNameHighLimit) { - alert("Please provide valid description: \n-Max 100 characters"); - return; - } - const project: NewProject = { - name: name.replace(/ /g, ""), - description: description.trim(), - }; - try { - const response = await api.createProject( - project, - localStorage.getItem("accessToken") ?? "", - ); - if (response.success) { - alert(`${project.name} added!`); - setDescription(""); - setName(""); - navigate("/admin"); - } else { - alert("Project not added, name could be taken"); - console.error(response.message); - } - } catch (error) { - alert("Project not added"); - console.error(error); - } - }; return (
{ e.preventDefault(); - void handleCreateProject(); + CreateProject({ name: name, description: description }); }} > TTIME Logo

Create a new project

- { - e.preventDefault(); setName(e.target.value); }} /> -
- { - e.preventDefault(); setDescription(e.target.value); }} - placeholder={"Description (Optional)"} /> -
+
-
- ); -} - -export default AddUserToProject; diff --git a/frontend/src/Components/AllTimeReportsInProject.tsx b/frontend/src/Components/AllTimeReportsInProject.tsx index aef1df1..4fa9ad8 100644 --- a/frontend/src/Components/AllTimeReportsInProject.tsx +++ b/frontend/src/Components/AllTimeReportsInProject.tsx @@ -17,7 +17,7 @@ function AllTimeReportsInProject(): JSX.Element { useEffect(() => { const getWeeklyReports = async (): Promise => { const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getAllWeeklyReportsForUser( + const response = await api.getWeeklyReportsForUser( projectName ?? "", token, ); @@ -37,9 +37,9 @@ function AllTimeReportsInProject(): JSX.Element {
{weeklyReports.map((newWeeklyReport, index) => (

diff --git a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx b/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx deleted file mode 100644 index a96c55b..0000000 --- a/frontend/src/Components/AllTimeReportsInProjectOtherUser.tsx +++ /dev/null @@ -1,73 +0,0 @@ -//Info: This component is used to display all the time reports for a project. It will display the week number, -//total time spent, and if the report has been signed or not. The user can click on a report to edit it. -import { useEffect, useState } from "react"; -import { WeeklyReport } from "../Types/goTypes"; -import { Link, useParams } from "react-router-dom"; -import { api } from "../API/API"; - -/** - * Renders a component that displays all the time reports for a specific project. - * @returns {JSX.Element} representing the component. - */ -function AllTimeReportsInProject(): JSX.Element { - const { username } = useParams(); - const { projectName } = useParams(); - const [weeklyReports, setWeeklyReports] = useState([]); - - useEffect(() => { - const getWeeklyReports = async (): Promise => { - const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getAllWeeklyReportsForUser( - projectName ?? "", - token, - username ?? "", - ); - console.log(response); - if (response.success) { - setWeeklyReports(response.data ?? []); - } else { - console.error(response.message); - } - }; - - void getWeeklyReports(); - }, [projectName, username]); - - return ( - <> -

{username}'s Time Reports

-
- {weeklyReports.map((newWeeklyReport, index) => ( - -
-

- {"Week: "} - {newWeeklyReport.week} -

-

- {"Total Time: "} - {newWeeklyReport.developmentTime + - newWeeklyReport.meetingTime + - newWeeklyReport.adminTime + - newWeeklyReport.ownWorkTime + - newWeeklyReport.studyTime + - newWeeklyReport.testingTime}{" "} - min -

-

- {"Signed: "} - {newWeeklyReport.signedBy ? "YES" : "NO"} -

-
- - ))} -
- - ); -} - -export default AllTimeReportsInProject; diff --git a/frontend/src/Components/ChangeProjectName.tsx b/frontend/src/Components/ChangeProjectName.tsx deleted file mode 100644 index 8348217..0000000 --- a/frontend/src/Components/ChangeProjectName.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { APIResponse, api } from "../API/API"; - -/** - * Changes the name of a project - * @param {string} props.projectName - Current project name - * @param {string} props.newProjectName - New project name - * @returns {void} - Nothing - */ -export default function ChangeProjectName(props: { - projectName: string; - newProjectName: string; -}): void { - if (props.projectName === "" || props.projectName === props.newProjectName) { - alert("You have to give a new name\n\nName not changed"); - return; - } - api - .changeProjectName( - props.projectName, - props.newProjectName, - localStorage.getItem("accessToken") ?? "", - ) - .then((response: APIResponse) => { - if (response.success) { - alert("Name changed successfully"); - location.reload(); - } else { - alert("Name not changed, name could be taken"); - console.error(response.message); - } - }) - .catch((error) => { - alert("Name not changed"); - console.error("An error occurred during change:", error); - }); -} diff --git a/frontend/src/Components/ChangeRole.tsx b/frontend/src/Components/ChangeRole.tsx deleted file mode 100644 index 54b1468..0000000 --- a/frontend/src/Components/ChangeRole.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { APIResponse, api } from "../API/API"; - -export interface ProjectRoleChange { - username: string; - role: "project_manager" | "member" | ""; - projectname: string; -} - -export default function ChangeRole(roleChangeInfo: ProjectRoleChange): void { - if ( - roleChangeInfo.username === "" || - roleChangeInfo.role === "" || - roleChangeInfo.projectname === "" - ) { - // FOR DEBUG - // console.log(roleChangeInfo.role + ": Role"); - // console.log(roleChangeInfo.projectname + ": P-Name"); - // console.log(roleChangeInfo.username + ": U-name"); - alert("You have to select a role"); - return; - } - api - .changeUserRole(roleChangeInfo, localStorage.getItem("accessToken") ?? "") - .then((response: APIResponse) => { - if (response.success) { - alert("Role changed successfully"); - location.reload(); - } else { - alert(response.message); - console.error(response.message); - } - }) - .catch((error) => { - alert(error); - console.error("An error occurred during change:", error); - }); -} diff --git a/frontend/src/Components/ChangeRoleView.tsx b/frontend/src/Components/ChangeRoleView.tsx deleted file mode 100644 index 9a69e34..0000000 --- a/frontend/src/Components/ChangeRoleView.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useState } from "react"; -import Button from "./Button"; -import ChangeRole, { ProjectRoleChange } from "./ChangeRole"; - -export default function ChangeRoleView(props: { - projectName: string; - username: string; - currentRole: string; -}): JSX.Element { - const [selectedRole, setSelectedRole] = useState< - "project_manager" | "member" | "" - >(""); - - const handleRoleChange = ( - event: React.ChangeEvent, - ): void => { - if (event.target.value === "member") { - setSelectedRole(event.target.value); - } else if (event.target.value === "project_manager") { - setSelectedRole(event.target.value); - } - }; - - const handleSubmit = (event: React.FormEvent): void => { - console.log("Cur: " + props.currentRole + " " + "new: " + selectedRole); - event.preventDefault(); - if (selectedRole === props.currentRole) { - alert(`Already ${props.currentRole}, nothing changed`); - return; - } - const roleChangeInfo: ProjectRoleChange = { - username: props.username, - projectname: props.projectName, - role: selectedRole, - }; - ChangeRole(roleChangeInfo); - }; - - return ( -
-

Select role:

- -
- -
- -
-
- ); -} diff --git a/frontend/src/Components/ChangeUserPassword.tsx b/frontend/src/Components/ChangeUserPassword.tsx deleted file mode 100644 index 0bc5222..0000000 --- a/frontend/src/Components/ChangeUserPassword.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { APIResponse, api } from "../API/API"; - -/** - * Changes the password of a user - * @param {string} props.username - The username of the user - * @param {string} props.newPassword - The new password - * @returns {void} - Nothing - */ -export default function ChangeUserPassword(props: { - username: string; - newPassword: string; -}): void { - if (props.username === localStorage.getItem("username")) { - alert("You cannot change admin password"); - return; - } - api - .changeUserPassword( - props.username, - props.newPassword, - localStorage.getItem("accessToken") ?? "", - ) - .then((response: APIResponse) => { - if (response.success) { - alert("Password changed successfully"); - location.reload(); - } else { - alert("Password not changed"); - console.error(response.message); - } - }) - .catch((error) => { - alert("Password not changed"); - console.error("An error occurred during change:", error); - }); -} diff --git a/frontend/src/Components/ChangeUsername.tsx b/frontend/src/Components/ChangeUsername.tsx index 608c24c..3c35e94 100644 --- a/frontend/src/Components/ChangeUsername.tsx +++ b/frontend/src/Components/ChangeUsername.tsx @@ -1,33 +1,34 @@ -import { APIResponse, api } from "../API/API"; -import { StrNameChange } from "../Types/goTypes"; +import React, { useState } from "react"; +import InputField from "./InputField"; -function ChangeUsername(props: { nameChange: StrNameChange }): void { - if ( - props.nameChange.newName === "" || - props.nameChange.newName === props.nameChange.prevName - ) { - alert("You have to give a new name\n\nName not changed"); - return; - } - if (props.nameChange.prevName === localStorage.getItem("username")) { - alert("You cannot change admin name"); - return; - } - api - .changeUserName(props.nameChange, localStorage.getItem("accessToken") ?? "") - .then((response: APIResponse) => { - if (response.success) { - alert("Name changed successfully"); - location.reload(); - } else { - alert("Name not changed, name could be taken"); - console.error(response.message); - } - }) - .catch((error) => { - alert("Name not changed"); - console.error("An error occurred during change:", error); - }); +function ChangeUsername(): JSX.Element { + const [newUsername, setNewUsername] = useState(""); + + const handleChange = (e: React.ChangeEvent): void => { + setNewUsername(e.target.value); + }; + + // const handleSubmit = async (): Promise => { + // try { + // // Call the API function to update the username + // await api.updateUsername(newUsername); + // // Optionally, add a success message or redirect the user + // } catch (error) { + // console.error("Error updating username:", error); + // // Optionally, handle the error + // } + // }; + + return ( +
+ +
+ ); } export default ChangeUsername; diff --git a/frontend/src/Components/DeleteProject.tsx b/frontend/src/Components/DeleteProject.tsx deleted file mode 100644 index 4add857..0000000 --- a/frontend/src/Components/DeleteProject.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { api, APIResponse } from "../API/API"; - -/** - * Use to delete a project from the system - * @param {string} props.projectToDelete - The projectname of project to delete - * @returns {void} Nothing - * @example - * const exampleProjectName = "project"; - * DeleteProject({ projectToDelete: exampleProjectName }); - */ - -function DeleteProject(props: { projectToDelete: string }): void { - api - .removeProject( - props.projectToDelete, - localStorage.getItem("accessToken") ?? "", - ) - .then((response: APIResponse) => { - if (response.success) { - alert("Project has been deleted!"); - location.reload(); - } else { - alert("Project has not been deleted"); - console.error(response.message); - } - }) - .catch((error) => { - alert("project has not been deleted"); - console.error("An error occurred during deletion:", error); - }); -} - -export default DeleteProject; diff --git a/frontend/src/Components/DeleteUser.tsx b/frontend/src/Components/DeleteUser.tsx index 7c5e8e8..db49724 100644 --- a/frontend/src/Components/DeleteUser.tsx +++ b/frontend/src/Components/DeleteUser.tsx @@ -3,7 +3,7 @@ import { api, APIResponse } from "../API/API"; /** * Use to remove a user from the system - * @param {string} props.usernameToDelete - The username of user to remove + * @param props - The username of user to remove * @returns {boolean} True if removed, false if not * @example * const exampleUsername = "user"; @@ -11,6 +11,7 @@ import { api, APIResponse } from "../API/API"; */ function DeleteUser(props: { usernameToDelete: string }): boolean { + //console.log(props.usernameToDelete); FOR DEBUG let removed = false; api .removeUser( @@ -19,17 +20,13 @@ function DeleteUser(props: { usernameToDelete: string }): boolean { ) .then((response: APIResponse) => { if (response.success) { - alert("User has been deleted!"); - location.reload(); removed = true; } else { - alert("User has not been deleted"); console.error(response.message); } }) .catch((error) => { - alert("User has not been deleted"); - console.error("An error occurred during deletion:", error); + console.error("An error occurred during creation:", error); }); return removed; } diff --git a/frontend/src/Components/DisplayUnsignedReports.tsx b/frontend/src/Components/DisplayUnsignedReports.tsx deleted file mode 100644 index 3cd32f8..0000000 --- a/frontend/src/Components/DisplayUnsignedReports.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useState, useEffect } from "react"; -import { Link, useParams } from "react-router-dom"; -import { api } from "../API/API"; -import { WeeklyReport } from "../Types/goTypes"; - -function DisplayUserProject(): JSX.Element { - const { projectName } = useParams(); - const [unsignedReports, setUnsignedReports] = useState([]); - const [usernames, setUsernames] = useState([]); - const token = localStorage.getItem("accessToken") ?? ""; - - useEffect(() => { - const getUnsignedReports = async (): Promise => { - const response = await api.getUnsignedReportsInProject( - projectName ?? "", - token, - ); - console.log(response); - if (response.success) { - setUnsignedReports(response.data ?? []); - const usernamesPromises = (response.data ?? []).map((report) => - api.getUsername(report.userId, token), - ); - const usernamesResponses = await Promise.all(usernamesPromises); - const usernames = usernamesResponses.map( - (res) => (res.data as { username?: string }).username ?? "", - ); - setUsernames(usernames); - } else { - console.error(response.message); - } - }; - - void getUnsignedReports(); - }, [projectName, token]); - - return ( - <> -

- All Unsigned Reports In: {projectName}{" "} -

-
- {unsignedReports.map((unsignedReport: WeeklyReport, index: number) => ( -

-
-
- Username: -

{usernames[index]}

{" "} - Week: -

{unsignedReport.week}

- Total Time: -

- {unsignedReport.developmentTime + - unsignedReport.meetingTime + - unsignedReport.adminTime + - unsignedReport.ownWorkTime + - unsignedReport.studyTime + - unsignedReport.testingTime} -

- Signed: -

NO

-
-
-
- -

- View Report -

- -
-
-
-

- ))} -
- - ); -} - -export default DisplayUserProject; diff --git a/frontend/src/Components/DisplayUserProjects.tsx b/frontend/src/Components/DisplayUserProjects.tsx index 551e809..f4fd782 100644 --- a/frontend/src/Components/DisplayUserProjects.tsx +++ b/frontend/src/Components/DisplayUserProjects.tsx @@ -1,7 +1,6 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Project } from "../Types/goTypes"; -import { useNavigate } from "react-router-dom"; -import GetProjects from "./GetProjects"; +import { Link } from "react-router-dom"; import { api } from "../API/API"; /** @@ -10,45 +9,33 @@ import { api } from "../API/API"; */ function DisplayUserProject(): JSX.Element { const [projects, setProjects] = useState([]); - const navigate = useNavigate(); - GetProjects({ - setProjectsProp: setProjects, - username: localStorage.getItem("username") ?? "", - }); - - const handleProjectClick = async (projectName: string): Promise => { + const getProjects = async (): Promise => { const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.checkIfProjectManager(projectName, token); - console.log(response.data); + const response = await api.getUserProjects(token); + console.log(response); if (response.success) { - if ( - (response.data as unknown as { isProjectManager: boolean }) - .isProjectManager - ) { - navigate(`/PMProjectPage/${projectName}`); - } else { - navigate(`/project/${projectName}`); - } + setProjects(response.data ?? []); } else { - // handle error console.error(response.message); } }; + // Call getProjects when the component mounts + useEffect(() => { + void getProjects(); + }, []); + return ( <>

Your Projects

- {projects.map((project) => ( -
void handleProjectClick(project.name)} - key={project.id} - > -

+ {projects.map((project, index) => ( + +

{project.name}

-
+ ))}
diff --git a/frontend/src/Components/EditWeeklyReport.tsx b/frontend/src/Components/EditWeeklyReport.tsx index 5037a76..be96329 100644 --- a/frontend/src/Components/EditWeeklyReport.tsx +++ b/frontend/src/Components/EditWeeklyReport.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { WeeklyReport, UpdateWeeklyReport } from "../Types/goTypes"; +import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes"; import { api } from "../API/API"; import { useNavigate, useParams } from "react-router-dom"; import Button from "./Button"; @@ -18,53 +18,47 @@ export default function GetWeeklyReport(): JSX.Element { const [testingTime, setTestingTime] = useState(0); const token = localStorage.getItem("accessToken") ?? ""; - const { projectName, fetchedWeek, signedOrUnsigned } = useParams<{ - projectName: string; - fetchedWeek: string; - signedOrUnsigned: string; - }>(); - const username = localStorage.getItem("userName") ?? ""; - console.log(projectName, fetchedWeek, signedOrUnsigned); + const { projectName } = useParams(); + const { fetchedWeek } = useParams(); + + const fetchWeeklyReport = async (): Promise => { + const response = await api.getWeeklyReport( + projectName ?? "", + fetchedWeek?.toString() ?? "0", + token, + ); + + if (response.success) { + const report: WeeklyReport = response.data ?? { + reportId: 0, + userId: 0, + projectId: 0, + week: 0, + developmentTime: 0, + meetingTime: 0, + adminTime: 0, + ownWorkTime: 0, + studyTime: 0, + testingTime: 0, + }; + setWeek(report.week); + setDevelopmentTime(report.developmentTime); + setMeetingTime(report.meetingTime); + setAdminTime(report.adminTime); + setOwnWorkTime(report.ownWorkTime); + setStudyTime(report.studyTime); + setTestingTime(report.testingTime); + } else { + console.error("Failed to fetch weekly report:", response.message); + } + }; useEffect(() => { - const fetchWeeklyReport = async (): Promise => { - const response = await api.getWeeklyReport( - projectName ?? "", - fetchedWeek ?? "", - token, - ); - - if (response.success) { - const report: WeeklyReport = response.data ?? { - reportId: 0, - userId: 0, - projectId: 0, - week: 0, - developmentTime: 0, - meetingTime: 0, - adminTime: 0, - ownWorkTime: 0, - studyTime: 0, - testingTime: 0, - }; - setWeek(report.week); - setDevelopmentTime(report.developmentTime); - setMeetingTime(report.meetingTime); - setAdminTime(report.adminTime); - setOwnWorkTime(report.ownWorkTime); - setStudyTime(report.studyTime); - setTestingTime(report.testingTime); - } else { - console.error("Failed to fetch weekly report:", response.message); - } - }; - void fetchWeeklyReport(); - }, [projectName, fetchedWeek, signedOrUnsigned, token]); + }); - const handleUpdateWeeklyReport = async (): Promise => { - const updateWeeklyReport: UpdateWeeklyReport = { - userName: username, + const handleNewWeeklyReport = async (): Promise => { + const newWeeklyReport: NewWeeklyReport = { projectName: projectName ?? "", week, developmentTime, @@ -75,14 +69,13 @@ export default function GetWeeklyReport(): JSX.Element { testingTime, }; - await api.updateWeeklyReport(updateWeeklyReport, token); + await api.submitWeeklyReport(newWeeklyReport, token); }; const navigate = useNavigate(); return ( <> -

Edit Time Report

{ @@ -92,16 +85,29 @@ export default function GetWeeklyReport(): JSX.Element { return; } e.preventDefault(); - void handleUpdateWeeklyReport(); - alert("Changes submitted"); + void handleNewWeeklyReport(); navigate(-1); }} >
-
-

Week: {week}

-
- + { + const weekNumber = parseInt(e.target.value.split("-W")[1]); + setWeek(weekNumber); + }} + onKeyDown={(event) => { + event.preventDefault(); + }} + onPaste={(event) => { + event.preventDefault(); + }} + /> @@ -121,31 +127,15 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={developmentTime === 0 ? "" : developmentTime} + value={developmentTime} onChange={(e) => { - if (e.target.value === "") { - setDevelopmentTime(0); - return; - } else { - setDevelopmentTime(parseInt(e.target.value)); - } + setDevelopmentTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} - onClick={() => { - if (signedOrUnsigned === "signed") { - alert("You cannot edit a signed report."); - } - }} - readOnly={signedOrUnsigned === "signed"} /> @@ -156,31 +146,15 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={meetingTime === 0 ? "" : meetingTime} + value={meetingTime} onChange={(e) => { - if (e.target.value === "") { - setMeetingTime(0); - return; - } else { - setMeetingTime(parseInt(e.target.value)); - } + setMeetingTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} - onClick={() => { - if (signedOrUnsigned === "signed") { - alert("You cannot edit a signed report."); - } - }} - readOnly={signedOrUnsigned === "signed"} /> @@ -191,31 +165,15 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={adminTime === 0 ? "" : adminTime} + value={adminTime} onChange={(e) => { - if (e.target.value === "") { - setAdminTime(0); - return; - } else { - setAdminTime(parseInt(e.target.value)); - } + setAdminTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} - onClick={() => { - if (signedOrUnsigned === "signed") { - alert("You cannot edit a signed report."); - } - }} - readOnly={signedOrUnsigned === "signed"} /> @@ -226,31 +184,15 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={ownWorkTime === 0 ? "" : ownWorkTime} + value={ownWorkTime} onChange={(e) => { - if (e.target.value === "") { - setOwnWorkTime(0); - return; - } else { - setOwnWorkTime(parseInt(e.target.value)); - } + setOwnWorkTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} - onClick={() => { - if (signedOrUnsigned === "signed") { - alert("You cannot edit a signed report."); - } - }} - readOnly={signedOrUnsigned === "signed"} /> @@ -261,31 +203,15 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={studyTime === 0 ? "" : studyTime} + value={studyTime} onChange={(e) => { - if (e.target.value === "") { - setStudyTime(0); - return; - } else { - setStudyTime(parseInt(e.target.value)); - } + setStudyTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} - onClick={() => { - if (signedOrUnsigned === "signed") { - alert("You cannot edit a signed report."); - } - }} - readOnly={signedOrUnsigned === "signed"} /> @@ -296,45 +222,27 @@ export default function GetWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={testingTime === 0 ? "" : testingTime} + value={testingTime} onChange={(e) => { - if (e.target.value === "") { - setTestingTime(0); - return; - } else { - setTestingTime(parseInt(e.target.value)); - } + setTestingTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} - onClick={() => { - if (signedOrUnsigned === "signed") { - alert("You cannot edit a signed report."); - } - }} - readOnly={signedOrUnsigned === "signed"} />
- {signedOrUnsigned !== "signed" && ( -
diff --git a/frontend/src/Components/GetProjectTimes.tsx b/frontend/src/Components/GetProjectTimes.tsx deleted file mode 100644 index 38288ec..0000000 --- a/frontend/src/Components/GetProjectTimes.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Dispatch, SetStateAction, useEffect } from "react"; -import { api } from "../API/API"; - -/** - * Interface for reported time per category + total time reported - */ -export interface projectTimes { - admin: number; - development: number; - meeting: number; - own_work: number; - study: number; - testing: number; - totalTime?: number; -} - -/** - * Gets all reported times for this project - * @param {Dispatch} props.setTimesProp - A setStateAction for the map you want to put times in - * @param {string} props.projectName - Username - * @returns {void} Nothing - * @example - * const projectName = "Example"; - * const [times, setTimes] = useState(); - * GetProjectTimes({ setTimesProp: setTimes, projectName: projectName }); - */ -function GetProjectTimes(props: { - setTimesProp: Dispatch>; - projectName: string; -}): void { - const setTimes: Dispatch> = - props.setTimesProp; - useEffect(() => { - const fetchUsers = async (): Promise => { - try { - const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getProjectTimes(props.projectName, token); - if (response.success && response.data) { - // Calculates total time reported - response.data.totalTime = response.data.admin; - response.data.totalTime += response.data.development; - response.data.totalTime += response.data.meeting; - response.data.totalTime += response.data.own_work; - response.data.totalTime += response.data.study; - response.data.totalTime += response.data.testing; - setTimes(response.data); - } else { - console.error("Failed to fetch project times:", response.message); - } - } catch (error) { - console.error("Error fetching times:", error); - } - }; - - void fetchUsers(); - }, [props.projectName, setTimes]); -} - -export default GetProjectTimes; diff --git a/frontend/src/Components/GetProjects.tsx b/frontend/src/Components/GetProjects.tsx index bd6c303..d6ab1f7 100644 --- a/frontend/src/Components/GetProjects.tsx +++ b/frontend/src/Components/GetProjects.tsx @@ -4,17 +4,14 @@ import { api } from "../API/API"; /** * Gets all projects that user is a member of - * @param {Dispatch} props.setProjectsProp - A setStateAction for the array you want to put projects in - * @param {string} props.username - Username + * @param props - A setStateAction for the array you want to put projects in * @returns {void} Nothing * @example - * const username = "Example"; * const [projects, setProjects] = useState([]); - * GetProjects({ setProjectsProp: setProjects, username: username }); + * GetAllUsers({ setProjectsProp: setProjects }); */ function GetProjects(props: { setProjectsProp: Dispatch>; - username: string; }): void { const setProjects: Dispatch> = props.setProjectsProp; @@ -22,7 +19,7 @@ function GetProjects(props: { const fetchUsers = async (): Promise => { try { const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.getUserProjects(props.username, token); + const response = await api.getUserProjects(token); if (response.success) { setProjects(response.data ?? []); } else { @@ -34,7 +31,7 @@ function GetProjects(props: { }; void fetchUsers(); - }, [props.username, setProjects]); + }, [setProjects]); } export default GetProjects; diff --git a/frontend/src/Components/GetUsersInProject.tsx b/frontend/src/Components/GetUsersInProject.tsx index eb32e9b..acdd965 100644 --- a/frontend/src/Components/GetUsersInProject.tsx +++ b/frontend/src/Components/GetUsersInProject.tsx @@ -1,25 +1,20 @@ import { Dispatch, useEffect } from "react"; +import { UserProjectMember } from "../Types/goTypes"; import { api } from "../API/API"; -export interface ProjectMember { - Username: string; - UserRole: string; -} - /** - * Gets all members of a project - * @param string - The project's name - * @param Dispatch - A setStateAction for the array you want to put members in + * Gets all projects that user is a member of + * @param props - A setStateAction for the array you want to put projects in * @returns {void} Nothing * @example - * const [users, setUsers] = useState([]); - * GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers }); + * const [projects, setProjects] = useState([]); + * GetAllUsers({ setProjectsProp: setProjects }); */ function GetUsersInProject(props: { projectName: string; - setUsersProp: Dispatch>; + setUsersProp: Dispatch>; }): void { - const setUsers: Dispatch> = + const setUsers: Dispatch> = props.setUsersProp; useEffect(() => { const fetchUsers = async (): Promise => { @@ -29,10 +24,10 @@ function GetUsersInProject(props: { if (response.success) { setUsers(response.data ?? []); } else { - console.error("Failed to fetch members:", response.message); + console.error("Failed to fetch projects:", response.message); } } catch (error) { - console.error("Error fetching members:", error); + console.error("Error fetching projects:", error); } }; void fetchUsers(); diff --git a/frontend/src/Components/Header.tsx b/frontend/src/Components/Header.tsx index 9be2f4b..eb4fa5a 100644 --- a/frontend/src/Components/Header.tsx +++ b/frontend/src/Components/Header.tsx @@ -1,6 +1,6 @@ //info: Header component to display the header of the page including the logo and user information where thr user can logout import { useState } from "react"; -import { Link, useNavigate } from "react-router-dom"; +import { Link } from "react-router-dom"; import backgroundImage from "../assets/1.jpg"; /** @@ -9,33 +9,23 @@ import backgroundImage from "../assets/1.jpg"; */ function Header(): JSX.Element { const [isOpen, setIsOpen] = useState(false); - const username = localStorage.getItem("username"); - const navigate = useNavigate(); const handleLogout = (): void => { localStorage.clear(); }; - const handleNavigation = (): void => { - if (username === "admin") { - navigate("/admin"); - } else { - navigate("/yourProjects"); - } - }; - return (
-
+ TTIME Logo -
+
{ * setExample(e.target.value); * }} + * value={example} * /> */ function InputField(props: { - label?: string; - placeholder?: string; - type?: string; - value?: string; - onChange?: (e: React.ChangeEvent) => void; + label: string; + type: string; + value: string; + onChange: (e: React.ChangeEvent) => void; }): JSX.Element { return ( -
+
-
-
-

- ); -} - -export default MemberInfoModal; diff --git a/frontend/src/Components/NavButton.tsx b/frontend/src/Components/NavButton.tsx deleted file mode 100644 index 05b3e97..0000000 --- a/frontend/src/Components/NavButton.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useNavigate } from "react-router-dom"; - -/** - * Renders a navigation button component for navigating to - * different page - * @returns The JSX element representing the navigation button. - */ -export default function NavButton(props: { - navTo: string; - label: string; -}): JSX.Element { - const navigate = useNavigate(); - const goBack = (): void => { - navigate(props.navTo); - }; - return ( - - ); -} diff --git a/frontend/src/Components/NewWeeklyReport.tsx b/frontend/src/Components/NewWeeklyReport.tsx index ffc8b21..a128b8d 100644 --- a/frontend/src/Components/NewWeeklyReport.tsx +++ b/frontend/src/Components/NewWeeklyReport.tsx @@ -12,103 +12,65 @@ import Button from "./Button"; */ export default function NewWeeklyReport(): JSX.Element { const [week, setWeek] = useState(0); - const [developmentTime, setDevelopmentTime] = useState(0); - const [meetingTime, setMeetingTime] = useState(0); - const [adminTime, setAdminTime] = useState(0); - const [ownWorkTime, setOwnWorkTime] = useState(0); - const [studyTime, setStudyTime] = useState(0); - const [testingTime, setTestingTime] = useState(0); + const [developmentTime, setDevelopmentTime] = useState(); + const [meetingTime, setMeetingTime] = useState(); + const [adminTime, setAdminTime] = useState(); + const [ownWorkTime, setOwnWorkTime] = useState(); + const [studyTime, setStudyTime] = useState(); + const [testingTime, setTestingTime] = useState(); const { projectName } = useParams(); const token = localStorage.getItem("accessToken") ?? ""; - const handleNewWeeklyReport = async (): Promise => { + const handleNewWeeklyReport = async (): Promise => { const newWeeklyReport: NewWeeklyReport = { projectName: projectName ?? "", week: week, - developmentTime: developmentTime, - meetingTime: meetingTime, - adminTime: adminTime, - ownWorkTime: ownWorkTime, - studyTime: studyTime, - testingTime: testingTime, + developmentTime: developmentTime ?? 0, + meetingTime: meetingTime ?? 0, + adminTime: adminTime ?? 0, + ownWorkTime: ownWorkTime ?? 0, + studyTime: studyTime ?? 0, + testingTime: testingTime ?? 0, }; - const response = await api.submitWeeklyReport(newWeeklyReport, token); - console.log(response); - if (response.success) { - return true; - } else { - return false; - } + await api.submitWeeklyReport(newWeeklyReport, token); }; const navigate = useNavigate(); - // Check if the browser is Chrome or Edge - const isChromeOrEdge = /Chrome|Edg/.test(navigator.userAgent); return ( <>
{ + if (week === 0) { + alert("Please enter a week number"); + e.preventDefault(); + return; + } e.preventDefault(); - void (async (): Promise => { - if (week === 0 || week > 53 || week < 1) { - alert("Please enter a valid week number"); - return; - } - - const success = await handleNewWeeklyReport(); - if (!success) { - alert( - "Error occurred! Your connection to the server might be lost or a time report for this week already exists, please check your connection or go to the edit page to edit your report or change week number.", - ); - return; - } - alert("Weekly report submitted successfully"); - navigate(-1); - })(); + void handleNewWeeklyReport(); + navigate(-1); }} >
- {isChromeOrEdge ? ( - { - const weekNumber = parseInt(e.target.value.split("-W")[1]); - setWeek(weekNumber); - }} - onKeyDown={(event) => { - const keyValue = event.key; - if (!/\d/.test(keyValue) && keyValue !== "Backspace") - event.preventDefault(); - }} - onPaste={(event) => { + { + setWeek(parseInt(e.target.value)); + }} + onKeyDown={(event) => { + const keyValue = event.key; + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); - }} - /> - ) : ( - { - const weekNumber = parseInt(e.target.value); - setWeek(weekNumber); - }} - onKeyDown={(event) => { - const keyValue = event.key; - if (!/\d/.test(keyValue) && keyValue !== "Backspace") - event.preventDefault(); - }} - onPaste={(event) => { - event.preventDefault(); - }} - /> - )} + }} + onPaste={(event) => { + event.preventDefault(); + }} + /> @@ -128,23 +90,13 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={developmentTime === 0 ? "" : developmentTime} + value={developmentTime} onChange={(e) => { - if (e.target.value === "") { - setDevelopmentTime(0); - return; - } else { - setDevelopmentTime(parseInt(e.target.value)); - } + setDevelopmentTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -157,23 +109,13 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={meetingTime === 0 ? "" : meetingTime} + value={meetingTime} onChange={(e) => { - if (e.target.value === "") { - setMeetingTime(0); - return; - } else { - setMeetingTime(parseInt(e.target.value)); - } + setMeetingTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -186,23 +128,13 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={adminTime === 0 ? "" : adminTime} + value={adminTime} onChange={(e) => { - if (e.target.value === "") { - setAdminTime(0); - return; - } else { - setAdminTime(parseInt(e.target.value)); - } + setAdminTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -215,23 +147,13 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={ownWorkTime === 0 ? "" : ownWorkTime} + value={ownWorkTime} onChange={(e) => { - if (e.target.value === "") { - setOwnWorkTime(0); - return; - } else { - setOwnWorkTime(parseInt(e.target.value)); - } + setOwnWorkTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -244,23 +166,13 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={studyTime === 0 ? "" : studyTime} + value={studyTime} onChange={(e) => { - if (e.target.value === "") { - setStudyTime(0); - return; - } else { - setStudyTime(parseInt(e.target.value)); - } + setStudyTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> @@ -273,23 +185,13 @@ export default function NewWeeklyReport(): JSX.Element { type="number" min="0" className="border-2 border-black rounded-md text-center w-1/2" - value={testingTime === 0 ? "" : testingTime} + value={testingTime} onChange={(e) => { - if (e.target.value === "") { - setTestingTime(0); - return; - } else { - setTestingTime(parseInt(e.target.value)); - } + setTestingTime(parseInt(e.target.value)); }} onKeyDown={(event) => { const keyValue = event.key; - if ( - !/\d/.test(keyValue) && - keyValue !== "Backspace" && - keyValue !== "ArrowLeft" && - keyValue !== "ArrowRight" - ) + if (!/\d/.test(keyValue) && keyValue !== "Backspace") event.preventDefault(); }} /> diff --git a/frontend/src/Components/OtherUsersTR.tsx b/frontend/src/Components/OtherUsersTR.tsx deleted file mode 100644 index 40e0b94..0000000 --- a/frontend/src/Components/OtherUsersTR.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import { useState, useEffect } from "react"; -import { WeeklyReport } from "../Types/goTypes"; -import { api } from "../API/API"; -import { useParams, useNavigate } from "react-router-dom"; -import Button from "./Button"; - -/** - * Renders the component for editing a weekly report. - * @returns JSX.Element - */ - -//This component does not yet work as intended. It is supposed to display the weekly report of a user in a project. -export default function OtherUsersTR(): JSX.Element { - const [week, setWeek] = useState(0); - const [developmentTime, setDevelopmentTime] = useState(0); - const [meetingTime, setMeetingTime] = useState(0); - const [adminTime, setAdminTime] = useState(0); - const [ownWorkTime, setOwnWorkTime] = useState(0); - const [studyTime, setStudyTime] = useState(0); - const [testingTime, setTestingTime] = useState(0); - const [reportId, setReportId] = useState(0); - - const token = localStorage.getItem("accessToken") ?? ""; - const { projectName } = useParams(); - const { username } = useParams(); - const { fetchedWeek } = useParams(); - const { signedOrUnsigned } = useParams(); - console.log(projectName, username, fetchedWeek, signedOrUnsigned); - - useEffect(() => { - const fetchUsersWeeklyReport = async (): Promise => { - const response = await api.getWeeklyReport( - projectName ?? "", - fetchedWeek?.toString() ?? "0", - token, - username ?? "", - ); - - if (response.success) { - const report: WeeklyReport = response.data ?? { - reportId: 0, - userId: 0, - projectId: 0, - week: 0, - developmentTime: 0, - meetingTime: 0, - adminTime: 0, - ownWorkTime: 0, - studyTime: 0, - testingTime: 0, - }; - setReportId(report.reportId); - setWeek(report.week); - setDevelopmentTime(report.developmentTime); - setMeetingTime(report.meetingTime); - setAdminTime(report.adminTime); - setOwnWorkTime(report.ownWorkTime); - setStudyTime(report.studyTime); - setTestingTime(report.testingTime); - } else { - console.error("Failed to fetch weekly report:", response.message); - } - }; - - void fetchUsersWeeklyReport(); - }); - - const handleUnsignWeeklyReport = async (): Promise => { - const response = await api.unsignReport(reportId, token); - console.log(response); - console.log(reportId); - if (response.success) { - return true; - } else { - return false; - } - }; - - const handleDeleteWeeklyReport = async (): Promise => { - const response = await api.deleteWeeklyReport(reportId, token); - console.log(response); - if (response.success) { - return true; - } - return false; - }; - const navigate = useNavigate(); - - return ( - <> -

{username}'s Report

-
-
-
-

Week: {week}

-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Activity - Total Time (min) -
Development - -
Meeting - -
Administration - -
Own Work - -
Studies - -
Testing - -
-
- {signedOrUnsigned === "signed" && ( -
-
-
- - ); -} diff --git a/frontend/src/Components/PMProjectMenu.tsx b/frontend/src/Components/PMProjectMenu.tsx index f0cb492..ce7c5c5 100644 --- a/frontend/src/Components/PMProjectMenu.tsx +++ b/frontend/src/Components/PMProjectMenu.tsx @@ -8,22 +8,22 @@ function PMProjectMenu(): JSX.Element {

{projectName}

-

+

Your Time Reports

-

+

New Time Report

-

+

Statistics

-

+

Unsigned Time Reports

diff --git a/frontend/src/Components/ProjectInfoModal.tsx b/frontend/src/Components/ProjectInfoModal.tsx index 4be3397..b153e9c 100644 --- a/frontend/src/Components/ProjectInfoModal.tsx +++ b/frontend/src/Components/ProjectInfoModal.tsx @@ -1,198 +1,62 @@ -import { useEffect, useRef, useState } from "react"; +import { useState } from "react"; import Button from "./Button"; -import GetUsersInProject, { ProjectMember } from "./GetUsersInProject"; -import { Link } from "react-router-dom"; -import GetProjectTimes, { projectTimes } from "./GetProjectTimes"; -import DeleteProject from "./DeleteProject"; -import InputField from "./InputField"; -import ProjectNameInput from "./Inputs/ProjectNameInput"; -import { alphanumeric } from "../Data/regex"; -import { projNameHighLimit, projNameLowLimit } from "../Data/constants"; -import ChangeProjectName from "./ChangeProjectName"; +import { UserProjectMember } from "../Types/goTypes"; +import GetUsersInProject from "./GetUsersInProject"; function ProjectInfoModal(props: { + isVisible: boolean; projectname: string; onClose: () => void; - onClick: (username: string, userRole: string) => void; + onClick: (username: string) => void; }): JSX.Element { - const [showInput, setShowInput] = useState(false); - const [users, setUsers] = useState([]); - const [times, setTimes] = useState(); - const [search, setSearch] = useState(""); - const [newProjName, setNewProjName] = useState(""); - const totalTime = useRef(0); + const [users, setUsers] = useState([]); GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers }); - - GetProjectTimes({ setTimesProp: setTimes, projectName: props.projectname }); - - const handleChangeNameView = (): void => { - if (showInput) { - setNewProjName(""); - setShowInput(false); - } else { - setShowInput(true); - } - }; - - const handleClickChangeName = (): void => { - if ( - newProjName.length > projNameHighLimit || - newProjName.length < projNameLowLimit || - !alphanumeric.test(newProjName) - ) { - alert( - "Please provide valid project name: \n-Between 10-99 characters \n-No special characters (.-!?/*)", - ); - return; - } - - if ( - confirm( - `Are you sure you want to change name of ${props.projectname} to ${newProjName}?`, - ) - ) { - ChangeProjectName({ - projectName: props.projectname, - newProjectName: newProjName, - }); - } else { - alert("Name was not changed!"); - } - }; - - useEffect(() => { - if (times?.totalTime !== undefined) { - totalTime.current = times.totalTime; - } - }, [times]); + if (!props.isVisible) return <>; return (
-
-
-

{props.projectname}

-

- (Change project name) -

- {showInput && ( - <> -

Change name:

-
- +
+

Project members:

+
+
    +
    + {users.map((user) => ( +
  • { + props.onClick(user.Username); }} - /> -
    -
    -
- - )} -

Statistics:

-
-

Number of members: {users.length}

-

- Total time reported:{" "} - {Math.floor(totalTime.current / 60 / 24) + " d "} - {Math.floor((totalTime.current / 60) % 24) + " h "} - {(totalTime.current % 60) + " m "} -

-
-

Project members:

-
- { - setSearch(e.target.value); - }} - /> -
    - {users - .filter((user) => { - return search.toLowerCase() === "" - ? user.Username - : user.Username.toLowerCase().includes( - search.toLowerCase(), - ); - }) - .map((user) => ( -
  • { - props.onClick(user.Username, user.UserRole); - }} - > - - Name: {user.Username} -
    - Role: {user.UserRole} -
    -
  • - ))} + > + + Name: {user.Username} +
    + Role: {user.UserRole} +
    + + ))}
-
-
+
+
+
diff --git a/frontend/src/Components/ProjectListAdmin.tsx b/frontend/src/Components/ProjectListAdmin.tsx index 6461dae..4ebdaf8 100644 --- a/frontend/src/Components/ProjectListAdmin.tsx +++ b/frontend/src/Components/ProjectListAdmin.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import { NewProject } from "../Types/goTypes"; import ProjectInfoModal from "./ProjectInfoModal"; -import MemberInfoModal from "./MemberInfoModal"; -import InputField from "./InputField"; +import UserInfoModal from "./UserInfoModal"; +import DeleteUser from "./DeleteUser"; /** * A list of projects for admin manage projects page, that sets an onClick @@ -19,79 +19,59 @@ export function ProjectListAdmin(props: { projects: NewProject[]; }): JSX.Element { const [projectModalVisible, setProjectModalVisible] = useState(false); - const [projectName, setProjectName] = useState(""); + const [projectname, setProjectname] = useState(""); const [userModalVisible, setUserModalVisible] = useState(false); const [username, setUsername] = useState(""); - const [userRole, setUserRole] = useState(""); - const [search, setSearch] = useState(""); - const handleClickUser = (username: string, userRole: string): void => { + const handleClickUser = (username: string): void => { setUsername(username); - setUserRole(userRole); setUserModalVisible(true); }; - const handleClickProject = (projectname: string): void => { - setProjectName(projectname); + const handleClickProject = (username: string): void => { + setProjectname(username); setProjectModalVisible(true); }; const handleCloseProject = (): void => { - setProjectName(""); + setProjectname(""); setProjectModalVisible(false); }; const handleCloseUser = (): void => { - setUsername(""); - setUserRole(""); + setProjectname(""); setUserModalVisible(false); }; return ( <> -

Manage Projects

- {projectModalVisible && ( - - )} - {userModalVisible && ( - - )} + + DeleteUser} + isVisible={userModalVisible} + username={username} + />
- { - setSearch(e.target.value); - }} - /> -
    - {props.projects - .filter((project) => { - return search.toLowerCase() === "" - ? project.name - : project.name.toLowerCase().includes(search.toLowerCase()); - }) - .map((project) => ( -
  • { - handleClickProject(project.name); - }} - > - {project.name} -
  • - ))} +
      + {props.projects.map((project) => ( +
    • { + handleClickProject(project.name); + }} + > + {project.name} +
    • + ))}
diff --git a/frontend/src/Components/ProjectMembers.tsx b/frontend/src/Components/ProjectMembers.tsx index e06ed75..73e29e5 100644 --- a/frontend/src/Components/ProjectMembers.tsx +++ b/frontend/src/Components/ProjectMembers.tsx @@ -1,79 +1,96 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Link, useParams } from "react-router-dom"; -import GetUsersInProject, { ProjectMember } from "./GetUsersInProject"; -import { api } from "../API/API"; function ProjectMembers(): JSX.Element { const { projectName } = useParams(); const [projectMembers, setProjectMembers] = useState([]); - GetUsersInProject({ - projectName: projectName ?? "", - setUsersProp: setProjectMembers, - }); + // const getProjectMembers = async (): Promise => { + // const token = localStorage.getItem("accessToken") ?? ""; + // const response = await api.getProjectMembers(projectName ?? "", token); + // console.log(response); + // if (response.success) { + // setProjectMembers(response.data ?? []); + // } else { + // console.error(response.message); + // } + // }; - const handleUserDeleteClick = async (username: string): Promise => { - const token = localStorage.getItem("accessToken") ?? ""; - const response = await api.removeUserFromProject( - username, - projectName ?? "", - token, - ); - console.log(response.data); + interface ProjectMember { + username: string; + role: string; + } - // Remove the deleted user from the state - setProjectMembers((prevMembers) => - prevMembers.filter((member) => member.Username !== username), - ); + const mockProjectMembers = [ + { + username: "username1", + role: "Project Manager", + }, + { + username: "username2", + role: "System Manager", + }, + { + username: "username3", + role: "Developer", + }, + { + username: "username4", + role: "Tester", + }, + { + username: "username5", + role: "Tester", + }, + { + username: "username6", + role: "Tester", + }, + ]; + + const getProjectMembers = async (): Promise => { + // Use the mock data + setProjectMembers(mockProjectMembers); + + await Promise.resolve(); }; + useEffect(() => { + void getProjectMembers(); + }); + return ( <> -

- All Members In: {projectName}{" "} -

- {projectMembers.map((projectMember: ProjectMember, index: number) => { - if (projectMember.Username === "admin") { - return null; // Skip rendering for admin user - } - return ( -

-
-
-

{projectMember.Username}

- Role: -

{projectMember.UserRole}

-
-
-
- {projectMember.Username !== - localStorage.getItem("username") && ( -

{ - confirm( - "Are you sure you want to delete this user? This action cannot be undone.", - ) && - void handleUserDeleteClick(projectMember.Username); - }} - > - Delete User -

- )} - -

- View Reports -

- -
+ {projectMembers.map((projectMember, index) => ( +

+
+
+

{projectMember.username}

+ Role: +

{projectMember.role}

+
+
+
+ +

+ View Reports +

+ + +

+ Change Role +

+
-

- ); - })} +
+

+ ))}
); diff --git a/frontend/src/Components/Register.tsx b/frontend/src/Components/Register.tsx index 7310e4f..df07c6e 100644 --- a/frontend/src/Components/Register.tsx +++ b/frontend/src/Components/Register.tsx @@ -3,52 +3,29 @@ import { NewUser } from "../Types/goTypes"; import { api } from "../API/API"; import Logo from "../assets/Logo.svg"; import Button from "./Button"; -import UsernameInput from "./Inputs/UsernameInput"; -import PasswordInput from "./Inputs/PasswordInput"; -import { alphanumeric, lowercase } from "../Data/regex"; -import { - passwordLength, - usernameLowLimit, - usernameUpLimit, -} from "../Data/constants"; +import InputField from "./InputField"; +import { useNavigate } from "react-router-dom"; /** * Renders a registration form for the admin to add new users in. * @returns The JSX element representing the registration form. */ export default function Register(): JSX.Element { - const [username, setUsername] = useState(""); - const [password, setPassword] = useState(""); - const [errMessage, setErrMessage] = useState(""); + const [username, setUsername] = useState(); + const [password, setPassword] = useState(); + const [errMessage, setErrMessage] = useState(); + + const nav = useNavigate(); const handleRegister = async (): Promise => { - if ( - username.length > usernameUpLimit || - username.length < usernameLowLimit || - !alphanumeric.test(username) - ) { - alert( - "Please provide valid username: \n-Between 5-10 characters \n-No special characters (.-!?/*)", - ); - return; - } - if (password.length !== passwordLength || !lowercase.test(password)) { - alert( - "Please provide valid password: \n-Exactly 6 characters \n-No uppercase letters \n-No numbers \n-No special characters (.-!?/*)", - ); - return; - } const newUser: NewUser = { - username: username, - password: password, + username: username ?? "", + password: password ?? "", }; const response = await api.registerUser(newUser); if (response.success) { - alert(`${newUser.username} added!`); - setPassword(""); - setUsername(""); + nav("/"); // Instantly navigate to the login page } else { - alert("User not added, name could be taken"); setErrMessage(response.message ?? "Unknown error"); console.error(errMessage); } @@ -58,7 +35,7 @@ export default function Register(): JSX.Element {
{ e.preventDefault(); void handleRegister(); @@ -66,28 +43,29 @@ export default function Register(): JSX.Element { > TTIME Logo

Register New User

- - { setUsername(e.target.value); }} /> -
- { setPassword(e.target.value); }} /> - -
+
- )} - {showPwordInput && ( -
- { - setNewPassword(e.target.value); - }} - /> -
- )} -

Member of these projects:

- -
-
); diff --git a/frontend/src/Components/UserListAdmin.tsx b/frontend/src/Components/UserListAdmin.tsx index 23e49db..c08b05c 100644 --- a/frontend/src/Components/UserListAdmin.tsx +++ b/frontend/src/Components/UserListAdmin.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import UserInfoModal from "./UserInfoModal"; -import InputField from "./InputField"; +import DeleteUser from "./DeleteUser"; /** * A list of users for admin manage users page, that sets an onClick @@ -16,7 +16,6 @@ import InputField from "./InputField"; export function UserListAdmin(props: { users: string[] }): JSX.Element { const [modalVisible, setModalVisible] = useState(false); const [username, setUsername] = useState(""); - const [search, setSearch] = useState(""); const handleClick = (username: string): void => { setUsername(username); @@ -30,39 +29,26 @@ export function UserListAdmin(props: { users: string[] }): JSX.Element { return ( <> -

Manage Users

DeleteUser} isVisible={modalVisible} username={username} />
- { - setSearch(e.target.value); - }} - /> -
    - {props.users - .filter((user) => { - return search.toLowerCase() === "" - ? user - : user.toLowerCase().includes(search.toLowerCase()); - }) - .map((user) => ( -
  • { - handleClick(user); - }} - > - {user} -
  • - ))} +
      + {props.users.map((user) => ( +
    • { + handleClick(user); + }} + > + {user} +
    • + ))}
diff --git a/frontend/src/Components/UserProjectListAdmin.tsx b/frontend/src/Components/UserProjectListAdmin.tsx index 8f28ce9..1b7b923 100644 --- a/frontend/src/Components/UserProjectListAdmin.tsx +++ b/frontend/src/Components/UserProjectListAdmin.tsx @@ -1,17 +1,35 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; +import { api } from "../API/API"; import { Project } from "../Types/goTypes"; -import GetProjects from "./GetProjects"; -function UserProjectListAdmin(props: { username: string }): JSX.Element { +function UserProjectListAdmin(): JSX.Element { const [projects, setProjects] = useState([]); - GetProjects({ setProjectsProp: setProjects, username: props.username }); + useEffect(() => { + const fetchProjects = async (): Promise => { + try { + const token = localStorage.getItem("accessToken") ?? ""; + // const username = props.username; + + const response = await api.getUserProjects(token); + if (response.success) { + setProjects(response.data ?? []); + } else { + console.error("Failed to fetch projects:", response.message); + } + } catch (error) { + console.error("Error fetching projects:", error); + } + }; + + void fetchProjects(); + }, []); return ( -
-
    +
    +
      {projects.map((project) => ( -
    • +
    • {project.name}
    • ))} diff --git a/frontend/src/Components/UserProjectMenu.tsx b/frontend/src/Components/UserProjectMenu.tsx index 4be4dee..e307e90 100644 --- a/frontend/src/Components/UserProjectMenu.tsx +++ b/frontend/src/Components/UserProjectMenu.tsx @@ -16,12 +16,12 @@ function UserProjectMenu(): JSX.Element {

      {projectName}

      -

      +

      Your Time Reports

      -

      +

      New Time Report

      diff --git a/frontend/src/Components/UserStatistics.tsx b/frontend/src/Components/UserStatistics.tsx deleted file mode 100644 index c84f1a0..0000000 --- a/frontend/src/Components/UserStatistics.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { useState, useEffect } from "react"; -import { useParams } from "react-router-dom"; -import { api } from "../API/API"; -import { Statistics } from "../Types/goTypes"; - -/** - * Renders the component for showing total time per role in a project. - * @returns JSX.Element - */ -export default function UserStatistics(): JSX.Element { - const [development, setDevelopment] = useState(0); - const [meeting, setMeeting] = useState(0); - const [admin, setAdmin] = useState(0); - const [own_work, setOwnWork] = useState(0); - const [study, setStudy] = useState(0); - const [testing, setTesting] = useState(0); - const total = development + meeting + admin + own_work + study + testing; - - const token = localStorage.getItem("accessToken") ?? ""; - const { projectName } = useParams(); - const { username } = useParams(); - - const fetchTimePerActivity = async (): Promise => { - const response = await api.getStatistics( - projectName ?? "", - token, - username ?? "", - ); - { - if (response.success) { - const statistics: Statistics = response.data ?? { - totalDevelopmentTime: 0, - totalMeetingTime: 0, - totalAdminTime: 0, - totalOwnWorkTime: 0, - totalStudyTime: 0, - totalTestingTime: 0, - }; - setDevelopment(statistics.totalDevelopmentTime); - setMeeting(statistics.totalMeetingTime); - setAdmin(statistics.totalAdminTime); - setOwnWork(statistics.totalOwnWorkTime); - setStudy(statistics.totalStudyTime); - setTesting(statistics.totalTestingTime); - } else { - console.error("Failed to fetch weekly report:", response.message); - } - } - }; - - useEffect(() => { - void fetchTimePerActivity(); - }); - - return ( - <> -

      - Total Time In: {projectName}{" "} -

      -
      -
      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      Activity - Total Time (min) -
      Development - -
      Meeting - -
      Administration - -
      Own Work - -
      Studies - -
      Testing - -
      In Total: -

      {total}

      -
      -
      -
      - - ); -} diff --git a/frontend/src/Components/ViewOtherTimeReport.tsx b/frontend/src/Components/ViewOtherTimeReport.tsx deleted file mode 100644 index 3689854..0000000 --- a/frontend/src/Components/ViewOtherTimeReport.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { useState, useEffect } from "react"; -import { WeeklyReport } from "../Types/goTypes"; -import { api } from "../API/API"; -import { useNavigate, useParams } from "react-router-dom"; -import Button from "./Button"; - -/** - * Renders the component for editing a weekly report. - * @returns JSX.Element - */ - -//This component does not yet work as intended. It is supposed to display the weekly report of a user in a project. -export default function GetOtherUsersReport(): JSX.Element { - const [week, setWeek] = useState(0); - const [developmentTime, setDevelopmentTime] = useState(0); - const [meetingTime, setMeetingTime] = useState(0); - const [adminTime, setAdminTime] = useState(0); - const [ownWorkTime, setOwnWorkTime] = useState(0); - const [studyTime, setStudyTime] = useState(0); - const [testingTime, setTestingTime] = useState(0); - const [reportId, setReportId] = useState(0); - - const token = localStorage.getItem("accessToken") ?? ""; - const { projectName } = useParams(); - const { username } = useParams(); - const { fetchedWeek } = useParams(); - - useEffect(() => { - const fetchUsersWeeklyReport = async (): Promise => { - const response = await api.getWeeklyReport( - projectName ?? "", - fetchedWeek?.toString() ?? "0", - token, - username ?? "", - ); - console.log(response); - if (response.success) { - const report: WeeklyReport = response.data ?? { - reportId: 0, - userId: 0, - projectId: 0, - week: 0, - developmentTime: 0, - meetingTime: 0, - adminTime: 0, - ownWorkTime: 0, - studyTime: 0, - testingTime: 0, - }; - setReportId(report.reportId); - setWeek(report.week); - setDevelopmentTime(report.developmentTime); - setMeetingTime(report.meetingTime); - setAdminTime(report.adminTime); - setOwnWorkTime(report.ownWorkTime); - setStudyTime(report.studyTime); - setTestingTime(report.testingTime); - } else { - console.error("Failed to fetch weekly report:", response.message); - } - }; - - void fetchUsersWeeklyReport(); - }); - - const handleSignWeeklyReport = async (): Promise => { - const response = await api.signReport(reportId, token); - if (response.success) { - return true; - } else { - return false; - } - }; - - const navigate = useNavigate(); - - return ( - <> -

      {username}'s Report

      -
      - { - e.preventDefault(); - void (async (): Promise => { - const success = await handleSignWeeklyReport(); - if (!success) { - alert("Failed to sign report!"); - return; - } - alert("Report successfully signed!"); - navigate(-1); - })(); - }} - > -
      -
      -

      Week: {week}

      -
      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      - Activity - - Total Time (min) -
      Development - -
      Meeting - -
      Administration - -
      Own Work - -
      Studies - -
      Testing - -
      -
      - -
      - - ); -} diff --git a/frontend/src/Containers/GenApiDemo.tsx b/frontend/src/Containers/GenApiDemo.tsx deleted file mode 100644 index 27092d8..0000000 --- a/frontend/src/Containers/GenApiDemo.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useEffect } from "react"; -import { GenApi } from "../API/GenApi"; - -// Instantiation of the API -const api = new GenApi(); - -export function GenApiDemo(): JSX.Element { - // Sync wrapper around the loginCreate method - const register = async (): Promise => { - const response = await api.login.loginCreate({ - username: "admin", - password: "admin", - }); - console.log(response.data); // This should be the inner type of the response - }; - - // Call the wrapper - useEffect(() => { - void register(); - }); - - return <>; -} diff --git a/frontend/src/Data/constants.ts b/frontend/src/Data/constants.ts deleted file mode 100644 index c803ad4..0000000 --- a/frontend/src/Data/constants.ts +++ /dev/null @@ -1,36 +0,0 @@ -//Different character limits certain strings - -/** - * Allowed character length for password - */ -export const passwordLength = 6; - -/** - * Lower limit for username length - */ -export const usernameLowLimit = 5; - -/** - * Upper limit for password length - */ -export const usernameUpLimit = 10; - -/** - * Lower limit for project name length - */ -export const projNameLowLimit = 10; - -/** - * Upper limit for project name length - */ -export const projNameHighLimit = 99; - -/** - * Upper limit for project description length - */ -export const projDescLowLimit = 0; - -/** - * Upper limit for project description length - */ -export const projDescHighLimit = 99; diff --git a/frontend/src/Data/regex.ts b/frontend/src/Data/regex.ts deleted file mode 100644 index ceb22cd..0000000 --- a/frontend/src/Data/regex.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Only alphanumerical characters - */ -export const alphanumeric = /^[a-zA-Z0-9]+$/; - -/** - * Only lowercase letters - */ -export const lowercase = /^[a-z]+$/; diff --git a/frontend/src/Pages/AdminPages/AdminChangeUsername.tsx b/frontend/src/Pages/AdminPages/AdminChangeUsername.tsx new file mode 100644 index 0000000..b130fae --- /dev/null +++ b/frontend/src/Pages/AdminPages/AdminChangeUsername.tsx @@ -0,0 +1,28 @@ +import BackButton from "../../Components/BackButton"; +import BasicWindow from "../../Components/BasicWindow"; +import Button from "../../Components/Button"; +import ChangeUsername from "../../Components/ChangeUsername"; + +function AdminChangeUsername(): JSX.Element { + const content = ( + <> + + + ); + + const buttons = ( + <> +