From 555e9cddcf1111730219ba6f3d561cf28deafe07 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Fri, 29 Mar 2024 01:27:16 -0400 Subject: [PATCH 001/400] rename auth directory to authentication --- devU-api/src/{auth => authentication}/auth.middleware.ts | 0 devU-api/src/{auth => authentication}/auth.service.ts | 0 .../login.developer/login.developer.controller.ts | 2 +- .../login.developer/login.developer.router.ts | 0 .../tests/login.developer.controller.test.ts | 2 +- .../login.saml/login.saml.controller.ts | 2 +- .../login.saml/login.saml.router.ts | 2 +- .../login.saml/tests/login.saml.controller.test.ts | 2 +- .../src/{auth => authentication}/login/login.controller.ts | 2 +- devU-api/src/{auth => authentication}/login/login.router.ts | 2 +- .../src/{auth => authentication}/login/login.validator.ts | 0 .../login/tests/login.controller.test.ts | 2 +- .../{auth => authentication}/logout/logout.controller.ts | 0 .../src/{auth => authentication}/logout/logout.router.ts | 0 .../logout/tests/logout.controller.test.ts | 0 .../{auth => authentication}/tests/auth.middleware.test.ts | 0 devU-api/src/router/index.ts | 6 +++--- 17 files changed, 11 insertions(+), 11 deletions(-) rename devU-api/src/{auth => authentication}/auth.middleware.ts (100%) rename devU-api/src/{auth => authentication}/auth.service.ts (100%) rename devU-api/src/{auth => authentication}/login.developer/login.developer.controller.ts (93%) rename devU-api/src/{auth => authentication}/login.developer/login.developer.router.ts (100%) rename devU-api/src/{auth => authentication}/login.developer/tests/login.developer.controller.test.ts (97%) rename devU-api/src/{auth => authentication}/login.saml/login.saml.controller.ts (95%) rename devU-api/src/{auth => authentication}/login.saml/login.saml.router.ts (93%) rename devU-api/src/{auth => authentication}/login.saml/tests/login.saml.controller.test.ts (98%) rename devU-api/src/{auth => authentication}/login/login.controller.ts (95%) rename devU-api/src/{auth => authentication}/login/login.router.ts (97%) rename devU-api/src/{auth => authentication}/login/login.validator.ts (100%) rename devU-api/src/{auth => authentication}/login/tests/login.controller.test.ts (98%) rename devU-api/src/{auth => authentication}/logout/logout.controller.ts (100%) rename devU-api/src/{auth => authentication}/logout/logout.router.ts (100%) rename devU-api/src/{auth => authentication}/logout/tests/logout.controller.test.ts (100%) rename devU-api/src/{auth => authentication}/tests/auth.middleware.test.ts (100%) diff --git a/devU-api/src/auth/auth.middleware.ts b/devU-api/src/authentication/auth.middleware.ts similarity index 100% rename from devU-api/src/auth/auth.middleware.ts rename to devU-api/src/authentication/auth.middleware.ts diff --git a/devU-api/src/auth/auth.service.ts b/devU-api/src/authentication/auth.service.ts similarity index 100% rename from devU-api/src/auth/auth.service.ts rename to devU-api/src/authentication/auth.service.ts diff --git a/devU-api/src/auth/login.developer/login.developer.controller.ts b/devU-api/src/authentication/login.developer/login.developer.controller.ts similarity index 93% rename from devU-api/src/auth/login.developer/login.developer.controller.ts rename to devU-api/src/authentication/login.developer/login.developer.controller.ts index 7d07dc6d..7f2ac588 100644 --- a/devU-api/src/auth/login.developer/login.developer.controller.ts +++ b/devU-api/src/authentication/login.developer/login.developer.controller.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from 'express' import UserService from '../../entities/user/user.service' -import AuthService from '../../auth/auth.service' +import AuthService from '../auth.service' import { GenericResponse } from '../../utils/apiResponse.utils' import { refreshCookieOptions } from '../../utils/cookie.utils' diff --git a/devU-api/src/auth/login.developer/login.developer.router.ts b/devU-api/src/authentication/login.developer/login.developer.router.ts similarity index 100% rename from devU-api/src/auth/login.developer/login.developer.router.ts rename to devU-api/src/authentication/login.developer/login.developer.router.ts diff --git a/devU-api/src/auth/login.developer/tests/login.developer.controller.test.ts b/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts similarity index 97% rename from devU-api/src/auth/login.developer/tests/login.developer.controller.test.ts rename to devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts index 59900e57..4bbeb1c2 100644 --- a/devU-api/src/auth/login.developer/tests/login.developer.controller.test.ts +++ b/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts @@ -3,7 +3,7 @@ import controller from '../login.developer.controller' import UserModel from '../../../entities/user/user.model' import UserService from '../../../entities/user/user.service' -import AuthService from '../../../auth/auth.service' +import AuthService from '../../auth.service' import Testing from '../../../utils/testing.utils' import { refreshCookieOptions } from '../../../utils/cookie.utils' diff --git a/devU-api/src/auth/login.saml/login.saml.controller.ts b/devU-api/src/authentication/login.saml/login.saml.controller.ts similarity index 95% rename from devU-api/src/auth/login.saml/login.saml.controller.ts rename to devU-api/src/authentication/login.saml/login.saml.controller.ts index b0dc0a8a..31c87d1f 100644 --- a/devU-api/src/auth/login.saml/login.saml.controller.ts +++ b/devU-api/src/authentication/login.saml/login.saml.controller.ts @@ -4,7 +4,7 @@ import { User } from 'devu-shared-modules' import environment from '../../environment' import UserService from '../../entities/user/user.service' -import AuthService from '../../auth/auth.service' +import AuthService from '../auth.service' import { samlStrategy } from '../../utils/passport/saml.passport' import { refreshCookieOptions } from '../../utils/cookie.utils' diff --git a/devU-api/src/auth/login.saml/login.saml.router.ts b/devU-api/src/authentication/login.saml/login.saml.router.ts similarity index 93% rename from devU-api/src/auth/login.saml/login.saml.router.ts rename to devU-api/src/authentication/login.saml/login.saml.router.ts index 554731cb..e60100a4 100644 --- a/devU-api/src/auth/login.saml/login.saml.router.ts +++ b/devU-api/src/authentication/login.saml/login.saml.router.ts @@ -3,7 +3,7 @@ import express from 'express' import controller from '../login.saml/login.saml.controller' -import { saml } from '../../auth/auth.middleware' +import { saml } from '../auth.middleware' import { authCallbackValidator } from '../login/login.validator' diff --git a/devU-api/src/auth/login.saml/tests/login.saml.controller.test.ts b/devU-api/src/authentication/login.saml/tests/login.saml.controller.test.ts similarity index 98% rename from devU-api/src/auth/login.saml/tests/login.saml.controller.test.ts rename to devU-api/src/authentication/login.saml/tests/login.saml.controller.test.ts index 79b26b84..c1ac399f 100644 --- a/devU-api/src/auth/login.saml/tests/login.saml.controller.test.ts +++ b/devU-api/src/authentication/login.saml/tests/login.saml.controller.test.ts @@ -3,7 +3,7 @@ import controller from '../login.saml.controller' import UserModel from '../../../entities/user/user.model' import UserService from '../../../entities/user/user.service' -import AuthService from '../../../auth/auth.service' +import AuthService from '../../auth.service' import Testing from '../../../utils/testing.utils' import { refreshCookieOptions } from '../../../utils/cookie.utils' diff --git a/devU-api/src/auth/login/login.controller.ts b/devU-api/src/authentication/login/login.controller.ts similarity index 95% rename from devU-api/src/auth/login/login.controller.ts rename to devU-api/src/authentication/login/login.controller.ts index 8be8a35b..33873a83 100644 --- a/devU-api/src/auth/login/login.controller.ts +++ b/devU-api/src/authentication/login/login.controller.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from 'express' import UserService from '../../entities/user/user.service' -import AuthService from '../../auth/auth.service' +import AuthService from '../auth.service' import ProviderService from '../../provider/provider.service' import { Unauthorized } from '../../utils/apiResponse.utils' diff --git a/devU-api/src/auth/login/login.router.ts b/devU-api/src/authentication/login/login.router.ts similarity index 97% rename from devU-api/src/auth/login/login.router.ts rename to devU-api/src/authentication/login/login.router.ts index 4f807cfb..d27355c2 100644 --- a/devU-api/src/auth/login/login.router.ts +++ b/devU-api/src/authentication/login/login.router.ts @@ -5,7 +5,7 @@ import environment from '../../environment' import controller from '../login/login.controller' -import { isValidRefreshToken, isRefreshNearingExpiration } from '../../auth/auth.middleware' +import { isValidRefreshToken, isRefreshNearingExpiration } from '../auth.middleware' import SamlRouter from '../login.saml/login.saml.router' import DeveloperRouter from '../login.developer/login.developer.router' diff --git a/devU-api/src/auth/login/login.validator.ts b/devU-api/src/authentication/login/login.validator.ts similarity index 100% rename from devU-api/src/auth/login/login.validator.ts rename to devU-api/src/authentication/login/login.validator.ts diff --git a/devU-api/src/auth/login/tests/login.controller.test.ts b/devU-api/src/authentication/login/tests/login.controller.test.ts similarity index 98% rename from devU-api/src/auth/login/tests/login.controller.test.ts rename to devU-api/src/authentication/login/tests/login.controller.test.ts index 24496dfc..db64dda6 100644 --- a/devU-api/src/auth/login/tests/login.controller.test.ts +++ b/devU-api/src/authentication/login/tests/login.controller.test.ts @@ -5,7 +5,7 @@ import controller from '../login.controller' import UserModel from '../../../entities/user/user.model' import UserService from '../../../entities/user/user.service' -import AuthService from '../../../auth/auth.service' +import AuthService from '../../auth.service' import ProviderService from '../../../provider/provider.service' import Testing from '../../../utils/testing.utils' diff --git a/devU-api/src/auth/logout/logout.controller.ts b/devU-api/src/authentication/logout/logout.controller.ts similarity index 100% rename from devU-api/src/auth/logout/logout.controller.ts rename to devU-api/src/authentication/logout/logout.controller.ts diff --git a/devU-api/src/auth/logout/logout.router.ts b/devU-api/src/authentication/logout/logout.router.ts similarity index 100% rename from devU-api/src/auth/logout/logout.router.ts rename to devU-api/src/authentication/logout/logout.router.ts diff --git a/devU-api/src/auth/logout/tests/logout.controller.test.ts b/devU-api/src/authentication/logout/tests/logout.controller.test.ts similarity index 100% rename from devU-api/src/auth/logout/tests/logout.controller.test.ts rename to devU-api/src/authentication/logout/tests/logout.controller.test.ts diff --git a/devU-api/src/auth/tests/auth.middleware.test.ts b/devU-api/src/authentication/tests/auth.middleware.test.ts similarity index 100% rename from devU-api/src/auth/tests/auth.middleware.test.ts rename to devU-api/src/authentication/tests/auth.middleware.test.ts diff --git a/devU-api/src/router/index.ts b/devU-api/src/router/index.ts index 76e85ced..71aa0597 100644 --- a/devU-api/src/router/index.ts +++ b/devU-api/src/router/index.ts @@ -6,8 +6,8 @@ import swagger from '../utils/swagger.utils' import userCourse from '../entities/userCourse/userCourse.router' import assignments from '../entities/assignment/assignment.router' import courses from '../entities/course/course.router' -import login from '../auth/login/login.router' -import logout from '../auth/logout/logout.router' +import login from '../authentication/login/login.router' +import logout from '../authentication/logout/logout.router' import status from '../status/status.router' import submissions from '../entities/submission/submission.router' import users from '../entities/user/user.router' @@ -22,7 +22,7 @@ import grader from '../entities/grader/grader.router' import categories from '../entities/category/category.router' import assignmentScore from '../entities/assignmentScore/assignmentScore.router' -import { isAuthorized } from '../auth/auth.middleware' +import { isAuthorized } from '../authentication/auth.middleware' import { NotFound } from '../utils/apiResponse.utils' import nonContainerAutoGraderRouter from "../entities/nonContainerAutoGrader/nonContainerAutoGrader.router"; From a9059c507531abcc4ef9ae73434cbc6e9cf6942e Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Fri, 29 Mar 2024 01:29:31 -0400 Subject: [PATCH 002/400] rename auth methods to authentication --- .../src/authentication/auth.middleware.ts | 10 +++--- .../tests/auth.middleware.test.ts | 12 +++---- devU-api/src/router/index.ts | 34 +++++++++---------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/devU-api/src/authentication/auth.middleware.ts b/devU-api/src/authentication/auth.middleware.ts index 0714dc9e..355795f5 100644 --- a/devU-api/src/authentication/auth.middleware.ts +++ b/devU-api/src/authentication/auth.middleware.ts @@ -9,7 +9,7 @@ import AuthService from './auth.service' import { GenericResponse, Unauthorized } from '../utils/apiResponse.utils' -function checkAuth(req: Request): [TokenType | null, GenericResponse | null] { +function checkAuthentication(req: Request): [TokenType | null, GenericResponse | null] { const authorization = req.headers.authorization if (!authorization) return [null, new GenericResponse('Missing authentication headers')] @@ -26,8 +26,8 @@ function checkAuth(req: Request): [TokenType | null, GenericResponse return [deserializedToken, null] } -export async function isAuthorized(req: Request, res: Response, next: NextFunction) { - const [currentUser, error] = checkAuth(req) +export async function isAuthenticated(req: Request, res: Response, next: NextFunction) { + const [currentUser, error] = checkAuthentication(req) if (!currentUser) return res.status(401).json(error) @@ -39,7 +39,7 @@ export async function isAuthorized(req: Request, res: Response, next: NextFuncti export async function isValidRefreshToken(req: Request, res: Response, next: NextFunction) { // If authorization header exists DON'T CHECK COOKIE AT ALL if (req.headers.authorization) { - const [refreshToken, error] = checkAuth(req) + const [refreshToken, error] = checkAuthentication(req) if (!refreshToken) return res.status(401).json(error) // Because the refresh token and access token are signed the same way the access token @@ -80,7 +80,7 @@ export async function isRefreshNearingExpiration(req: Request, res: Response, ne export const saml = passport.authenticate('saml', { session: false }) export default { - isAuthorized, + isAuthorized: isAuthenticated, isValidRefreshToken, isRefreshNearingExpiration, } diff --git a/devU-api/src/authentication/tests/auth.middleware.test.ts b/devU-api/src/authentication/tests/auth.middleware.test.ts index ca879932..0eb88e6e 100644 --- a/devU-api/src/authentication/tests/auth.middleware.test.ts +++ b/devU-api/src/authentication/tests/auth.middleware.test.ts @@ -2,7 +2,7 @@ import { AccessToken, RefreshToken } from 'devu-shared-modules' import environment from '../../environment' -import { isAuthorized, isValidRefreshToken, isRefreshNearingExpiration } from '../auth.middleware' +import { isAuthenticated, isValidRefreshToken, isRefreshNearingExpiration } from '../auth.middleware' import AuthService from '../auth.service' @@ -32,7 +32,7 @@ describe('AuthMiddleware', () => { beforeEach(async () => { AuthService.validateJwt = jest.fn().mockImplementation(() => expectedAccessToken) - await isAuthorized(req, res, next) + await isAuthenticated(req, res, next) }) test('Expect access token to be added to req', () => expect(req.currentUser).toEqual(expectedAccessToken)) @@ -44,7 +44,7 @@ describe('AuthMiddleware', () => { describe('Authorization Failure', () => { test('Missing authorization header', async () => { delete req.headers.authorization - await isAuthorized(req, res, next) + await isAuthenticated(req, res, next) expect(res.status).toBeCalledWith(401) expect(next).toHaveBeenCalledTimes(0) @@ -52,7 +52,7 @@ describe('AuthMiddleware', () => { test('Missing Bearer', async () => { req.headers.authorization = 'NotBearer and.a.fake.token' - await isAuthorized(req, res, next) + await isAuthenticated(req, res, next) expect(res.status).toBeCalledWith(401) expect(next).toHaveBeenCalledTimes(0) @@ -60,14 +60,14 @@ describe('AuthMiddleware', () => { test('Missing Token', async () => { req.headers.authorization = 'Bearer' // missing token - await isAuthorized(req, res, next) + await isAuthenticated(req, res, next) expect(res.status).toBeCalledWith(401) expect(next).toHaveBeenCalledTimes(0) }) test('Invalid token', async () => { AuthService.validateJwt = jest.fn().mockImplementation(() => null) - await isAuthorized(req, res, next) + await isAuthenticated(req, res, next) expect(res.status).toHaveBeenCalledWith(401) expect(next).toHaveBeenCalledTimes(0) diff --git a/devU-api/src/router/index.ts b/devU-api/src/router/index.ts index 71aa0597..599f232c 100644 --- a/devU-api/src/router/index.ts +++ b/devU-api/src/router/index.ts @@ -22,29 +22,29 @@ import grader from '../entities/grader/grader.router' import categories from '../entities/category/category.router' import assignmentScore from '../entities/assignmentScore/assignmentScore.router' -import { isAuthorized } from '../authentication/auth.middleware' +import { isAuthenticated } from '../authentication/auth.middleware' import { NotFound } from '../utils/apiResponse.utils' import nonContainerAutoGraderRouter from "../entities/nonContainerAutoGrader/nonContainerAutoGrader.router"; const Router = express.Router() -Router.use('/assignments', isAuthorized, assignments) -Router.use('/assignment-problems', isAuthorized, assignmentProblem) -Router.use('/users', isAuthorized, users) -Router.use('/courses', isAuthorized, courses) -Router.use('/categories', isAuthorized, category) -Router.use('/user-courses', isAuthorized, userCourse) -Router.use('/submissions', isAuthorized, submissions) -Router.use('/submission-scores', isAuthorized, submissionScore) -Router.use('/nonContainerAutoGrader', isAuthorized, nonContainerAutoGraderRouter) -Router.use('/container-auto-graders', isAuthorized, containerAutoGrader) -Router.use('/submission-problem-scores', isAuthorized, submissionProblemScore) -Router.use('/file-upload', isAuthorized, fileUpload) -Router.use('/deadline-extensions', isAuthorized, deadlineExtensions) -Router.use('/grade', isAuthorized, grader) -Router.use('/categories', isAuthorized, categories) -Router.use('/assignment-scores', isAuthorized, assignmentScore) +Router.use('/assignments', isAuthenticated, assignments) +Router.use('/assignment-problems', isAuthenticated, assignmentProblem) +Router.use('/users', isAuthenticated, users) +Router.use('/courses', isAuthenticated, courses) +Router.use('/categories', isAuthenticated, category) +Router.use('/user-courses', isAuthenticated, userCourse) +Router.use('/submissions', isAuthenticated, submissions) +Router.use('/submission-scores', isAuthenticated, submissionScore) +Router.use('/nonContainerAutoGrader', isAuthenticated, nonContainerAutoGraderRouter) +Router.use('/container-auto-graders', isAuthenticated, containerAutoGrader) +Router.use('/submission-problem-scores', isAuthenticated, submissionProblemScore) +Router.use('/file-upload', isAuthenticated, fileUpload) +Router.use('/deadline-extensions', isAuthenticated, deadlineExtensions) +Router.use('/grade', isAuthenticated, grader) +Router.use('/categories', isAuthenticated, categories) +Router.use('/assignment-scores', isAuthenticated, assignmentScore) Router.use('/login', login) Router.use('/logout', logout) From 19dacef6aaed3f8b5e198c4f1be47c71d44453f4 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Fri, 29 Mar 2024 01:37:58 -0400 Subject: [PATCH 003/400] rename auth files to authentication --- .../{auth.middleware.ts => authentication.middleware.ts} | 2 +- .../{auth.service.ts => authentication.service.ts} | 0 .../login.developer/login.developer.controller.ts | 2 +- .../login.developer/tests/login.developer.controller.test.ts | 2 +- .../src/authentication/login.saml/login.saml.controller.ts | 2 +- devU-api/src/authentication/login.saml/login.saml.router.ts | 2 +- .../login.saml/tests/login.saml.controller.test.ts | 2 +- devU-api/src/authentication/login/login.controller.ts | 2 +- devU-api/src/authentication/login/login.router.ts | 2 +- .../src/authentication/login/tests/login.controller.test.ts | 2 +- ...h.middleware.test.ts => authentication.middleware.test.ts} | 4 ++-- devU-api/src/router/index.ts | 2 +- devU-api/src/utils/swagger.utils.ts | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) rename devU-api/src/authentication/{auth.middleware.ts => authentication.middleware.ts} (98%) rename devU-api/src/authentication/{auth.service.ts => authentication.service.ts} (100%) rename devU-api/src/authentication/tests/{auth.middleware.test.ts => authentication.middleware.test.ts} (98%) diff --git a/devU-api/src/authentication/auth.middleware.ts b/devU-api/src/authentication/authentication.middleware.ts similarity index 98% rename from devU-api/src/authentication/auth.middleware.ts rename to devU-api/src/authentication/authentication.middleware.ts index 355795f5..2874d18f 100644 --- a/devU-api/src/authentication/auth.middleware.ts +++ b/devU-api/src/authentication/authentication.middleware.ts @@ -5,7 +5,7 @@ import environment from '../environment' import { AccessToken, RefreshToken } from 'devu-shared-modules' -import AuthService from './auth.service' +import AuthService from './authentication.service' import { GenericResponse, Unauthorized } from '../utils/apiResponse.utils' diff --git a/devU-api/src/authentication/auth.service.ts b/devU-api/src/authentication/authentication.service.ts similarity index 100% rename from devU-api/src/authentication/auth.service.ts rename to devU-api/src/authentication/authentication.service.ts diff --git a/devU-api/src/authentication/login.developer/login.developer.controller.ts b/devU-api/src/authentication/login.developer/login.developer.controller.ts index 7f2ac588..c43de25a 100644 --- a/devU-api/src/authentication/login.developer/login.developer.controller.ts +++ b/devU-api/src/authentication/login.developer/login.developer.controller.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from 'express' import UserService from '../../entities/user/user.service' -import AuthService from '../auth.service' +import AuthService from '../authentication.service' import { GenericResponse } from '../../utils/apiResponse.utils' import { refreshCookieOptions } from '../../utils/cookie.utils' diff --git a/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts b/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts index 4bbeb1c2..babcdea8 100644 --- a/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts +++ b/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts @@ -3,7 +3,7 @@ import controller from '../login.developer.controller' import UserModel from '../../../entities/user/user.model' import UserService from '../../../entities/user/user.service' -import AuthService from '../../auth.service' +import AuthService from '../../authentication.service' import Testing from '../../../utils/testing.utils' import { refreshCookieOptions } from '../../../utils/cookie.utils' diff --git a/devU-api/src/authentication/login.saml/login.saml.controller.ts b/devU-api/src/authentication/login.saml/login.saml.controller.ts index 31c87d1f..d6d697e3 100644 --- a/devU-api/src/authentication/login.saml/login.saml.controller.ts +++ b/devU-api/src/authentication/login.saml/login.saml.controller.ts @@ -4,7 +4,7 @@ import { User } from 'devu-shared-modules' import environment from '../../environment' import UserService from '../../entities/user/user.service' -import AuthService from '../auth.service' +import AuthService from '../authentication.service' import { samlStrategy } from '../../utils/passport/saml.passport' import { refreshCookieOptions } from '../../utils/cookie.utils' diff --git a/devU-api/src/authentication/login.saml/login.saml.router.ts b/devU-api/src/authentication/login.saml/login.saml.router.ts index e60100a4..72913c9d 100644 --- a/devU-api/src/authentication/login.saml/login.saml.router.ts +++ b/devU-api/src/authentication/login.saml/login.saml.router.ts @@ -3,7 +3,7 @@ import express from 'express' import controller from '../login.saml/login.saml.controller' -import { saml } from '../auth.middleware' +import { saml } from '../authentication.middleware' import { authCallbackValidator } from '../login/login.validator' diff --git a/devU-api/src/authentication/login.saml/tests/login.saml.controller.test.ts b/devU-api/src/authentication/login.saml/tests/login.saml.controller.test.ts index c1ac399f..2c13e03a 100644 --- a/devU-api/src/authentication/login.saml/tests/login.saml.controller.test.ts +++ b/devU-api/src/authentication/login.saml/tests/login.saml.controller.test.ts @@ -3,7 +3,7 @@ import controller from '../login.saml.controller' import UserModel from '../../../entities/user/user.model' import UserService from '../../../entities/user/user.service' -import AuthService from '../../auth.service' +import AuthService from '../../authentication.service' import Testing from '../../../utils/testing.utils' import { refreshCookieOptions } from '../../../utils/cookie.utils' diff --git a/devU-api/src/authentication/login/login.controller.ts b/devU-api/src/authentication/login/login.controller.ts index 33873a83..6120eecf 100644 --- a/devU-api/src/authentication/login/login.controller.ts +++ b/devU-api/src/authentication/login/login.controller.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from 'express' import UserService from '../../entities/user/user.service' -import AuthService from '../auth.service' +import AuthService from '../authentication.service' import ProviderService from '../../provider/provider.service' import { Unauthorized } from '../../utils/apiResponse.utils' diff --git a/devU-api/src/authentication/login/login.router.ts b/devU-api/src/authentication/login/login.router.ts index d27355c2..df4d0d1f 100644 --- a/devU-api/src/authentication/login/login.router.ts +++ b/devU-api/src/authentication/login/login.router.ts @@ -5,7 +5,7 @@ import environment from '../../environment' import controller from '../login/login.controller' -import { isValidRefreshToken, isRefreshNearingExpiration } from '../auth.middleware' +import { isValidRefreshToken, isRefreshNearingExpiration } from '../authentication.middleware' import SamlRouter from '../login.saml/login.saml.router' import DeveloperRouter from '../login.developer/login.developer.router' diff --git a/devU-api/src/authentication/login/tests/login.controller.test.ts b/devU-api/src/authentication/login/tests/login.controller.test.ts index db64dda6..fb40e612 100644 --- a/devU-api/src/authentication/login/tests/login.controller.test.ts +++ b/devU-api/src/authentication/login/tests/login.controller.test.ts @@ -5,7 +5,7 @@ import controller from '../login.controller' import UserModel from '../../../entities/user/user.model' import UserService from '../../../entities/user/user.service' -import AuthService from '../../auth.service' +import AuthService from '../../authentication.service' import ProviderService from '../../../provider/provider.service' import Testing from '../../../utils/testing.utils' diff --git a/devU-api/src/authentication/tests/auth.middleware.test.ts b/devU-api/src/authentication/tests/authentication.middleware.test.ts similarity index 98% rename from devU-api/src/authentication/tests/auth.middleware.test.ts rename to devU-api/src/authentication/tests/authentication.middleware.test.ts index 0eb88e6e..0f60a05f 100644 --- a/devU-api/src/authentication/tests/auth.middleware.test.ts +++ b/devU-api/src/authentication/tests/authentication.middleware.test.ts @@ -2,9 +2,9 @@ import { AccessToken, RefreshToken } from 'devu-shared-modules' import environment from '../../environment' -import { isAuthenticated, isValidRefreshToken, isRefreshNearingExpiration } from '../auth.middleware' +import { isAuthenticated, isValidRefreshToken, isRefreshNearingExpiration } from '../authentication.middleware' -import AuthService from '../auth.service' +import AuthService from '../authentication.service' import Testing from '../../utils/testing.utils' import { Unauthorized } from '../../utils/apiResponse.utils' diff --git a/devU-api/src/router/index.ts b/devU-api/src/router/index.ts index 599f232c..24435f3a 100644 --- a/devU-api/src/router/index.ts +++ b/devU-api/src/router/index.ts @@ -22,7 +22,7 @@ import grader from '../entities/grader/grader.router' import categories from '../entities/category/category.router' import assignmentScore from '../entities/assignmentScore/assignmentScore.router' -import { isAuthenticated } from '../authentication/auth.middleware' +import { isAuthenticated } from '../authentication/authentication.middleware' import { NotFound } from '../utils/apiResponse.utils' import nonContainerAutoGraderRouter from "../entities/nonContainerAutoGrader/nonContainerAutoGrader.router"; diff --git a/devU-api/src/utils/swagger.utils.ts b/devU-api/src/utils/swagger.utils.ts index bcb55255..32c115cd 100644 --- a/devU-api/src/utils/swagger.utils.ts +++ b/devU-api/src/utils/swagger.utils.ts @@ -25,7 +25,7 @@ const swaggerOptioner = { './src/router/*.ts', './src/entities/*/*.router.ts', './src/entities/*/*.model.ts', './src/fileUpload/*.router.ts', './src/fileUpload/*.model.ts', - './src/auth/*/*.router.ts', './src/auth/*/*.model.ts'], + './src/authentication/*/*.router.ts', './src/authentication/*/*.model.ts'], } const swaggerSpec = swaggerJSDoc(swaggerOptioner) From 0787f19dce763850a29e9a1ac268dc711c0606e8 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Thu, 4 Apr 2024 19:27:54 -0400 Subject: [PATCH 004/400] Grader service updates WIP, first draft mostly done --- .../src/entities/grader/grader.controller.ts | 10 +-- .../src/entities/grader/grader.service.ts | 71 +++++++++++++------ .../entities/submission/submission.service.ts | 2 +- 3 files changed, 55 insertions(+), 28 deletions(-) diff --git a/devU-api/src/entities/grader/grader.controller.ts b/devU-api/src/entities/grader/grader.controller.ts index bd766ca0..1a2ced3d 100644 --- a/devU-api/src/entities/grader/grader.controller.ts +++ b/devU-api/src/entities/grader/grader.controller.ts @@ -2,17 +2,17 @@ import { Request, Response, NextFunction } from 'express' import GraderService from './grader.service' -import { GenericResponse, NotFound } from '../../utils/apiResponse.utils' +import { GenericResponse } from '../../utils/apiResponse.utils' //, NotFound -import { serialize } from '../grader/grader.serializer' +//import { serialize } from '../grader/grader.serializer' export async function grade(req: Request, res: Response, next: NextFunction) { try { const submissionId = parseInt(req.params.id) - const grade = await GraderService.grade(submissionId) - if (!grade || grade.length === 0) return res.status(404).json(NotFound) + const response = await GraderService.grade(submissionId) //grade + //if (!grade || grade.length === 0) return res.status(404).json(NotFound) - const response = serialize(grade) + //const response = serialize(grade) res.status(200).json(response) } catch (err) { diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index bb248f49..2333ff62 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -5,12 +5,16 @@ import nonContainerAutograderService from '../nonContainerAutoGrader/nonContaine import containerAutograderService from '../containerAutoGrader/containerAutoGrader.service' import assignmentProblemService from '../assignmentProblem/assignmentProblem.service' import assignmentScoreService from '../assignmentScore/assignmentScore.service' +import courseService from '../course/course.service' +import { addJob, openDirectory, uploadFile } from '../../tango/tango.service' import { SubmissionScore, SubmissionProblemScore, ContainerAutoGrader, AssignmentScore } from 'devu-shared-modules' import { checkAnswer } from '../nonContainerAutoGrader/nonContainerAutoGrader.grader' import { serialize as serializeNonContainer } from '../nonContainerAutoGrader/nonContainerAutoGrader.serializer' -import { serialize as serializeContainer } from '../containerAutoGrader/containerAutoGrader.serializer' import { serialize as serializeAssignmentScore } from '../assignmentScore/assignmentScore.serializer' +import { downloadFile } from '../../fileStorage' + +import crypto from 'crypto' export async function grade(submissionId: number) { const submission = await submissionService.retrieve(submissionId) @@ -20,10 +24,10 @@ export async function grade(submissionId: number) { const content = JSON.parse(submission.content) const form = content.form - const filepaths = content.filepaths //Using the field name that was written on the whiteboard for now + const filepaths: string[] = content.filepaths //Using the field name that was written on the whiteboard for now const nonContainerAutograders = await nonContainerAutograderService.listByAssignmentId(assignmentId) - const containerAutograders = await containerAutograderService.listByAssignmentId(assignmentId) + //const containerAutograders = await containerAutograderService.listByAssignmentId(assignmentId) const assignmentProblems = await assignmentProblemService.list(assignmentId) @@ -52,26 +56,49 @@ export async function grade(submissionId: number) { } //Run Container Autograders - //Mock functionality, this is not finalized!!!! - for (const filepath of filepaths) { - const containerGrader = containerAutograders.find(grader => grader.autogradingImage === filepath) //PLACEHOLDER, I'm just using autogradingImage temporarily to associate graders to files - - if (containerGrader) { - const gradeResults = await mockContainerCheckAnswer(filepath, serializeContainer(containerGrader)) - - for (const result of gradeResults) { - const problemScoreObj: SubmissionProblemScore = { - submissionId: submissionId, - assignmentProblemId: 1, //PLACEHOLDER, an assignmentProblem must exist in the db for this to work - score: result.score, - feedback: result.feedback, + try { + const {graderData, makefileData, autogradingImage, timeout} = await containerAutograderService.getGraderByAssignmentId(assignmentId) + const bucketName = await courseService.retrieve(submission.courseId).then((course) => { + return course ? ((course.name).toLowerCase().replace(/ /g, '-') + course.number + course.semester + course.id).toLowerCase() : 'submission' + }) + + const labName = bucketName + '-' + submission.assignmentId + const optionFiles = [] + const openResponse = await openDirectory(labName) + var response = null + if (openResponse) { + if (!(openResponse.files["Graderfile"]) || openResponse.files["Graderfile"] !== crypto.createHash('md5').update(graderData).digest('hex')) { + const graderFile = new File([new Blob([graderData])], "Graderfile") + await uploadFile(labName, graderFile, "Graderfile") + } + if (!(openResponse.files["Makefile"]) || openResponse.files["Makefile"] !== crypto.createHash('md5').update(makefileData).digest('hex')) { + const makefile = new File([new Blob([makefileData])], "Makefile") + await uploadFile(labName, makefile, "Makefile") + } + for (const filepath of filepaths){ + const buffer = await downloadFile(bucketName, filepath) + const file = new File([new Blob([buffer])], filepath) + if (await uploadFile(labName, file, filepath)) { + optionFiles.push({localFile: filepath, destFile: filepath}) } - allScores.push(await submissionProblemScoreService.create(problemScoreObj)) - score += result.score - feedback += result.feedback + '\n' } + + const jobOptions = { + image: autogradingImage, + files: [{localFile: "Graderfile", destFile: "Graderfile"}, + {localFile: "Makefile", destFile: "Makefile"},] + .concat(optionFiles), + jobName: labName, + output_file: labName, + timeout: timeout, + callback_url: "" + } + response = await addJob(labName, jobOptions) } - } + } catch (e) { + throw e + } + //remember, immediate callback is made when job has been added to queue, not sure how we're handling the rest of it yet though lmao //Grading is finished. Create SubmissionScore and AssignmentScore and save to db. const scoreObj: SubmissionScore = { @@ -87,7 +114,7 @@ export async function grade(submissionId: number) { const assignmentScore = serializeAssignmentScore(assignmentScoreModel) assignmentScore.score = score assignmentScoreService.update(assignmentScore) - + } else { //Otherwise make a new one const assignmentScore: AssignmentScore = { assignmentId: submission.assignmentId, @@ -97,7 +124,7 @@ export async function grade(submissionId: number) { assignmentScoreService.create(assignmentScore) } - return allScores + return response } //Temporary mock function, delete when the container autograder grading function is written diff --git a/devU-api/src/entities/submission/submission.service.ts b/devU-api/src/entities/submission/submission.service.ts index 2a54dd4a..bb205b6d 100644 --- a/devU-api/src/entities/submission/submission.service.ts +++ b/devU-api/src/entities/submission/submission.service.ts @@ -32,7 +32,7 @@ export async function create(submission: Submission, file?: Express.Multer.File filename: filename, } const content = JSON.parse(submission.content) - content.filepaths.push(filename) + content.filepaths.push(fileModel.filename) submission.content = JSON.stringify(content) await fileConn().save(fileModel) From 549497d1baecb389b3142cc6fb93ca4c3c7772ba Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Fri, 5 Apr 2024 02:43:59 -0400 Subject: [PATCH 005/400] misc fixes for grader service updates, first draft still not functional --- devU-api/package-lock.json | 64 +++++++++++++++++++ devU-api/package.json | 4 +- .../containerAutoGrader.model.ts | 14 ++-- .../containerAutoGrader.router.ts | 2 +- .../containerAutoGrader.service.ts | 1 + .../src/entities/grader/grader.controller.ts | 2 +- .../src/entities/grader/grader.service.ts | 2 +- .../entities/submission/submission.model.ts | 4 +- .../entities/submission/submission.router.ts | 2 +- .../entities/submission/submission.service.ts | 2 +- devU-api/src/fileStorage.ts | 2 +- devU-api/src/tango/tango.service.ts | 3 +- 12 files changed, 86 insertions(+), 16 deletions(-) diff --git a/devU-api/package-lock.json b/devU-api/package-lock.json index 927af045..b4be152a 100644 --- a/devU-api/package-lock.json +++ b/devU-api/package-lock.json @@ -21,6 +21,7 @@ "minio": "^7.0.18", "morgan": "^1.10.0", "multer": "^1.4.2", + "node-fetch": "^2.7.0", "passport": "^0.6.0", "passport-saml": "^3.2.2", "pg": "^8.4.0", @@ -43,6 +44,7 @@ "@types/morgan": "^1.9.2", "@types/multer": "^1.4.7", "@types/node": "^15.12.2", + "@types/node-fetch": "^2.6.11", "@types/passport": "^1.0.6", "@types/passport-strategy": "^0.2.35", "@types/swagger-jsdoc": "^6.0.0", @@ -2149,6 +2151,30 @@ "integrity": "sha512-dvMUE/m2LbXPwlvVuzCyslTEtQ2ZwuuFClDrOQ6mp2CenCg971719PTILZ4I6bTP27xfFFc+o7x2TkLuun/MPw==", "dev": true }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -9271,6 +9297,44 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/devU-api/package.json b/devU-api/package.json index 647ccabe..87acfc77 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -14,7 +14,7 @@ "pre-commit": "lint-staged", "generate-config": "docker run --pull always -v $(pwd)/config:/config --user $(id -u):$(id -g) --rm ubautograding/devtools /generateConfig.sh config/default.yml", "populate-db": "ts-node-dev ./scripts/populate-db.ts", - "tango": "ts-node-dev ./src/tango/tests/tango.endpoint.test.ts" + "tango": "ts-node-dev ./src/tango/tests/tango.endpoint.test.ts" }, "lint-staged": { "./**/*.{js,ts,json,md}": [ @@ -40,6 +40,7 @@ "@types/morgan": "^1.9.2", "@types/multer": "^1.4.7", "@types/node": "^15.12.2", + "@types/node-fetch": "^2.6.11", "@types/passport": "^1.0.6", "@types/passport-strategy": "^0.2.35", "@types/swagger-jsdoc": "^6.0.0", @@ -69,6 +70,7 @@ "minio": "^7.0.18", "morgan": "^1.10.0", "multer": "^1.4.2", + "node-fetch": "^2.7.0", "passport": "^0.6.0", "passport-saml": "^3.2.2", "pg": "^8.4.0", diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts index bf66321b..0f012bf3 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts @@ -22,21 +22,21 @@ export default class ContainerAutoGraderModel { * schemas: * ContainerAutoGrader: * type: object - * required: [assignmentId, graderFile, autogradingImage, timeout] + * required: [assignmentId, autogradingImage, timeout, graderFile, makefileFile] * properties: * assignmentId: * type: integer - * graderFile: - * type: string - * description: Filename of already uploaded grader file - * makeFileFile: - * type: string - * description: Filename of already uploaded makefile * autogradingImage: * type: string * timeout: * type: integer * description: Must be a positive integer + * graderFile: + * type: string + * format: binary + * makefileFile: + * type: string + * format: binary */ @PrimaryGeneratedColumn() diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts index 398bb016..ff6b37f1 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts @@ -53,7 +53,7 @@ Router.get('/:id', asInt(), ContainerAutoGraderController.detail); * description: OK * requestBody: * content: - * application/x-www-form-urlencoded: + * multipart/form-data: * schema: * $ref: '#/components/schemas/ContainerAutoGrader' */ diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index 782e3f0d..271accbe 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -42,6 +42,7 @@ export async function create(containerAutoGrader: ContainerAutoGrader, graderInp containerAutoGrader.graderFile = filename if (makefileInputFile) { + const bucket: string = 'makefiles' const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename) containerAutoGrader.makefileFile = makefileFilename diff --git a/devU-api/src/entities/grader/grader.controller.ts b/devU-api/src/entities/grader/grader.controller.ts index 1a2ced3d..ab2a622c 100644 --- a/devU-api/src/entities/grader/grader.controller.ts +++ b/devU-api/src/entities/grader/grader.controller.ts @@ -14,7 +14,7 @@ export async function grade(req: Request, res: Response, next: NextFunction) { //const response = serialize(grade) - res.status(200).json(response) + res.status(200).json({message: response?.jobId + ", " + response?.statusId + ", " + response?.statusMsg}) } catch (err) { res.status(400).json(new GenericResponse(err.message)) } diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index 2333ff62..6f16f789 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -96,7 +96,7 @@ export async function grade(submissionId: number) { response = await addJob(labName, jobOptions) } } catch (e) { - throw e + console.log(e) } //remember, immediate callback is made when job has been added to queue, not sure how we're handling the rest of it yet though lmao diff --git a/devU-api/src/entities/submission/submission.model.ts b/devU-api/src/entities/submission/submission.model.ts index fdf2fda0..8dae3b14 100644 --- a/devU-api/src/entities/submission/submission.model.ts +++ b/devU-api/src/entities/submission/submission.model.ts @@ -24,7 +24,6 @@ export default class SubmissionModel { * schemas: * Submission: * type: object - * required: [courseId, assignmentId, userId, content, type, submitterIp, submittedBy] * properties: * courseId: * type: integer @@ -38,6 +37,9 @@ export default class SubmissionModel { * type: string * submittedBy: * type: integer + * files: + * type: string + * format: binary */ @PrimaryGeneratedColumn() id: number diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index f5966369..8a899f4e 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -68,7 +68,7 @@ Router.get('/:id', asInt(), SubmissionController.detail) * description: OK * requestBody: * content: - * application/x-www-form-urlencoded: + * multipart/form-data: * schema: * $ref: '#/components/schemas/Submission' */ diff --git a/devU-api/src/entities/submission/submission.service.ts b/devU-api/src/entities/submission/submission.service.ts index bb205b6d..f8b70400 100644 --- a/devU-api/src/entities/submission/submission.service.ts +++ b/devU-api/src/entities/submission/submission.service.ts @@ -32,7 +32,7 @@ export async function create(submission: Submission, file?: Express.Multer.File filename: filename, } const content = JSON.parse(submission.content) - content.filepaths.push(fileModel.filename) + content.filepaths = [fileModel.filename] submission.content = JSON.stringify(content) await fileConn().save(fileModel) diff --git a/devU-api/src/fileStorage.ts b/devU-api/src/fileStorage.ts index 00d9bd19..87a8fa79 100644 --- a/devU-api/src/fileStorage.ts +++ b/devU-api/src/fileStorage.ts @@ -62,11 +62,11 @@ export async function downloadFile(bucketName: string, filename: string): Promis return new Promise((resolve, reject) => { const fileData: Buffer[] = [] + console.log(bucketName, filename) minioClient.getObject(bucketName, filename, (err, dataStream) => { if (err) { reject(new Error('File failed to download from MinIO because'+err.message)) } - dataStream.on('data', (chunk:any) => { fileData.push(chunk) }) diff --git a/devU-api/src/tango/tango.service.ts b/devU-api/src/tango/tango.service.ts index 59c335fe..e4075eea 100644 --- a/devU-api/src/tango/tango.service.ts +++ b/devU-api/src/tango/tango.service.ts @@ -1,4 +1,5 @@ import './tango.types' +import fetch from 'node-fetch' const tangoHost = `http://${(process.env.TANGO_KEY ?? 'localhost:3000')}` const tangoKey = process.env.TANGO_KEY ?? 'test' @@ -25,7 +26,7 @@ export async function uploadFile(courselab: string, file: File, fileName: string const url = `${tangoHost}/upload/${tangoKey}/${courselab}/` const formData = new FormData() formData.append('file', file) - const response = await fetch(url, { method: 'POST', body: formData, headers: { 'filename': fileName } }) + const response = await fetch(url, { method: 'POST', body: formData as any, headers: { 'filename': fileName } }) return response.ok ? await response.json() : null } From 9d8a60244757190044a9aeb7b85f2a3788a55c31 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Fri, 5 Apr 2024 17:42:43 -0400 Subject: [PATCH 006/400] first draft of grader.service container autograding functional --- devU-api/src/entities/grader/grader.service.ts | 17 +++++++---------- devU-api/src/fileStorage.ts | 5 ++--- devU-api/src/tango/tango.service.ts | 8 +++----- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index 6f16f789..4336fedd 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -12,7 +12,7 @@ import { SubmissionScore, SubmissionProblemScore, ContainerAutoGrader, Assignmen import { checkAnswer } from '../nonContainerAutoGrader/nonContainerAutoGrader.grader' import { serialize as serializeNonContainer } from '../nonContainerAutoGrader/nonContainerAutoGrader.serializer' import { serialize as serializeAssignmentScore } from '../assignmentScore/assignmentScore.serializer' -import { downloadFile } from '../../fileStorage' +import { downloadFile, initializeMinio } from '../../fileStorage' import crypto from 'crypto' @@ -59,30 +59,27 @@ export async function grade(submissionId: number) { try { const {graderData, makefileData, autogradingImage, timeout} = await containerAutograderService.getGraderByAssignmentId(assignmentId) const bucketName = await courseService.retrieve(submission.courseId).then((course) => { - return course ? ((course.name).toLowerCase().replace(/ /g, '-') + course.number + course.semester + course.id).toLowerCase() : 'submission' + return course ? (course.number + course.semester + course.id).toLowerCase() : 'submission' }) + initializeMinio(bucketName) + var response = null const labName = bucketName + '-' + submission.assignmentId const optionFiles = [] const openResponse = await openDirectory(labName) - var response = null if (openResponse) { if (!(openResponse.files["Graderfile"]) || openResponse.files["Graderfile"] !== crypto.createHash('md5').update(graderData).digest('hex')) { - const graderFile = new File([new Blob([graderData])], "Graderfile") - await uploadFile(labName, graderFile, "Graderfile") + await uploadFile(labName, graderData, "Graderfile") } if (!(openResponse.files["Makefile"]) || openResponse.files["Makefile"] !== crypto.createHash('md5').update(makefileData).digest('hex')) { - const makefile = new File([new Blob([makefileData])], "Makefile") - await uploadFile(labName, makefile, "Makefile") + await uploadFile(labName, makefileData, "Makefile") } for (const filepath of filepaths){ const buffer = await downloadFile(bucketName, filepath) - const file = new File([new Blob([buffer])], filepath) - if (await uploadFile(labName, file, filepath)) { + if (await uploadFile(labName, buffer, filepath)) { optionFiles.push({localFile: filepath, destFile: filepath}) } } - const jobOptions = { image: autogradingImage, files: [{localFile: "Graderfile", destFile: "Graderfile"}, diff --git a/devU-api/src/fileStorage.ts b/devU-api/src/fileStorage.ts index 87a8fa79..91390e78 100644 --- a/devU-api/src/fileStorage.ts +++ b/devU-api/src/fileStorage.ts @@ -50,7 +50,7 @@ export async function uploadFile(bucketName: string, file: Express.Multer.File, return new Promise((resolve, reject) => { minioClient.putObject(bucketName, filename, file.buffer, (err, etag) => { if (err) { - reject(new Error('File failed to upload because'+err.message)) + reject(new Error('File failed to upload because '+err.message)) } else { resolve(etag.etag) } @@ -62,10 +62,9 @@ export async function downloadFile(bucketName: string, filename: string): Promis return new Promise((resolve, reject) => { const fileData: Buffer[] = [] - console.log(bucketName, filename) minioClient.getObject(bucketName, filename, (err, dataStream) => { if (err) { - reject(new Error('File failed to download from MinIO because'+err.message)) + reject(new Error('File failed to download from MinIO because '+err.message)) } dataStream.on('data', (chunk:any) => { fileData.push(chunk) diff --git a/devU-api/src/tango/tango.service.ts b/devU-api/src/tango/tango.service.ts index e4075eea..82001d14 100644 --- a/devU-api/src/tango/tango.service.ts +++ b/devU-api/src/tango/tango.service.ts @@ -1,7 +1,7 @@ import './tango.types' import fetch from 'node-fetch' -const tangoHost = `http://${(process.env.TANGO_KEY ?? 'localhost:3000')}` +const tangoHost = `http://tango:3000` const tangoKey = process.env.TANGO_KEY ?? 'test' // for more info https://docs.autolabproject.com/tango-rest/ @@ -22,11 +22,9 @@ export async function openDirectory(courselab: string): Promise { +export async function uploadFile(courselab: string, file: Buffer, fileName: string): Promise { const url = `${tangoHost}/upload/${tangoKey}/${courselab}/` - const formData = new FormData() - formData.append('file', file) - const response = await fetch(url, { method: 'POST', body: formData as any, headers: { 'filename': fileName } }) + const response = await fetch(url, { method: 'POST', body: file, headers: { 'filename': fileName } }) return response.ok ? await response.json() : null } From dd34452b3d7b0fce7a7cb771f2b1eb658f1a73d3 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Tue, 9 Apr 2024 08:22:11 -0400 Subject: [PATCH 007/400] roles WIP --- .../authorization/authorization.middleware.ts | 67 ++++++++++++++++++ .../entities/assignment/assignment.router.ts | 27 +++++--- .../courseScore/courseScore.router.ts | 3 +- .../nonContainerAutoGrader.controller.ts | 2 + .../nonContainerAutoGrader.router.ts | 12 ++-- devU-api/src/entities/role/role.model.ts | 69 +++++++++++++++++++ .../submissionScore/submissionScore.router.ts | 4 ++ devU-api/src/entities/user/user.model.ts | 1 + devU-api/src/router/courseData.router.ts | 50 ++++++++++++++ devU-api/src/router/index.ts | 58 ++++++++-------- 10 files changed, 247 insertions(+), 46 deletions(-) create mode 100644 devU-api/src/authorization/authorization.middleware.ts create mode 100644 devU-api/src/entities/role/role.model.ts create mode 100644 devU-api/src/router/courseData.router.ts diff --git a/devU-api/src/authorization/authorization.middleware.ts b/devU-api/src/authorization/authorization.middleware.ts new file mode 100644 index 00000000..14297050 --- /dev/null +++ b/devU-api/src/authorization/authorization.middleware.ts @@ -0,0 +1,67 @@ +import {NextFunction, Request, Response} from "express"; +import {GenericResponse} from "../utils/apiResponse.utils"; + + + + +export async function isAuthorized(req: Request, res: Response, next: NextFunction){ + + // Option 1: Add user id to every path where users could be able to access only their content <-- painful for + // certain paths (/course/2/assignments/45/user/12) need to validate later that the id in the url matches the data.. + // not to mention the duplicate data + + // Option 2: pull the data and check a field to see if they can access <-- seems more likely to produce bugs + + // return the middleware based on: + // -what perm in the role do they need? + // -is there a field that is cool as long as it matches this user's id? + + // role permissions include: + // -grade anything (put/post) + // -delete grades? + // -create submissions on behalf of students + // -create assignments +categories + // -create graders + // -upload container autograders + // -is delete separate? or can they delete if they can edit? + // -view unreleased assignments + // -manage extensions + // -add userCourse + // --should be instructor only since you could add more instructors.. otherwise, need a check to see if they are adding a userCourse at an allowed role + // --Or have separate paths for add-student and add-user + // -add a role + // -muck around with rosters + // -Download the grading code + // -view all submissions (Without being able to grade them) + // -access based on sections?.. + + // Two default roles + // -student - can only access what everyone can access + + // Add extra endpoint for authorization + // -GET /released-assignments (available to all) + // -GET /assignments (with authorization check) + + // everyone can (default role/students role) + // -GET released assignments + // -GET submissions and scores that match their id + // -POST assignments on their own behalf + + // need a filter after the fact too. pre-check to see if they can access the endpoint at all. post-filter to filter down to the data they are allowed to access + // -or is this just part of each endpoint? eg. GET /courses only returns the courses that user is enrolled (Or every course if you're system-admin) + + if(!req.currentUser){ + return res.status(401).json(new GenericResponse('Need to be logged in for this endpoint')) + } + + // check if they are enrolled in the course -or- if they are system-level admin + // -if they have a userCourse, pull their role + + req.path + + // if(false) { + return res.status(403).json(new GenericResponse('You do not have permission to access this endpoint')) + // } + + // next() +} \ No newline at end of file diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index eb762ec9..b6338e56 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -3,10 +3,11 @@ import express from 'express' // Middleware import validator from './assignment.validator' -import { asInt } from '../../middleware/validator/generic.validator' +import {asInt} from '../../middleware/validator/generic.validator' // Controller import AssignmentsController from './assignment.controller' +import {isAuthorized} from "../../authorization/authorization.middleware"; const Router = express.Router() @@ -21,7 +22,11 @@ const Router = express.Router() * '200': * description: OK */ -Router.get('/', AssignmentsController.get) +Router.get('/', isAuthorized, AssignmentsController.get) + +// TODO: What endpoints to have? +Router.get('/released', isAuthorized, AssignmentsController.get) +Router.get('/unreleased', isAuthorized, AssignmentsController.get) /** * @swagger @@ -39,9 +44,9 @@ Router.get('/', AssignmentsController.get) * description: Enter assignment id * required: true * schema: - * type: integer + * type: integer */ -Router.get('/:id', asInt(), AssignmentsController.detail) +Router.get('/:id', isAuthorized, asInt(), AssignmentsController.detail) /** @@ -60,9 +65,9 @@ Router.get('/:id', asInt(), AssignmentsController.detail) * description: Enter course id * required: true * schema: - * type: integer + * type: integer */ -Router.get('/course/:courseId', asInt('courseId'), AssignmentsController.getByCourse) +Router.get('/course/:courseId', isAuthorized, asInt('courseId'), AssignmentsController.getByCourse) /** * @swagger @@ -80,7 +85,7 @@ Router.get('/course/:courseId', asInt('courseId'), AssignmentsController.getByCo * schema: * $ref: '#/components/schemas/Assignment' */ -Router.post('/', validator, AssignmentsController.post) +Router.post('/', isAuthorized, validator, AssignmentsController.post) /** * @swagger @@ -97,14 +102,14 @@ Router.post('/', validator, AssignmentsController.post) * in: path * required: true * schema: - * type: integer + * type: integer * requestBody: * content: * application/x-www-form-urlencoded: * schema: * $ref: '#/components/schemas/Assignment' */ -Router.put('/:id', asInt(), validator, AssignmentsController.put) +Router.put('/:id', isAuthorized, asInt(), validator, AssignmentsController.put) /** * @swagger @@ -121,8 +126,8 @@ Router.put('/:id', asInt(), validator, AssignmentsController.put) * in: path * required: true * schema: - * type: integer + * type: integer */ -Router.delete('/:id', asInt(), AssignmentsController._delete) +Router.delete('/:id', isAuthorized, asInt(), AssignmentsController._delete) export default Router diff --git a/devU-api/src/entities/courseScore/courseScore.router.ts b/devU-api/src/entities/courseScore/courseScore.router.ts index f87edec9..382acefa 100644 --- a/devU-api/src/entities/courseScore/courseScore.router.ts +++ b/devU-api/src/entities/courseScore/courseScore.router.ts @@ -4,6 +4,7 @@ import express from 'express' //validators import validator from './courseScore.validator' import { asInt } from '../../middleware/validator/generic.validator' +import { isAuthorized } from "../../authorization/authorization.middleware"; //Controller import CourseScoreController from './courseScore.controller' @@ -21,7 +22,7 @@ const Router = express.Router() * '200': * description: OK */ -Router.get('/', CourseScoreController.get) +Router.get('/', isAuthorized, CourseScoreController.get) /** diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts index 085eb27c..cd4c3c0c 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts @@ -19,7 +19,9 @@ export async function getByAssignmentId(req: Request, res: Response, next: NextF const nonContainerAutoGraders = await NonContainerAutoGraderService.listByAssignmentId(assignmentId) if (!nonContainerAutoGraders) return res.status(404).json(NotFound) + res.status(200).json(nonContainerAutoGraders.map(serialize)) + webhook() } catch (err) { next(err) } diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts index 6ce0e103..55e253a9 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts @@ -12,7 +12,7 @@ const Router = express.Router() /** * @swagger - * /nonContainerAutoGrader: + * /nonContainerAutoGraders: * get: * summary: Retrieve a list of all nonContainerAutoGraders * tags: @@ -25,7 +25,7 @@ Router.get('/', nonContainerQuestions.get) /** * @swagger - * /nonContainerAutoGrader/byAssignmentId/{assignmentId}: + * /nonContainerAutoGraders/byAssignmentId/{assignmentId}: * get: * summary: Retrieve a list of all nonContainerAutoGrader with the assignment ID * tags: @@ -44,7 +44,7 @@ Router.get('/byAssignmentId/:assignmentId', asInt("assignmentId"), nonContainerQ /** * @swagger - * /nonContainerAutoGrader/byId/{id}: + * /nonContainerAutoGraders/byId/{id}: * get: * summary: Retrieve a single question * tags: @@ -63,7 +63,7 @@ Router.get('/byId/:id', asInt(), nonContainerQuestions.detail) /** * @swagger - * /nonContainerAutoGrader: + * /nonContainerAutoGraders: * post: * summary: Create a question * tags: @@ -81,7 +81,7 @@ Router.post('/', validator, nonContainerQuestions.post) /** * @swagger - * /nonContainerAutoGrader: + * /nonContainerAutoGraders: * put: * summary: Update a question * tags: @@ -105,7 +105,7 @@ Router.put('/:id', asInt(), validator, nonContainerQuestions.put) /** * @swagger - * /nonContainerAutoGrader/{id}: + * /nonContainerAutoGraders/{id}: * delete: * summary: Delete a question * tags: diff --git a/devU-api/src/entities/role/role.model.ts b/devU-api/src/entities/role/role.model.ts new file mode 100644 index 00000000..2e2f871b --- /dev/null +++ b/devU-api/src/entities/role/role.model.ts @@ -0,0 +1,69 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, + JoinColumn, + ManyToOne +} from 'typeorm' + +import CourseModel from '../course/course.model' +import UserModel from '../user/user.model' + +@Entity('courseScore') +export default class CourseScoreModel { + /** + * @swagger + * tags: + * - name: Role + * components: + * schemas: + * Role: + * type: object + * required: [] + * properties: + * permission: + * type: boolean + */ + @PrimaryGeneratedColumn() + id: number + + @Column({name: 'course_id'}) + @JoinColumn({name: 'course_id'}) + @ManyToOne(() => CourseModel) + courseId: number + + + @Column({name: 'user_id'}) + @JoinColumn({name: 'user_id'}) + @ManyToOne(() => UserModel) + userId: number + + @Column({name: 'name'}) + score: string + + @Column({name: 'grades-view'}) + grades_view: boolean + + @Column({name: 'grades-edit'}) + grades_edit: boolean + + @Column({name: 'grades-view-self'}) + grades_viewSelf: boolean + + @Column({name: 'grades-edit-self'}) + grades_editSelf: boolean + + @CreateDateColumn({name: 'created_at'}) + createdAt: Date + + @UpdateDateColumn({name: 'updated_at'}) + updatedAt: Date + + @DeleteDateColumn({name: 'deleted_at'}) + deletedAt?: Date + +} + diff --git a/devU-api/src/entities/submissionScore/submissionScore.router.ts b/devU-api/src/entities/submissionScore/submissionScore.router.ts index c31419a0..7dd3124f 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.router.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.router.ts @@ -7,6 +7,7 @@ import { asInt } from '../../middleware/validator/generic.validator' // Controller import SubmissionScoreController from './submissionScore.controller' +import {isAuthorized} from "../../authorization/authorization.middleware"; const Router = express.Router() @@ -23,6 +24,9 @@ const Router = express.Router() */ Router.get('/', SubmissionScoreController.get) +Router.get('/user/:userId', isAuthorized, SubmissionScoreController.getByUser) + + /** * @swagger * /submission-scores/{id}: diff --git a/devU-api/src/entities/user/user.model.ts b/devU-api/src/entities/user/user.model.ts index 3dffae1e..0a5815cb 100644 --- a/devU-api/src/entities/user/user.model.ts +++ b/devU-api/src/entities/user/user.model.ts @@ -35,6 +35,7 @@ export default class UserModel { @Column({ length: 320, unique: true, nullable: false }) email: string + // TODO: @Column({ name: 'external_id', length: 320, unique: true, nullable: false }) externalId: string diff --git a/devU-api/src/router/courseData.router.ts b/devU-api/src/router/courseData.router.ts new file mode 100644 index 00000000..7a1a78f3 --- /dev/null +++ b/devU-api/src/router/courseData.router.ts @@ -0,0 +1,50 @@ +import express, { Request, Response, NextFunction } from 'express' +import swaggerUi from 'swagger-ui-express' + +import swagger from '../utils/swagger.utils' + +import userCourse from '../entities/userCourse/userCourse.router' +import assignments from '../entities/assignment/assignment.router' +import courses from '../entities/course/course.router' +import login from '../authentication/login/login.router' +import logout from '../authentication/logout/logout.router' +import status from '../status/status.router' +import submissions from '../entities/submission/submission.router' +import users from '../entities/user/user.router' +import submissionScore from '../entities/submissionScore/submissionScore.router' +import containerAutoGrader from '../entities/containerAutoGrader/containerAutoGrader.router' +import assignmentProblem from '../entities/assignmentProblem/assignmentProblem.router' +import submissionProblemScore from '../entities/submissionProblemScore/submissionProblemScore.router' +import deadlineExtensions from "../entities/deadlineExtensions/deadlineExtensions.router"; +import fileUpload from '../fileUpload/fileUpload.router' +import grader from '../entities/grader/grader.router' +import categories from '../entities/category/category.router' +import assignmentScore from '../entities/assignmentScore/assignmentScore.router' + + +import nonContainerAutoGraderRouter from "../entities/nonContainerAutoGrader/nonContainerAutoGrader.router"; + +import {isAuthorized} from "../authorization/authorization.middleware"; +import {asInt} from "../middleware/validator/generic.validator"; + +const Router = express.Router() + +Router.use('/file-upload', isAuthorized, fileUpload) + +Router.use('/user-courses', isAuthorized, userCourse) +Router.use('/categories', isAuthorized, categories) + +Router.use('/assignments', assignments) +Router.use('/assignment-scores', isAuthorized, assignmentScore) +Router.use('/assignment/:assignmentId/assignment-problems', asInt('assignmentId'), assignmentProblem) +// Router.use('/submissions', isAuthorized, submissions) +Router.use('/assignment/:assignmentId/submissions/', isAuthorized, submissions) +Router.use('/assignment/:assignmentId/submission-scores', isAuthorized, submissionScore) +Router.use('/assignment/:assignmentId/nonContainerAutoGraders', isAuthorized, nonContainerAutoGraderRouter, isAuthorized) +Router.use('/assignment/:assignmentId/container-auto-graders', isAuthorized, containerAutoGrader) +Router.use('/assignment/:assignmentId/submission-problem-scores', isAuthorized, submissionProblemScore) +Router.use('/assignment/:assignmentId/deadline-extensions', isAuthorized, deadlineExtensions) + +Router.use('/grade', isAuthorized, grader) + +export default Router diff --git a/devU-api/src/router/index.ts b/devU-api/src/router/index.ts index 24435f3a..c0368e79 100644 --- a/devU-api/src/router/index.ts +++ b/devU-api/src/router/index.ts @@ -3,48 +3,50 @@ import swaggerUi from 'swagger-ui-express' import swagger from '../utils/swagger.utils' -import userCourse from '../entities/userCourse/userCourse.router' -import assignments from '../entities/assignment/assignment.router' import courses from '../entities/course/course.router' import login from '../authentication/login/login.router' import logout from '../authentication/logout/logout.router' import status from '../status/status.router' -import submissions from '../entities/submission/submission.router' import users from '../entities/user/user.router' -import submissionScore from '../entities/submissionScore/submissionScore.router' -import containerAutoGrader from '../entities/containerAutoGrader/containerAutoGrader.router' -import assignmentProblem from '../entities/assignmentProblem/assignmentProblem.router' -import submissionProblemScore from '../entities/submissionProblemScore/submissionProblemScore.router' -import deadlineExtensions from "../entities/deadlineExtensions/deadlineExtensions.router"; -import fileUpload from '../fileUpload/fileUpload.router' -import category from '../entities/category/category.router' -import grader from '../entities/grader/grader.router' -import categories from '../entities/category/category.router' -import assignmentScore from '../entities/assignmentScore/assignmentScore.router' +import courseRoutes from './courseData.router' import { isAuthenticated } from '../authentication/authentication.middleware' import { NotFound } from '../utils/apiResponse.utils' -import nonContainerAutoGraderRouter from "../entities/nonContainerAutoGrader/nonContainerAutoGrader.router"; +import {asInt} from "../middleware/validator/generic.validator"; const Router = express.Router() -Router.use('/assignments', isAuthenticated, assignments) -Router.use('/assignment-problems', isAuthenticated, assignmentProblem) + + +Router.use('/course/:courseId', isAuthenticated, asInt('courseId'), courseRoutes) +Router.use('/c/:courseId', isAuthenticated, asInt('courseId'), courseRoutes) + +// TODO: Decide if we want to pull the course object (And return a 404 if not found) in middleware here or let it +// happen later... probably do this check in isAuthorized +// Router.use('/course/:courseId', isAuthenticated, asInt('courseId'), getCourse, courseRoutes) +// Router.use('/c/:courseId', isAuthenticated, asInt('courseId'), getCourse, courseRoutes) + + +// Router.use('/c/:courseid/assignments', isAuthenticated, assignments) +// Router.use('/assignment-problems', isAuthenticated, assignmentProblem) Router.use('/users', isAuthenticated, users) Router.use('/courses', isAuthenticated, courses) -Router.use('/categories', isAuthenticated, category) -Router.use('/user-courses', isAuthenticated, userCourse) -Router.use('/submissions', isAuthenticated, submissions) -Router.use('/submission-scores', isAuthenticated, submissionScore) -Router.use('/nonContainerAutoGrader', isAuthenticated, nonContainerAutoGraderRouter) -Router.use('/container-auto-graders', isAuthenticated, containerAutoGrader) -Router.use('/submission-problem-scores', isAuthenticated, submissionProblemScore) -Router.use('/file-upload', isAuthenticated, fileUpload) -Router.use('/deadline-extensions', isAuthenticated, deadlineExtensions) -Router.use('/grade', isAuthenticated, grader) -Router.use('/categories', isAuthenticated, categories) -Router.use('/assignment-scores', isAuthenticated, assignmentScore) +// TODO: Courses by user + +// Router.use('/categories', isAuthenticated, category) +// Router.use('/user-courses', isAuthenticated, userCourse) +// Router.use('/submissions', isAuthenticated, submissions) +// Router.use('/course/:courseid/assignment/:assignmentid/submissions/', isAuthenticated, submissions) +// Router.use('/submission-scores', isAuthenticated, submissionScore) +// Router.use('/nonContainerAutoGrader', isAuthenticated, nonContainerAutoGraderRouter) +// Router.use('/container-auto-graders', isAuthenticated, containerAutoGrader) +// Router.use('/submission-problem-scores', isAuthenticated, submissionProblemScore) +// Router.use('/file-upload', isAuthenticated, fileUpload) +// Router.use('/deadline-extensions', isAuthenticated, deadlineExtensions) +// Router.use('/grade', isAuthenticated, grader) +// Router.use('/categories', isAuthenticated, categories) +// Router.use('/assignment-scores', isAuthenticated, assignmentScore) Router.use('/login', login) Router.use('/logout', logout) From df1a7ce6aac156a5e913dc7039efd4b9b65f31a9 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Thu, 11 Apr 2024 18:08:10 -0400 Subject: [PATCH 008/400] Update populate-db file to accurate who made the submission and added file submissions --- devU-api/scripts/populate-db.ts | 567 +++++++++++--------------------- 1 file changed, 195 insertions(+), 372 deletions(-) diff --git a/devU-api/scripts/populate-db.ts b/devU-api/scripts/populate-db.ts index cc31b80a..c52f69d8 100644 --- a/devU-api/scripts/populate-db.ts +++ b/devU-api/scripts/populate-db.ts @@ -1,62 +1,69 @@ const API_URL = 'http://localhost:3001' +let apiToken:{[key:string]:string} = {} +let content = '' +let file; +let makefile; + +async function fetchToken( email: string, externalId:string) { + const urlencoded = new URLSearchParams() + urlencoded.append('email', email) + urlencoded.append('externalId', externalId) + + const options = { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: urlencoded, + } + + const res = await fetch(API_URL + '/login/developer', options) + const responseBody = await res.json() + const tmp = res.headers.get('Set-Cookie')?.split('=')[1] + + if (tmp === undefined) { + throw Error('Api token not found') + } + + apiToken[externalId]= tmp.split(';')[0] + return responseBody.userId +} -// fetch token with login route automatically -async function fetchToken() { - const options = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: '{"email":"name@buffalo.edu","externalId":"101"}', - } - - const rep = await fetch(API_URL + '/login/developer', options) - // get token in set-cookie header which will be in the format refreshToken='...' - const tmp = rep.headers.get('Set-Cookie')?.split('=')[1] - - if (tmp === undefined) { - throw Error('Api token not found') - } - // remove ';Max-Age' - apiToken = tmp.split(';')[0] +async function initAdmin(){ + await fetchToken('admin@admin.admin', 'admin') } -let apiToken = '' //Returns the ID of the newly created entry -async function SendPOST(path: string, requestBody: string) { - let response = await fetch(API_URL + path, { - method: 'POST', - headers: { - 'Authorization': 'Bearer ' + apiToken, - 'Content-Type': 'application/json', - }, - body: requestBody, - }) - if (response.status == 401 || response.status == 403) { - console.log('Status code: ' + response.status) - const responseBody = await response.json() - console.log(responseBody) - throw new Error('401 or 403 HTTP Response Received, make sure you set the TOKEN constant to a valid auth token') - } else if (response.status >= 400) { - console.log('Status code: ' + response.status) - const responseBody = await response.json() - console.log(responseBody) - throw new Error('400/500 Level HTTP Response Received: ' + response.status) - } else { +async function SendPOST(path: string, requestBody: string|FormData , externalId:string) { + const headers = new Headers() + headers.append('Authorization', `Bearer ${apiToken[externalId]}`) + headers.append('Content-Type', 'application/json') + if (requestBody instanceof FormData) { + headers.delete('Content-Type') + }else if (typeof requestBody === 'object') { + requestBody = JSON.stringify(requestBody) + } + + let response = await fetch(API_URL + path, { + method: 'POST', + headers:headers, + body: requestBody, + }) + return await response.json() - } } async function CreateCourse(name:string, number:string, semester:string) { - const courseData = { + const courseData = { name: name, semester: semester, number: number, startDate: '2024-01-24T00:00:00-0500', endDate: '2024-05-10T23:59:59-0500', - }; - return await SendPOST('/courses', JSON.stringify(courseData), 'admin'); + }; + console.log("Creating course: ", courseData.name) + return await SendPOST('/courses', JSON.stringify(courseData), 'admin'); } @@ -66,358 +73,174 @@ async function createAssignment(courseId:number, name:string, categoryName:strin const dueDate = new Date(time + 7 * 24 * 60 * 60 * 1000).toISOString(); const endDate = new Date(time + 14 * 24 * 60 * 60 * 1000).toISOString(); - const assignmentData = { - courseId: courseId, - name: name, - startDate: startDate, - dueDate: dueDate, - endDate: endDate, - categoryName: categoryName, - description: "This is a test assignment for course Id:."+courseId, - maxFileSize: 1024*20, - disableHandins: false - }; - return await SendPOST('/assignments', JSON.stringify(assignmentData), 'admin'); + const assignmentData = { + courseId: courseId, + name: name, + startDate: startDate, + dueDate: dueDate, + endDate: endDate, + categoryName: categoryName, + description: "This is a test assignment for course Id:."+courseId, + maxFileSize: 1024*20, + disableHandins: false + }; + console.log(`Creating assignment for Course Id: ${courseId}, Name: ${name}`) + return await SendPOST('/assignments', JSON.stringify(assignmentData), 'admin'); } async function createNonContainerAutoGrader(assignmentId:number, problemName:string, Score:number, Regex:string, isRegex:boolean){ - const problemData = { - assignmentId: assignmentId, - question: problemName, - score: Score, - correctString: Regex, - isRegex: isRegex + const problemData = { + assignmentId: assignmentId, + question: problemName, + score: Score, + correctString: Regex, + isRegex: isRegex }; -return await SendPOST('/nonContainerAutoGrader', JSON.stringify(problemData), 'admin'); + console.log("Creating NonContainerAutoGrader for Assignment Id: ",assignmentId ) + return await SendPOST('/nonContainerAutoGrader', JSON.stringify(problemData), 'admin'); } -async function createSubmission(courseId:number, assignmentId:number, userId:number, externalId:string, content?:string, file?:File) { - const time = new Date().toISOString(); - - content = content || `This is a test submission for assignment Id: ${assignmentId}`; - const fullContent = `{"filepath":"${file ? file.name : ''}","form":${JSON.stringify(content)}}`; +async function createContainerAutoGrader(assignmentId:number, imageName:string, timeout:number, graderFile:File, makefile?:File){ + const formData = new FormData(); + formData.append('assignmentId', assignmentId.toString()); + formData.append('autogradingImage', imageName); + formData.append('graderFile', graderFile); + formData.append('timeout', timeout.toString()); + if (makefile) { + formData.append('makefileFile', makefile); + } + console.log("Creating ContainerAutoGrader for Assignment Id: ",assignmentId ) + return await SendPOST('/container-auto-graders', formData, 'admin'); +} - const submissionData = { - createdAt: time, - updatedAt: time, - courseId: courseId, - assignmentId: assignmentId, - userId: userId, - content: fullContent - }; - return await SendPOST('/submissions', JSON.stringify(submissionData), externalId); +async function createSubmission(courseId:number, assignmentId:number, userId:number, externalId:string, content?:string, file?:File) { + const time = new Date().toISOString(); + + content = content || `This is a test submission for assignment Id: ${assignmentId}`; + const fullContent = `{"form":${JSON.stringify(content)}}`; + + let response; + console.log("Creating submission for assignment Id: ", assignmentId) + if (file) { + const formData = new FormData(); + formData.append('content', fullContent); + formData.append('createdAt', time); + formData.append('updatedAt', time); + formData.append('courseId', courseId.toString()); + formData.append('assignmentId', assignmentId.toString()); + formData.append('userId', userId.toString()); + formData.append('files', file); + + response = await SendPOST('/submissions', formData, externalId) + }else{ + const submissionData = { + createdAt: time, + updatedAt: time, + courseId: courseId, + assignmentId: assignmentId, + userId: userId, + content: fullContent + }; + //@ts-ignore + response = await SendPOST('/submissions', submissionData, externalId) + } + return response } async function createAssignmentProblem(assignmentId:number, problemName:string, maxScore:number){ - const problemData = { - assignmentId: assignmentId, - problemName: problemName, - maxScore: maxScore + const problemData = { + assignmentId: assignmentId, + problemName: problemName, + maxScore: maxScore }; return await SendPOST('/assignment-problems', JSON.stringify(problemData), 'admin'); } async function gradeSubmission(submissionId:number){ - return await SendPOST(`/grade/${submissionId}`, '', 'admin') + return await SendPOST(`/grade/${submissionId}`, '', 'admin') } -async function RunRequests() { - try { - await fetchToken() - - //Users - const userBilly = await SendPOST('/users', JSON.stringify({ - email: 'billy@buffalo.edu', externalId: 'billy', preferredName: 'Billiam', - })) - const userBob = await SendPOST('/users', JSON.stringify({ - email: 'bob@buffalo.edu', externalId: 'bob', preferredName: 'Bobby', - })) - const userJones = await SendPOST('/users', JSON.stringify({ - email: 'jones@buffalo.edu', externalId: 'jones', preferredName: 'Jones', - })) - - - //Courses - const course312 = await SendPOST('/courses', JSON.stringify({ - name: 'Web Applications', - semester: 's2024', - number: 'CSE312', - startDate: '2024-01-24T00:00:00-0500', - endDate: '2024-05-10T23:59:59-0500', - })) - const course302 = await SendPOST('/courses', JSON.stringify({ - name: 'Intro to Experiential Learning', - semester: 's2024', - number: 'CSE302', - startDate: '2024-01-24T00:00:00-0500', - endDate: '2024-05-07T23:59:59-0500', - })) - - - //UserCourse - SendPOST('/user-courses', JSON.stringify({ - userId: userBilly, courseId: course302, level: 'student', dropped: false, - })) - SendPOST('/user-courses', JSON.stringify({ - userId: userBob, courseId: course312, level: 'student', dropped: false, - })) - SendPOST('/user-courses', JSON.stringify({ - userId: userJones, courseId: course302, level: 'student', dropped: false, - })) - SendPOST('/user-courses', JSON.stringify({ - userId: userJones, courseId: course312, level: 'student', dropped: false, - })) - - //Categories - SendPOST('/categories', JSON.stringify({ - courseId: course312, name: 'Homework' - })) - SendPOST('/categories', JSON.stringify({ - courseId: course312, name: 'Quizzes' - })) - SendPOST('/categories', JSON.stringify({ - courseId: course302, name: 'Sprints' - })) - - //Assignments - const assign312_1 = await SendPOST('/assignments', JSON.stringify({ - courseId: course312, - name: 'Homework 1', - startDate: '2024-02-05T00:00:00-0500', - dueDate: '2024-02-26T23:59:59-0500', - endDate: '2024-03-11T23:59:59-0500', - gradingType: 'code', - categoryName: 'Homework', - description: 'HTTP', - maxFileSize: 10000000, - maxSubmissions: 10, - disableHandins: false, - })) - const assign312_2 = await SendPOST('/assignments', JSON.stringify({ - courseId: course312, - name: 'Homework 2', - startDate: '2024-02-19T00:00:00-0500', - dueDate: '2024-03-11T23:59:59-0500', - endDate: '2024-04-01T23:59:59-0500', - gradingType: 'code', - categoryName: 'Homework', - description: 'Authentication', - maxFileSize: 10000000, - maxSubmissions: 10, - disableHandins: false, - })) - SendPOST('/assignments', JSON.stringify({ - courseId: course312, - name: 'Homework 3', - startDate: '2024-03-04T00:00:00-0500', - dueDate: '2024-04-01T23:59:59-0500', - endDate: '2024-04-15T23:59:59-0500', - gradingType: 'code', - categoryName: 'Homework', - description: 'Image Uploads', - maxFileSize: 10000000, - maxSubmissions: 10, - disableHandins: false, - })) - const assign312_quiz = await SendPOST('/assignments', JSON.stringify({ - courseId: course312, - name: '2/19 Quiz', - startDate: '2024-02-09T16:00:00-0500', - dueDate: '2024-02-09T17:00:00-0500', - endDate: '2024-02-09T17:00:00-0500', - gradingType: 'non-code', - categoryName: 'Quizzes', - description: 'Pop quiz!', - maxFileSize: 10000000, - maxSubmissions: 1, - disableHandins: false, - })) - const assign302_1 = await SendPOST('/assignments', JSON.stringify({ - courseId: course302, - name: 'Sprint 1', - startDate: '2024-01-26T00:00:00-0500', - dueDate: '2024-02-17T23:59:59-0500', - endDate: '2024-02-17T23:59:59-0500', - gradingType: 'manual', - categoryName: 'Sprints', - description: 'The First Sprint', - maxFileSize: 10000000, - disableHandins: false, - })) - const assign302_2 = await SendPOST('/assignments', JSON.stringify({ - courseId: course302, - name: 'Sprint 2', - startDate: '2024-02-17T00:00:00-0500', - dueDate: '2024-03-09T23:59:59-0500', - endDate: '2024-03-09T23:59:59-0500', - gradingType: 'manual', - categoryName: 'Sprints', - description: 'The Second Sprint', - maxFileSize: 10000000, - disableHandins: false, - })) - SendPOST('/assignments', JSON.stringify({ - courseId: course302, - name: 'Sprint 3', - startDate: '2024-04-06T00:00:00-0500', - dueDate: '2024-04-06T23:59:59-0500', - endDate: '2024-04-06T23:59:59-0500', - gradingType: 'manual', - categoryName: 'Sprints', - description: 'The Third Sprint', - maxFileSize: 10000000, - disableHandins: false, - })) - - - //AssignmentProblems - - SendPOST("/assignment-problems", JSON.stringify({ - assignmentId: assign312_quiz, problemName: "Of the following letters A-D, which is B?", maxScore: 5 - })) - SendPOST("/assignment-problems", JSON.stringify({ - assignmentId: assign312_quiz, problemName: "Of the following letters A-D, which is C?", maxScore: 5 - })) - - - //Submissions - const submission_billy_302_1 = await SendPOST('/submissions', JSON.stringify({ - courseId: course302, - assignmentId: assign302_1, - userId: userBilly, - content: 'I finished all my tasks for sprint 1!', - type: 'text', - submitterIp: '127.0.0.1', - submittedBy: userBilly, - })) - SendPOST('/submissions', JSON.stringify({ - courseId: course302, - assignmentId: assign302_2, - userId: userBilly, - content: 'I finished all my tasks for sprint 2!', - type: 'text', - submitterIp: '127.0.0.1', - submittedBy: userBilly, - })) - const submission_bob_312_1 = await SendPOST('/submissions', JSON.stringify({ - courseId: course312, - assignmentId: assign312_1, - userId: userBob, - content: 'jesse pleaseee can i have a good grade pleaseeee', - type: 'text', - submitterIp: '127.0.0.1', - submittedBy: userBob, - })) - SendPOST('/submissions', JSON.stringify({ - courseId: course312, - assignmentId: assign312_2, - userId: userBob, - content: 'im begging you jesse please show mercy', - type: 'text', - submitterIp: '127.0.0.1', - submittedBy: userBob, - })) - const submission_bob_312_quiz1 = await SendPOST('/submissions', JSON.stringify({ - courseId: course312, - assignmentId: assign312_quiz, - userId: userBob, - content: '{"form":{"Of the following letters A-D, which is B?":"B","Of the following letters A-D, which is C?":"D"},"filepaths":""}', - type: 'json', - submitterIp: '127.0.0.1', - submittedBy: userBob, - })) - - - - //SubmissionScores - SendPOST('/submission-scores', JSON.stringify({ - submissionId: submission_billy_302_1, - score: 90, - feedback: 'Good work, but please make sure you come to each team meeting fully prepared.', - releasedAt: '2024-02-27T07:17:27-0500', - })) - SendPOST('/submission-scores', JSON.stringify({ - submissionId: submission_bob_312_1, score: 20, feedback: 'no', releasedAt: '2024-03-02T18:34:57-0500', - })) - SendPOST('/submission-scores', JSON.stringify({ - submissionId: submission_bob_312_1, - score: 5, - feedback: '1/2 Questions Correct', - releasedAt: '2024-02-09T17:00:00-0500', - })) - - // non container auto grader - SendPOST('/nonContainerAutoGrader', JSON.stringify({ - 'assignmentId': 1, - 'question': 'What do you call fake spaghetti?', - 'score': 1, - 'correctString': '/^An impasta\\.$/', - 'isRegex': true, - })) - SendPOST('/nonContainerAutoGrader', JSON.stringify({ - assignmentId: 1, - question: 'why did the chicken cross the road', - score: 100, - correctString: 'because he wanted to get to the other side', - isRegex: false, - })) - SendPOST('/nonContainerAutoGrader', JSON.stringify({ - assignmentId: 1, - question: 'Why couldn\'t the bicycle stand up by itself?', - score: 1, - correctString: '/^It was (two|2)-tired\\.$/', - isRegex: true, - })) - SendPOST("/nonContainerAutoGrader", JSON.stringify({ - assignmentId: assign312_quiz, - question: "Of the following letters A-D, which is B?", - score: 5, - correctString: "B", - isRegex: false - })) - SendPOST("/nonContainerAutoGrader", JSON.stringify({ - assignmentId: assign312_quiz, - question: "Of the following letters A-D, which is C?", - score: 5, - correctString: "C", - isRegex: false, - })) - - //Grading (creates a SubmissionScore and SubmissionProblemScores) - SendPOST("/grade/" + submission_bob_312_quiz1, JSON.stringify({})) - - SendPOST('/deadline-extensions',JSON.stringify({ - assignmentId:1, - creatorId:1, - deadlineDate:"2024-05-23T03:32:32.813Z", - userId:2 - })) - - //AssignmentScore - ROUTE NOT FUNCTIONAL - // SendPOST("/assignment-score", JSON.stringify({ - // assignmentId: assign302_1, userId: userBilly, score: 90 - // })) - // SendPOST("/assignment-score", JSON.stringify({ - // assignmentId: assign312_1, userId: userBob, score: 20 - // })) - - - - - //CategoryScores - ROUTE NOT FUNCTIONAL - - - //CourseScores - ROUTE NOT FUNCTIONAL - - console.log('Script completed successfully!') - } catch (e) { +async function runCourseAndSubmission() { + try { + //Create users + const billy= (await fetchToken('billy@buffalo.edu', 'billy')) + const bob = (await fetchToken('bob@buffalo.edu', 'bob')) + + //Create courses + const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024')).id + const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id + + //Create enroll students + await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId1}, level:student, dropped:false}`, 'admin') + await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId2}, level:student, dropped:false}`, 'admin') + await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId1}, level:student, dropped:false}`, 'admin') + await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId2}, level:student, dropped:false}`, 'admin') + + //Create assignments + const assignmentId1 = (await createAssignment(courseId1, 'Course1 Assignment 1', 'Quiz')).id + const assignmentId2 =(await createAssignment(courseId1, 'Course1 Assignment 2', 'Homework')).id + + const assignmentId3 = (await createAssignment(courseId2, 'Course2 Assignment 1', 'Quiz')).id + const assignmentId4 =(await createAssignment(courseId2, 'Course2 Assignment 2', 'Homework')).id + + const problemName1 = (await createAssignmentProblem(assignmentId1, 'Please answer A', 10)).problemName + const problemName2 = (await createAssignmentProblem(assignmentId1, 'Please answer B', 10)).problemName + + const problemName3 = (await createAssignmentProblem(assignmentId3, 'Please NOT answer A', 10)).problemName + const problemName4 = (await createAssignmentProblem(assignmentId3, 'Please NOT answer B', 10)).problemName + + //NonContainerAutoGrader + await createNonContainerAutoGrader(assignmentId1, problemName1, 10, 'A', false) + await createNonContainerAutoGrader(assignmentId1, problemName2, 10, 'B', false) + await createNonContainerAutoGrader(assignmentId3, problemName1, 10, '/^[^Aa]+$/', true) + await createNonContainerAutoGrader(assignmentId3, problemName2, 10, '/^[^Bb]+$/', true) + + //ContainerAutoGrader + file = new File(['This is a test grader file'], 'grader.code'); + await createContainerAutoGrader(assignmentId2, 'NewestImageInTheWorld', 300, file) + + file = new File(['This is another test grader file'], 'grader.code'); + makefile = new File(['This is a test makefile'], 'makefile'); + await createContainerAutoGrader(assignmentId4, 'OldestImageInTheWorld', 300, file, makefile) + + //Create submissions + content = `{"${problemName1}": "A", "${problemName2}": "B"}` + const submissionId1 = (await createSubmission(courseId1, assignmentId1, billy, 'billy',content)).id + + content = `{"${problemName1}": "C", "${problemName2}": "D"}` + const submissionId2 = (await createSubmission(courseId1, assignmentId1, bob, 'bob',content)).id + + content = `{"${problemName3}": "A", "${problemName4}": "B"}` + const submissionId3 = (await createSubmission(courseId2, assignmentId3, billy, 'billy',content)).id + + content = `{"${problemName3}": "C", "${problemName4}": "D"}` + const submissionId4 = (await createSubmission(courseId2, assignmentId3, bob, 'bob',content)).id + + file = new File(['This is a test file1'], 'test.txt'); + const submissionId5 = (await createSubmission(courseId1, assignmentId2, billy, 'billy',undefined, file)).id + const submissionId6 = (await createSubmission(courseId1, assignmentId2, bob, 'bob',undefined, file)).id + + file = new File(['These are lines of codes'], 'code.code'); + const submissionId7 = (await createSubmission(courseId1, assignmentId4, billy, 'billy',undefined, file)).id + const submissionId8 = (await createSubmission(courseId1, assignmentId4, bob, 'bob',undefined, file)).id + + //Grade the submissions + for (const submissionId of [submissionId1, submissionId2, submissionId3, submissionId4, submissionId5, submissionId6, submissionId7, submissionId8]){ + console.log('Grading submission: ', submissionId) + await gradeSubmission(submissionId) + } + + console.log('Script completed successfully!') + } catch (e) { console.error(e) - } + } } -RunRequests() \ No newline at end of file + +initAdmin() +runCourseAndSubmission() \ No newline at end of file From a582d75f44cbc141bf56bc6e9c4d60ef04680890 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Thu, 11 Apr 2024 18:11:42 -0400 Subject: [PATCH 009/400] fixed bug where course number contains space will cause error for creating buckets and update containerAutoGrader file upload where the userId in fileModel is now finished --- .../containerAutoGrader.service.ts | 26 +++++++++---------- .../src/entities/course/course.service.ts | 2 +- .../entities/submission/submission.service.ts | 5 +++- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index 782e3f0d..3e64ef7c 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -11,7 +11,7 @@ import {generateFilename} from "../../utils/fileUpload.utils"; const connect = () => getRepository(ContainerAutoGraderModel) const fileConn = () => getRepository(FileModel) -async function filesUpload(bucket: string, file: Express.Multer.File, containerAutoGrader: ContainerAutoGrader,filename: string) { +async function filesUpload(bucket: string, file: Express.Multer.File, containerAutoGrader: ContainerAutoGrader,filename: string, userId: number) { const Etag: string = await uploadFile(bucket, file,filename) const assignmentId = containerAutoGrader.assignmentId @@ -22,13 +22,13 @@ async function filesUpload(bucket: string, file: Express.Multer.File, containerA filename: filename, assignmentId: assignmentId, } - //TODO: This is a temporary fix to get the function to pass. CourseId and UserId should be modified in the future + //TODO: This is a temporary fix to get the function to pass. CourseId should be modified in the future fileModel.courseId = 1 - fileModel.userId = 1 + fileModel.userId = userId await fileConn().save(fileModel) - return Etag + return Etag } @@ -38,12 +38,12 @@ export async function create(containerAutoGrader: ContainerAutoGrader, graderInp if (existingContainerAutoGrader) throw new Error('Container Auto Grader already exists for this assignment, please update instead of creating a new one') const bucket: string = 'graders' const filename: string = generateFilename(graderInputFile.originalname, userId) - await filesUpload(bucket, graderInputFile, containerAutoGrader, filename) + await filesUpload(bucket, graderInputFile, containerAutoGrader, filename, userId) containerAutoGrader.graderFile = filename if (makefileInputFile) { const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) - await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename) + await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) containerAutoGrader.makefileFile = makefileFilename } @@ -56,14 +56,14 @@ export async function update(containerAutoGrader: ContainerAutoGrader, graderInp if (graderInputFile) { const bucket: string = 'graders' const filename: string = generateFilename(graderInputFile.originalname, userId) - await filesUpload(bucket, graderInputFile, containerAutoGrader, filename) + await filesUpload(bucket, graderInputFile, containerAutoGrader, filename, userId) containerAutoGrader.graderFile = filename } if (makefileInputFile) { const bucket: string = 'makefiles' const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) - await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename) + await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) containerAutoGrader.makefileFile = makefileFilename } @@ -72,15 +72,15 @@ export async function update(containerAutoGrader: ContainerAutoGrader, graderInp } export async function _delete(id: number) { - return await connect().softDelete({ id, deletedAt: IsNull() }) + return await connect().softDelete({ id, deletedAt: IsNull() }) } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOne({ id, deletedAt: IsNull() }) } export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().find({ deletedAt: IsNull() }) } //The grader has not changed to the new function, so the fake function keep here for now to avoid error @@ -99,9 +99,9 @@ export async function getGraderByAssignmentId(assignmentId: number){ let makefileData; if (makefileFile){ - makefileData = await downloadFile('makefiles', makefileFile) + makefileData = await downloadFile('makefiles', makefileFile) }else{ - makefileData = await downloadFile('makefiles', 'defaultMakefile') // Put actual default makefile name here + makefileData = await downloadFile('makefiles', 'defaultMakefile') // Put actual default makefile name here } return {graderData, makefileData, autogradingImage, timeout} diff --git a/devU-api/src/entities/course/course.service.ts b/devU-api/src/entities/course/course.service.ts index d779dc19..1b0b7071 100644 --- a/devU-api/src/entities/course/course.service.ts +++ b/devU-api/src/entities/course/course.service.ts @@ -11,7 +11,7 @@ const connect = () => getRepository(CourseModel) export async function create(course: Course) { const output = await connect().save(course) - const bucketName = (course.number + course.semester + course.id).toLowerCase() + const bucketName = (course.number + course.semester + course.id).replace(/ /g, '-').toLowerCase() await initializeMinio(bucketName) return output } diff --git a/devU-api/src/entities/submission/submission.service.ts b/devU-api/src/entities/submission/submission.service.ts index 2a54dd4a..48ccbc71 100644 --- a/devU-api/src/entities/submission/submission.service.ts +++ b/devU-api/src/entities/submission/submission.service.ts @@ -15,7 +15,7 @@ export async function create(submission: Submission, file?: Express.Multer.File if (file) { const bucket: string = await getRepository(CourseModel).findOne({ id: submission.courseId }).then((course) => { if (course) { - return (course.number + course.semester + course.id).toLowerCase() + return (course.number + course.semester + course.id).replace(/ /g, '-').toLowerCase() } return 'submission' }) @@ -32,6 +32,9 @@ export async function create(submission: Submission, file?: Express.Multer.File filename: filename, } const content = JSON.parse(submission.content) + if (!content.filepaths) { + content.filepaths = [] + } content.filepaths.push(filename) submission.content = JSON.stringify(content) From c1e5733ee16a94dbff6dde7b91950b595e36eca3 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Fri, 12 Apr 2024 02:16:00 -0400 Subject: [PATCH 010/400] Added file upload to submission form, added multipart POST request function --- .../containerAutoGrader.controller.ts | 16 ++++++++++ .../containerAutoGrader.model.ts | 14 ++++----- .../containerAutoGrader.router.ts | 23 ++++++++++++-- .../containerAutoGrader.service.ts | 7 ++++- .../submission/submission.controller.ts | 9 +++--- .../entities/submission/submission.model.ts | 4 ++- .../entities/submission/submission.router.ts | 2 +- .../components/pages/assignmentDetailPage.tsx | 31 ++++++++++++++++--- .../components/pages/submissionDetailPage.tsx | 2 +- .../pages/submissionFeedbackPage.tsx | 3 +- devU-client/src/services/request.service.ts | 21 +++++++++++++ 11 files changed, 108 insertions(+), 24 deletions(-) diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts index 01d00eb1..04bb6ba1 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts @@ -30,6 +30,21 @@ export async function detail(req: Request, res: Response, next: NextFunction) { } } +export async function getByAssignment(req: Request, res: Response, next: NextFunction) { + try { + const assignmentId = parseInt(req.params.id) + const containerAutoGrader = await ContainerAutoGraderService.getGraderEntityByAssignmentId(assignmentId) + + if (!containerAutoGrader) return containerAutoGrader + + const response = serialize(containerAutoGrader) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} + /* * for the post method, I changed how the upload is handled. I am now using fields instead of * single(for the purpose of uploading grader file and makefile). But I set the makefile to be @@ -99,4 +114,5 @@ export default { post, put, _delete, + getByAssignment, } \ No newline at end of file diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts index bf66321b..0f012bf3 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts @@ -22,21 +22,21 @@ export default class ContainerAutoGraderModel { * schemas: * ContainerAutoGrader: * type: object - * required: [assignmentId, graderFile, autogradingImage, timeout] + * required: [assignmentId, autogradingImage, timeout, graderFile, makefileFile] * properties: * assignmentId: * type: integer - * graderFile: - * type: string - * description: Filename of already uploaded grader file - * makeFileFile: - * type: string - * description: Filename of already uploaded makefile * autogradingImage: * type: string * timeout: * type: integer * description: Must be a positive integer + * graderFile: + * type: string + * format: binary + * makefileFile: + * type: string + * format: binary */ @PrimaryGeneratedColumn() diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts index 398bb016..923dc15d 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts @@ -41,6 +41,25 @@ Router.get('/', ContainerAutoGraderController.get); */ Router.get('/:id', asInt(), ContainerAutoGraderController.detail); +/** + * @swagger + * /container-auto-graders/assignment/{id}: + * get: + * summary: Retrieve an assignment's container auto grader + * tags: + * - ContainerAutoGraders + * responses: + * '200': + * description: OK + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: integer + */ +Router.get('/assignment/:id', asInt(), ContainerAutoGraderController.getByAssignment); + /** * @swagger * /container-auto-graders: @@ -53,7 +72,7 @@ Router.get('/:id', asInt(), ContainerAutoGraderController.detail); * description: OK * requestBody: * content: - * application/x-www-form-urlencoded: + * multipart/form-data: * schema: * $ref: '#/components/schemas/ContainerAutoGrader' */ @@ -77,7 +96,7 @@ Router.post('/', upload.fields([{name: 'graderFile'},{name: 'makefileFile'}]), v * type: integer * requestBody: * content: - * application/x-www-form-urlencoded: + * multipart/form-data: * schema: * $ref: '#/components/schemas/ContainerAutoGrader' */ diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index 782e3f0d..d6ede2bd 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -107,6 +107,10 @@ export async function getGraderByAssignmentId(assignmentId: number){ return {graderData, makefileData, autogradingImage, timeout} } +export async function getGraderEntityByAssignmentId(assignmentId: number) { + return await connect().findOne({ assignmentId, deletedAt: IsNull() }) + } + export default { create, retrieve, @@ -114,5 +118,6 @@ export default { _delete, list, getGraderByAssignmentId, - listByAssignmentId + listByAssignmentId, + getGraderEntityByAssignmentId, } \ No newline at end of file diff --git a/devU-api/src/entities/submission/submission.controller.ts b/devU-api/src/entities/submission/submission.controller.ts index 0ce42a26..7a073b22 100644 --- a/devU-api/src/entities/submission/submission.controller.ts +++ b/devU-api/src/entities/submission/submission.controller.ts @@ -1,4 +1,3 @@ -import { Submission } from 'devu-shared-modules' import { Request, Response, NextFunction } from 'express' import SubmissionService from '../submission/submission.service' @@ -43,12 +42,12 @@ export async function post(req: Request, res: Response, next: NextFunction) { // If this is hit, developer messed up above in the chain by not checking the user for auth if (!req.currentUser?.userId) return res.status(400).json(new GenericResponse('Request requires auth')) - const requestBody: Submission = req.body + const reqSubmission = req.body - requestBody.submitterIp = req.header('x-forwarded-for') || req.socket.remoteAddress - requestBody.submittedBy = req.currentUser?.userId + reqSubmission.submitterIp = req.header('x-forwarded-for') || req.socket.remoteAddress + reqSubmission.submittedBy = req.currentUser?.userId - const submission = await SubmissionService.create(requestBody, req.file) + const submission = await SubmissionService.create(reqSubmission, req.file) const response = serialize(submission) res.status(201).json(response) diff --git a/devU-api/src/entities/submission/submission.model.ts b/devU-api/src/entities/submission/submission.model.ts index fdf2fda0..8dae3b14 100644 --- a/devU-api/src/entities/submission/submission.model.ts +++ b/devU-api/src/entities/submission/submission.model.ts @@ -24,7 +24,6 @@ export default class SubmissionModel { * schemas: * Submission: * type: object - * required: [courseId, assignmentId, userId, content, type, submitterIp, submittedBy] * properties: * courseId: * type: integer @@ -38,6 +37,9 @@ export default class SubmissionModel { * type: string * submittedBy: * type: integer + * files: + * type: string + * format: binary */ @PrimaryGeneratedColumn() id: number diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index f5966369..8a899f4e 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -68,7 +68,7 @@ Router.get('/:id', asInt(), SubmissionController.detail) * description: OK * requestBody: * content: - * application/x-www-form-urlencoded: + * multipart/form-data: * schema: * $ref: '#/components/schemas/Submission' */ diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index 9c8e6e5f..91868546 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -1,7 +1,7 @@ import React,{ useState, useEffect } from 'react' import {Link} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' -import { AssignmentProblem, Submission, SubmissionScore, SubmissionProblemScore, Assignment } from 'devu-shared-modules' +import { AssignmentProblem, Submission, SubmissionScore, SubmissionProblemScore, Assignment, ContainerAutoGrader } from 'devu-shared-modules' import RequestService from 'services/request.service' import ErrorPage from './errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' @@ -21,12 +21,14 @@ const AssignmentDetailPage = () => { const [error, setError] = useState(null) const [loading, setLoading] = useState(true) const [formData, setFormData] = useState({}) + const [file, setFile] = useState() const [assignmentProblems, setAssignmentProblems] = useState(new Array()) const [submissions, setSubmissions] = useState(new Array()) const [submissionScores, setSubmissionScores] = useState(new Array()) const [submissionProblemScores, setSubmissionProblemScores] = useState(new Array()) const [assignment, setAssignment] = useState() - + const [containerAutograder, setContainerAutograder] = useState() + useEffect(() => { fetchData() }, []); @@ -55,6 +57,9 @@ const AssignmentDetailPage = () => { }) const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) setSubmissionProblemScores(submissionProblemScoresReq) + + const containerAutograder = await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`) + setContainerAutograder(containerAutograder) } catch(error) { setError(error) @@ -70,13 +75,15 @@ const AssignmentDetailPage = () => { const key = e.target.id setFormData(prevState => ({...prevState,[key] : value})) } + const handleFileChange = (e : React.ChangeEvent) => { + setFile(e.target.files?.item(0)) + } const handleSubmit = async () => { const contentField = { filepaths : [], form : formData, } - const submission = { userId : userId, assignmentId : assignmentId, @@ -87,9 +94,22 @@ const AssignmentDetailPage = () => { setLoading(true) try { - const response = await RequestService.post('/api/submissions', submission) + if (containerAutograder) { + const submission = new FormData + submission.append('userId', String(userId)) + submission.append('assignmentId', assignmentId) + submission.append('courseId', courseId) + submission.append('content', JSON.stringify(contentField)) + if (file) submission.append('files', file) + + var response = await RequestService.postMultipart('/api/submissions', submission) + } else { + + var response = await RequestService.post('/api/submissions', submission) + } + setAlert({ autoDelete: true, type: 'success', message: 'Submission Sent' }) - + // Now you can use submissionResponse.id here await RequestService.post(`/api/grade/${response.id}`, {} ) setAlert({ autoDelete: true, type: 'success', message: 'Submission Graded' }) @@ -118,6 +138,7 @@ const AssignmentDetailPage = () => { ))} + {containerAutograder && ()}
diff --git a/devU-client/src/components/pages/submissionDetailPage.tsx b/devU-client/src/components/pages/submissionDetailPage.tsx index 93bef132..9b2238e3 100644 --- a/devU-client/src/components/pages/submissionDetailPage.tsx +++ b/devU-client/src/components/pages/submissionDetailPage.tsx @@ -120,7 +120,7 @@ const SubmissionDetailPage = () => { {submissionScore?.score ?? "N/A"} - View Feedback + View Feedback

Submission Content:

diff --git a/devU-client/src/components/pages/submissionFeedbackPage.tsx b/devU-client/src/components/pages/submissionFeedbackPage.tsx index 8661e7ec..0e86309f 100644 --- a/devU-client/src/components/pages/submissionFeedbackPage.tsx +++ b/devU-client/src/components/pages/submissionFeedbackPage.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react' -import { useParams } from 'react-router-dom' +import { Link, useParams } from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' @@ -61,6 +61,7 @@ const SubmissionFeedbackPage = () => {
{sps.feedback}
))} + View Submission Details ) } diff --git a/devU-client/src/services/request.service.ts b/devU-client/src/services/request.service.ts index 0b533ec1..0657f34d 100644 --- a/devU-client/src/services/request.service.ts +++ b/devU-client/src/services/request.service.ts @@ -95,6 +95,24 @@ async function post( return _fetchWrapper(proxy, request) } +async function postMultipart( + url: string, + body: FormData, + options: RequestInit = {}, +): Promise { + const proxy = _replaceUrl(url) + const authToken = await getToken() + + const request: any = { + method: 'POST', + headers: { authorization: `Bearer ${authToken}` }, + body: body, + ...options, + } + + return _fetchWrapper(proxy, request) +} + async function put(url: string, body: Record, options: RequestInit = {}): Promise { const token = await getToken() const proxy = _replaceUrl(url) @@ -136,5 +154,8 @@ export default { put, delete: deleteRequest, + postMultipart, + //putMultipart, + upload, } From e2d6d6852980ba7ab1bd8f73b9fccac1385f5c6e Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Fri, 12 Apr 2024 03:25:22 -0400 Subject: [PATCH 011/400] Created rudimentary container autograder creation form --- .../containerAutoGrader.controller.ts | 8 +- .../containerAutoGrader.service.ts | 4 - .../src/components/authenticatedRouter.tsx | 2 + .../components/pages/assignmentDetailPage.tsx | 13 +-- .../pages/containerAutoGraderForm.tsx | 82 +++++++++++++++++++ devU-client/src/services/request.service.ts | 16 +++- 6 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 devU-client/src/components/pages/containerAutoGraderForm.tsx diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts index 04bb6ba1..fa60b5ce 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts @@ -33,13 +33,9 @@ export async function detail(req: Request, res: Response, next: NextFunction) { export async function getByAssignment(req: Request, res: Response, next: NextFunction) { try { const assignmentId = parseInt(req.params.id) - const containerAutoGrader = await ContainerAutoGraderService.getGraderEntityByAssignmentId(assignmentId) + const containerAutoGrader = await ContainerAutoGraderService.listByAssignmentId(assignmentId) - if (!containerAutoGrader) return containerAutoGrader - - const response = serialize(containerAutoGrader) - - res.status(200).json(response) + res.status(200).json(containerAutoGrader.map(serialize)) } catch (err) { next(err) } diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index d6ede2bd..0c74afd4 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -107,9 +107,6 @@ export async function getGraderByAssignmentId(assignmentId: number){ return {graderData, makefileData, autogradingImage, timeout} } -export async function getGraderEntityByAssignmentId(assignmentId: number) { - return await connect().findOne({ assignmentId, deletedAt: IsNull() }) - } export default { create, @@ -119,5 +116,4 @@ export default { list, getGraderByAssignmentId, listByAssignmentId, - getGraderEntityByAssignmentId, } \ No newline at end of file diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index a3a6ac40..b360047c 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -24,6 +24,7 @@ import NonContainerAutoGraderForm from './pages/nonContainerAutoGraderForm' import GradebookStudentPage from './pages/gradebookStudentPage' import GradebookInstructorPage from './pages/gradebookInstructorPage' import SubmissionFeedbackPage from './pages/submissionFeedbackPage' +import ContainerAutoGraderForm from './pages/containerAutoGraderForm' const AuthenticatedRouter = () => ( @@ -45,6 +46,7 @@ const AuthenticatedRouter = () => ( + diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index 91868546..bb28c149 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -27,7 +27,7 @@ const AssignmentDetailPage = () => { const [submissionScores, setSubmissionScores] = useState(new Array()) const [submissionProblemScores, setSubmissionProblemScores] = useState(new Array()) const [assignment, setAssignment] = useState() - const [containerAutograder, setContainerAutograder] = useState() + const [containerAutograder, setContainerAutograder] = useState() useEffect(() => { fetchData() @@ -42,7 +42,7 @@ const AssignmentDetailPage = () => { const assignmentProblemsReq = await RequestService.get(`/api/assignment-problems/${assignmentId}`) setAssignmentProblems(assignmentProblemsReq) - const submissionsReq = await RequestService.get(`/api/submissions?assignment=${assignmentId}&user=${userId}`) + const submissionsReq = await RequestService.get(`/api/submissions?assignmentId=${assignmentId}&userId=${userId}`) submissionsReq.sort((a, b) => (Date.parse(b.createdAt ?? '') - Date.parse(a.createdAt ?? ''))) setSubmissions(submissionsReq) @@ -58,7 +58,7 @@ const AssignmentDetailPage = () => { const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) setSubmissionProblemScores(submissionProblemScoresReq) - const containerAutograder = await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`) + const containerAutograder = (await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`)).pop() ?? null setContainerAutograder(containerAutograder) } catch(error) { @@ -94,17 +94,16 @@ const AssignmentDetailPage = () => { setLoading(true) try { - if (containerAutograder) { + if (file) { const submission = new FormData submission.append('userId', String(userId)) submission.append('assignmentId', assignmentId) submission.append('courseId', courseId) submission.append('content', JSON.stringify(contentField)) - if (file) submission.append('files', file) + submission.append('files', file) var response = await RequestService.postMultipart('/api/submissions', submission) } else { - var response = await RequestService.post('/api/submissions', submission) } @@ -130,6 +129,8 @@ const AssignmentDetailPage = () => { Update Assignment


Add Non-Container Auto-Graders +


+ Add Container Auto-Grader {/**Assignment Problems & Submission */} {assignmentProblems.map(assignmentProblem => ( diff --git a/devU-client/src/components/pages/containerAutoGraderForm.tsx b/devU-client/src/components/pages/containerAutoGraderForm.tsx new file mode 100644 index 00000000..c5be5109 --- /dev/null +++ b/devU-client/src/components/pages/containerAutoGraderForm.tsx @@ -0,0 +1,82 @@ +import React,{useState} from 'react' +import PageWrapper from 'components/shared/layouts/pageWrapper' +import styles from './nonContainerAutoGraderForm.scss' +import TextField from 'components/shared/inputs/textField' +import Button from 'components/shared/inputs/button' +import { useActionless } from 'redux/hooks' +import { SET_ALERT } from 'redux/types/active.types' +import RequestService from 'services/request.service' + +const ContainerAutoGraderForm = () => { + const [setAlert] = useActionless(SET_ALERT) + + const [graderFile, setGraderFile] = useState() + const [makefile, setMakefile] = useState() + const [formData,setFormData] = useState({ + assignmentId: '', + autogradingImage: '', + timeout: '', + }) + + const handleChange = (value: String, e : React.ChangeEvent) => { + const key = e.target.id + setFormData(prevState => ({...prevState,[key] : value})) + } + //This is janky but it works and I'm too tired to come up with a better solution + //this is done because the files need to be uniquely identified for multer to parse them from the multipart + const handleGraderfileChange = (e : React.ChangeEvent) => { + setGraderFile(e.target.files?.item(0)) + } + const handleMakefileChange = (e : React.ChangeEvent) => { + setMakefile(e.target.files?.item(0)) + } + + const handleSubmit = () => { + + const multipart = new FormData + multipart.append('assignmentId', formData.assignmentId) + multipart.append('autogradingImage', formData.autogradingImage) + multipart.append('timeout', String(formData.timeout)) + if (graderFile) multipart.append('graderFile', graderFile) + if (makefile) multipart.append('makefileFile', makefile) + + RequestService.postMultipart('/api/container-auto-graders/', multipart) + .then(() => { + setAlert({ autoDelete: true, type: 'success', message: 'Container Auto-Grader Added' }) + }) + .catch((err: Error) => { + console.log(err.message) + setAlert({ autoDelete: false, type: 'error', message: err.message }) + }) + + + setFormData({ + assignmentId: '', + autogradingImage: '', + timeout: '', + }) + } + + return( + +

Non Container Auto Grader Form

+
+

Add a Non-Container Auto Grader

+ + + + +
+ +
+



+ +
+
+

Existing Problems

+
+
+ ) +} + +export default ContainerAutoGraderForm \ No newline at end of file diff --git a/devU-client/src/services/request.service.ts b/devU-client/src/services/request.service.ts index 0657f34d..b1fb89b2 100644 --- a/devU-client/src/services/request.service.ts +++ b/devU-client/src/services/request.service.ts @@ -125,6 +125,20 @@ async function put(url: string, body: Record, options: Req }) } +async function putMultipart(url: string, body: FormData, options: RequestInit = {}): Promise { + const proxy = _replaceUrl(url) + const authToken = await getToken() + + const request: any = { + method: 'PUT', + headers: { authorization: `Bearer ${authToken}` }, + body: body, + ...options, + } + + return _fetchWrapper(proxy, request) +} + async function deleteRequest(url: string, options: RequestInit = {}): Promise { const authToken = await getToken() const proxy = _replaceUrl(url) @@ -155,7 +169,7 @@ export default { delete: deleteRequest, postMultipart, - //putMultipart, + putMultipart, upload, } From fa624402a95d79298faf87bee09a90c2c523c6d2 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Fri, 12 Apr 2024 09:12:52 -0400 Subject: [PATCH 012/400] Updated Styling for Course Detail Page Made the Course Detail Page more closely resemble the figma --- .../components/pages/courseDetailPage.scss | 39 +++++--- .../src/components/pages/courseDetailPage.tsx | 98 ++++++++++++++++++- 2 files changed, 117 insertions(+), 20 deletions(-) diff --git a/devU-client/src/components/pages/courseDetailPage.scss b/devU-client/src/components/pages/courseDetailPage.scss index a9074688..643ffe12 100644 --- a/devU-client/src/components/pages/courseDetailPage.scss +++ b/devU-client/src/components/pages/courseDetailPage.scss @@ -1,20 +1,29 @@ @import 'variables'; -.assignmentName { - color: $text-color; - text-decoration: none; - font-size: 14px; +.categoriesContainer { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; /* adjust this value to set the space between the cards */ } -.button { - justify-content: center; - background-color: $primary; - color: $text-color; - border: 0px; - padding: 10px 40px; - border-radius: 50px; - font-size: 14px; - font-weight: 700; - text-decoration: none; - cursor: pointer; +.header { + display: flex; + align-items: center; +} + +.smallLine { + width: 50px; /* adjust this value to set the length of the small line */ + border-top: 3px solid white; /* adjust this value to set the color and thickness of the line */ + margin-right: 10px; /* adjust this value to set the space between the line and the text */ +} + +.largeLine { + flex-grow: 1; + border-top: 3px solid white; /* adjust this value to set the color and thickness of the line */ + margin-left: 10px; /* adjust this value to set the space between the line and the text */ + margin-right: 10px; /* add this line to create some space between the line and the button */ +} + +.buttons { + margin : 0 10px; } \ No newline at end of file diff --git a/devU-client/src/components/pages/courseDetailPage.tsx b/devU-client/src/components/pages/courseDetailPage.tsx index 756561d5..5e2527e9 100644 --- a/devU-client/src/components/pages/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courseDetailPage.tsx @@ -1,17 +1,105 @@ -import React from 'react' -import { Link, useParams } from 'react-router-dom' +import React,{ useState,useEffect } from 'react' +import { useParams, useHistory } from 'react-router-dom' +import RequestService from 'services/request.service' +import { Course, Assignment } from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' + +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' +import Typography from '@mui/material/Typography' +import { CardActionArea } from '@mui/material' +import List from '@mui/material/List' +import ListItem from '@mui/material/ListItem' +import ListItemButton from '@mui/material/ListItemButton' +import ListItemText from '@mui/material/ListItemText' +import Button from '@mui/material/Button' +import Stack from '@mui/material/Stack' + + import styles from './courseDetailPage.scss' const CourseDetailPage = () => { + const history = useHistory() const { courseId } = useParams<{courseId: string}>() + const [courseInfo, setCourseInfo] = useState(null) + const [categoryMap, setCategoryMap] = useState>({}) + + const fetchCourseInfo = async () => { + RequestService.get(`/api/courses/${courseId}`) + .then((course) => { + setCourseInfo(course) + }) + RequestService.get(`/api/assignments/course/${courseId}`) + .then((assignments) => { + console.log(assignments) + let categoryMap : Record = {} + assignments.forEach((assignment : Assignment) => { + if (assignment.categoryName in categoryMap) { + categoryMap[assignment.categoryName].push(assignment) + } + else { + categoryMap[assignment.categoryName] = [assignment] + } + }) + console.log(categoryMap) + setCategoryMap(categoryMap) + }) + } + + useEffect(() => { + fetchCourseInfo() + }, []) return( -

Course Detail Page

- See Assignments
- View Gradebook + {courseInfo ? ( +
+
+
+

{courseInfo.name}

+
+ + + + + + + +
+ +
+ {Object.keys(categoryMap).map((category, index) => ( + + + + + {category} + + + + + {categoryMap[category].map((assignment, index) => ( + + + + + + ))} + + + ))} +
+
+ ) : ( +

Error fetching Course Information

+ )}
) } From 8fbb99ebe17ef41dbd6aad23e052fa3f619642b5 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:42:13 -0400 Subject: [PATCH 013/400] Updated course detail cards Updated the course detail cards to be scrollable and max height --- .../src/components/pages/courseDetailPage.tsx | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/devU-client/src/components/pages/courseDetailPage.tsx b/devU-client/src/components/pages/courseDetailPage.tsx index 5e2527e9..1a421e41 100644 --- a/devU-client/src/components/pages/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courseDetailPage.tsx @@ -8,7 +8,6 @@ import PageWrapper from 'components/shared/layouts/pageWrapper' import Card from '@mui/material/Card' import CardContent from '@mui/material/CardContent' import Typography from '@mui/material/Typography' -import { CardActionArea } from '@mui/material' import List from '@mui/material/List' import ListItem from '@mui/material/ListItem' import ListItemButton from '@mui/material/ListItemButton' @@ -71,20 +70,17 @@ const CourseDetailPage = () => { history.push(`/courses/${courseId}/update`) }}>Edit Course -
{Object.keys(categoryMap).map((category, index) => ( - - - - {category} - - - - + + + {category} + + + {categoryMap[category].map((assignment, index) => ( From 79531e9bb959ce9e4a5a83cb2ab32d1729eb6a3f Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:56:44 -0400 Subject: [PATCH 014/400] Added redirects to the Assignments On the course detail page, added redirects for the assignments to the detail page for those assignmnets. --- devU-client/src/components/pages/courseDetailPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-client/src/components/pages/courseDetailPage.tsx b/devU-client/src/components/pages/courseDetailPage.tsx index 1a421e41..c0cdb278 100644 --- a/devU-client/src/components/pages/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courseDetailPage.tsx @@ -83,7 +83,7 @@ const CourseDetailPage = () => { {categoryMap[category].map((assignment, index) => ( - + {history.push(`/courses/${courseId}/assignments/${assignment.id}`)}}> From f9ad8d723b31090ad49e23353f78bae8eb3e545e Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Mon, 15 Apr 2024 00:43:09 -0400 Subject: [PATCH 015/400] Roles. Very much a WIP. Remaking all routes, adding authorization to every path -Adding a way to check if a user is editing/accessing their own content --- .../authorization/authorization.middleware.ts | 145 ++++++++++-- .../assignment/assignment.controller.ts | 29 +-- .../entities/assignment/assignment.router.ts | 75 ++++-- .../entities/assignment/assignment.service.ts | 101 +++++---- .../assignmentProblem.controller.ts | 2 +- .../assignmentProblem.router.ts | 30 +-- .../assignmentScore/assignmentScore.router.ts | 54 ++--- .../assignmentScore.service.ts | 4 +- .../src/entities/category/category.router.ts | 33 +-- .../src/entities/category/category.service.ts | 2 +- .../categoryScore/categoryScore.router.ts | 21 +- .../containerAutoGrader.router.ts | 21 +- devU-api/src/entities/course/course.model.ts | 2 + devU-api/src/entities/course/course.router.ts | 54 ++++- .../courseScore/courseScore.router.ts | 20 +- .../deadlineExtensions.router.ts | 22 +- devU-api/src/entities/grader/grader.router.ts | 11 +- .../nonContainerAutoGrader.router.ts | 37 ++- devU-api/src/entities/role/role.controller.ts | 106 +++++++++ devU-api/src/entities/role/role.model.ts | 92 ++++++-- devU-api/src/entities/role/role.router.ts | 152 +++++++++++++ devU-api/src/entities/role/role.serializer.ts | 50 ++++ devU-api/src/entities/role/role.service.ts | 53 +++++ devU-api/src/entities/role/role.validator.ts | 18 ++ .../role/tests/userCourse.controller.test.ts | 214 ++++++++++++++++++ .../role/tests/userCourse.serializer.test.ts | 42 ++++ .../entities/submission/submission.router.ts | 66 +++++- .../submissionProblemScore.controller.ts | 2 +- .../submissionProblemScore.router.ts | 26 ++- .../submissionScore/submissionScore.router.ts | 45 ++-- devU-api/src/entities/user/user.model.ts | 1 - devU-api/src/entities/user/user.router.ts | 39 +++- .../userCourse/userCourse.controller.ts | 134 ++++++----- .../entities/userCourse/userCourse.model.ts | 1 + .../entities/userCourse/userCourse.router.ts | 55 ++--- .../entities/userCourse/userCourse.service.ts | 9 +- .../userCourse/userCourse.validator.ts | 1 + devU-api/src/fileUpload/fileUpload.router.ts | 13 +- devU-api/src/router/courseData.router.ts | 48 ++-- devU-api/src/router/index.ts | 22 +- devu-shared/src/index.ts | 3 +- devu-shared/src/types/role.types.ts | 23 ++ docker-compose.yml | 2 +- 43 files changed, 1432 insertions(+), 448 deletions(-) create mode 100644 devU-api/src/entities/role/role.controller.ts create mode 100644 devU-api/src/entities/role/role.router.ts create mode 100644 devU-api/src/entities/role/role.serializer.ts create mode 100644 devU-api/src/entities/role/role.service.ts create mode 100644 devU-api/src/entities/role/role.validator.ts create mode 100644 devU-api/src/entities/role/tests/userCourse.controller.test.ts create mode 100644 devU-api/src/entities/role/tests/userCourse.serializer.test.ts create mode 100644 devu-shared/src/types/role.types.ts diff --git a/devU-api/src/authorization/authorization.middleware.ts b/devU-api/src/authorization/authorization.middleware.ts index 14297050..d8675e70 100644 --- a/devU-api/src/authorization/authorization.middleware.ts +++ b/devU-api/src/authorization/authorization.middleware.ts @@ -1,10 +1,57 @@ import {NextFunction, Request, Response} from "express"; -import {GenericResponse} from "../utils/apiResponse.utils"; - - - +import {NotFound} from "../utils/apiResponse.utils"; +import AssignmentService from '../entities/assignment/assignment.service' +import CourseService from '../entities/course/course.service' +import UserCourseService from '../entities/userCourse/userCourse.service' +import RoleService from "../entities/role/role.service"; + +/** + * Are you authorized to access this endpoint? + * + * @param permission if the user has this permission, they are authorized + * @param permissionIfSelf if the user does not have the first 'permission', but they have the 'permissionIfSelf' and + * 'expectedSelfId' matches their user id, then they are authorized + * @param expectedSelfId id that must match the user id if using a self permission + */ +export function isAuthorized(permission: string, permissionIfSelf?: string, expectedSelfId?: string) { + return async function (req: Request, res: Response, next: NextFunction) { + const courseId = parseInt(req.params.courseId) + const userId = req.currentUser?.userId + + if(!courseId || !userId){ + return res.status(404).json(NotFound) + } + + const userCourse = await UserCourseService.retrieveByCourseAndUser(courseId, userId) + + if(!userCourse){ + return res.status(404).json(NotFound) + } + + RoleService.retrieve() + // Pull course + // Pull userCourse + // Pull role + + // if anything doesn't exist, return a 404 (prevents leaking the course list since you get the same error if the course exists or not if you're not enrolled) + + // Check permission + + // 403 if no permission + + + // if (!req.params[routeParameter]) + // return res + // .status(400) + // .json(new GenericResponse(`Missing ${routeParameter} param on ${routeParameter} required request`)) + // + // const id = parseInt(req.params[routeParameter]) + // + // if (isNaN(id)) return res.status(400).json(new GenericResponse(routeParameter + ' is expected to be a number')) + + next() + } -export async function isAuthorized(req: Request, res: Response, next: NextFunction){ // Option 1: Add user id to every path where users could be able to access only their content <-- painful for // certain paths (/course/2/assignments/45/user/12) need to validate later that the id in the url matches the data.. @@ -50,18 +97,86 @@ export async function isAuthorized(req: Request, res: Response, next: NextFuncti // need a filter after the fact too. pre-check to see if they can access the endpoint at all. post-filter to filter down to the data they are allowed to access // -or is this just part of each endpoint? eg. GET /courses only returns the courses that user is enrolled (Or every course if you're system-admin) - if(!req.currentUser){ - return res.status(401).json(new GenericResponse('Need to be logged in for this endpoint')) + // if(!req.currentUser){ + // return res.status(401).json(new GenericResponse('Need to be logged in for this endpoint')) + // } + // + // if("course doesn't exist" || "your are not enrolled in this course"){ + // return res.status(404).json(new GenericResponse('Course not found or you\'re enrolled in this course')) + // } + // + // if("course doesn't exist"){ + // return res.status(404).json(new GenericResponse('Course not found')) + // } + // + // // check if they are enrolled in the course -or- if they are system-level admin + // // -if they have a userCourse, pull their role + // + // req.path + // + // // if(false) { + // return res.status(403).json(new GenericResponse('You do not have permission to access this endpoint')) + // // } + + // next() +} + +/** + * Checks if the user is authorized to view a specific assignment. If the assignment is released, check if they have + * the 'assignmentViewReleased' permission. If the assignment is not released, check if they have the + * 'assignmentViewAll' permission + * + * Assumes the request has the assignment id as a request param named 'assignmentId'. The id is verified to be an int, + * but not checked to be a valid assignment id + */ +export async function isAuthorizedByAssignmentStatus(req: Request, res: Response, next: NextFunction) { + const assignmentId = parseInt(req.params['assignmentId']) + const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) + const permissionString = isAssignmentReleased ? 'assignmentViewReleased' : 'assignmentViewAll' + await isAuthorized(permissionString)(req, res, next) + // TODO: this needs to be tested +} + +/** + * Authorized if the user has the 'permission' or if they have the 'selfPermission' and their id matches the selfIdParam + * @param permission + * @param selfPermission + * @param selfIdParam + */ +export async function isAuthorizedIfSelfPathParam(permission: string, selfPermission: string, selfIdParam: string) { + return async function (req: Request, res: Response, next: NextFunction) { + + + const assignmentId = parseInt(req.params['assignmentId']) + const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) + const permissionString = isAssignmentReleased ? 'assignmentViewReleased' : 'assignmentViewAll' + await isAuthorized(permissionString)(req, res, next) + // TODO: this needs to be tested } +} - // check if they are enrolled in the course -or- if they are system-level admin - // -if they have a userCourse, pull their role - req.path +export async function isAuthorizedIfSelfBodyParam(permission: string, selfPermission: string, selfIdField: string) { + return async function (req: Request, res: Response, next: NextFunction) { - // if(false) { - return res.status(403).json(new GenericResponse('You do not have permission to access this endpoint')) - // } - // next() -} \ No newline at end of file + const assignmentId = parseInt(req.params['assignmentId']) + const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) + const permissionString = isAssignmentReleased ? 'assignmentViewReleased' : 'assignmentViewAll' + await isAuthorized(permissionString)(req, res, next) + // TODO: this needs to be tested + } +} + +export async function isAuthorizedIfSelfField(permission: string, selfPermission: string, selfIdField: string) { + return async function (req: Request, res: Response, next: NextFunction) { + + + const assignmentId = parseInt(req.params['assignmentId']) + const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) + const permissionString = isAssignmentReleased ? 'assignmentViewReleased' : 'assignmentViewAll' + await isAuthorized(permissionString)(req, res, next) + // TODO: this needs to be tested + } +} + diff --git a/devU-api/src/entities/assignment/assignment.controller.ts b/devU-api/src/entities/assignment/assignment.controller.ts index effe417d..3c4e0eff 100644 --- a/devU-api/src/entities/assignment/assignment.controller.ts +++ b/devU-api/src/entities/assignment/assignment.controller.ts @@ -6,21 +6,11 @@ import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.util import { serialize } from './assignment.serializer' -export async function get(req: Request, res: Response, next: NextFunction) { - try { - const assignments = await AssignmentService.list() - const response = assignments.map(serialize) - - res.status(200).json(response) - } catch (err) { - next(err) - } -} - export async function detail(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id) - const assignment = await AssignmentService.retrieve(id) + const courseId = parseInt(req.params.courseId) + const assignment = await AssignmentService.retrieve(id, courseId) if (!assignment) return res.status(404).json(NotFound) @@ -44,6 +34,18 @@ export async function getByCourse(req: Request, res: Response, next: NextFunctio next(err) } } +export async function getReleased(req: Request, res: Response, next: NextFunction) { + try { + const courseId = parseInt(req.params.courseId) + const assignments = await AssignmentService.listReleased(courseId) + + const response = assignments.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} export async function post(req: Request, res: Response, next: NextFunction) { try { @@ -82,4 +84,5 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { get, detail, post, put, _delete, getByCourse } + +export default { detail, post, put, _delete, getByCourse, getReleased} diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index b6338e56..c22d9690 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -7,53 +7,58 @@ import {asInt} from '../../middleware/validator/generic.validator' // Controller import AssignmentsController from './assignment.controller' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import {isAuthorized, isAuthorizedByAssignmentStatus} from "../../authorization/authorization.middleware"; const Router = express.Router() + /** * @swagger - * /assignments: + * /course/:courseId/assignments/released: * get: - * summary: Retrieve a list of assignments + * summary: Retrieve a list of a course's assignments that have been released (Start date prior to current time) * tags: * - Assignments * responses: * '200': * description: OK + * parameters: + * - name: courseId + * in: path + * description: Enter course id + * required: true + * schema: + * type: integer */ -Router.get('/', isAuthorized, AssignmentsController.get) +Router.get('/released', isAuthorized('assignmentViewReleased'), AssignmentsController.getReleased) -// TODO: What endpoints to have? -Router.get('/released', isAuthorized, AssignmentsController.get) -Router.get('/unreleased', isAuthorized, AssignmentsController.get) /** * @swagger - * /assignments/{id}: + * /course/:courseId/assignments: * get: - * summary: Retrieve a single assignment + * summary: Retrieve a list of a course's assignments * tags: * - Assignments * responses: * '200': * description: OK * parameters: - * - name: id + * - name: courseId * in: path - * description: Enter assignment id + * description: Enter course id * required: true * schema: * type: integer */ -Router.get('/:id', isAuthorized, asInt(), AssignmentsController.detail) +Router.get('/', isAuthorized('assignmentViewAll'), AssignmentsController.getByCourse) /** * @swagger - * /assignments/course/{courseId}: + * /course/:courseId/assignments/{id}: * get: - * summary: Retrieve a list of a course's assignments + * summary: Retrieve a single assignment * tags: * - Assignments * responses: @@ -66,12 +71,21 @@ Router.get('/:id', isAuthorized, asInt(), AssignmentsController.detail) * required: true * schema: * type: integer + * - name: id + * in: path + * description: Enter assignment id + * required: true + * schema: + * type: integer */ -Router.get('/course/:courseId', isAuthorized, asInt('courseId'), AssignmentsController.getByCourse) +Router.get('/:id', asInt(), isAuthorizedByAssignmentStatus, AssignmentsController.detail) + + + /** * @swagger - * /assignments: + * /course/:courseId/assignments * post: * summary: Create an assignment * tags: @@ -79,17 +93,24 @@ Router.get('/course/:courseId', isAuthorized, asInt('courseId'), AssignmentsCont * responses: * '200': * description: OK + * parameters: + * - name: courseId + * in: path + * description: Enter course id + * required: true + * schema: + * type: integer * requestBody: * content: * application/x-www-form-urlencoded: * schema: * $ref: '#/components/schemas/Assignment' */ -Router.post('/', isAuthorized, validator, AssignmentsController.post) +Router.post('/', isAuthorized('assignmentEditAll'), validator, AssignmentsController.post) /** * @swagger - * /assignments/{id}: + * /course/:courseId/assignments/{id}: * put: * summary: Update an assignment * tags: @@ -98,6 +119,12 @@ Router.post('/', isAuthorized, validator, AssignmentsController.post) * '200': * description: OK * parameters: + * - name: courseId + * in: path + * description: Enter course id + * required: true + * schema: + * type: integer * - name: id * in: path * required: true @@ -109,11 +136,11 @@ Router.post('/', isAuthorized, validator, AssignmentsController.post) * schema: * $ref: '#/components/schemas/Assignment' */ -Router.put('/:id', isAuthorized, asInt(), validator, AssignmentsController.put) +Router.put('/:id', isAuthorized('assignmentEditAll'), asInt(), validator, AssignmentsController.put) /** * @swagger - * /assignments/{id}: + * /course/:courseId/assignments/{id}: * delete: * summary: Delete an assignment * tags: @@ -122,12 +149,18 @@ Router.put('/:id', isAuthorized, asInt(), validator, AssignmentsController.put) * '200': * description: OK * parameters: + * - name: courseId + * in: path + * description: Enter course id + * required: true + * schema: + * type: integer * - name: id * in: path * required: true * schema: * type: integer */ -Router.delete('/:id', isAuthorized, asInt(), AssignmentsController._delete) +Router.delete('/:id', isAuthorized('assignmentEditAll'), asInt(), AssignmentsController._delete) export default Router diff --git a/devU-api/src/entities/assignment/assignment.service.ts b/devU-api/src/entities/assignment/assignment.service.ts index c41917cf..6fafa88b 100644 --- a/devU-api/src/entities/assignment/assignment.service.ts +++ b/devU-api/src/entities/assignment/assignment.service.ts @@ -1,65 +1,86 @@ -import { getRepository, IsNull } from 'typeorm' +import {getRepository, IsNull} from 'typeorm' import AssignmentModel from './assignment.model' -import { Assignment } from 'devu-shared-modules' +import {Assignment} from 'devu-shared-modules' +import {start} from "node:repl"; const connect = () => getRepository(AssignmentModel) export async function create(assignment: Assignment) { - return await connect().save(assignment) + return await connect().save(assignment) } export async function update(assignment: Assignment) { - const { - id, - name, - startDate, - dueDate, - endDate, - categoryName, - description, - maxFileSize, - maxSubmissions, - disableHandins, - } = assignment - - if (!id) throw new Error('Missing Id') - - return await connect().update(id, { - name, - startDate, - dueDate, - endDate, - categoryName, - description, - maxFileSize, - maxSubmissions, - disableHandins, - }) + const { + id, + name, + startDate, + dueDate, + endDate, + categoryName, + description, + maxFileSize, + maxSubmissions, + disableHandins, + } = assignment + + if (!id) throw new Error('Missing Id') + + return await connect().update(id, { + name, + startDate, + dueDate, + endDate, + categoryName, + description, + maxFileSize, + maxSubmissions, + disableHandins, + }) } export async function _delete(id: number) { - return await connect().softDelete({ id, deletedAt: IsNull() }) + return await connect().softDelete({id, deletedAt: IsNull()}) } -export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) +export async function retrieve(id: number, courseId: number) { + return await connect().findOne({id, deletedAt: IsNull()}) } export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().find({deletedAt: IsNull()}) } export async function listByCourse(courseId: number) { - return await connect().find({ courseId, deletedAt: IsNull() }) + return await connect().find({'courseId': courseId, deletedAt: IsNull()}) +} + +export async function listReleased(courseId: number) { + // TODO: filter by start date after current time + return await connect().find({'courseId': courseId, deletedAt: IsNull()}) +} + + +export async function isReleased(id: number) { + const assignment = await connect().findOne({id, deletedAt: IsNull()}) + + if (!assignment) { + return false + } + + const startDate = assignment?.startDate + const currentDate = new Date(Date.now()) + + return startDate && startDate < currentDate } export default { - create, - retrieve, - update, - _delete, - list, - listByCourse, + create, + retrieve, + update, + _delete, + list, + listByCourse, + isReleased, } diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts index 93bd1022..2de14416 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts @@ -8,7 +8,7 @@ import { serialize } from './assignmentProblem.serializer' export async function get(req: Request, res: Response, next: NextFunction) { try { - const assignmentId = parseInt(req.params.id) + const assignmentId = parseInt(req.params.assignmentId) const assignmentProblems = await AssignmentProblemService.list(assignmentId) const response = assignmentProblems.map(serialize) diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts index f7e96bd3..8dbc1676 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts @@ -3,7 +3,9 @@ import express from 'express' // Middleware import validator from './assignmentProblem.validator' -import { asInt } from '../../middleware/validator/generic.validator' +import {asInt} from '../../middleware/validator/generic.validator' +import {isAuthorized, isAuthorizedByAssignmentStatus} from "../../authorization/authorization.middleware"; + // Controller import AssignmentProblemController from './assignmentProblem.controller' @@ -12,7 +14,7 @@ const Router = express.Router() /** * @swagger - * /assignment-problems/{assignment-id}: + * /course/:courseId/assignment/:assignmentId/assignment-problems: * get: * summary: Retrieve a list of assignment problems belonging to an assignment by assignment id * tags: @@ -21,18 +23,18 @@ const Router = express.Router() * '200': * description: OK * parameters: - * - name: assignment-id + * - name: assignmentId * description: Enter Assignment Id * in: path * required: true * schema: - * type: integer + * type: integer */ -Router.get('/:id', asInt(), AssignmentProblemController.get) +Router.get('/', isAuthorizedByAssignmentStatus, asInt(), AssignmentProblemController.get) /** * @swagger - * /assignment-problems/detail/{id}: + * /course/:courseId/assignment/:assignmentId/assignment-problems/{id}: * get: * summary: Retrieve a single assignment problem's details * tags: @@ -46,13 +48,13 @@ Router.get('/:id', asInt(), AssignmentProblemController.get) * in: path * required: true * schema: - * type: integer + * type: integer */ -Router.get('/detail/:id', asInt(), AssignmentProblemController.detail) +Router.get('/:id', isAuthorizedByAssignmentStatus, asInt(), AssignmentProblemController.detail) /** * @swagger - * /assignment-problems: + * /course/:courseId/assignment/:assignmentId/assignment-problems: * post: * summary: Create an assignment problem * tags: @@ -66,11 +68,11 @@ Router.get('/detail/:id', asInt(), AssignmentProblemController.detail) * schema: * $ref: '#/components/schemas/AssignmentProblem' */ -Router.post('/', validator, AssignmentProblemController.post) +Router.post('/', isAuthorized('assignmentEditAll'), validator, AssignmentProblemController.post) /** * @swagger - * /assignment-problems: + * /course/:courseId/assignment/:assignmentId/assignment-problems: * put: * summary: Update an assignment problem * tags: @@ -84,11 +86,11 @@ Router.post('/', validator, AssignmentProblemController.post) * schema: * $ref: '#/components/schemas/AssignmentProblem' */ -Router.put('/:id', asInt(), validator, AssignmentProblemController.put) +Router.put('/:id', isAuthorized('assignmentEditAll'), asInt(), validator, AssignmentProblemController.put) /** * @swagger - * /assignment-problems/{id}: + * /course/:courseId/assignment/:assignmentId/assignment-problems/{id}: * delete: * summary: Delete an assignment problem * tags: @@ -102,6 +104,6 @@ Router.put('/:id', asInt(), validator, AssignmentProblemController.put) * schema: * $ref: '#/components/schemas/AssignmentProblem' */ -Router.delete('/:id', asInt(), AssignmentProblemController._delete) +Router.delete('/:id', isAuthorized('assignmentEditAll'), asInt(), AssignmentProblemController._delete) export default Router diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts index c4fb0e0c..7b2e9887 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts @@ -4,6 +4,7 @@ import express from 'express' //Middleware import validator from './assignmentScore.validator' import { asInt } from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; //Controller import AssignmentScoreController from './assignmentScore.controller' @@ -12,27 +13,27 @@ const Router = express.Router() /** * @swagger - * /assignment-scores/{assignment-id}: + * /course/:courseId/assignment-scores: * get: - * summary: Retrieve a list of assignment scores belonging to an assignment by assignment id + * summary: Retrieve a list of assignment scores belonging to a course * tags: * - AssignmentScore * responses: * '200': * description: OK * parameters: - * - name: assignment-id + * - name: assignmentId TODO * description: Enter Assignment Id * in: path * required: true * schema: * type: integer */ -Router.get('/:id', asInt(), AssignmentScoreController.get) +Router.get('/:id', isAuthorized('scoresViewAll'), asInt(), AssignmentScoreController.getByCourse) /** * @swagger - * /assignment-scores/detail/{id}: + * /course/:courseId/assignment-scores/{id}: * get: * summary: Retrieve a single assignment score's details * tags: @@ -48,11 +49,11 @@ Router.get('/:id', asInt(), AssignmentScoreController.get) * schema: * type: integer */ -Router.get('/detail/:id', asInt(), AssignmentScoreController.detail) +Router.get('/:id', isAuthorizedIfSelf(''), asInt(), AssignmentScoreController.detail) /** * @swagger - * /assignment-scores/user/{user-id}: + * /course/:courseId/assignment-scores/user/{userId}: * get: * summary: Retrieve a list of assignment scores belonging to a user * tags: @@ -61,18 +62,18 @@ Router.get('/detail/:id', asInt(), AssignmentScoreController.detail) * '200': * description: OK * parameters: - * - name: user-id + * - name: userId * description: Enter User Id * in: path * required: true * schema: * type: integer */ -Router.get('/user/:id', asInt(), AssignmentScoreController.getByUser) +Router.get('/user/:id', isAuthorizedIfSelf(''), asInt(), AssignmentScoreController.getByUser) /** * @swagger - * /assignment-scores/detail/{assignment-id}/{user-id}: + * /course/:courseId/assignment-scores/detail/{assignment-id}/{user-id}: * get: * summary: Retrieve an assignment score belonging to a specific assignment and user * tags: @@ -94,31 +95,12 @@ Router.get('/user/:id', asInt(), AssignmentScoreController.getByUser) * schema: * type: integer */ -Router.get('/detail/:id/:userId', asInt(), asInt('userId'), AssignmentScoreController.detailByUser) +Router.get('/detail/:id/:userId', isAuthorized(''), asInt(), asInt('userId'), AssignmentScoreController.detailByUser) -/** - * @swagger - * /assignment-scores/course/{course-id}: - * get: - * summary: Retrieve a list of assignment scores belonging to a user - * tags: - * - AssignmentScore - * responses: - * '200': - * description: OK - * parameters: - * - name: course-id - * description: Enter Course Id - * in: path - * required: true - * schema: - * type: integer - */ -Router.get('/course/:id', asInt(), AssignmentScoreController.getByCourse) /** * @swagger - * /assignment-scores: + * /course/:courseId/assignment-scores: * post: * summary: Create an assignment score * tags: @@ -132,11 +114,11 @@ Router.get('/course/:id', asInt(), AssignmentScoreController.getByCourse) * schema: * $ref: '#/components/schemas/AssignmentScore' */ -Router.post('/', validator, AssignmentScoreController.post) +Router.post('/', isAuthorized('scoresEditAll'), validator, AssignmentScoreController.post) /** * @swagger - * /assignment-scores/{id}: + * /course/:courseId/assignment-scores/{id}: * put: * summary: Update an assignment score * tags: @@ -156,11 +138,11 @@ Router.post('/', validator, AssignmentScoreController.post) * schema: * $ref: '#/components/schemas/AssignmentScore' */ -Router.put('/:id', asInt(), validator, AssignmentScoreController.put) +Router.put('/:id', isAuthorized('scoresEditAll'), asInt(), validator, AssignmentScoreController.put) /** * @swagger - * /assignment-scores/{id}: + * /course/:courseId/assignment-scores/{id}: * delete: * summary: Delete an assignment score * tags: @@ -175,6 +157,6 @@ Router.put('/:id', asInt(), validator, AssignmentScoreController.put) * schema: * type: integer */ -Router.delete('/:id', asInt(), AssignmentScoreController._delete) +Router.delete('/:id', isAuthorized('scoresEditAll'), asInt(), AssignmentScoreController._delete) export default Router \ No newline at end of file diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.service.ts b/devU-api/src/entities/assignmentScore/assignmentScore.service.ts index 4ab2aecf..a62952c0 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.service.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.service.ts @@ -27,11 +27,11 @@ export async function retrieve(id: number) { return await connect().findOne({ id, deletedAt: IsNull() }) } -export async function list(assignmentId: number) { +export async function list(assignmentId: number) { // TODO: There's no way this is right. Test to verify that it should be 'assignmentId': assignmentId return await connect().find({ assignmentId, deletedAt: IsNull() }) } -export async function retrieveByUser(assignmentId: number, userId: number) { +export async function retrieveByUser(assignmentId: number, userId: number) { //TODO: This can't be right.. can it? return await connect().findOne({ assignmentId, userId, deletedAt: IsNull() }) } diff --git a/devU-api/src/entities/category/category.router.ts b/devU-api/src/entities/category/category.router.ts index 01d8fc3d..6a848eea 100644 --- a/devU-api/src/entities/category/category.router.ts +++ b/devU-api/src/entities/category/category.router.ts @@ -2,27 +2,16 @@ import express from 'express' import validator from './category.validator' import { asInt } from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; import CategoryController from './category.controller' const Router = express.Router() -/** - * @swagger - * /categories: - * get: - * summary: Retrieve a list of categories - * tags: - * - Categories - * responses: - * '200': - * description: OK - */ -Router.get('/', CategoryController.get) /** * @swagger - * /categories/{id}: + * /course/:courseId/categories/{id}: * get: * summary: Retrieve a single category * tags: @@ -38,11 +27,11 @@ Router.get('/', CategoryController.get) * schema: * type: integer */ -Router.get('/:id', asInt(), CategoryController.detail) +Router.get('/:id', isAuthorized('enrolled'), asInt(), CategoryController.detail) /** * @swagger - * /categories/course/{courseId}: + * /course/:courseId/categories/: * get: * summary: Retrieve a list of categories by courseId * tags: @@ -58,11 +47,11 @@ Router.get('/:id', asInt(), CategoryController.detail) * schema: * type: integer */ -Router.get('/course/:courseId', asInt('courseId'), CategoryController.getByCourse) +Router.get('/', isAuthorized('enrolled'), CategoryController.getByCourse) /** * @swagger - * /categories: + * /course/:courseId/categories: * post: * summary: Create a category * tags: @@ -76,11 +65,11 @@ Router.get('/course/:courseId', asInt('courseId'), CategoryController.getByCours * schema: * $ref: '#/components/schemas/Category' */ -Router.post('/', validator, CategoryController.post) +Router.post('/', isAuthorized('courseEdit'), validator, CategoryController.post) /** * @swagger - * /categories: + * /course/:courseId/categories: * put: * summary: Update a category * tags: @@ -100,11 +89,11 @@ Router.post('/', validator, CategoryController.post) * schema: * $ref: '#/components/schemas/Category' */ -Router.put('/:id', asInt(), validator, CategoryController.put) +Router.put('/:id', isAuthorized('courseEdit'), asInt(), validator, CategoryController.put) /** * @swagger - * /categories/{id}: + * /course/:courseId/categories/{id}: * delete: * summary: Delete a category * tags: @@ -119,6 +108,6 @@ Router.put('/:id', asInt(), validator, CategoryController.put) * schema: * type: integer */ -Router.delete('/:id', asInt(), CategoryController._delete) +Router.delete('/:id', isAuthorized('courseEdit'), asInt(), CategoryController._delete) export default Router \ No newline at end of file diff --git a/devU-api/src/entities/category/category.service.ts b/devU-api/src/entities/category/category.service.ts index 673b31ff..4a8200eb 100644 --- a/devU-api/src/entities/category/category.service.ts +++ b/devU-api/src/entities/category/category.service.ts @@ -28,7 +28,7 @@ export async function list() { return await connect().find({ deletedAt: IsNull() }) } -export async function listByCourse(courseId: number) { +export async function listByCourse(courseId: number) { // TODO? return await connect().find({ courseId, deletedAt: IsNull() }) } diff --git a/devU-api/src/entities/categoryScore/categoryScore.router.ts b/devU-api/src/entities/categoryScore/categoryScore.router.ts index b378e202..bc5ad215 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.router.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.router.ts @@ -4,6 +4,7 @@ import express from 'express' //validators import validator from './categoryScore.validator' import { asInt } from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; //Controller import CategoryScoreController from './categoryScore.controller' @@ -12,7 +13,7 @@ const Router = express.Router() /** * @swagger - * /category-score: + * /course/:courseId/category-score: * get: * summary: Retrieve a list of all category scores * tags: @@ -21,12 +22,12 @@ const Router = express.Router() * '200': * description: OK */ -Router.get('/', CategoryScoreController.get) +Router.get('/', isAuthorized('scoresViewAll'), CategoryScoreController.getByCourse) /** * @swagger - * /category-score/{id}: + * /course/:courseId/category-score/{id}: * get: * summary: Retrieve a single category score * tags: @@ -41,11 +42,11 @@ Router.get('/', CategoryScoreController.get) * schema: * type: integer */ -Router.get('/:id', asInt(), CategoryScoreController.detail) +Router.get('/:id', isAuthorized('scoresViewSelfReleased'), asInt(), CategoryScoreController.detail) /** * @swagger - * /category-score: + * /course/:courseId/category-score: * post: * summary: Create a category score * tags: @@ -59,11 +60,11 @@ Router.get('/:id', asInt(), CategoryScoreController.detail) * schema: * $ref: '#/components/schemas/CategoryScore' */ -Router.post('/', validator, CategoryScoreController.post) +Router.post('/', isAuthorized('scoresEditAll'), validator, CategoryScoreController.post) /** * @swagger - * /category-score/{id}: + * /course/:courseId/category-score/{id}: * put: * summary: Update a category score * tags: @@ -83,11 +84,11 @@ Router.post('/', validator, CategoryScoreController.post) * schema: * $ref: '#/components/schemas/CategoryScore' */ -Router.put('/:id', validator, asInt(), CategoryScoreController.put) +Router.put('/:id', isAuthorized('scoresEditAll'), validator, asInt(), CategoryScoreController.put) /** * @swagger - * /category-score/{id}: + * /course/:courseId/category-score/{id}: * delete: * summary: Delete a category score * tags: @@ -102,6 +103,6 @@ Router.put('/:id', validator, asInt(), CategoryScoreController.put) * schema: * type: integer */ -Router.delete('/:id', asInt(), CategoryScoreController._delete) +Router.delete('/:id', isAuthorized('scoresEditAll'), asInt(), CategoryScoreController._delete) export default Router \ No newline at end of file diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts index 398bb016..71543113 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts @@ -3,6 +3,7 @@ import multer from 'multer'; import validator from './containerAutoGrader.validator'; import { asInt } from '../../middleware/validator/generic.validator'; +import {isAuthorized} from "../../authorization/authorization.middleware"; import ContainerAutoGraderController from './containerAutoGrader.controller'; @@ -11,7 +12,7 @@ const upload = multer(); /** * @swagger - * /container-auto-graders: + * /course/:courseId/assignment/:assignmentId/container-auto-graders: * get: * summary: Retrieve a list of all container auto graders * tags: @@ -20,11 +21,11 @@ const upload = multer(); * '200': * description: OK */ -Router.get('/', ContainerAutoGraderController.get); +Router.get('/', isAuthorized('assignmentViewAll'), ContainerAutoGraderController.get); /** * @swagger - * /container-auto-graders/{id}: + * /course/:courseId/assignment/:assignmentId/container-auto-graders/{id}: * get: * summary: Retrieve a single container auto grader * tags: @@ -39,11 +40,11 @@ Router.get('/', ContainerAutoGraderController.get); * schema: * type: integer */ -Router.get('/:id', asInt(), ContainerAutoGraderController.detail); +Router.get('/:id',isAuthorized('assignmentViewAll'), asInt(), ContainerAutoGraderController.detail); /** * @swagger - * /container-auto-graders: + * /course/:courseId/assignment/:assignmentId/container-auto-graders: * post: * summary: Create a new container auto grader * tags: @@ -57,11 +58,11 @@ Router.get('/:id', asInt(), ContainerAutoGraderController.detail); * schema: * $ref: '#/components/schemas/ContainerAutoGrader' */ -Router.post('/', upload.fields([{name: 'graderFile'},{name: 'makefileFile'}]), validator, ContainerAutoGraderController.post); +Router.post('/', isAuthorized('assignmentEditAll'), upload.fields([{name: 'graderFile'},{name: 'makefileFile'}]), validator, ContainerAutoGraderController.post); /** * @swagger - * /container-auto-graders/{id}: + * /course/:courseId/assignment/:assignmentId/container-auto-graders/{id}: * put: * summary: Update a container auto grader's grader file and/or makefile * tags: @@ -81,11 +82,11 @@ Router.post('/', upload.fields([{name: 'graderFile'},{name: 'makefileFile'}]), v * schema: * $ref: '#/components/schemas/ContainerAutoGrader' */ -Router.put('/:id', asInt(), upload.fields([{name: 'graderFile'},{name: 'makefileFile'}]), validator, ContainerAutoGraderController.put); +Router.put('/:id', isAuthorized('assignmentEditAll'), asInt(), upload.fields([{name: 'graderFile'},{name: 'makefileFile'}]), validator, ContainerAutoGraderController.put); /** * @swagger - * /container-auto-graders/{id}: + * /course/:courseId/assignment/:assignmentId/container-auto-graders/{id}: * delete: * summary: Delete a container auto grader * tags: @@ -100,6 +101,6 @@ Router.put('/:id', asInt(), upload.fields([{name: 'graderFile'},{name: 'makefile * schema: * type: integer */ -Router.delete('/:id', asInt(), ContainerAutoGraderController._delete); +Router.delete('/:id', isAuthorized('assignmentEditAll'), asInt(), ContainerAutoGraderController._delete); export default Router; \ No newline at end of file diff --git a/devU-api/src/entities/course/course.model.ts b/devU-api/src/entities/course/course.model.ts index 380ea7ce..5dbffff6 100644 --- a/devU-api/src/entities/course/course.model.ts +++ b/devU-api/src/entities/course/course.model.ts @@ -33,6 +33,8 @@ export default class CourseModel { @PrimaryGeneratedColumn() id: number + // TODO: Do we add OG instructor here so creator can have all permissions and also can't be removed from the course? + @Column({ length: 128 }) name: string diff --git a/devU-api/src/entities/course/course.router.ts b/devU-api/src/entities/course/course.router.ts index 188bbf0b..c3b58e99 100644 --- a/devU-api/src/entities/course/course.router.ts +++ b/devU-api/src/entities/course/course.router.ts @@ -4,6 +4,7 @@ import express from 'express' // Middleware import validator from './course.validator' import { asInt } from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; // Controller import CourseController from './course.controller' @@ -21,11 +22,24 @@ const Router = express.Router() * '200': * description: OK */ -Router.get('/', CourseController.get) +Router.get('/', isAuthorized(''), CourseController.get) /** * @swagger - * /courses/{id}: + * /courses/user/{userId}: + * get: + * summary: Retrieve a list of courses by user + * tags: + * - Courses + * responses: + * '200': + * description: OK + */ +Router.get('/user/:userId', isAuthorized(''), CourseController.getByUser) // TODO + +/** + * @swagger + * /courses/{courseId}: * get: * summary: Retrieve a single course * tags: @@ -40,7 +54,7 @@ Router.get('/', CourseController.get) * schema: * type: integer */ -Router.get('/:id', asInt(), CourseController.detail) +Router.get('/:courseId', isAuthorized('enrolled'), asInt('courseId'), CourseController.detail) /** * @swagger @@ -58,11 +72,31 @@ Router.get('/:id', asInt(), CourseController.detail) * schema: * $ref: '#/components/schemas/Course' */ -Router.post('/', validator, CourseController.post) +Router.post('/', /*isAuthorized('admin..'),*/ validator, CourseController.post) +// TODO: Eventually, only admins can create courses. For now, anyone can create their own courses and they gain all permissions for that course + +/** + * @swagger + * /courses: + * post: + * summary: Creates a course and adds the requester as an instructor in the course. Intended to be used during + * development only. Production will have an admin who can create courses and add the first instructor + * tags: + * - Courses + * responses: + * '200': + * description: OK + * requestBody: + * content: + * application/x-www-form-urlencoded: + * schema: + * $ref: '#/components/schemas/Course' + */ +Router.post('/instructor', validator, CourseController.postAddInstructor) /** * @swagger - * /courses/{id}: + * /courses/{courseId}: * put: * summary: Update a course * tags: @@ -71,7 +105,7 @@ Router.post('/', validator, CourseController.post) * '200': * description: OK * parameters: - * - name: id + * - name: courseId * in: path * required: true * schema: @@ -82,11 +116,11 @@ Router.post('/', validator, CourseController.post) * schema: * $ref: '#/components/schemas/Course' */ -Router.put('/:id', asInt(), validator, CourseController.put) +Router.put('/:courseId', isAuthorized('courseEdit'), asInt('courseId'), validator, CourseController.put) /** * @swagger - * /courses/{id}: + * /courses/{courseId}: * delete: * summary: Delete a course * tags: @@ -95,12 +129,12 @@ Router.put('/:id', asInt(), validator, CourseController.put) * '200': * description: OK * parameters: - * - name: id + * - name: courseId * in: path * required: true * schema: * type: integer */ -Router.delete('/:id', asInt(), CourseController._delete) +Router.delete('/:courseId', isAuthorized('courseEdit'), asInt('courseId'), CourseController._delete) export default Router diff --git a/devU-api/src/entities/courseScore/courseScore.router.ts b/devU-api/src/entities/courseScore/courseScore.router.ts index 382acefa..41857bea 100644 --- a/devU-api/src/entities/courseScore/courseScore.router.ts +++ b/devU-api/src/entities/courseScore/courseScore.router.ts @@ -13,7 +13,7 @@ const Router = express.Router() /** * @swagger - * /course-score: + * /course/:courseId/course-score: * get: * summary: Retrieve a list of all course scores * tags: @@ -22,12 +22,12 @@ const Router = express.Router() * '200': * description: OK */ -Router.get('/', isAuthorized, CourseScoreController.get) +Router.get('/', isAuthorized('scoresViewAll'), CourseScoreController.get) /** * @swagger - * /course-score/{id}: + * /course/:courseId/course-score/{id}: * get: * summary: Retrieve a single course score * tags: @@ -42,11 +42,11 @@ Router.get('/', isAuthorized, CourseScoreController.get) * schema: * type: integer */ -Router.get('/:id', asInt(), CourseScoreController.detail) +Router.get('/:id', isAuthorized(''), asInt(), CourseScoreController.detail) /** * @swagger - * /course-score: + * /course/:courseId/course-score: * post: * summary: Create a course score * tags: @@ -60,11 +60,11 @@ Router.get('/:id', asInt(), CourseScoreController.detail) * schema: * $ref: '#/components/schemas/CourseScore' */ -Router.post('/', validator, CourseScoreController.post) +Router.post('/', isAuthorized('scoresEditAll'), validator, CourseScoreController.post) /** * @swagger - * /course-score/{id}: + * /course/:courseId/course-score/{id}: * put: * summary: Update a course score * tags: @@ -84,11 +84,11 @@ Router.post('/', validator, CourseScoreController.post) * schema: * $ref: '#/components/schemas/CourseScore' */ -Router.put('/:id', validator, asInt(), CourseScoreController.put) +Router.put('/:id', isAuthorized('scoresEditAll'), validator, asInt(), CourseScoreController.put) /** * @swagger - * /course-score/{id}: + * /course/:courseId/course-score/{id}: * delete: * summary: Delete a course score * tags: @@ -103,6 +103,6 @@ Router.put('/:id', validator, asInt(), CourseScoreController.put) * schema: * type: integer */ -Router.delete('/:id', asInt(), CourseScoreController._delete) +Router.delete('/:id', isAuthorized('scoresEditAll'), asInt(), CourseScoreController._delete) export default Router diff --git a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts index 0dc93e54..4226e9ca 100644 --- a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts +++ b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts @@ -4,6 +4,7 @@ import express from 'express' // Middleware import validator from './deadlineExtensions.validator' import { asInt } from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; // Controller import DeadlineExtensionsController from './deadlineExtensions.controller' @@ -12,7 +13,7 @@ const Router = express.Router() /** * @swagger - * /deadline-extensions: + * /course/:courseId/assignment/:assignmentId/deadline-extensions: * get: * summary: Retrieve all deadline-extensions * tags: @@ -21,11 +22,11 @@ const Router = express.Router() * '200': * description: OK */ -Router.get('/', DeadlineExtensionsController.get) +Router.get('/', isAuthorized('assignmentViewAll'), DeadlineExtensionsController.get) /** * @swagger - * /deadline-extensions/{id}: + * /course/:courseId/assignment/:assignmentId/deadline-extensions/{id}: * get: * summary: Retrieve a single Deadline-Extension * tags: @@ -40,11 +41,12 @@ Router.get('/', DeadlineExtensionsController.get) * schema: * type: integer */ -Router.get('/:id', asInt(), DeadlineExtensionsController.detail) +Router.get('/:id', isAuthorized(''), asInt(), DeadlineExtensionsController.detail) +// TODO: self or assignmentViewAll /** * @swagger - * /deadline-extensions: + * /course/:courseId/assignment/:assignmentId/deadline-extensions: * post: * summary: Create a Deadline-Extension * tags: @@ -58,11 +60,11 @@ Router.get('/:id', asInt(), DeadlineExtensionsController.detail) * schema: * $ref: '#/components/schemas/DeadlineExtensionsModel' */ -Router.post('/', validator, DeadlineExtensionsController.post) +Router.post('/', isAuthorized('assignmentEditAll'), validator, DeadlineExtensionsController.post) /** * @swagger - * /deadline-extensions/{id}: + * /course/:courseId/assignment/:assignmentId/deadline-extensions/{id}: * put: * summary: Update a Deadline-Extensions * tags: @@ -82,11 +84,11 @@ Router.post('/', validator, DeadlineExtensionsController.post) * schema: * $ref: '#/components/schemas/DeadlineExtensionsModel' */ -Router.put('/:id', asInt(), validator, DeadlineExtensionsController.put) +Router.put('/:id', isAuthorized('assignmentEditAll'), asInt(), validator, DeadlineExtensionsController.put) /** * @swagger - * /deadline-extensions/{id}: + * /course/:courseId/assignment/:assignmentId/deadline-extensions/{id}: * delete: * summary: Delete a Deadline-Extension * tags: @@ -101,6 +103,6 @@ Router.put('/:id', asInt(), validator, DeadlineExtensionsController.put) * schema: * type: integer */ -Router.delete('/:id', asInt(), DeadlineExtensionsController._delete) +Router.delete('/:id', isAuthorized('assignmentEditAll'), asInt(), DeadlineExtensionsController._delete) export default Router diff --git a/devU-api/src/entities/grader/grader.router.ts b/devU-api/src/entities/grader/grader.router.ts index 1ed309a8..35002d20 100644 --- a/devU-api/src/entities/grader/grader.router.ts +++ b/devU-api/src/entities/grader/grader.router.ts @@ -4,6 +4,7 @@ import express from 'express' // Middleware //import validator from './grader.validator' import { asInt } from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; // Controller import GraderController from './grader.controller' @@ -15,7 +16,7 @@ const Router = express.Router() * tags: * - name: Grader * description: - * /grade/{id}: + * /course/:courseId/grade/{id}: * post: * summary: Grade a submission, currently only with non-container autograders * tags: @@ -30,6 +31,12 @@ const Router = express.Router() * schema: * type: integer */ -Router.post('/:id', asInt(), GraderController.grade) +Router.post('/:id', isAuthorized('enrolled'), asInt(), GraderController.grade) +// TODO: This one might be tricky. Can every student hit this endpoint for their own submissions? That's probably +// the easiest way to handle it. If not, how does this endpoint get hit when they make a submission without +// sacrificing RESTfullness? eg. Sometimes we want to make submissions without running the grader so the front end +// should have control of this endpoint +// -or- do we only let students hit this once per submission. Regrades must be by someone with permission. Then +// add a second endpoint that is /regrade export default Router \ No newline at end of file diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts index 55e253a9..af2aa05a 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts @@ -3,29 +3,18 @@ import express from 'express' // Middleware import validator from './nonContainerAutoGrader.validator' -import { asInt } from '../../middleware/validator/generic.validator' +import {asInt} from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; // Controller import nonContainerQuestions from './nonContainerAutoGrader.controller' const Router = express.Router() -/** - * @swagger - * /nonContainerAutoGraders: - * get: - * summary: Retrieve a list of all nonContainerAutoGraders - * tags: - * - NonContainerAutoGraders - * responses: - * '200': - * description: OK - */ -Router.get('/', nonContainerQuestions.get) /** * @swagger - * /nonContainerAutoGraders/byAssignmentId/{assignmentId}: + * /course/:courseId/assignment/:assignmentId/nonContainerAutoGraders: * get: * summary: Retrieve a list of all nonContainerAutoGrader with the assignment ID * tags: @@ -40,13 +29,13 @@ Router.get('/', nonContainerQuestions.get) * schema: * type: integer */ -Router.get('/byAssignmentId/:assignmentId', asInt("assignmentId"), nonContainerQuestions.getByAssignmentId) +Router.get('/', isAuthorized('assignmentViewAll'), nonContainerQuestions.getByAssignmentId) /** * @swagger - * /nonContainerAutoGraders/byId/{id}: + * /course/:courseId/assignment/:assignmentId/nonContainerAutoGraders/byId/{id}: * get: - * summary: Retrieve a single question + * summary: Retrieve a single non container auto grader * tags: * - NonContainerAutoGraders * responses: @@ -59,11 +48,11 @@ Router.get('/byAssignmentId/:assignmentId', asInt("assignmentId"), nonContainerQ * schema: * type: integer */ -Router.get('/byId/:id', asInt(), nonContainerQuestions.detail) +Router.get('/byId/:id', isAuthorized('assignmentViewAll'), asInt(), nonContainerQuestions.detail) /** * @swagger - * /nonContainerAutoGraders: + * /course/:courseId/assignment/:assignmentId/nonContainerAutoGraders: * post: * summary: Create a question * tags: @@ -77,11 +66,11 @@ Router.get('/byId/:id', asInt(), nonContainerQuestions.detail) * schema: * $ref: '#/components/schemas/NonContainerAutoGrader' */ -Router.post('/', validator, nonContainerQuestions.post) +Router.post('/', isAuthorized('assignmentEditAll'), validator, nonContainerQuestions.post) /** * @swagger - * /nonContainerAutoGraders: + * /course/:courseId/assignment/:assignmentId/nonContainerAutoGraders: * put: * summary: Update a question * tags: @@ -101,11 +90,11 @@ Router.post('/', validator, nonContainerQuestions.post) * schema: * $ref: '#/components/schemas/NonContainerAutoGrader' */ -Router.put('/:id', asInt(), validator, nonContainerQuestions.put) +Router.put('/:id', isAuthorized('assignmentEditAll'), asInt(), validator, nonContainerQuestions.put) /** * @swagger - * /nonContainerAutoGraders/{id}: + * /course/:courseId/assignment/:assignmentId/nonContainerAutoGraders/{id}: * delete: * summary: Delete a question * tags: @@ -120,6 +109,6 @@ Router.put('/:id', asInt(), validator, nonContainerQuestions.put) * schema: * type: integer */ -Router.delete('/:id', asInt(), nonContainerQuestions._delete) +Router.delete('/:id', isAuthorized('assignmentEditAll'), asInt(), nonContainerQuestions._delete) export default Router diff --git a/devU-api/src/entities/role/role.controller.ts b/devU-api/src/entities/role/role.controller.ts new file mode 100644 index 00000000..c01e1fe1 --- /dev/null +++ b/devU-api/src/entities/role/role.controller.ts @@ -0,0 +1,106 @@ +import { Request, Response, NextFunction } from 'express' + +import RoleService from './role.service' +import { serialize } from './role.serializer' + +import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' + +export async function getAll(req: Request, res: Response, next: NextFunction) { + try { + + const userCourses = await RoleService.listAll() + + res.status(200).json(userCourses.map(serialize)) + } catch (err) { + next(err) + } +} + +export async function get(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(req.params.id) + const userCourses = await RoleService.list(id) + + res.status(200).json(userCourses.map(serialize)) + } catch (err) { + next(err) + } +} + +export async function getByUser(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(req.params.id) + const userCourses = await RoleService.list(id) + + res.status(200).json(userCourses.map(serialize)) + } catch (err) { + next(err) + } +} + +export async function getByCourse(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(req.params.id) + const userCourses = await RoleService.listByCourse(id) + + const response = userCourses.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} + +export async function detail(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(req.params.id) + const userCourse = await RoleService.retrieve(id) + + if (!userCourse) return res.status(404).json(NotFound) + + const response = serialize(userCourse) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} + +export async function post(req: Request, res: Response, next: NextFunction) { + try { + const userCourse = await RoleService.create(req.body) + const response = serialize(userCourse) + + res.status(201).json(response) + } catch (err) { + res.status(400).json(new GenericResponse(err.message)) + } +} + +export async function put(req: Request, res: Response, next: NextFunction) { + try { + req.body.id = parseInt(req.params.id) + const results = await RoleService.update(req.body) + + if (!results.affected) return res.status(404).json(NotFound) + + res.status(200).json(Updated) + } catch (err) { + next(err) + } +} + +export async function _delete(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(req.params.id) + const results = await RoleService._delete(id) + + if (!results.affected) return res.status(404).json(NotFound) + + res.status(204).send() + } catch (err) { + next(err) + } +} + +export default { get, getByCourse, getByUser, getAll, detail, post, put, _delete } diff --git a/devU-api/src/entities/role/role.model.ts b/devU-api/src/entities/role/role.model.ts index 2e2f871b..d1284516 100644 --- a/devU-api/src/entities/role/role.model.ts +++ b/devU-api/src/entities/role/role.model.ts @@ -10,7 +10,6 @@ import { } from 'typeorm' import CourseModel from '../course/course.model' -import UserModel from '../user/user.model' @Entity('courseScore') export default class CourseScoreModel { @@ -35,35 +34,88 @@ export default class CourseScoreModel { @ManyToOne(() => CourseModel) courseId: number + // @Column({name: 'user_id'}) + // @JoinColumn({name: 'user_id'}) + // @ManyToOne(() => UserModel) + // userId: number - @Column({name: 'user_id'}) - @JoinColumn({name: 'user_id'}) - @ManyToOne(() => UserModel) - userId: number + @CreateDateColumn({name: 'created_at'}) + createdAt: Date + + @UpdateDateColumn({name: 'updated_at'}) + updatedAt: Date + + @DeleteDateColumn({name: 'deleted_at'}) + deletedAt?: Date @Column({name: 'name'}) - score: string + name: string - @Column({name: 'grades-view'}) - grades_view: boolean - @Column({name: 'grades-edit'}) - grades_edit: boolean + // All the permission options // - @Column({name: 'grades-view-self'}) - grades_viewSelf: boolean + // For default permission that everyone in the course should have + @Column({name: 'enrolled'}) + enrolled: boolean - @Column({name: 'grades-edit-self'}) - grades_editSelf: boolean + @Column({name: 'course_edit'}) + courseEdit: boolean - @CreateDateColumn({name: 'created_at'}) - createdAt: Date + @Column({name: 'course_view'}) + courseView: boolean - @UpdateDateColumn({name: 'updated_at'}) - updatedAt: Date + @Column({name: 'assignment_view_all'}) + assignmentViewAll: boolean + + @Column({name: 'assignment_edit_all'}) // includes category_edit + autograders_edit_all + assignmentEditAll: boolean + + // student perm + @Column({name: 'assignment_view_release'}) + assignmentViewReleased: boolean + + @Column({name: 'scores_view_all'}) + scoresViewAll: boolean + + @Column({name: 'scores_edit_all'}) + scoresEditAll: boolean + + // student perm + @Column({name: 'scores_view_self_released'}) + scoresViewSelfReleased: boolean + + @Column({name: 'role_edit_all'}) // TODO: can only delete a role if no one has that role. Some way of preventing the course from being soft-locked + roleEditAll: boolean + + @Column({name: 'role_view_all'}) + roleViewAll: boolean + + @Column({name: 'role_view_self'}) // everyone can do this so the front end knows what to render + roleViewSelf: boolean + + + @Column({name: 'submission_change_state'}) // For soft-soft delete. Marked as "doesn't count" but can still be viewed by the student + submissionChangeState: boolean + + @Column({name: 'submission_create_all'}) + submissionCreateAll: boolean + + // student perm + @Column({name: 'submission_create_self'}) + submissionCreateSelf: boolean + + @Column({name: 'submission_view_all'}) + submissionViewAll: boolean + + @Column({name: 'user_course_edit_all'}) // TODO: Don't let the last instructor change their role + userCourseEditAll: boolean + + + // TODO: Add the special roles + // -Student has the default permissions + // -Instructor has all permissions + // -TA.. choose the defaults for the TA role - @DeleteDateColumn({name: 'deleted_at'}) - deletedAt?: Date } diff --git a/devU-api/src/entities/role/role.router.ts b/devU-api/src/entities/role/role.router.ts new file mode 100644 index 00000000..b8e4563d --- /dev/null +++ b/devU-api/src/entities/role/role.router.ts @@ -0,0 +1,152 @@ +// Libraries +import express from 'express' + +// Middleware +import validator from './role.validator' +import {asInt} from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; + +// Controller +import RoleController from './role.controller' + +const Router = express.Router() + +/** + * @swagger + * /course/:courseId/roles: + * get: + * summary: List of all roles for a course + * tags: + * - Roles + * responses: + * '200': + * description: OK + */ +Router.get('/', isAuthorized('roleViewAll'), RoleController.getAll) + + +/** + * @swagger + * /course/:courseId/roles/user/{userId}: + * get: + * summary: Retrieve the role of a given user + * tags: + * - Roles + * responses: + * '200': + * description: OK + * parameters: + * - name: userId + * description: Enter User Id + * in: path + * required: true + * schema: + * type: integer + */ +Router.get('/user/:userId', asInt('userId'), isAuthorized(''), RoleController.getByUser) +// TODO: roleViewSelf or roleViewAll + +/** + * @swagger + * /course/:courseId/roles: + * get: + * summary: Retrieve a list of all of a course's roles. + * tags: + * - Roles + * responses: + * '200': + * description: OK + * parameters: + * - name: courseId + * description: Enter Course Id + * in: path + * required: true + * schema: + * type: integer + */ +Router.get('/', isAuthorized('roleViewAll'), RoleController.getByCourse) + +/** + * @swagger + * /course/:courseId/roles/{id}: + * get: + * summary: Retrieve a single role by id + * tags: + * - Roles + * responses: + * '200': + * description: OK + * parameters: + * - name: id + * description: Enter role id + * in: path + * required: true + * schema: + * type: integer + */ +Router.get('/:id', isAuthorized('roleViewAll'), asInt(), RoleController.detail) +// TODO: make sure all details are checking the courseId. Add matching 'detailByCourse' for each and only admin can hit 'detail'? + + +/** + * @swagger + * /course/:courseId/roles: + * post: + * summary: Create a new role + * tags: + * - Roles + * responses: + * '200': + * description: OK + * requestBody: + * content: + * application/x-www-form-urlencoded: + * schema: + * $ref: '#/components/schemas/Role' + */ +Router.post('/', isAuthorized('roleEditAll'), validator, RoleController.post) + +/** + * @swagger + * /course/:courseId/roles/{id}: + * put: + * summary: Update a role association + * tags: + * - Roles + * responses: + * '200': + * description: OK + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: integer + * requestBody: + * content: + * application/x-www-form-urlencoded: + * schema: + * $ref: '#/components/schemas/Role' + */ +Router.put('/:id', isAuthorized('roleEditAll'), asInt(), validator, RoleController.put) + +/** + * @swagger + * /course/:courseId/roles/{id}: + * delete: + * summary: Delete a role + * tags: + * - Roles + * responses: + * '200': + * description: OK + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: integer + */ +Router.delete('/:id', isAuthorized('roleEditAll'), asInt(), RoleController._delete) + +export default Router diff --git a/devU-api/src/entities/role/role.serializer.ts b/devU-api/src/entities/role/role.serializer.ts new file mode 100644 index 00000000..3c94bc3a --- /dev/null +++ b/devU-api/src/entities/role/role.serializer.ts @@ -0,0 +1,50 @@ +import { Role } from 'devu-shared-modules' + +import RoleModel from './role.model' + +export function serialize(role: RoleModel): Role { + return { + id: role.id, + createdAt: role.createdAt.toISOString(), + updatedAt: role.updatedAt.toISOString(), + name: role.name, + + enrolled: role.enrolled, + courseEdit: role.courseEdit, + courseView: role.courseView, + assignmentViewAll: role.assignmentViewAll, + assignmentEditAll: role.assignmentEditAll, + assignmentViewReleased: role.assignmentViewReleased, + scoresViewAll: role.scoresViewAll, + scoresEditAll: role.scoresEditAll, + scoresViewSelfReleased: role.scoresViewSelfReleased, + roleEditAll: role.roleEditAll, + roleViewAll: role.roleViewAll, + roleViewSelf: role.roleViewSelf, + submissionCreateAll: role.submissionCreateAll, + submissionChangeState: role.submissionChangeState, + submissionCreateSelf: role.submissionCreateSelf, + submissionViewAll: role.submissionViewAll, + userCourseEditAll: role.userCourseEditAll, + } +} + +// courseEdit: boolean +// assignmentViewAll: boolean +// assignmentEditAll: boolean +// assignmentViewReleased: boolean +// scoresViewAll: boolean +// scoresEditAll: boolean +// scoresViewSelfReleased: boolean +// roleEditAll: boolean +// roleViewAll: boolean +// roleViewSelf: boolean +// submissionCreateAll: boolean +// submissionChangeState: boolean +// submissionCreateSelf: boolean +// usercourseEditAll: boolean +// +// const x = 'name' +// const y = ['course_edit','assignment_view_all','assignment_edit_all','assignment_view_release','scores_view_all','scores_edit_all','scores_view_self_released','role_edit_all','role_view_all','role_view_self','submission_create_all','submission_change_state','submission_create_self', 'usercourse_edit_all'] +// +// console.log(x, y) \ No newline at end of file diff --git a/devU-api/src/entities/role/role.service.ts b/devU-api/src/entities/role/role.service.ts new file mode 100644 index 00000000..4c42e75d --- /dev/null +++ b/devU-api/src/entities/role/role.service.ts @@ -0,0 +1,53 @@ +import { getRepository, IsNull } from 'typeorm' + +import { Role as RoleType } from 'devu-shared-modules' + +import Role from './role.model' + +const connect = () => getRepository(Role) + +export async function create(role: RoleType) { + return await connect().save(role) +} + +export async function update(role: RoleType) { + const { id, level, dropped } = role + + if (!id) throw new Error('Missing Id') + + return await connect().update(id, { level, dropped }) +} + +export async function _delete(id: number) { + return await connect().softDelete({ id, deletedAt: IsNull() }) +} + +export async function retrieve(id: number) { + return await connect().findOne({id, deletedAt: IsNull()}) +} + +export async function retrieveByCourseAndName(courseId: number, name: string) { + return await connect().findOne({ 'courseId': courseId, 'name': name, deletedAt: IsNull() }) +} + +export async function list(userId: number) { + return await connect().find({ userId, deletedAt: IsNull() }) +} +export async function listAll() { + return await connect().find({ deletedAt: IsNull() }) +} + +export async function listByCourse(courseId: number) { + return await connect().find({ 'courseId': courseId, deletedAt: IsNull() }) +} + +export default { + create, + retrieve, + retrieveByCourseAndName, + update, + _delete, + list, + listAll, + listByCourse, +} diff --git a/devU-api/src/entities/role/role.validator.ts b/devU-api/src/entities/role/role.validator.ts new file mode 100644 index 00000000..d3464d8d --- /dev/null +++ b/devU-api/src/entities/role/role.validator.ts @@ -0,0 +1,18 @@ +import { check } from 'express-validator' + +import { userCourseLevels } from 'devu-shared-modules' + +import validate from '../../middleware/validator/generic.validator' + +const userId = check('userId').isNumeric() +const courseId = check('courseId').isNumeric() +const dropped = check('dropped').isBoolean() + +// TODO: Check if role is valid for the course +const level = check('level') + .trim() + .isIn([...userCourseLevels]) + +const validator = [userId, courseId, level, dropped, validate] + +export default validator diff --git a/devU-api/src/entities/role/tests/userCourse.controller.test.ts b/devU-api/src/entities/role/tests/userCourse.controller.test.ts new file mode 100644 index 00000000..1ce39d6f --- /dev/null +++ b/devU-api/src/entities/role/tests/userCourse.controller.test.ts @@ -0,0 +1,214 @@ +import { UpdateResult } from 'typeorm' + +import { UserCourse } from 'devu-shared-modules' + +import controller from '../userCourse.controller' + +import UserCourseModel from '../userCourse.model' + +import UserCourseService from '../userCourse.service' + +import { serialize } from '../userCourse.serializer' + +import Testing from '../../../utils/testing.utils' +import { GenericResponse, NotFound, Updated } from '../../../utils/apiResponse.utils' + +// Testing Globals +let req: any +let res: any +let next: any + +let mockedUserCourses: UserCourseModel[] +let mockedUserCourse: UserCourseModel +let expectedResults: UserCourse[] +let expectedResult: UserCourse +let expectedError: Error +let expectedUserId: number + +let expectedDbResult: UpdateResult + +describe('UserCourseController', () => { + beforeEach(() => { + expectedUserId = 1234 + + req = Testing.fakeRequest() + res = Testing.fakeResponse() + next = Testing.fakeNext() + + req.currentUser.userId = expectedUserId + + mockedUserCourses = Testing.generateTypeOrmArray(UserCourseModel, 3) + mockedUserCourse = Testing.generateTypeOrm(UserCourseModel) + + expectedResults = mockedUserCourses.map(serialize) + expectedResult = serialize(mockedUserCourse) + expectedError = new Error('Expected Error') + + expectedDbResult = {} as UpdateResult + }) + + describe('GET - /user-course', () => { + describe('200 - Ok', () => { + beforeEach(async () => { + UserCourseService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedUserCourses)) + await controller.get(req, res, next) // what we're testing + }) + + test('UserId is passed to UserCourseService', () => expect(UserCourseService.list).toBeCalledWith(expectedUserId)) + test('Returns list of userCourses', () => expect(res.json).toBeCalledWith(expectedResults)) + test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) + }) + + describe('400 - Bad request', () => { + test('Next called with expected error', async () => { + UserCourseService.list = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + + try { + await controller.get(req, res, next) + + fail('Expected test to throw') + } catch { + expect(next).toBeCalledWith(expectedError) + } + }) + }) + }) + + describe('GET - /user-course/:id', () => { + describe('200 - Ok', () => { + beforeEach(async () => { + UserCourseService.retrieve = jest.fn().mockImplementation(() => Promise.resolve(mockedUserCourse)) + await controller.detail(req, res, next) + }) + + test('Returns expected user', () => expect(res.json).toBeCalledWith(expectedResult)) + test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) + }) + + describe('404 - Not Found', () => { + beforeEach(async () => { + UserCourseService.retrieve = jest.fn().mockImplementation(() => Promise.resolve()) // No results + await controller.detail(req, res, next) + }) + + test('Status code is 404 on missing userCourse', () => expect(res.status).toBeCalledWith(404)) + test('Responds with NotFound on missing userCourse', () => expect(res.json).toBeCalledWith(NotFound)) + test('Next not called on missing userCourse', () => expect(next).toBeCalledTimes(0)) + }) + + describe('400 - Bad Request', () => { + test('Next called with expected error', async () => { + UserCourseService.retrieve = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + + try { + await controller.detail(req, res, next) + + fail('Expected test to throw') + } catch { + expect(next).toBeCalledWith(expectedError) + } + }) + }) + }) + + describe('POST - /user-course', () => { + describe('201 - Created', () => { + beforeEach(async () => { + UserCourseService.create = jest.fn().mockImplementation(() => Promise.resolve(mockedUserCourse)) + await controller.post(req, res, next) + }) + + test('Returns expected user', () => expect(res.json).toBeCalledWith(expectedResult)) + test('Status code is 201', () => expect(res.status).toBeCalledWith(201)) + }) + + describe('400 - Bad Request', () => { + beforeEach(async () => { + UserCourseService.create = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + + try { + await controller.post(req, res, next) + + fail('Expected test to throw') + } catch { + // continue to tests + } + }) + + test('Status code is 400', () => expect(res.status).toBeCalledWith(400)) + test('Responds with generic error', () => + expect(res.json).toBeCalledWith(new GenericResponse(expectedError.message))) + test('Next not called', () => expect(next).toBeCalledTimes(0)) + }) + }) + + describe('PUT - /user-course/:id', () => { + describe('200 - Ok', () => { + beforeEach(async () => { + expectedDbResult.affected = 1 // mocking service return shape + UserCourseService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + await controller.put(req, res, next) + }) + + test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) + test('Returns Updated message', () => expect(res.json).toBeCalledWith(Updated)) + test('Next is not called', () => expect(next).toHaveBeenCalledTimes(0)) + }) + + describe('404 - Not Found', () => { + beforeEach(async () => { + expectedDbResult.affected = 0 // No records affected in db + UserCourseService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + await controller.put(req, res, next) + }) + + test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) + test('Returns Not found message', () => expect(res.json).toBeCalledWith(NotFound)) + test('Next is not called', () => expect(next).toHaveBeenCalledTimes(0)) + }) + + describe('400 - Bad Request', () => { + beforeEach(async () => { + UserCourseService.update = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + await controller.put(req, res, next) + }) + + test('Next is called with error', () => expect(next).toBeCalledWith(expectedError)) + }) + }) + + describe('DELETE - /user-course/:id', () => { + describe('204 - No Content', () => { + beforeEach(async () => { + expectedDbResult.affected = 1 + UserCourseService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + await controller._delete(req, res, next) + }) + + test('Status code is 204', () => expect(res.status).toBeCalledWith(204)) + test('Response to have no content', () => expect(res.send).toBeCalledWith()) + test('Next not called', () => expect(next).toBeCalledTimes(0)) + }) + + describe('404 - Not Found', () => { + beforeEach(async () => { + expectedDbResult.affected = 0 + UserCourseService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + await controller._delete(req, res, next) + }) + + test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) + test('Response to have no content', () => expect(res.json).toBeCalledWith(NotFound)) + test('Next not called', () => expect(next).toBeCalledTimes(0)) + }) + + describe('400 - Bad Request', () => { + beforeEach(async () => { + UserCourseService._delete = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + await controller._delete(req, res, next) + }) + + test('Next called with expected error', () => expect(next).toBeCalledWith(expectedError)) + }) + }) +}) diff --git a/devU-api/src/entities/role/tests/userCourse.serializer.test.ts b/devU-api/src/entities/role/tests/userCourse.serializer.test.ts new file mode 100644 index 00000000..2706bd64 --- /dev/null +++ b/devU-api/src/entities/role/tests/userCourse.serializer.test.ts @@ -0,0 +1,42 @@ +import { serialize } from '../userCourse.serializer' + +import UserCourseModel from '../userCourse.model' + +import Testing from '../../../utils/testing.utils' + +let mockUserCourse: UserCourseModel + +describe('UserCourse Serializer', () => { + beforeEach(() => { + mockUserCourse = Testing.generateTypeOrm(UserCourseModel) + + mockUserCourse.id = 10 + mockUserCourse.userId = 50 + mockUserCourse.courseId = 100 + mockUserCourse.level = 'ta' + mockUserCourse.dropped = false + mockUserCourse.createdAt = new Date() + mockUserCourse.updatedAt = new Date() + }) + + describe('Serializing UserCourse', () => { + test('CourseUser values exist in the response', () => { + const expectedResult = serialize(mockUserCourse) + + expect(expectedResult).toBeDefined() + expect(expectedResult.id).toEqual(mockUserCourse.id) + expect(expectedResult.userId).toEqual(mockUserCourse.userId) + expect(expectedResult.courseId).toEqual(mockUserCourse.courseId) + expect(expectedResult.level).toEqual(mockUserCourse.level) + expect(expectedResult.dropped).toEqual(mockUserCourse.dropped) + }) + + test('CreatedAt and ModifiedAt are ISO strings', () => { + const expectedResult = serialize(mockUserCourse) + + expect(expectedResult).toBeDefined() + expect(expectedResult.updatedAt).toEqual(mockUserCourse.updatedAt.toISOString()) + expect(expectedResult.createdAt).toEqual(mockUserCourse.createdAt.toISOString()) + }) + }) +}) diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index f5966369..3aaaf68d 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -4,7 +4,8 @@ import Multer from 'multer' // Middleware import validator from '../submission/submission.validator' -import { asInt } from '../../middleware/validator/generic.validator' +import {asInt} from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; // Controller import SubmissionController from '../submission/submission.controller' @@ -14,9 +15,10 @@ const upload = Multer() /** * @swagger - * /submissions: + * /course/:courseId/assignment/:assignmentId/submissions: * get: * summary: Retrieve a list of submissions + * authorization: submissionViewAll * tags: * - Submissions * responses: @@ -33,13 +35,13 @@ const upload = Multer() * required: false * schema: * type: integer - * + * */ -Router.get('/', SubmissionController.get) +Router.get('/', isAuthorized('submissionViewAll'), SubmissionController.get) /** * @swagger - * /submissions/{id}: + * /course/:courseId/assignment/:assignmentId/submissions/{id}: * get: * summary: Retrieve a single submission * tags: @@ -54,11 +56,12 @@ Router.get('/', SubmissionController.get) * schema: * type: integer */ -Router.get('/:id', asInt(), SubmissionController.detail) +Router.get('/:id', isAuthorized(''), asInt(), SubmissionController.detail) +// TODO: submissionViewAll or enrolled/self /** * @swagger - * /submissions: + * /course/:courseId/assignment/:assignmentId/submissions: * post: * summary: Create a submission * tags: @@ -72,11 +75,54 @@ Router.get('/:id', asInt(), SubmissionController.detail) * schema: * $ref: '#/components/schemas/Submission' */ -Router.post('/',upload.single("files"), validator, SubmissionController.post) +Router.post('/', isAuthorized(''), upload.single("files"), validator, SubmissionController.post) +// TODO: submissionCreateSelf or submissionCreateAll + + +/** + * @swagger + * /course/:courseId/assignment/:assignmentId/submissions/{id}/revoke: + * delete: + * summary: Revokes a submission. The submission's score will not count and the submission will not count towards the + * user's submission total or numbering. User's can still view their revoked submissions + * tags: + * - Submissions + * responses: + * '200': + * description: OK + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: integer + */ +Router.put('/:id/revoke', isAuthorized('submissionChangeState'), asInt(), SubmissionController.revoke) + + +/** + * @swagger + * /course/:courseId/assignment/:assignmentId/submissions/{id}/unrevoke: + * delete: + * summary: Un-revokes a submission + * tags: + * - Submissions + * responses: + * '200': + * description: OK + * parameters: + * - name: id + * in: path + * required: true + * schema: + * type: integer + */ +Router.put('/:id/unrevoke', isAuthorized('submissionChangeState'), asInt(), SubmissionController.unrevoke) + /** * @swagger - * /submissions/{id}: + * /course/:courseId/assignment/:assignmentId/submissions/{id}: * delete: * summary: Delete a submission * tags: @@ -91,6 +137,6 @@ Router.post('/',upload.single("files"), validator, SubmissionController.post) * schema: * type: integer */ -Router.delete('/:id', asInt(), SubmissionController._delete) +Router.delete('/:id', isAuthorized('no one can delete submissions'), asInt(), SubmissionController._delete) export default Router diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts index 8e6dfab1..cf9cbf8c 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts @@ -9,7 +9,7 @@ import { serialize } from './submissionProblemScore.serializer' export async function get(req: Request, res: Response, next: NextFunction) { try { - const submissionId = parseInt(req.params.id) + const submissionId = parseInt(req.params.submissionId) const submissionProblemScores = await SubmissionProblemScoreService.list(submissionId) const response = submissionProblemScores.map(serialize) diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts index 0a806a46..ba1f6bfc 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts @@ -4,6 +4,7 @@ import express from 'express' // Middleware import validator from '../submissionProblemScore/submissionProblemScore.validator' import { asInt } from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; // Controller import SubmissionProblemScoreController from '../submissionProblemScore/submissionProblemScore.controller' @@ -12,7 +13,7 @@ const Router = express.Router() /** * @swagger - * /submission-problem-scores/{submission-id}: + * /course/:courseId/assignment/:assignmentId/submission/:{submissionId}/submission-problem-scores: * get: * summary: Retrieve a list of submission problem scores belonging to a submission by submission id * tags: @@ -21,18 +22,20 @@ const Router = express.Router() * '200': * description: OK * parameters: - * - name: submission-id + * - name: submissionId * description: Enter Submission Id * in: path * required: true * schema: * type: integer */ -Router.get('/:id', asInt(), SubmissionProblemScoreController.get) +Router.get('/submission/:submissionId', isAuthorized(''), asInt('submissionId'), SubmissionProblemScoreController.get) +// TODO: scoresViewAll or scoresViewSelfReleased by the submission owner + /** * @swagger - * /submission-problem-scores/detail/{id}: + * /course/:courseId/assignment/:assignmentId/submission-problem-scores/detail/{id}: * get: * summary: Retrieve a single submission problem score * tags: @@ -48,11 +51,12 @@ Router.get('/:id', asInt(), SubmissionProblemScoreController.get) * schema: * type: integer */ -Router.get('/detail/:id', asInt(), SubmissionProblemScoreController.detail) +Router.get('/detail/:id', isAuthorized(''), asInt(), SubmissionProblemScoreController.detail) +// TODO: scoresViewAll or scoresViewSelfReleased by the submission problem score /** * @swagger - * /submission-problem-scores: + * /course/:courseId/assignment/:assignmentId/submission-problem-scores: * post: * summary: Create a submission problem score * tags: @@ -66,11 +70,11 @@ Router.get('/detail/:id', asInt(), SubmissionProblemScoreController.detail) * schema: * $ref: '#/components/schemas/SubmissionProblemScore' */ -Router.post('/', validator, SubmissionProblemScoreController.post) +Router.post('/', isAuthorized('scoresEditAll'), validator, SubmissionProblemScoreController.post) /** * @swagger - * /submission-problem-scores: + * /course/:courseId/assignment/:assignmentId/submission-problem-scores: * put: * summary: Update a submission problem score * tags: @@ -90,11 +94,11 @@ Router.post('/', validator, SubmissionProblemScoreController.post) * schema: * $ref: '#/components/schemas/SubmissionProblemScore' */ -Router.put('/:id', asInt(), validator, SubmissionProblemScoreController.put) +Router.put('/:id', isAuthorized('scoresEditAll'), asInt(), validator, SubmissionProblemScoreController.put) /** * @swagger - * /submission-problem-scores/{id}: + * /course/:courseId/assignment/:assignmentId/submission-problem-scores/{id}: * delete: * summary: Delete a submission problem score * tags: @@ -109,6 +113,6 @@ Router.put('/:id', asInt(), validator, SubmissionProblemScoreController.put) * schema: * type: integer */ -Router.delete('/:id', asInt(), SubmissionProblemScoreController._delete) +Router.delete('/:id', isAuthorized('scoresEditAll'), asInt(), SubmissionProblemScoreController._delete) export default Router diff --git a/devU-api/src/entities/submissionScore/submissionScore.router.ts b/devU-api/src/entities/submissionScore/submissionScore.router.ts index 9fb55109..e95f38c1 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.router.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.router.ts @@ -3,17 +3,18 @@ import express from 'express' // Middleware import validator from './submissionScore.validator' -import { asInt } from '../../middleware/validator/generic.validator' +import {asInt} from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; // Controller import SubmissionScoreController from './submissionScore.controller' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import {isInt} from "validator"; const Router = express.Router() /** * @swagger - * /submission-scores: + * /course/:courseId/assignment/:assignmentId/submission-scores: * get: * summary: Retrieve a list of submission score * tags: @@ -21,21 +22,32 @@ const Router = express.Router() * responses: * '200': * description: OK + */ +Router.get('/', isAuthorized('scoresViewAll'), SubmissionScoreController.get) + +/** + * @swagger + * /course/:courseId/assignment/:assignmentId/submission-scores/user/:userId: + * get: + * summary: Retrieve a list of the released submission scores by user + * tags: + * - SubmissionScores + * responses: + * '200': + * description: OK * parameters: - * - name: submission - * in: query + * - name: userId + * in: path * required: false * schema: * type: integer */ -Router.get('/', SubmissionScoreController.get) - -Router.get('/user/:userId', isAuthorized, SubmissionScoreController.getByUser) +Router.get('/user/:userId', isInt('userId'), isAuthorized(''), SubmissionScoreController.getByUser) /** * @swagger - * /submission-scores/{id}: + * /course/:courseId/assignment/:assignmentId/submission-scores/{id}: * get: * summary: Retrieve a single submission score * tags: @@ -50,11 +62,12 @@ Router.get('/user/:userId', isAuthorized, SubmissionScoreController.getByUser) * schema: * type: integer */ -Router.get('/:id', asInt(), SubmissionScoreController.detail) +Router.get('/:id', isAuthorized(''), asInt(), SubmissionScoreController.detail) +// TODO:.. self or all /** * @swagger - * /submission-scores: + * /course/:courseId/assignment/:assignmentId/submission-scores: * post: * summary: Create a submission score * tags: @@ -68,11 +81,11 @@ Router.get('/:id', asInt(), SubmissionScoreController.detail) * schema: * $ref: '#/components/schemas/SubmissionScore' */ -Router.post('/', validator, SubmissionScoreController.post) +Router.post('/', isAuthorized('scoresEditAll'), validator, SubmissionScoreController.post) /** * @swagger - * /submission-scores: + * /course/:courseId/assignment/:assignmentId/submission-scores: * put: * summary: Update a submission score * tags: @@ -92,11 +105,11 @@ Router.post('/', validator, SubmissionScoreController.post) * schema: * $ref: '#/components/schemas/SubmissionScore' */ -Router.put('/:id', asInt(), validator, SubmissionScoreController.put) +Router.put('/:id', isAuthorized('scoresEditAll'), asInt(), validator, SubmissionScoreController.put) /** * @swagger - * /submission-scores/{id}: + * /course/:courseId/assignment/:assignmentId/submission-scores/{id}: * delete: * summary: Delete a submission score * tags: @@ -111,6 +124,6 @@ Router.put('/:id', asInt(), validator, SubmissionScoreController.put) * schema: * type: integer */ -Router.delete('/:id', asInt(), SubmissionScoreController._delete) +Router.delete('/:id', isAuthorized('scoresEditAll'), asInt(), SubmissionScoreController._delete) export default Router diff --git a/devU-api/src/entities/user/user.model.ts b/devU-api/src/entities/user/user.model.ts index 0a5815cb..3dffae1e 100644 --- a/devU-api/src/entities/user/user.model.ts +++ b/devU-api/src/entities/user/user.model.ts @@ -35,7 +35,6 @@ export default class UserModel { @Column({ length: 320, unique: true, nullable: false }) email: string - // TODO: @Column({ name: 'external_id', length: 320, unique: true, nullable: false }) externalId: string diff --git a/devU-api/src/entities/user/user.router.ts b/devU-api/src/entities/user/user.router.ts index 74693f34..40183cf4 100644 --- a/devU-api/src/entities/user/user.router.ts +++ b/devU-api/src/entities/user/user.router.ts @@ -2,6 +2,7 @@ import express from 'express' import validator from './user.validator' import { asInt } from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; import UserController from './user.controller' @@ -18,7 +19,7 @@ const Router = express.Router() * '200': * description: OK */ -Router.get('/', UserController.get) +Router.get('/', isAuthorized('admin'), UserController.get) /** * @swagger @@ -37,7 +38,33 @@ Router.get('/', UserController.get) * schema: * type: integer */ -Router.get('/:id', asInt(), UserController.detail) +Router.get('/:id', isAuthorized(''), asInt(), UserController.detail) +// self or admin + + +/** + * @swagger + * /users/{userId}/courses: + * get: + * summary: Retrieve all courses associated with a user + * tags: + * - Users + * responses: + * '200': + * description: OK + * parameters: + * - name: course-id + * in: path + * required: true + * schema: + * type: integer + * - name: level + * in: query + * required: false + * schema: + * type: string + */ +Router.get('/course/:id', isAuthorized(''), asInt(), UserController.getCoursesByUser) /** * @swagger @@ -61,7 +88,7 @@ Router.get('/:id', asInt(), UserController.detail) * schema: * type: string */ -Router.get('/course/:id', asInt(), UserController.getByCourse) +Router.get('/course/:id', isAuthorized(''), asInt(), UserController.getByCourse) /** * @swagger @@ -80,6 +107,7 @@ Router.get('/course/:id', asInt(), UserController.getByCourse) * $ref: '#/components/schemas/User' */ Router.post('/', validator, UserController.post) +// TODO: do we need authorizer for this? /** * @swagger @@ -103,7 +131,8 @@ Router.post('/', validator, UserController.post) * schema: * $ref: '#/components/schemas/User' */ -Router.put('/:id', asInt(), validator, UserController.put) +Router.put('/:id', isAuthorized(''), asInt(), validator, UserController.put) +// TODO: self or admin /** * @swagger @@ -122,6 +151,6 @@ Router.put('/:id', asInt(), validator, UserController.put) * schema: * type: integer */ -Router.delete('/:id', asInt(), UserController._delete) +Router.delete('/:id', isAuthorized('no one. Eventually admin only'), asInt(), UserController._delete) export default Router diff --git a/devU-api/src/entities/userCourse/userCourse.controller.ts b/devU-api/src/entities/userCourse/userCourse.controller.ts index ddb1cb78..b112f828 100644 --- a/devU-api/src/entities/userCourse/userCourse.controller.ts +++ b/devU-api/src/entities/userCourse/userCourse.controller.ts @@ -1,95 +1,111 @@ -import { Request, Response, NextFunction } from 'express' +import {Request, Response, NextFunction} from 'express' import UserCourseService from './userCourse.service' -import { serialize } from './userCourse.serializer' +import {serialize} from './userCourse.serializer' -import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' +import {GenericResponse, NotFound, Updated} from '../../utils/apiResponse.utils' export async function getAll(req: Request, res: Response, next: NextFunction) { - try { + try { - const userCourses = await UserCourseService.listAll() + const userCourses = await UserCourseService.listAll() - res.status(200).json(userCourses.map(serialize)) - } catch (err) { - next(err) - } + res.status(200).json(userCourses.map(serialize)) + } catch (err) { + next(err) + } } export async function get(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const userCourses = await UserCourseService.list(id) - - res.status(200).json(userCourses.map(serialize)) - } catch (err) { - next(err) - } + try { + const id = parseInt(req.params.id) + const userCourses = await UserCourseService.list(id) + + res.status(200).json(userCourses.map(serialize)) + } catch (err) { + next(err) + } } export async function getByCourse(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const userCourses = await UserCourseService.listByCourse(id) + try { + const id = parseInt(req.params.id) + const userCourses = await UserCourseService.listByCourse(id) - const response = userCourses.map(serialize) + const response = userCourses.map(serialize) - res.status(200).json(response) - } catch (err) { - next(err) - } + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function detail(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const userCourse = await UserCourseService.retrieve(id) + try { + const id = parseInt(req.params.id) + const userCourse = await UserCourseService.retrieve(id) - if (!userCourse) return res.status(404).json(NotFound) + if (!userCourse) return res.status(404).json(NotFound) - const response = serialize(userCourse) + const response = serialize(userCourse) - res.status(200).json(response) - } catch (err) { - next(err) - } + res.status(200).json(response) + } catch (err) { + next(err) + } +} + +export async function detailByUser(req: Request, res: Response, next: NextFunction) { + try { + const courseId = parseInt(req.params.courseId) + const userId = parseInt(req.params.userId) + const userCourse = await UserCourseService.retrieveByCourseAndUser(courseId, userId) + + if (!userCourse) return res.status(404).json(NotFound) + + const response = serialize(userCourse) + + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function post(req: Request, res: Response, next: NextFunction) { - try { - const userCourse = await UserCourseService.create(req.body) - const response = serialize(userCourse) - - res.status(201).json(response) - } catch (err) { - res.status(400).json(new GenericResponse(err.message)) - } + try { + const userCourse = await UserCourseService.create(req.body) + const response = serialize(userCourse) + + res.status(201).json(response) + } catch (err) { + res.status(400).json(new GenericResponse(err.message)) + } } export async function put(req: Request, res: Response, next: NextFunction) { - try { - req.body.id = parseInt(req.params.id) - const results = await UserCourseService.update(req.body) + try { + req.body.id = parseInt(req.params.id) + const results = await UserCourseService.update(req.body) - if (!results.affected) return res.status(404).json(NotFound) + if (!results.affected) return res.status(404).json(NotFound) - res.status(200).json(Updated) - } catch (err) { - next(err) - } + res.status(200).json(Updated) + } catch (err) { + next(err) + } } export async function _delete(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const results = await UserCourseService._delete(id) + try { + const id = parseInt(req.params.id) + const results = await UserCourseService._delete(id) - if (!results.affected) return res.status(404).json(NotFound) + if (!results.affected) return res.status(404).json(NotFound) - res.status(204).send() - } catch (err) { - next(err) - } + res.status(204).send() + } catch (err) { + next(err) + } } -export default { get, getByCourse, getAll, detail, post, put, _delete } +export default {get, getByCourse, getAll, detail, detailByUser, post, put, _delete} diff --git a/devU-api/src/entities/userCourse/userCourse.model.ts b/devU-api/src/entities/userCourse/userCourse.model.ts index ed150a89..8623e6b7 100644 --- a/devU-api/src/entities/userCourse/userCourse.model.ts +++ b/devU-api/src/entities/userCourse/userCourse.model.ts @@ -59,6 +59,7 @@ export default class UserCourseModel { @ManyToOne(() => CourseModel) courseId: number + // TODO: Change to role @Column({ name: 'type', type: 'enum', enum: userCourseLevels }) level: UserCourseLevel diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index 12bd74e7..df142382 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -4,71 +4,60 @@ import express from 'express' // Middleware import validator from './userCourse.validator' import { asInt } from '../../middleware/validator/generic.validator' +import {isAuthorized} from "../../authorization/authorization.middleware"; // Controller import UserCourseController from './userCourse.controller' const Router = express.Router() -/** - * @swagger - * /user-courses/: - * get: - * summary: List of all user-course. - * tags: - * - UserCourses - * responses: - * '200': - * description: OK - */ -Router.get('/', UserCourseController.getAll) - /** * @swagger - * /user-courses/user/{user-id}: + * /course/:courseId/user-courses/: * get: - * summary: Retrieve a list of all of a user's user-course associations. + * summary: Retrieve a list of all of a course's user-course associations. * tags: * - UserCourses * responses: * '200': * description: OK * parameters: - * - name: user-id - * description: Enter User Id + * - name: course-id + * description: Enter Course Id * in: path * required: true * schema: * type: integer */ -Router.get('/user/:id', asInt(), UserCourseController.get) +Router.get('/course/:id', isAuthorized(''), asInt(), UserCourseController.getByCourse) /** * @swagger - * /user-courses/course/{course-id}: + * /course/:courseId/user-courses/{id}: * get: - * summary: Retrieve a list of all of a course's user-course associations. + * summary: Retrieve a single user-course association * tags: * - UserCourses * responses: * '200': * description: OK * parameters: - * - name: course-id - * description: Enter Course Id + * - name: id + * description: Enter User-Course Id * in: path * required: true * schema: * type: integer */ -Router.get('/course/:id', asInt(), UserCourseController.getByCourse) +Router.get('/:id', isAuthorized(''), asInt(), UserCourseController.detail) +// TODO: self or all /** * @swagger - * /user-courses/{id}: + * /course/:courseId/user-courses/user/{userId}: * get: - * summary: Retrieve a single user-course association + * summary: Retrieve a single user-course by course and user * tags: * - UserCourses * responses: @@ -82,11 +71,13 @@ Router.get('/course/:id', asInt(), UserCourseController.getByCourse) * schema: * type: integer */ -Router.get('/:id', asInt(), UserCourseController.detail) +Router.get('/user/:userId', isAuthorized(''), asInt('userId'), UserCourseController.detailByUser) +// TODO: self or all + /** * @swagger - * /user-courses: + * /course/:courseId/user-courses: * post: * summary: Create a new user-course association * tags: @@ -100,11 +91,11 @@ Router.get('/:id', asInt(), UserCourseController.detail) * schema: * $ref: '#/components/schemas/UserCourse' */ -Router.post('/', validator, UserCourseController.post) +Router.post('/', isAuthorized('userCourseEditAll'), validator, UserCourseController.post) /** * @swagger - * /users-courses/{id}: + * /course/:courseId/users-courses/{id}: * put: * summary: Update a user-course association * tags: @@ -124,11 +115,11 @@ Router.post('/', validator, UserCourseController.post) * schema: * $ref: '#/components/schemas/UserCourse' */ -Router.put('/:id', asInt(), validator, UserCourseController.put) +Router.put('/:id', isAuthorized('userCourseEditAll'), asInt(), validator, UserCourseController.put) /** * @swagger - * /user-courses/{id}: + * /course/:courseId/user-courses/{id}: * delete: * summary: Delete a user-course association * tags: @@ -143,6 +134,6 @@ Router.put('/:id', asInt(), validator, UserCourseController.put) * schema: * type: integer */ -Router.delete('/:id', asInt(), UserCourseController._delete) +Router.delete('/:id', isAuthorized('userCourseEditAll'), asInt(), UserCourseController._delete) export default Router diff --git a/devU-api/src/entities/userCourse/userCourse.service.ts b/devU-api/src/entities/userCourse/userCourse.service.ts index 1012fcc6..8e807d5d 100644 --- a/devU-api/src/entities/userCourse/userCourse.service.ts +++ b/devU-api/src/entities/userCourse/userCourse.service.ts @@ -26,20 +26,25 @@ export async function retrieve(id: number) { return await connect().findOne({ id, deletedAt: IsNull() }) } -export async function list(userId: number) { +export async function retrieveByCourseAndUser(courseId: number, userId: number) { + return await connect().findOne({ 'courseId': courseId, 'userId': userId, deletedAt: IsNull() }) +} + +export async function list(userId: number) { // TODO: look into/test this return await connect().find({ userId, deletedAt: IsNull() }) } export async function listAll() { return await connect().find({ deletedAt: IsNull() }) } -export async function listByCourse(courseId: number) { +export async function listByCourse(courseId: number) { // TODO: look into/test this return await connect().find({ courseId, deletedAt: IsNull() }) } export default { create, retrieve, + retrieveByCourseAndUser, update, _delete, list, diff --git a/devU-api/src/entities/userCourse/userCourse.validator.ts b/devU-api/src/entities/userCourse/userCourse.validator.ts index 93729017..d3464d8d 100644 --- a/devU-api/src/entities/userCourse/userCourse.validator.ts +++ b/devU-api/src/entities/userCourse/userCourse.validator.ts @@ -8,6 +8,7 @@ const userId = check('userId').isNumeric() const courseId = check('courseId').isNumeric() const dropped = check('dropped').isBoolean() +// TODO: Check if role is valid for the course const level = check('level') .trim() .isIn([...userCourseLevels]) diff --git a/devU-api/src/fileUpload/fileUpload.router.ts b/devU-api/src/fileUpload/fileUpload.router.ts index e49e3ef0..9e27ce7b 100644 --- a/devU-api/src/fileUpload/fileUpload.router.ts +++ b/devU-api/src/fileUpload/fileUpload.router.ts @@ -5,6 +5,7 @@ import validator from './fileUpload.validator'; import FileUploadController from './fileUpload.controller'; import {fileUploadTypes} from '../../devu-shared-modules'; +import {isAuthorized} from "../../authorization/authorization.middleware"; const Router = express.Router(); const upload = multer(); @@ -23,27 +24,27 @@ const upload = multer(); const fields: Field[] = fileUploadTypes.map(name => ({name})) /** * @swagger - * /file-upload/{bucketName}: + * /course/:courseId/file-upload/{bucketName}: * get: * summary: Retrieve a list of all files in the bucket */ -Router.get('/:bucketName', FileUploadController.get); +Router.get('/:bucketName', isAuthorized('courseView'), FileUploadController.get); /** * @swagger - * /file-upload/{bucketName}/{fileName}: + * /course/:courseId/file-upload/{bucketName}/{fileName}: * get: * summary: Retrieve a single file from the bucket */ -Router.get('/:bucketName/:fileName', FileUploadController.detail); +Router.get('/:bucketName/:fileName', isAuthorized('courseView'), FileUploadController.detail); /** * @swagger - * /file-upload/: + * /course/:courseId/file-upload/: * post: * summary: Upload a new file to the bucket */ -Router.post('/', upload.fields(fields), validator, FileUploadController.post); +Router.post('/', isAuthorized('enrolled'), upload.fields(fields), validator, FileUploadController.post); export default Router; \ No newline at end of file diff --git a/devU-api/src/router/courseData.router.ts b/devU-api/src/router/courseData.router.ts index 7a1a78f3..b2d97cf0 100644 --- a/devU-api/src/router/courseData.router.ts +++ b/devU-api/src/router/courseData.router.ts @@ -1,16 +1,9 @@ -import express, { Request, Response, NextFunction } from 'express' -import swaggerUi from 'swagger-ui-express' +import express from 'express' -import swagger from '../utils/swagger.utils' import userCourse from '../entities/userCourse/userCourse.router' import assignments from '../entities/assignment/assignment.router' -import courses from '../entities/course/course.router' -import login from '../authentication/login/login.router' -import logout from '../authentication/logout/logout.router' -import status from '../status/status.router' import submissions from '../entities/submission/submission.router' -import users from '../entities/user/user.router' import submissionScore from '../entities/submissionScore/submissionScore.router' import containerAutoGrader from '../entities/containerAutoGrader/containerAutoGrader.router' import assignmentProblem from '../entities/assignmentProblem/assignmentProblem.router' @@ -19,32 +12,39 @@ import deadlineExtensions from "../entities/deadlineExtensions/deadlineExtension import fileUpload from '../fileUpload/fileUpload.router' import grader from '../entities/grader/grader.router' import categories from '../entities/category/category.router' +import categoryScores from '../entities/categoryScore/categoryScore.router' +import courseScores from '../entities/courseScore/courseScore.router' import assignmentScore from '../entities/assignmentScore/assignmentScore.router' +import role from '../entities/role/role.router' import nonContainerAutoGraderRouter from "../entities/nonContainerAutoGrader/nonContainerAutoGrader.router"; -import {isAuthorized} from "../authorization/authorization.middleware"; import {asInt} from "../middleware/validator/generic.validator"; -const Router = express.Router() +const assignmentRouter = express.Router(); +assignmentRouter.use('/assignment-problems', assignmentProblem) +assignmentRouter.use('/container-auto-graders', containerAutoGrader) +assignmentRouter.use('/deadline-extensions', deadlineExtensions) +assignmentRouter.use('/non-container-auto-graders', nonContainerAutoGraderRouter) +assignmentRouter.use('/submissions', submissions) +assignmentRouter.use('/submission-problem-scores', submissionProblemScore) +assignmentRouter.use('/submission-scores', submissionScore) -Router.use('/file-upload', isAuthorized, fileUpload) -Router.use('/user-courses', isAuthorized, userCourse) -Router.use('/categories', isAuthorized, categories) +const Router = express.Router() +Router.use('/assignment/:assignmentId/', asInt('assignmentId'), assignmentRouter) +Router.use('/a/:assignmentId/', asInt('assignmentId'), assignmentRouter) Router.use('/assignments', assignments) -Router.use('/assignment-scores', isAuthorized, assignmentScore) -Router.use('/assignment/:assignmentId/assignment-problems', asInt('assignmentId'), assignmentProblem) -// Router.use('/submissions', isAuthorized, submissions) -Router.use('/assignment/:assignmentId/submissions/', isAuthorized, submissions) -Router.use('/assignment/:assignmentId/submission-scores', isAuthorized, submissionScore) -Router.use('/assignment/:assignmentId/nonContainerAutoGraders', isAuthorized, nonContainerAutoGraderRouter, isAuthorized) -Router.use('/assignment/:assignmentId/container-auto-graders', isAuthorized, containerAutoGrader) -Router.use('/assignment/:assignmentId/submission-problem-scores', isAuthorized, submissionProblemScore) -Router.use('/assignment/:assignmentId/deadline-extensions', isAuthorized, deadlineExtensions) - -Router.use('/grade', isAuthorized, grader) +Router.use('/assignment-scores', assignmentScore) +Router.use('/categories', categories) +Router.use('/category-scores', categoryScores) +Router.use('/course-scores', courseScores) +Router.use('/file-upload', fileUpload) +Router.use('/grade', grader) +Router.use('/roles', role) +Router.use('/user-courses', userCourse) + export default Router diff --git a/devU-api/src/router/index.ts b/devU-api/src/router/index.ts index c0368e79..a82f3963 100644 --- a/devU-api/src/router/index.ts +++ b/devU-api/src/router/index.ts @@ -19,34 +19,19 @@ const Router = express.Router() +// TODO: Decide if we want to pull the course object (And return a 404 if not found) in middleware here or let it +// happen later... do this check in isAuthorized + Router.use('/course/:courseId', isAuthenticated, asInt('courseId'), courseRoutes) Router.use('/c/:courseId', isAuthenticated, asInt('courseId'), courseRoutes) -// TODO: Decide if we want to pull the course object (And return a 404 if not found) in middleware here or let it -// happen later... probably do this check in isAuthorized // Router.use('/course/:courseId', isAuthenticated, asInt('courseId'), getCourse, courseRoutes) // Router.use('/c/:courseId', isAuthenticated, asInt('courseId'), getCourse, courseRoutes) - -// Router.use('/c/:courseid/assignments', isAuthenticated, assignments) -// Router.use('/assignment-problems', isAuthenticated, assignmentProblem) Router.use('/users', isAuthenticated, users) Router.use('/courses', isAuthenticated, courses) // TODO: Courses by user -// Router.use('/categories', isAuthenticated, category) -// Router.use('/user-courses', isAuthenticated, userCourse) -// Router.use('/submissions', isAuthenticated, submissions) -// Router.use('/course/:courseid/assignment/:assignmentid/submissions/', isAuthenticated, submissions) -// Router.use('/submission-scores', isAuthenticated, submissionScore) -// Router.use('/nonContainerAutoGrader', isAuthenticated, nonContainerAutoGraderRouter) -// Router.use('/container-auto-graders', isAuthenticated, containerAutoGrader) -// Router.use('/submission-problem-scores', isAuthenticated, submissionProblemScore) -// Router.use('/file-upload', isAuthenticated, fileUpload) -// Router.use('/deadline-extensions', isAuthenticated, deadlineExtensions) -// Router.use('/grade', isAuthenticated, grader) -// Router.use('/categories', isAuthenticated, categories) -// Router.use('/assignment-scores', isAuthenticated, assignmentScore) Router.use('/login', login) Router.use('/logout', logout) @@ -55,6 +40,7 @@ Router.use('/logout', logout) Router.use('/docs', swaggerUi.serve, swaggerUi.setup(swagger)) Router.use('/status', status) + Router.use('/', (req: Request, res: Response, next: NextFunction) => res.status(404).send(NotFound)) export default Router diff --git a/devu-shared/src/index.ts b/devu-shared/src/index.ts index 294e611d..d83081ea 100644 --- a/devu-shared/src/index.ts +++ b/devu-shared/src/index.ts @@ -11,7 +11,7 @@ export * from './types/validation.types' export * from './types/userCourse.types' export * from './types/category.types' export * from './types/codeAssignment.types' -export * from './types/containerAutoGrader.types'; +export * from './types/containerAutoGrader.types' export * from './types/assignmentProblem.types' export * from './types/submissionProblemScore.types' export * from './types/categoryScore.types' @@ -20,6 +20,7 @@ export * from './types/fileUpload.types' export * from './types/nonContainerAutoGrader.types' export * from './types/deadlineExtensions.types' export * from './types/grader.types' +export * from './types/role.types' export * from './utils/object.utils' export * from './utils/string.utils' diff --git a/devu-shared/src/types/role.types.ts b/devu-shared/src/types/role.types.ts new file mode 100644 index 00000000..b4bc4a37 --- /dev/null +++ b/devu-shared/src/types/role.types.ts @@ -0,0 +1,23 @@ +export type Role = { + id?: number + createdAt?: string + updatedAt?: string + name: string + assignmentViewAll: boolean + assignmentEditAll: boolean + assignmentViewReleased: boolean + courseEdit: boolean + courseView: boolean + enrolled: boolean + roleEditAll: boolean + roleViewAll: boolean + roleViewSelf: boolean + scoresEditAll: boolean + scoresViewAll: boolean + scoresViewSelfReleased: boolean + submissionChangeState: boolean + submissionCreateAll: boolean + submissionCreateSelf: boolean + submissionViewAll: boolean + userCourseEditAll: boolean +} diff --git a/docker-compose.yml b/docker-compose.yml index e6519ed1..29cfec3a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,7 +25,7 @@ services: context: . dockerfile: api.Dockerfile environment: - TANGO_KEY: devutangokey # todo load in from env file. for now this is defined in tango section below + TANGO_KEY: devutangokey # TODO: load in from env file. for now this is defined in tango section below WAIT_HOSTS: db:5432 dev: 0 # value here is irrelevant; just here to make sure dev env exists depends_on: From 9eba65c21512db964721ebdd23dce3b632cc5746 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Wed, 17 Apr 2024 17:25:30 -0400 Subject: [PATCH 016/400] Finished the first pass of the roles restructure. Many TODOs remaining, but the main structure is now in place --- devU-api/@types/express/index.d.ts | 1 + devU-api/scripts/populate-db.ts | 8 +- .../authorization/authorization.middleware.ts | 176 +++++--------- .../assignment/assignment.controller.ts | 2 +- .../entities/assignment/assignment.router.ts | 1 - .../entities/assignment/assignment.service.ts | 6 +- .../tests/assignment.controller.test.ts | 4 +- .../assignmentProblem.router.ts | 10 +- .../assignmentScore/assignmentScore.router.ts | 10 +- .../categoryScore/categoryScore.controller.ts | 14 +- .../categoryScore/categoryScore.router.ts | 1 + .../categoryScore/categoryScore.service.ts | 34 +-- .../src/entities/course/course.controller.ts | 34 ++- devU-api/src/entities/course/course.router.ts | 9 +- .../src/entities/course/course.service.ts | 5 + .../courseScore/courseScore.router.ts | 3 +- .../deadlineExtensions.router.ts | 2 +- .../grader/tests/grader.controller.test.ts | 6 +- .../grader/tests/grader.serializer.test.ts | 2 +- .../nonContainerAutoGrader.controller.ts | 1 - devU-api/src/entities/role/role.controller.ts | 44 +--- devU-api/src/entities/role/role.defaults.ts | 52 +++++ devU-api/src/entities/role/role.model.ts | 17 +- devU-api/src/entities/role/role.router.ts | 20 -- devU-api/src/entities/role/role.serializer.ts | 22 +- devU-api/src/entities/role/role.service.ts | 45 ++-- devU-api/src/entities/role/role.validator.ts | 34 ++- .../role/tests/userCourse.controller.test.ts | 215 +----------------- .../role/tests/userCourse.serializer.test.ts | 43 +--- .../submission/submission.controller.ts | 16 +- .../entities/submission/submission.router.ts | 4 +- .../submissionProblemScore.controller.ts | 2 +- .../submissionProblemScore.router.ts | 7 +- .../submissionProblemScore.service.ts | 30 +-- .../submissionScore.controller.ts | 15 +- .../submissionScore/submissionScore.router.ts | 7 +- .../submissionScore.service.ts | 12 + devU-api/src/entities/user/user.controller.ts | 7 +- devU-api/src/entities/user/user.router.ts | 37 +-- devU-api/src/entities/user/user.service.ts | 4 +- .../tests/userCourse.serializer.test.ts | 4 +- .../entities/userCourse/userCourse.model.ts | 10 +- .../entities/userCourse/userCourse.router.ts | 11 +- .../userCourse/userCourse.serializer.ts | 2 +- .../entities/userCourse/userCourse.service.ts | 4 +- .../userCourse/userCourse.validator.ts | 8 +- devU-api/src/fileUpload/fileUpload.router.ts | 6 +- .../test/fileUpload.serializer.test.ts | 17 +- devu-shared/src/types/role.types.ts | 2 +- devu-shared/src/types/userCourse.types.ts | 15 +- 50 files changed, 394 insertions(+), 647 deletions(-) create mode 100644 devU-api/src/entities/role/role.defaults.ts diff --git a/devU-api/@types/express/index.d.ts b/devU-api/@types/express/index.d.ts index 0aeda2e4..4f4d5383 100644 --- a/devU-api/@types/express/index.d.ts +++ b/devU-api/@types/express/index.d.ts @@ -6,6 +6,7 @@ declare global { // Auth Data currentUser?: AccessToken // Deserialized access token refreshUser?: RefreshToken // Deserialized refresh token + ownerId?: string // The owner of the requested resource(s) } } } diff --git a/devU-api/scripts/populate-db.ts b/devU-api/scripts/populate-db.ts index c52f69d8..19ce17d4 100644 --- a/devU-api/scripts/populate-db.ts +++ b/devU-api/scripts/populate-db.ts @@ -176,10 +176,10 @@ async function runCourseAndSubmission() { const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id //Create enroll students - await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId1}, level:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId2}, level:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId1}, level:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId2}, level:student, dropped:false}`, 'admin') + await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId1}, role:student, dropped:false}`, 'admin') + await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId2}, role:student, dropped:false}`, 'admin') + await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId1}, role:student, dropped:false}`, 'admin') + await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId2}, role:student, dropped:false}`, 'admin') //Create assignments const assignmentId1 = (await createAssignment(courseId1, 'Course1 Assignment 1', 'Quiz')).id diff --git a/devU-api/src/authorization/authorization.middleware.ts b/devU-api/src/authorization/authorization.middleware.ts index d8675e70..4847f7d9 100644 --- a/devU-api/src/authorization/authorization.middleware.ts +++ b/devU-api/src/authorization/authorization.middleware.ts @@ -1,9 +1,11 @@ import {NextFunction, Request, Response} from "express"; -import {NotFound} from "../utils/apiResponse.utils"; +import {NotFound, GenericResponse, Unauthorized} from "../utils/apiResponse.utils"; import AssignmentService from '../entities/assignment/assignment.service' -import CourseService from '../entities/course/course.service' import UserCourseService from '../entities/userCourse/userCourse.service' import RoleService from "../entities/role/role.service"; +import {serialize} from "../entities/role/role.serializer"; +import {Role} from "../../devu-shared-modules"; + /** * Are you authorized to access this endpoint? @@ -11,114 +13,56 @@ import RoleService from "../entities/role/role.service"; * @param permission if the user has this permission, they are authorized * @param permissionIfSelf if the user does not have the first 'permission', but they have the 'permissionIfSelf' and * 'expectedSelfId' matches their user id, then they are authorized - * @param expectedSelfId id that must match the user id if using a self permission + * @param ownerId id that must match the user id if using a self permission */ -export function isAuthorized(permission: string, permissionIfSelf?: string, expectedSelfId?: string) { +export function isAuthorized(permission: string, permissionIfSelf?: string) { return async function (req: Request, res: Response, next: NextFunction) { const courseId = parseInt(req.params.courseId) const userId = req.currentUser?.userId - if(!courseId || !userId){ + if (!courseId || !userId) { return res.status(404).json(NotFound) } + // Pull userCourse const userCourse = await UserCourseService.retrieveByCourseAndUser(courseId, userId) - if(!userCourse){ + // If the course doesn't exist or the user is not enrolled, return the same error, so we don't leak information + // about what courses exist + if (!userCourse) { return res.status(404).json(NotFound) } - RoleService.retrieve() - // Pull course - // Pull userCourse // Pull role + let role: Role | undefined + const roleModel = await RoleService.retrieveByCourseAndName(courseId, userCourse.role) - // if anything doesn't exist, return a 404 (prevents leaking the course list since you get the same error if the course exists or not if you're not enrolled) - - // Check permission + if (!roleModel) { + role = RoleService.retrieveDefaultByName(userCourse.role) + } else { + role = serialize(roleModel) + } - // 403 if no permission + if (!role) { + return res.status(403).json(new GenericResponse("Invalid role: " + userCourse.role)) + } + // check for permission + if (role[permission as keyof typeof role]) { + // authorized! + next() + } - // if (!req.params[routeParameter]) - // return res - // .status(400) - // .json(new GenericResponse(`Missing ${routeParameter} param on ${routeParameter} required request`)) - // - // const id = parseInt(req.params[routeParameter]) - // - // if (isNaN(id)) return res.status(400).json(new GenericResponse(routeParameter + ' is expected to be a number')) + if (permissionIfSelf && req.ownerId && role[permissionIfSelf as keyof typeof role]) { + // can access if they are the owner + if (req.currentUser?.userId && req.currentUser?.userId === parseInt(req.ownerId)) { + // authorized to access their own content + next() + } + } - next() + return res.status(403).json(Unauthorized) } - - - // Option 1: Add user id to every path where users could be able to access only their content <-- painful for - // certain paths (/course/2/assignments/45/user/12) need to validate later that the id in the url matches the data.. - // not to mention the duplicate data - - // Option 2: pull the data and check a field to see if they can access <-- seems more likely to produce bugs - - // return the middleware based on: - // -what perm in the role do they need? - // -is there a field that is cool as long as it matches this user's id? - - // role permissions include: - // -grade anything (put/post) - // -delete grades? - // -create submissions on behalf of students - // -create assignments +categories - // -create graders - // -upload container autograders - // -is delete separate? or can they delete if they can edit? - // -view unreleased assignments - // -manage extensions - // -add userCourse - // --should be instructor only since you could add more instructors.. otherwise, need a check to see if they are adding a userCourse at an allowed role - // --Or have separate paths for add-student and add-user - // -add a role - // -muck around with rosters - // -Download the grading code - // -view all submissions (Without being able to grade them) - // -access based on sections?.. - - // Two default roles - // -student - can only access what everyone can access - - // Add extra endpoint for authorization - // -GET /released-assignments (available to all) - // -GET /assignments (with authorization check) - - // everyone can (default role/students role) - // -GET released assignments - // -GET submissions and scores that match their id - // -POST assignments on their own behalf - - // need a filter after the fact too. pre-check to see if they can access the endpoint at all. post-filter to filter down to the data they are allowed to access - // -or is this just part of each endpoint? eg. GET /courses only returns the courses that user is enrolled (Or every course if you're system-admin) - - // if(!req.currentUser){ - // return res.status(401).json(new GenericResponse('Need to be logged in for this endpoint')) - // } - // - // if("course doesn't exist" || "your are not enrolled in this course"){ - // return res.status(404).json(new GenericResponse('Course not found or you\'re enrolled in this course')) - // } - // - // if("course doesn't exist"){ - // return res.status(404).json(new GenericResponse('Course not found')) - // } - // - // // check if they are enrolled in the course -or- if they are system-level admin - // // -if they have a userCourse, pull their role - // - // req.path - // - // // if(false) { - // return res.status(403).json(new GenericResponse('You do not have permission to access this endpoint')) - // // } - - // next() } /** @@ -134,49 +78,45 @@ export async function isAuthorizedByAssignmentStatus(req: Request, res: Response const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) const permissionString = isAssignmentReleased ? 'assignmentViewReleased' : 'assignmentViewAll' await isAuthorized(permissionString)(req, res, next) - // TODO: this needs to be tested + // TODO: Authorization based on released status. This way is bad. It has hard-coded permission strings and won't + // work for sub-entities (eg. submissionProblemScores shouldn't be viewable if the assignment is not released) + // TODO: check if scores are released + // TODO: rework this to be proper middleware, if keeping this at all } -/** - * Authorized if the user has the 'permission' or if they have the 'selfPermission' and their id matches the selfIdParam - * @param permission - * @param selfPermission - * @param selfIdParam - */ -export async function isAuthorizedIfSelfPathParam(permission: string, selfPermission: string, selfIdParam: string) { - return async function (req: Request, res: Response, next: NextFunction) { +// Maybe do this instead +// export async function extractAssignmentStatus(req: Request, res: Response, next: NextFunction) { +// const assignmentId = parseInt(req.params['assignmentId']) +// const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) +// +// next() +// } - const assignmentId = parseInt(req.params['assignmentId']) - const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) - const permissionString = isAssignmentReleased ? 'assignmentViewReleased' : 'assignmentViewAll' - await isAuthorized(permissionString)(req, res, next) - // TODO: this needs to be tested +export function extractOwnerByPathParam(ownerParam: string) { + return async function (req: Request, res: Response, next: NextFunction) { + req.ownerId = req.params[ownerParam] + next() } } -export async function isAuthorizedIfSelfBodyParam(permission: string, selfPermission: string, selfIdField: string) { +export function extractOwnerByBodyParam(ownerBodyParam: string) { return async function (req: Request, res: Response, next: NextFunction) { - - - const assignmentId = parseInt(req.params['assignmentId']) - const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) - const permissionString = isAssignmentReleased ? 'assignmentViewReleased' : 'assignmentViewAll' - await isAuthorized(permissionString)(req, res, next) - // TODO: this needs to be tested + req.ownerId = req.body[ownerBodyParam] + next() } } -export async function isAuthorizedIfSelfField(permission: string, selfPermission: string, selfIdField: string) { +export function extractOwnerOfCourseContentByResourceField(serviceFunction: (resourceId: number, courseId: number) => Promise, extractOwnerId: (resource: T) => string, resourceIdParam: string) { return async function (req: Request, res: Response, next: NextFunction) { + const resourceId = parseInt(req.params[resourceIdParam]) + const courseId = parseInt(req.params.courseId) + const resource = await serviceFunction(resourceId, courseId) + req.ownerId = extractOwnerId(resource) - const assignmentId = parseInt(req.params['assignmentId']) - const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) - const permissionString = isAssignmentReleased ? 'assignmentViewReleased' : 'assignmentViewAll' - await isAuthorized(permissionString)(req, res, next) - // TODO: this needs to be tested + next() } } diff --git a/devU-api/src/entities/assignment/assignment.controller.ts b/devU-api/src/entities/assignment/assignment.controller.ts index 3c4e0eff..930aa5d5 100644 --- a/devU-api/src/entities/assignment/assignment.controller.ts +++ b/devU-api/src/entities/assignment/assignment.controller.ts @@ -37,7 +37,7 @@ export async function getByCourse(req: Request, res: Response, next: NextFunctio export async function getReleased(req: Request, res: Response, next: NextFunction) { try { const courseId = parseInt(req.params.courseId) - const assignments = await AssignmentService.listReleased(courseId) + const assignments = await AssignmentService.listByCourseReleased(courseId) const response = assignments.map(serialize) diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index c22d9690..9867a0cb 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -82,7 +82,6 @@ Router.get('/:id', asInt(), isAuthorizedByAssignmentStatus, AssignmentsControlle - /** * @swagger * /course/:courseId/assignments diff --git a/devU-api/src/entities/assignment/assignment.service.ts b/devU-api/src/entities/assignment/assignment.service.ts index 6fafa88b..e69758b1 100644 --- a/devU-api/src/entities/assignment/assignment.service.ts +++ b/devU-api/src/entities/assignment/assignment.service.ts @@ -3,7 +3,6 @@ import {getRepository, IsNull} from 'typeorm' import AssignmentModel from './assignment.model' import {Assignment} from 'devu-shared-modules' -import {start} from "node:repl"; const connect = () => getRepository(AssignmentModel) @@ -45,7 +44,7 @@ export async function _delete(id: number) { } export async function retrieve(id: number, courseId: number) { - return await connect().findOne({id, deletedAt: IsNull()}) + return await connect().findOne({"id": id, "courseId": courseId, deletedAt: IsNull()}) } export async function list() { @@ -56,7 +55,7 @@ export async function listByCourse(courseId: number) { return await connect().find({'courseId': courseId, deletedAt: IsNull()}) } -export async function listReleased(courseId: number) { +export async function listByCourseReleased(courseId: number) { // TODO: filter by start date after current time return await connect().find({'courseId': courseId, deletedAt: IsNull()}) } @@ -82,5 +81,6 @@ export default { _delete, list, listByCourse, + listByCourseReleased, isReleased, } diff --git a/devU-api/src/entities/assignment/tests/assignment.controller.test.ts b/devU-api/src/entities/assignment/tests/assignment.controller.test.ts index ec2a9c0d..b9a2c908 100644 --- a/devU-api/src/entities/assignment/tests/assignment.controller.test.ts +++ b/devU-api/src/entities/assignment/tests/assignment.controller.test.ts @@ -54,7 +54,7 @@ describe('AssignmentController', () => { describe('200 - Ok', () => { beforeEach(async () => { AssignmentService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedAssignments)) - await controller.get(req, res, next) // what we're testing + await controller.getByCourse(req, res, next) // what we're testing }) test('Returns list of assignments', () => expect(res.json).toBeCalledWith(expectedResults)) @@ -66,7 +66,7 @@ describe('AssignmentController', () => { AssignmentService.list = jest.fn().mockImplementation(() => Promise.reject(expectedError)) try { - await controller.get(req, res, next) + await controller.getByCourse(req, res, next) fail('Expected test to throw') } catch { diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts index 8dbc1676..8e44b8fa 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts @@ -4,7 +4,7 @@ import express from 'express' // Middleware import validator from './assignmentProblem.validator' import {asInt} from '../../middleware/validator/generic.validator' -import {isAuthorized, isAuthorizedByAssignmentStatus} from "../../authorization/authorization.middleware"; +import {isAuthorized} from "../../authorization/authorization.middleware"; // Controller @@ -30,7 +30,9 @@ const Router = express.Router() * schema: * type: integer */ -Router.get('/', isAuthorizedByAssignmentStatus, asInt(), AssignmentProblemController.get) +Router.get('/', isAuthorized('enrolled'), asInt(), AssignmentProblemController.get) +// Router.get('/', isAuthorizedByAssignmentStatus, asInt(), AssignmentProblemController.get) +// TODO: assignment released status /** * @swagger @@ -50,7 +52,9 @@ Router.get('/', isAuthorizedByAssignmentStatus, asInt(), AssignmentProblemContro * schema: * type: integer */ -Router.get('/:id', isAuthorizedByAssignmentStatus, asInt(), AssignmentProblemController.detail) +Router.get('/:id', isAuthorized('assignmentEditAll'), asInt(), AssignmentProblemController.detail) +// Router.get('/:id', isAuthorizedByAssignmentStatus, asInt(), AssignmentProblemController.detail) +// TODO: assignment released status /** * @swagger diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts index 7b2e9887..4b4264c3 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts @@ -3,8 +3,8 @@ import express from 'express' //Middleware import validator from './assignmentScore.validator' -import { asInt } from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import {asInt} from '../../middleware/validator/generic.validator' +import {extractOwnerByPathParam, isAuthorized} from "../../authorization/authorization.middleware"; //Controller import AssignmentScoreController from './assignmentScore.controller' @@ -49,7 +49,7 @@ Router.get('/:id', isAuthorized('scoresViewAll'), asInt(), AssignmentScoreContro * schema: * type: integer */ -Router.get('/:id', isAuthorizedIfSelf(''), asInt(), AssignmentScoreController.detail) +Router.get('/:id', isAuthorized('scoresViewAll'), asInt(), AssignmentScoreController.detail) /** * @swagger @@ -69,7 +69,7 @@ Router.get('/:id', isAuthorizedIfSelf(''), asInt(), AssignmentScoreController.de * schema: * type: integer */ -Router.get('/user/:id', isAuthorizedIfSelf(''), asInt(), AssignmentScoreController.getByUser) +Router.get('/user/:id', extractOwnerByPathParam('userId'), isAuthorized('scoresViewAll', 'scoresViewSelfReleased'), asInt(), AssignmentScoreController.getByUser) /** * @swagger @@ -95,7 +95,7 @@ Router.get('/user/:id', isAuthorizedIfSelf(''), asInt(), AssignmentScoreControll * schema: * type: integer */ -Router.get('/detail/:id/:userId', isAuthorized(''), asInt(), asInt('userId'), AssignmentScoreController.detailByUser) +Router.get('/detail/:id/:userId', asInt(), asInt('userId'), extractOwnerByPathParam('userId'), isAuthorized('scoresViewAll', 'scoresViewSelfReleased'), AssignmentScoreController.detailByUser) /** diff --git a/devU-api/src/entities/categoryScore/categoryScore.controller.ts b/devU-api/src/entities/categoryScore/categoryScore.controller.ts index 47c45e6e..15c900a6 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.controller.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.controller.ts @@ -17,6 +17,18 @@ export async function get(req: Request, res: Response, next: NextFunction) { } } +export async function getByCourse(req: Request, res: Response, next: NextFunction) { + try { + const courseId = parseInt(req.params.courseId) + const categoryScores = await CategoryScoreService.listByCourse(courseId) + const response = categoryScores.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} + export async function detail(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id) @@ -68,4 +80,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { get, detail, post, put, _delete } +export default { get, getByCourse, detail, post, put, _delete } diff --git a/devU-api/src/entities/categoryScore/categoryScore.router.ts b/devU-api/src/entities/categoryScore/categoryScore.router.ts index bc5ad215..1677a290 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.router.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.router.ts @@ -43,6 +43,7 @@ Router.get('/', isAuthorized('scoresViewAll'), CategoryScoreController.getByCour * type: integer */ Router.get('/:id', isAuthorized('scoresViewSelfReleased'), asInt(), CategoryScoreController.detail) +// TODO: They can see everyone's scores... chance to self or all /** * @swagger diff --git a/devU-api/src/entities/categoryScore/categoryScore.service.ts b/devU-api/src/entities/categoryScore/categoryScore.service.ts index cf53e5a6..2f5df621 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.service.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.service.ts @@ -1,29 +1,29 @@ -import { getRepository, IsNull } from 'typeorm' +import {getRepository, IsNull} from 'typeorm' import CategoryScoreModel from './categoryScore.model' -import { CategoryScore } from 'devu-shared-modules' +import {CategoryScore} from 'devu-shared-modules' const connect = () => getRepository(CategoryScoreModel) export async function create(categoryScore: CategoryScore) { - return await connect().save(categoryScore) + return await connect().save(categoryScore) } export async function update(categoryScore: CategoryScore) { - const { id, courseId, userId, categoryId, score } = categoryScore + const {id, courseId, userId, categoryId, score} = categoryScore - if (!id) throw new Error('Missing Id') + if (!id) throw new Error('Missing Id') - return await connect().update(id, { courseId, userId, categoryId, score }) + return await connect().update(id, {courseId, userId, categoryId, score}) } export async function _delete(id: number) { - return await connect().softDelete({ id, deletedAt: IsNull() }) + return await connect().softDelete({id, deletedAt: IsNull()}) } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOne({id, deletedAt: IsNull()}) } // Retrieve all the categoryScores linked to a particular category (TODO: This endpoint doesn't have a path) @@ -32,14 +32,18 @@ export async function retrieve(id: number) { export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().find({deletedAt: IsNull()}) +} + +export async function listByCourse(courseId: number) { + return await connect().find({courseId, deletedAt: IsNull()}) } export default { - create, - retrieve, - update, - _delete, - // listByCategory, - list, + create, + retrieve, + update, + _delete, + listByCourse, + list, } diff --git a/devU-api/src/entities/course/course.controller.ts b/devU-api/src/entities/course/course.controller.ts index 0de81de7..0da98033 100644 --- a/devU-api/src/entities/course/course.controller.ts +++ b/devU-api/src/entities/course/course.controller.ts @@ -5,6 +5,7 @@ import CourseService from './course.service' import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' import { serialize } from './course.serializer' +import UserCourseService from "../userCourse/userCourse.service"; export async function get(req: Request, res: Response, next: NextFunction) { try { @@ -16,6 +17,17 @@ export async function get(req: Request, res: Response, next: NextFunction) { next(err) } } +export async function getByUser(req: Request, res: Response, next: NextFunction) { + try { + const userId = parseInt(req.params.userId) + const courses = await CourseService.listByUser(userId) + const response = courses.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} export async function detail(req: Request, res: Response, next: NextFunction) { try { @@ -43,6 +55,26 @@ export async function post(req: Request, res: Response, next: NextFunction) { } } +export async function postAddInstructor(req: Request, res: Response, next: NextFunction) { + try { + const course = await CourseService.create(req.body) + const response = serialize(course) + + if(req.currentUser?.userId) { + await UserCourseService.create({ + userId: req.currentUser?.userId, + courseId: course.id, + dropped: false, + role: 'instructor' + }) + } + + res.status(201).json(response) + } catch (err) { + res.status(400).json(new GenericResponse(err.message)) + } +} + export async function put(req: Request, res: Response, next: NextFunction) { try { req.body.id = parseInt(req.params.id) @@ -69,4 +101,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { get, detail, post, put, _delete } +export default { get, getByUser, detail, post, postAddInstructor, put, _delete } diff --git a/devU-api/src/entities/course/course.router.ts b/devU-api/src/entities/course/course.router.ts index c3b58e99..292dd10d 100644 --- a/devU-api/src/entities/course/course.router.ts +++ b/devU-api/src/entities/course/course.router.ts @@ -22,7 +22,10 @@ const Router = express.Router() * '200': * description: OK */ -Router.get('/', isAuthorized(''), CourseController.get) +// Router.get('/', isAuthorized(''), CourseController.get) +Router.get('/', CourseController.get) +// TODO: Top-level authorization + /** * @swagger @@ -35,7 +38,8 @@ Router.get('/', isAuthorized(''), CourseController.get) * '200': * description: OK */ -Router.get('/user/:userId', isAuthorized(''), CourseController.getByUser) // TODO +Router.get('/user/:userId', asInt('userId'), CourseController.getByUser) +// TODO: Top-level authorization /** * @swagger @@ -56,6 +60,7 @@ Router.get('/user/:userId', isAuthorized(''), CourseController.getByUser) // TOD */ Router.get('/:courseId', isAuthorized('enrolled'), asInt('courseId'), CourseController.detail) + /** * @swagger * /courses: diff --git a/devU-api/src/entities/course/course.service.ts b/devU-api/src/entities/course/course.service.ts index 1b0b7071..0008ab97 100644 --- a/devU-api/src/entities/course/course.service.ts +++ b/devU-api/src/entities/course/course.service.ts @@ -33,6 +33,10 @@ export async function retrieve(id: number) { export async function list() { return await connect().find({ deletedAt: IsNull() }) } +export async function listByUser(userId: number) { + // TODO: lookup using UserCourses + return await connect().find({ deletedAt: IsNull() }) +} export default { create, @@ -40,4 +44,5 @@ export default { update, _delete, list, + listByUser, } diff --git a/devU-api/src/entities/courseScore/courseScore.router.ts b/devU-api/src/entities/courseScore/courseScore.router.ts index 41857bea..5a03c4c1 100644 --- a/devU-api/src/entities/courseScore/courseScore.router.ts +++ b/devU-api/src/entities/courseScore/courseScore.router.ts @@ -42,7 +42,8 @@ Router.get('/', isAuthorized('scoresViewAll'), CourseScoreController.get) * schema: * type: integer */ -Router.get('/:id', isAuthorized(''), asInt(), CourseScoreController.detail) +Router.get('/:id', isAuthorized('scoresViewAll'), asInt(), CourseScoreController.detail) +// TODO: view self /** * @swagger diff --git a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts index 4226e9ca..81453433 100644 --- a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts +++ b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts @@ -41,7 +41,7 @@ Router.get('/', isAuthorized('assignmentViewAll'), DeadlineExtensionsController. * schema: * type: integer */ -Router.get('/:id', isAuthorized(''), asInt(), DeadlineExtensionsController.detail) +Router.get('/:id', isAuthorized('assignmentViewAll'), asInt(), DeadlineExtensionsController.detail) // TODO: self or assignmentViewAll /** diff --git a/devU-api/src/entities/grader/tests/grader.controller.test.ts b/devU-api/src/entities/grader/tests/grader.controller.test.ts index 0c317991..ff194992 100644 --- a/devU-api/src/entities/grader/tests/grader.controller.test.ts +++ b/devU-api/src/entities/grader/tests/grader.controller.test.ts @@ -1,4 +1,4 @@ -import { GraderInfo, SubmissionScore, SubmissionProblemScore } from '../../../devu-shared-modules' +import { GraderInfo, SubmissionScore, SubmissionProblemScore } from 'devu-shared-modules' import SubmissionModel from '../../submission/submission.model' import controller from '../grader.controller' @@ -7,9 +7,9 @@ import GraderService from '../grader.service' import { serialize } from '../grader.serializer' -import Testing from '../../utils/testing.utils' +import Testing from '../../../utils/testing.utils' -import { GenericResponse } from '../../utils/apiResponse.utils' +import { GenericResponse } from '../../../utils/apiResponse.utils' //THIS TEST FAILS, diff --git a/devU-api/src/entities/grader/tests/grader.serializer.test.ts b/devU-api/src/entities/grader/tests/grader.serializer.test.ts index 5d3c9b28..4a90f1a7 100644 --- a/devU-api/src/entities/grader/tests/grader.serializer.test.ts +++ b/devU-api/src/entities/grader/tests/grader.serializer.test.ts @@ -3,7 +3,7 @@ import { serialize } from '../grader.serializer' import SubmissionScoreModel from '../../submissionScore/submissionScore.model' import SubmissionProblemScoreModel from '../../submissionProblemScore/submissionProblemScore.model' -import Testing from '../../utils/testing.utils' +import Testing from '../../../utils/testing.utils' let mockSubmissionScore: SubmissionScoreModel let mockSubmissionProblemScore1: SubmissionProblemScoreModel diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts index cd4c3c0c..313fa3f4 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts @@ -21,7 +21,6 @@ export async function getByAssignmentId(req: Request, res: Response, next: NextF res.status(200).json(nonContainerAutoGraders.map(serialize)) - webhook() } catch (err) { next(err) } diff --git a/devU-api/src/entities/role/role.controller.ts b/devU-api/src/entities/role/role.controller.ts index c01e1fe1..de1a66e3 100644 --- a/devU-api/src/entities/role/role.controller.ts +++ b/devU-api/src/entities/role/role.controller.ts @@ -8,31 +8,9 @@ import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.util export async function getAll(req: Request, res: Response, next: NextFunction) { try { - const userCourses = await RoleService.listAll() + const roles = await RoleService.listAll() - res.status(200).json(userCourses.map(serialize)) - } catch (err) { - next(err) - } -} - -export async function get(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const userCourses = await RoleService.list(id) - - res.status(200).json(userCourses.map(serialize)) - } catch (err) { - next(err) - } -} - -export async function getByUser(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const userCourses = await RoleService.list(id) - - res.status(200).json(userCourses.map(serialize)) + res.status(200).json(roles.map(serialize)) } catch (err) { next(err) } @@ -40,10 +18,10 @@ export async function getByUser(req: Request, res: Response, next: NextFunction) export async function getByCourse(req: Request, res: Response, next: NextFunction) { try { - const id = parseInt(req.params.id) - const userCourses = await RoleService.listByCourse(id) + const courseId = parseInt(req.params.courseId) + const roles = await RoleService.listByCourse(courseId) - const response = userCourses.map(serialize) + const response = roles.map(serialize) res.status(200).json(response) } catch (err) { @@ -54,11 +32,11 @@ export async function getByCourse(req: Request, res: Response, next: NextFunctio export async function detail(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id) - const userCourse = await RoleService.retrieve(id) + const role = await RoleService.retrieve(id) - if (!userCourse) return res.status(404).json(NotFound) + if (!role) return res.status(404).json(NotFound) - const response = serialize(userCourse) + const response = serialize(role) res.status(200).json(response) } catch (err) { @@ -68,8 +46,8 @@ export async function detail(req: Request, res: Response, next: NextFunction) { export async function post(req: Request, res: Response, next: NextFunction) { try { - const userCourse = await RoleService.create(req.body) - const response = serialize(userCourse) + const role = await RoleService.create(req.body) + const response = serialize(role) res.status(201).json(response) } catch (err) { @@ -103,4 +81,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { get, getByCourse, getByUser, getAll, detail, post, put, _delete } +export default { getByCourse, getAll, detail, post, put, _delete } diff --git a/devU-api/src/entities/role/role.defaults.ts b/devU-api/src/entities/role/role.defaults.ts new file mode 100644 index 00000000..d46e3207 --- /dev/null +++ b/devU-api/src/entities/role/role.defaults.ts @@ -0,0 +1,52 @@ +import {Role} from 'devu-shared-modules' + + +const student: Role = { + + name: 'student', + assignmentViewAll: false, + assignmentEditAll: false, + assignmentViewReleased: true, + courseEdit: false, + courseViewAll: false, + enrolled: true, + roleEditAll: false, + roleViewAll: false, + roleViewSelf: true, + scoresEditAll: false, + scoresViewAll: false, + scoresViewSelfReleased: true, + submissionChangeState: false, + submissionCreateAll: false, + submissionCreateSelf: true, + submissionViewAll: false, + userCourseEditAll: false, +} + +const instructor: Role = { + + name: 'instructor', + assignmentViewAll: true, + assignmentEditAll: true, + assignmentViewReleased: true, + courseEdit: true, + courseViewAll: true, + enrolled: true, + roleEditAll: true, + roleViewAll: true, + roleViewSelf: true, + scoresEditAll: true, + scoresViewAll: true, + scoresViewSelfReleased: true, + submissionChangeState: true, + submissionCreateAll: true, + submissionCreateSelf: true, + submissionViewAll: true, + userCourseEditAll: true, +} + +const defaultRoles: Map = new Map() +defaultRoles.set('student', student) +defaultRoles.set('instructor', instructor) + +export default defaultRoles diff --git a/devU-api/src/entities/role/role.model.ts b/devU-api/src/entities/role/role.model.ts index d1284516..307531cf 100644 --- a/devU-api/src/entities/role/role.model.ts +++ b/devU-api/src/entities/role/role.model.ts @@ -11,8 +11,8 @@ import { import CourseModel from '../course/course.model' -@Entity('courseScore') -export default class CourseScoreModel { +@Entity('role') +export default class RoleModel { /** * @swagger * tags: @@ -54,15 +54,15 @@ export default class CourseScoreModel { // All the permission options // - // For default permission that everyone in the course should have + // For default permission that everyone in the course should always have @Column({name: 'enrolled'}) enrolled: boolean @Column({name: 'course_edit'}) courseEdit: boolean - @Column({name: 'course_view'}) - courseView: boolean + @Column({name: 'course_view_all'}) + courseViewAll: boolean @Column({name: 'assignment_view_all'}) assignmentViewAll: boolean @@ -110,12 +110,5 @@ export default class CourseScoreModel { @Column({name: 'user_course_edit_all'}) // TODO: Don't let the last instructor change their role userCourseEditAll: boolean - - // TODO: Add the special roles - // -Student has the default permissions - // -Instructor has all permissions - // -TA.. choose the defaults for the TA role - - } diff --git a/devU-api/src/entities/role/role.router.ts b/devU-api/src/entities/role/role.router.ts index b8e4563d..6eecc96e 100644 --- a/devU-api/src/entities/role/role.router.ts +++ b/devU-api/src/entities/role/role.router.ts @@ -25,26 +25,6 @@ const Router = express.Router() Router.get('/', isAuthorized('roleViewAll'), RoleController.getAll) -/** - * @swagger - * /course/:courseId/roles/user/{userId}: - * get: - * summary: Retrieve the role of a given user - * tags: - * - Roles - * responses: - * '200': - * description: OK - * parameters: - * - name: userId - * description: Enter User Id - * in: path - * required: true - * schema: - * type: integer - */ -Router.get('/user/:userId', asInt('userId'), isAuthorized(''), RoleController.getByUser) -// TODO: roleViewSelf or roleViewAll /** * @swagger diff --git a/devU-api/src/entities/role/role.serializer.ts b/devU-api/src/entities/role/role.serializer.ts index 3c94bc3a..ab6ead33 100644 --- a/devU-api/src/entities/role/role.serializer.ts +++ b/devU-api/src/entities/role/role.serializer.ts @@ -11,7 +11,7 @@ export function serialize(role: RoleModel): Role { enrolled: role.enrolled, courseEdit: role.courseEdit, - courseView: role.courseView, + courseViewAll: role.courseViewAll, assignmentViewAll: role.assignmentViewAll, assignmentEditAll: role.assignmentEditAll, assignmentViewReleased: role.assignmentViewReleased, @@ -28,23 +28,3 @@ export function serialize(role: RoleModel): Role { userCourseEditAll: role.userCourseEditAll, } } - -// courseEdit: boolean -// assignmentViewAll: boolean -// assignmentEditAll: boolean -// assignmentViewReleased: boolean -// scoresViewAll: boolean -// scoresEditAll: boolean -// scoresViewSelfReleased: boolean -// roleEditAll: boolean -// roleViewAll: boolean -// roleViewSelf: boolean -// submissionCreateAll: boolean -// submissionChangeState: boolean -// submissionCreateSelf: boolean -// usercourseEditAll: boolean -// -// const x = 'name' -// const y = ['course_edit','assignment_view_all','assignment_edit_all','assignment_view_release','scores_view_all','scores_edit_all','scores_view_self_released','role_edit_all','role_view_all','role_view_self','submission_create_all','submission_change_state','submission_create_self', 'usercourse_edit_all'] -// -// console.log(x, y) \ No newline at end of file diff --git a/devU-api/src/entities/role/role.service.ts b/devU-api/src/entities/role/role.service.ts index 4c42e75d..f28f9d10 100644 --- a/devU-api/src/entities/role/role.service.ts +++ b/devU-api/src/entities/role/role.service.ts @@ -1,53 +1,56 @@ -import { getRepository, IsNull } from 'typeorm' +import {getRepository, IsNull} from 'typeorm' -import { Role as RoleType } from 'devu-shared-modules' +import {Role as RoleType} from 'devu-shared-modules' import Role from './role.model' +import Defaults from './role.defaults' const connect = () => getRepository(Role) export async function create(role: RoleType) { - return await connect().save(role) + return await connect().save(role) } export async function update(role: RoleType) { - const { id, level, dropped } = role + const {id, name, assignmentViewAll, assignmentEditAll, assignmentViewReleased, courseEdit, courseViewAll, enrolled, roleEditAll, roleViewAll, roleViewSelf, scoresEditAll, scoresViewAll, scoresViewSelfReleased, submissionChangeState, submissionCreateAll, submissionCreateSelf, submissionViewAll, userCourseEditAll} = role - if (!id) throw new Error('Missing Id') + if (!id) throw new Error('Missing Id') - return await connect().update(id, { level, dropped }) + return await connect().update(id, {name, assignmentViewAll, assignmentEditAll, assignmentViewReleased, courseEdit, courseViewAll, enrolled, roleEditAll, roleViewAll, roleViewSelf, scoresEditAll, scoresViewAll, scoresViewSelfReleased, submissionChangeState, submissionCreateAll, submissionCreateSelf, submissionViewAll, userCourseEditAll}) } export async function _delete(id: number) { - return await connect().softDelete({ id, deletedAt: IsNull() }) + return await connect().softDelete({id, deletedAt: IsNull()}) } export async function retrieve(id: number) { - return await connect().findOne({id, deletedAt: IsNull()}) + return await connect().findOne({id, deletedAt: IsNull()}) } export async function retrieveByCourseAndName(courseId: number, name: string) { - return await connect().findOne({ 'courseId': courseId, 'name': name, deletedAt: IsNull() }) + return await connect().findOne({'courseId': courseId, 'name': name, deletedAt: IsNull()}) } -export async function list(userId: number) { - return await connect().find({ userId, deletedAt: IsNull() }) + +export function retrieveDefaultByName(name: string) { + return Defaults.get(name) } + export async function listAll() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().find({deletedAt: IsNull()}) } export async function listByCourse(courseId: number) { - return await connect().find({ 'courseId': courseId, deletedAt: IsNull() }) + return await connect().find({'courseId': courseId, deletedAt: IsNull()}) } export default { - create, - retrieve, - retrieveByCourseAndName, - update, - _delete, - list, - listAll, - listByCourse, + create, + retrieve, + retrieveByCourseAndName, + retrieveDefaultByName, + update, + _delete, + listAll, + listByCourse, } diff --git a/devU-api/src/entities/role/role.validator.ts b/devU-api/src/entities/role/role.validator.ts index d3464d8d..7c1ae011 100644 --- a/devU-api/src/entities/role/role.validator.ts +++ b/devU-api/src/entities/role/role.validator.ts @@ -1,18 +1,30 @@ -import { check } from 'express-validator' - -import { userCourseLevels } from 'devu-shared-modules' +import {check} from 'express-validator' import validate from '../../middleware/validator/generic.validator' -const userId = check('userId').isNumeric() -const courseId = check('courseId').isNumeric() -const dropped = check('dropped').isBoolean() +const name = check('name').isString() +const assignmentViewAll = check('assignmentViewAll').isBoolean() +const assignmentEditAll = check('assignmentEditAll').isBoolean() +const assignmentViewReleased = check('assignmentViewReleased').isBoolean() +const courseEdit = check('courseEdit').isBoolean() +const courseViewAll = check('courseViewAll').isBoolean() +const enrolled = check('enrolled').isBoolean() +const roleEditAll = check('roleEditAll').isBoolean() +const roleViewAll = check('roleViewAll').isBoolean() +const roleViewSelf = check('roleViewSelf').isBoolean() +const scoresEditAll = check('scoresEditAll').isBoolean() +const scoresViewAll = check('scoresViewAll').isBoolean() +const scoresViewSelfReleased = check('scoresViewSelfReleased').isBoolean() +const submissionChangeState = check('submissionChangeState').isBoolean() +const submissionCreateAll = check('submissionCreateAll').isBoolean() +const submissionCreateSelf = check('submissionCreateSelf').isBoolean() +const submissionViewAll = check('submissionViewAll').isBoolean() +const userCourseEditAll = check('userCourseEditAll').isBoolean() + -// TODO: Check if role is valid for the course -const level = check('level') - .trim() - .isIn([...userCourseLevels]) +const validator = [name, assignmentViewAll, assignmentEditAll, assignmentViewReleased, courseEdit, courseViewAll, + enrolled, roleEditAll, roleViewAll, roleViewSelf, scoresEditAll, scoresViewAll, scoresViewSelfReleased, + submissionChangeState, submissionCreateAll, submissionCreateSelf, submissionViewAll, userCourseEditAll, validate] -const validator = [userId, courseId, level, dropped, validate] export default validator diff --git a/devU-api/src/entities/role/tests/userCourse.controller.test.ts b/devU-api/src/entities/role/tests/userCourse.controller.test.ts index 1ce39d6f..fb96d0f9 100644 --- a/devU-api/src/entities/role/tests/userCourse.controller.test.ts +++ b/devU-api/src/entities/role/tests/userCourse.controller.test.ts @@ -1,214 +1 @@ -import { UpdateResult } from 'typeorm' - -import { UserCourse } from 'devu-shared-modules' - -import controller from '../userCourse.controller' - -import UserCourseModel from '../userCourse.model' - -import UserCourseService from '../userCourse.service' - -import { serialize } from '../userCourse.serializer' - -import Testing from '../../../utils/testing.utils' -import { GenericResponse, NotFound, Updated } from '../../../utils/apiResponse.utils' - -// Testing Globals -let req: any -let res: any -let next: any - -let mockedUserCourses: UserCourseModel[] -let mockedUserCourse: UserCourseModel -let expectedResults: UserCourse[] -let expectedResult: UserCourse -let expectedError: Error -let expectedUserId: number - -let expectedDbResult: UpdateResult - -describe('UserCourseController', () => { - beforeEach(() => { - expectedUserId = 1234 - - req = Testing.fakeRequest() - res = Testing.fakeResponse() - next = Testing.fakeNext() - - req.currentUser.userId = expectedUserId - - mockedUserCourses = Testing.generateTypeOrmArray(UserCourseModel, 3) - mockedUserCourse = Testing.generateTypeOrm(UserCourseModel) - - expectedResults = mockedUserCourses.map(serialize) - expectedResult = serialize(mockedUserCourse) - expectedError = new Error('Expected Error') - - expectedDbResult = {} as UpdateResult - }) - - describe('GET - /user-course', () => { - describe('200 - Ok', () => { - beforeEach(async () => { - UserCourseService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedUserCourses)) - await controller.get(req, res, next) // what we're testing - }) - - test('UserId is passed to UserCourseService', () => expect(UserCourseService.list).toBeCalledWith(expectedUserId)) - test('Returns list of userCourses', () => expect(res.json).toBeCalledWith(expectedResults)) - test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) - }) - - describe('400 - Bad request', () => { - test('Next called with expected error', async () => { - UserCourseService.list = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - - try { - await controller.get(req, res, next) - - fail('Expected test to throw') - } catch { - expect(next).toBeCalledWith(expectedError) - } - }) - }) - }) - - describe('GET - /user-course/:id', () => { - describe('200 - Ok', () => { - beforeEach(async () => { - UserCourseService.retrieve = jest.fn().mockImplementation(() => Promise.resolve(mockedUserCourse)) - await controller.detail(req, res, next) - }) - - test('Returns expected user', () => expect(res.json).toBeCalledWith(expectedResult)) - test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) - }) - - describe('404 - Not Found', () => { - beforeEach(async () => { - UserCourseService.retrieve = jest.fn().mockImplementation(() => Promise.resolve()) // No results - await controller.detail(req, res, next) - }) - - test('Status code is 404 on missing userCourse', () => expect(res.status).toBeCalledWith(404)) - test('Responds with NotFound on missing userCourse', () => expect(res.json).toBeCalledWith(NotFound)) - test('Next not called on missing userCourse', () => expect(next).toBeCalledTimes(0)) - }) - - describe('400 - Bad Request', () => { - test('Next called with expected error', async () => { - UserCourseService.retrieve = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - - try { - await controller.detail(req, res, next) - - fail('Expected test to throw') - } catch { - expect(next).toBeCalledWith(expectedError) - } - }) - }) - }) - - describe('POST - /user-course', () => { - describe('201 - Created', () => { - beforeEach(async () => { - UserCourseService.create = jest.fn().mockImplementation(() => Promise.resolve(mockedUserCourse)) - await controller.post(req, res, next) - }) - - test('Returns expected user', () => expect(res.json).toBeCalledWith(expectedResult)) - test('Status code is 201', () => expect(res.status).toBeCalledWith(201)) - }) - - describe('400 - Bad Request', () => { - beforeEach(async () => { - UserCourseService.create = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - - try { - await controller.post(req, res, next) - - fail('Expected test to throw') - } catch { - // continue to tests - } - }) - - test('Status code is 400', () => expect(res.status).toBeCalledWith(400)) - test('Responds with generic error', () => - expect(res.json).toBeCalledWith(new GenericResponse(expectedError.message))) - test('Next not called', () => expect(next).toBeCalledTimes(0)) - }) - }) - - describe('PUT - /user-course/:id', () => { - describe('200 - Ok', () => { - beforeEach(async () => { - expectedDbResult.affected = 1 // mocking service return shape - UserCourseService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - await controller.put(req, res, next) - }) - - test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) - test('Returns Updated message', () => expect(res.json).toBeCalledWith(Updated)) - test('Next is not called', () => expect(next).toHaveBeenCalledTimes(0)) - }) - - describe('404 - Not Found', () => { - beforeEach(async () => { - expectedDbResult.affected = 0 // No records affected in db - UserCourseService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - await controller.put(req, res, next) - }) - - test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) - test('Returns Not found message', () => expect(res.json).toBeCalledWith(NotFound)) - test('Next is not called', () => expect(next).toHaveBeenCalledTimes(0)) - }) - - describe('400 - Bad Request', () => { - beforeEach(async () => { - UserCourseService.update = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - await controller.put(req, res, next) - }) - - test('Next is called with error', () => expect(next).toBeCalledWith(expectedError)) - }) - }) - - describe('DELETE - /user-course/:id', () => { - describe('204 - No Content', () => { - beforeEach(async () => { - expectedDbResult.affected = 1 - UserCourseService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - await controller._delete(req, res, next) - }) - - test('Status code is 204', () => expect(res.status).toBeCalledWith(204)) - test('Response to have no content', () => expect(res.send).toBeCalledWith()) - test('Next not called', () => expect(next).toBeCalledTimes(0)) - }) - - describe('404 - Not Found', () => { - beforeEach(async () => { - expectedDbResult.affected = 0 - UserCourseService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - await controller._delete(req, res, next) - }) - - test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) - test('Response to have no content', () => expect(res.json).toBeCalledWith(NotFound)) - test('Next not called', () => expect(next).toBeCalledTimes(0)) - }) - - describe('400 - Bad Request', () => { - beforeEach(async () => { - UserCourseService._delete = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - await controller._delete(req, res, next) - }) - - test('Next called with expected error', () => expect(next).toBeCalledWith(expectedError)) - }) - }) -}) +// TODO: Role testing. Lots of it! diff --git a/devU-api/src/entities/role/tests/userCourse.serializer.test.ts b/devU-api/src/entities/role/tests/userCourse.serializer.test.ts index 2706bd64..fb96d0f9 100644 --- a/devU-api/src/entities/role/tests/userCourse.serializer.test.ts +++ b/devU-api/src/entities/role/tests/userCourse.serializer.test.ts @@ -1,42 +1 @@ -import { serialize } from '../userCourse.serializer' - -import UserCourseModel from '../userCourse.model' - -import Testing from '../../../utils/testing.utils' - -let mockUserCourse: UserCourseModel - -describe('UserCourse Serializer', () => { - beforeEach(() => { - mockUserCourse = Testing.generateTypeOrm(UserCourseModel) - - mockUserCourse.id = 10 - mockUserCourse.userId = 50 - mockUserCourse.courseId = 100 - mockUserCourse.level = 'ta' - mockUserCourse.dropped = false - mockUserCourse.createdAt = new Date() - mockUserCourse.updatedAt = new Date() - }) - - describe('Serializing UserCourse', () => { - test('CourseUser values exist in the response', () => { - const expectedResult = serialize(mockUserCourse) - - expect(expectedResult).toBeDefined() - expect(expectedResult.id).toEqual(mockUserCourse.id) - expect(expectedResult.userId).toEqual(mockUserCourse.userId) - expect(expectedResult.courseId).toEqual(mockUserCourse.courseId) - expect(expectedResult.level).toEqual(mockUserCourse.level) - expect(expectedResult.dropped).toEqual(mockUserCourse.dropped) - }) - - test('CreatedAt and ModifiedAt are ISO strings', () => { - const expectedResult = serialize(mockUserCourse) - - expect(expectedResult).toBeDefined() - expect(expectedResult.updatedAt).toEqual(mockUserCourse.updatedAt.toISOString()) - expect(expectedResult.createdAt).toEqual(mockUserCourse.createdAt.toISOString()) - }) - }) -}) +// TODO: Role testing. Lots of it! diff --git a/devU-api/src/entities/submission/submission.controller.ts b/devU-api/src/entities/submission/submission.controller.ts index 7a073b22..c0bc963b 100644 --- a/devU-api/src/entities/submission/submission.controller.ts +++ b/devU-api/src/entities/submission/submission.controller.ts @@ -55,6 +55,20 @@ export async function post(req: Request, res: Response, next: NextFunction) { next(err) } } +export async function revoke(req: Request, res: Response, next: NextFunction) { + try { + // TODO: Revoke a submission + } catch (err:any) { + next(err) + } +} +export async function unrevoke(req: Request, res: Response, next: NextFunction) { + try { + // TODO: Unrevoke a submission + } catch (err:any) { + next(err) + } +} export async function _delete(req: Request, res: Response, next: NextFunction) { try { @@ -69,4 +83,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { get, detail, post, _delete } +export default { get, detail, post, revoke, unrevoke, _delete } diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index 2240dfd1..d254365f 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -56,7 +56,7 @@ Router.get('/', isAuthorized('submissionViewAll'), SubmissionController.get) * schema: * type: integer */ -Router.get('/:id', isAuthorized(''), asInt(), SubmissionController.detail) +Router.get('/:id', isAuthorized('enrolled'), asInt(), SubmissionController.detail) // TODO: submissionViewAll or enrolled/self /** @@ -75,7 +75,7 @@ Router.get('/:id', isAuthorized(''), asInt(), SubmissionController.detail) * schema: * $ref: '#/components/schemas/Submission' */ -Router.post('/', isAuthorized(''), upload.single("files"), validator, SubmissionController.post) +Router.post('/', isAuthorized('enrolled'), upload.single("files"), validator, SubmissionController.post) // TODO: submissionCreateSelf or submissionCreateAll diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts index cf9cbf8c..378ea987 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts @@ -22,7 +22,7 @@ export async function get(req: Request, res: Response, next: NextFunction) { export async function detail(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id) - const submissionProblemScore = await SubmissionProblemScoreService.retrieve(id) + const submissionProblemScore = await SubmissionProblemScoreService.retrieve(id, -1) // TODO: what to do about course id if (!submissionProblemScore) return res.status(404).json(NotFound) diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts index ba1f6bfc..a6a4952b 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts @@ -3,12 +3,13 @@ import express from 'express' // Middleware import validator from '../submissionProblemScore/submissionProblemScore.validator' -import { asInt } from '../../middleware/validator/generic.validator' +import {asInt} from '../../middleware/validator/generic.validator' import {isAuthorized} from "../../authorization/authorization.middleware"; // Controller import SubmissionProblemScoreController from '../submissionProblemScore/submissionProblemScore.controller' + const Router = express.Router() /** @@ -29,7 +30,7 @@ const Router = express.Router() * schema: * type: integer */ -Router.get('/submission/:submissionId', isAuthorized(''), asInt('submissionId'), SubmissionProblemScoreController.get) +Router.get('/submission/:submissionId', asInt('submissionId'), isAuthorized('enrolled'), SubmissionProblemScoreController.get) // TODO: scoresViewAll or scoresViewSelfReleased by the submission owner @@ -51,7 +52,7 @@ Router.get('/submission/:submissionId', isAuthorized(''), asInt('submissionId'), * schema: * type: integer */ -Router.get('/detail/:id', isAuthorized(''), asInt(), SubmissionProblemScoreController.detail) +Router.get('/detail/:id', isAuthorized('enrolled'), asInt(), SubmissionProblemScoreController.detail) // TODO: scoresViewAll or scoresViewSelfReleased by the submission problem score /** diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts index ac88a329..3d5f7b29 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts @@ -1,39 +1,39 @@ -import { getRepository, IsNull } from 'typeorm' +import {getRepository, IsNull} from 'typeorm' -import { SubmissionProblemScore } from 'devu-shared-modules' +import {SubmissionProblemScore} from 'devu-shared-modules' import SubmissionProblemScoreModel from './submissionProblemScore.model' const connect = () => getRepository(SubmissionProblemScoreModel) export async function create(submissionProblemScore: SubmissionProblemScore) { - return await connect().save(submissionProblemScore) + return await connect().save(submissionProblemScore) } export async function update(submissionProblemScore: SubmissionProblemScore) { - const { id, submissionId, assignmentProblemId, score, feedback, releasedAt } = submissionProblemScore + const {id, submissionId, assignmentProblemId, score, feedback, releasedAt} = submissionProblemScore - if (!id) throw new Error('Missing Id') + if (!id) throw new Error('Missing Id') - return await connect().update(id, { submissionId, assignmentProblemId, score, feedback, releasedAt }) + return await connect().update(id, {submissionId, assignmentProblemId, score, feedback, releasedAt}) } export async function _delete(id: number) { - return await connect().softDelete({ id, deletedAt: IsNull() }) + return await connect().softDelete({id, deletedAt: IsNull()}) } -export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) +export async function retrieve(id: number, courseId: number) { // TODO: Submission Problem Score doesn't know its courseId (neither does submission score). Best way to ensure the request is authorized based on course permissions? + return await connect().findOne({id, deletedAt: IsNull()}) } export async function list(submissionId: number) { - return await connect().find({ submissionId: submissionId, deletedAt: IsNull() }) + return await connect().find({submissionId: submissionId, deletedAt: IsNull()}) } export default { - create, - retrieve, - update, - _delete, - list, + create, + retrieve, + update, + _delete, + list, } diff --git a/devU-api/src/entities/submissionScore/submissionScore.controller.ts b/devU-api/src/entities/submissionScore/submissionScore.controller.ts index 10d290e7..04ac2bc4 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.controller.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.controller.ts @@ -19,6 +19,19 @@ export async function get(req: Request, res: Response, next: NextFunction) { next(err) } } +export async function getByUser(req: Request, res: Response, next: NextFunction) { + try { + const userId = parseInt(req.params.userId) + const assignmentId = parseInt(req.params.assignmentId) + + const submissionScores = await SubmissionScoreService.listByUser(userId, assignmentId) + const response = submissionScores.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} export async function detail(req: Request, res: Response, next: NextFunction) { try { @@ -72,4 +85,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { get, detail, post, put, _delete } +export default { get, getByUser, detail, post, put, _delete } diff --git a/devU-api/src/entities/submissionScore/submissionScore.router.ts b/devU-api/src/entities/submissionScore/submissionScore.router.ts index e95f38c1..17efa28d 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.router.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.router.ts @@ -4,11 +4,10 @@ import express from 'express' // Middleware import validator from './submissionScore.validator' import {asInt} from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import {extractOwnerByPathParam, isAuthorized} from "../../authorization/authorization.middleware"; // Controller import SubmissionScoreController from './submissionScore.controller' -import {isInt} from "validator"; const Router = express.Router() @@ -42,7 +41,7 @@ Router.get('/', isAuthorized('scoresViewAll'), SubmissionScoreController.get) * schema: * type: integer */ -Router.get('/user/:userId', isInt('userId'), isAuthorized(''), SubmissionScoreController.getByUser) +Router.get('/user/:userId', asInt('userId'), extractOwnerByPathParam('userId'), isAuthorized('scoresViewAll', 'scoresViewSelfReleased'), SubmissionScoreController.getByUser) /** @@ -62,7 +61,7 @@ Router.get('/user/:userId', isInt('userId'), isAuthorized(''), SubmissionScoreCo * schema: * type: integer */ -Router.get('/:id', isAuthorized(''), asInt(), SubmissionScoreController.detail) +Router.get('/:id', isAuthorized('scoresViewAll'), asInt(), SubmissionScoreController.detail) // TODO:.. self or all /** diff --git a/devU-api/src/entities/submissionScore/submissionScore.service.ts b/devU-api/src/entities/submissionScore/submissionScore.service.ts index 283f4083..ee0a4c3b 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.service.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.service.ts @@ -47,10 +47,22 @@ export async function list(submissionId?: number) { return await connect().find(options) } +export async function listByUser(userId: number, assignmentId: number) { + const options: FindManyOptions = { + where: { + userId: userId, + assignmentId: assignmentId, + deletedAt: IsNull(), + } + } + return await connect().find(options) +} + export default { create, retrieve, update, _delete, list, + listByUser, } diff --git a/devU-api/src/entities/user/user.controller.ts b/devU-api/src/entities/user/user.controller.ts index 64e58b06..dc312c3c 100644 --- a/devU-api/src/entities/user/user.controller.ts +++ b/devU-api/src/entities/user/user.controller.ts @@ -4,8 +4,6 @@ import UserService from './user.service' import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' -import { UserCourseLevel } from 'devu-shared-modules' - import { serialize } from './user.serializer' export async function get(req: Request, res: Response, next: NextFunction) { @@ -37,9 +35,9 @@ export async function detail(req: Request, res: Response, next: NextFunction) { export async function getByCourse(req: Request, res: Response, next: NextFunction) { try { const courseId = parseInt(req.params.id) - const userLevel = req.query.level as UserCourseLevel | undefined + const userRole = req.query.role - const users = await UserService.listByCourse(courseId, userLevel) + const users = await UserService.listByCourse(courseId, userRole as string) const response = users.map(u => { if (u) return serialize(u) }) res.status(200).json(response) @@ -48,6 +46,7 @@ export async function getByCourse(req: Request, res: Response, next: NextFunctio } } + export async function post(req: Request, res: Response, next: NextFunction) { try { const user = await UserService.create(req.body) diff --git a/devU-api/src/entities/user/user.router.ts b/devU-api/src/entities/user/user.router.ts index 40183cf4..fa7f95f5 100644 --- a/devU-api/src/entities/user/user.router.ts +++ b/devU-api/src/entities/user/user.router.ts @@ -1,7 +1,7 @@ import express from 'express' import validator from './user.validator' -import { asInt } from '../../middleware/validator/generic.validator' +import {asInt} from '../../middleware/validator/generic.validator' import {isAuthorized} from "../../authorization/authorization.middleware"; import UserController from './user.controller' @@ -38,34 +38,12 @@ Router.get('/', isAuthorized('admin'), UserController.get) * schema: * type: integer */ -Router.get('/:id', isAuthorized(''), asInt(), UserController.detail) +// Router.get('/:id', extractOwnerByPathParam('id'), isAuthorized('admin', 'self'), asInt(), UserController.detail) +Router.get('/:id', asInt(), UserController.detail) // self or admin +// TODO: Add top level authorization. Currently, all authorization is at the course level -/** - * @swagger - * /users/{userId}/courses: - * get: - * summary: Retrieve all courses associated with a user - * tags: - * - Users - * responses: - * '200': - * description: OK - * parameters: - * - name: course-id - * in: path - * required: true - * schema: - * type: integer - * - name: level - * in: query - * required: false - * schema: - * type: string - */ -Router.get('/course/:id', isAuthorized(''), asInt(), UserController.getCoursesByUser) - /** * @swagger * /users/course/{course-id}: @@ -82,13 +60,13 @@ Router.get('/course/:id', isAuthorized(''), asInt(), UserController.getCoursesBy * required: true * schema: * type: integer - * - name: level + * - name: role * in: query * required: false * schema: * type: string */ -Router.get('/course/:id', isAuthorized(''), asInt(), UserController.getByCourse) +Router.get('/course/:id', isAuthorized('courseViewAll'), asInt(), UserController.getByCourse) /** * @swagger @@ -131,7 +109,7 @@ Router.post('/', validator, UserController.post) * schema: * $ref: '#/components/schemas/User' */ -Router.put('/:id', isAuthorized(''), asInt(), validator, UserController.put) +Router.put('/:id', asInt(), validator, UserController.put) // TODO: self or admin /** @@ -152,5 +130,6 @@ Router.put('/:id', isAuthorized(''), asInt(), validator, UserController.put) * type: integer */ Router.delete('/:id', isAuthorized('no one. Eventually admin only'), asInt(), UserController._delete) +// TODO: authorization export default Router diff --git a/devU-api/src/entities/user/user.service.ts b/devU-api/src/entities/user/user.service.ts index c7687b10..abc0bb13 100644 --- a/devU-api/src/entities/user/user.service.ts +++ b/devU-api/src/entities/user/user.service.ts @@ -32,10 +32,10 @@ export async function list() { return await connect().find({ deletedAt: IsNull() }) } -export async function listByCourse(courseId: number, userLevel?: string) { +export async function listByCourse(courseId: number, userRole?: string) { const userCourses = await UserCourseService.listByCourse(courseId) const userPromises = userCourses - .filter(uc => !(userLevel) || uc.level === userLevel) + .filter(uc => !(userRole) || uc.role === userRole) .map(uc => ( connect().findOne({ id: uc.userId, deletedAt: IsNull()}) )) diff --git a/devU-api/src/entities/userCourse/tests/userCourse.serializer.test.ts b/devU-api/src/entities/userCourse/tests/userCourse.serializer.test.ts index 2706bd64..8b713297 100644 --- a/devU-api/src/entities/userCourse/tests/userCourse.serializer.test.ts +++ b/devU-api/src/entities/userCourse/tests/userCourse.serializer.test.ts @@ -13,7 +13,7 @@ describe('UserCourse Serializer', () => { mockUserCourse.id = 10 mockUserCourse.userId = 50 mockUserCourse.courseId = 100 - mockUserCourse.level = 'ta' + mockUserCourse.role = 'ta' mockUserCourse.dropped = false mockUserCourse.createdAt = new Date() mockUserCourse.updatedAt = new Date() @@ -27,7 +27,7 @@ describe('UserCourse Serializer', () => { expect(expectedResult.id).toEqual(mockUserCourse.id) expect(expectedResult.userId).toEqual(mockUserCourse.userId) expect(expectedResult.courseId).toEqual(mockUserCourse.courseId) - expect(expectedResult.level).toEqual(mockUserCourse.level) + expect(expectedResult.role).toEqual(mockUserCourse.role) expect(expectedResult.dropped).toEqual(mockUserCourse.dropped) }) diff --git a/devU-api/src/entities/userCourse/userCourse.model.ts b/devU-api/src/entities/userCourse/userCourse.model.ts index 8623e6b7..eb5e07f2 100644 --- a/devU-api/src/entities/userCourse/userCourse.model.ts +++ b/devU-api/src/entities/userCourse/userCourse.model.ts @@ -9,7 +9,6 @@ import { DeleteDateColumn, } from 'typeorm' -import { UserCourseLevel, userCourseLevels } from 'devu-shared-modules' import UserModel from '../user/user.model' import CourseModel from '../course/course.model' @@ -25,13 +24,13 @@ export default class UserCourseModel { * schemas: * UserCourse: * type: object - * required: [userId, courseId, level] + * required: [userId, courseId, role] * properties: * userId: * type: integer * courseId: * type: integer - * level: + * role: * type: string * description: Must be either "student", "ta", or "instructor" * dropped: @@ -59,9 +58,8 @@ export default class UserCourseModel { @ManyToOne(() => CourseModel) courseId: number - // TODO: Change to role - @Column({ name: 'type', type: 'enum', enum: userCourseLevels }) - level: UserCourseLevel + @Column({ name: 'role' }) + role: string @Column() dropped: boolean diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index df142382..d4dd8abe 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -3,8 +3,8 @@ import express from 'express' // Middleware import validator from './userCourse.validator' -import { asInt } from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import {asInt} from '../../middleware/validator/generic.validator' +import {extractOwnerByPathParam, isAuthorized} from "../../authorization/authorization.middleware"; // Controller import UserCourseController from './userCourse.controller' @@ -30,7 +30,7 @@ const Router = express.Router() * schema: * type: integer */ -Router.get('/course/:id', isAuthorized(''), asInt(), UserCourseController.getByCourse) +Router.get('/course/:id', isAuthorized('courseViewAll'), asInt(), UserCourseController.getByCourse) /** * @swagger @@ -50,7 +50,7 @@ Router.get('/course/:id', isAuthorized(''), asInt(), UserCourseController.getByC * schema: * type: integer */ -Router.get('/:id', isAuthorized(''), asInt(), UserCourseController.detail) +Router.get('/:id', isAuthorized('courseViewAll'), asInt(), UserCourseController.detail) // TODO: self or all /** @@ -71,8 +71,7 @@ Router.get('/:id', isAuthorized(''), asInt(), UserCourseController.detail) * schema: * type: integer */ -Router.get('/user/:userId', isAuthorized(''), asInt('userId'), UserCourseController.detailByUser) -// TODO: self or all +Router.get('/user/:userId', extractOwnerByPathParam('userId'), isAuthorized('courseViewAll', 'enrolled'), asInt('userId'), UserCourseController.detailByUser) /** diff --git a/devU-api/src/entities/userCourse/userCourse.serializer.ts b/devU-api/src/entities/userCourse/userCourse.serializer.ts index 999a0ef0..3ce68efc 100644 --- a/devU-api/src/entities/userCourse/userCourse.serializer.ts +++ b/devU-api/src/entities/userCourse/userCourse.serializer.ts @@ -7,7 +7,7 @@ export function serialize(userCourse: UserCourseModel): UserCourse { id: userCourse.id, userId: userCourse.userId, courseId: userCourse.courseId, - level: userCourse.level, + role: userCourse.role, dropped: userCourse.dropped, createdAt: userCourse.createdAt.toISOString(), updatedAt: userCourse.updatedAt.toISOString(), diff --git a/devU-api/src/entities/userCourse/userCourse.service.ts b/devU-api/src/entities/userCourse/userCourse.service.ts index 8e807d5d..9453dd50 100644 --- a/devU-api/src/entities/userCourse/userCourse.service.ts +++ b/devU-api/src/entities/userCourse/userCourse.service.ts @@ -11,11 +11,11 @@ export async function create(userCourse: UserCourseType) { } export async function update(userCourse: UserCourseType) { - const { id, level, dropped } = userCourse + const { id, role, dropped } = userCourse if (!id) throw new Error('Missing Id') - return await connect().update(id, { level, dropped }) + return await connect().update(id, { role: role, dropped }) } export async function _delete(id: number) { diff --git a/devU-api/src/entities/userCourse/userCourse.validator.ts b/devU-api/src/entities/userCourse/userCourse.validator.ts index d3464d8d..bd565c16 100644 --- a/devU-api/src/entities/userCourse/userCourse.validator.ts +++ b/devU-api/src/entities/userCourse/userCourse.validator.ts @@ -1,7 +1,5 @@ import { check } from 'express-validator' -import { userCourseLevels } from 'devu-shared-modules' - import validate from '../../middleware/validator/generic.validator' const userId = check('userId').isNumeric() @@ -9,10 +7,8 @@ const courseId = check('courseId').isNumeric() const dropped = check('dropped').isBoolean() // TODO: Check if role is valid for the course -const level = check('level') - .trim() - .isIn([...userCourseLevels]) +const role = check('role') -const validator = [userId, courseId, level, dropped, validate] +const validator = [userId, courseId, role, dropped, validate] export default validator diff --git a/devU-api/src/fileUpload/fileUpload.router.ts b/devU-api/src/fileUpload/fileUpload.router.ts index 9e27ce7b..81752a24 100644 --- a/devU-api/src/fileUpload/fileUpload.router.ts +++ b/devU-api/src/fileUpload/fileUpload.router.ts @@ -5,7 +5,7 @@ import validator from './fileUpload.validator'; import FileUploadController from './fileUpload.controller'; import {fileUploadTypes} from '../../devu-shared-modules'; -import {isAuthorized} from "../../authorization/authorization.middleware"; +import {isAuthorized} from "../authorization/authorization.middleware"; const Router = express.Router(); const upload = multer(); @@ -28,7 +28,7 @@ const fields: Field[] = fileUploadTypes.map(name => ({name})) * get: * summary: Retrieve a list of all files in the bucket */ -Router.get('/:bucketName', isAuthorized('courseView'), FileUploadController.get); +Router.get('/:bucketName', isAuthorized('courseViewAll'), FileUploadController.get); /** * @swagger @@ -36,7 +36,7 @@ Router.get('/:bucketName', isAuthorized('courseView'), FileUploadController.get) * get: * summary: Retrieve a single file from the bucket */ -Router.get('/:bucketName/:fileName', isAuthorized('courseView'), FileUploadController.detail); +Router.get('/:bucketName/:fileName', isAuthorized('courseViewAll'), FileUploadController.detail); /** * @swagger diff --git a/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts b/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts index dd26f46f..a62eb147 100644 --- a/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts +++ b/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts @@ -7,15 +7,18 @@ let fakeFileUpload: FileUpload describe('fileUpload.serializer', () => { beforeEach(() => { let fieldName: string = "submissions" - const originalNames: string[] = ["test1.txt", "test2.txt", "test3.txt"] - const fileNames: string[] = ["modified1.txt", "modified2.txt", "modified3.txt"] - const etags: string[] = ["etag1", "etag2", "etag3"] + // const originalNames: string[] = ["test1.txt", "test2.txt", "test3.txt"] + // const fileNames: string[] = ["modified1.txt", "modified2.txt", "modified3.txt"] + // const etags: string[] = ["etag1", "etag2", "etag3"] + const originalName: string = "test1.txt" + const filename: string = "modified1.txt" + const etag: string = "etag1" fakeFileUpload = { fieldName: fieldName, - originalName: originalNames, - fileName: fileNames, - etags: etags + originalName: originalName, + filename: filename, + etags: etag } }); describe('Serializing fileUpload', () => { @@ -24,7 +27,7 @@ describe('fileUpload.serializer', () => { expect(actualResult).toBeDefined() expect(actualResult.fieldName).toEqual(fakeFileUpload.fieldName) expect(actualResult.originalName).toEqual(fakeFileUpload.originalName) - expect(actualResult.fileName).toEqual(fakeFileUpload.fileName) + expect(actualResult.filename).toEqual(fakeFileUpload.filename) expect(actualResult.etags).toEqual(fakeFileUpload.etags) }) diff --git a/devu-shared/src/types/role.types.ts b/devu-shared/src/types/role.types.ts index b4bc4a37..38c3e539 100644 --- a/devu-shared/src/types/role.types.ts +++ b/devu-shared/src/types/role.types.ts @@ -7,7 +7,7 @@ export type Role = { assignmentEditAll: boolean assignmentViewReleased: boolean courseEdit: boolean - courseView: boolean + courseViewAll: boolean enrolled: boolean roleEditAll: boolean roleViewAll: boolean diff --git a/devu-shared/src/types/userCourse.types.ts b/devu-shared/src/types/userCourse.types.ts index 7476d58d..815d7a15 100644 --- a/devu-shared/src/types/userCourse.types.ts +++ b/devu-shared/src/types/userCourse.types.ts @@ -3,20 +3,7 @@ export type UserCourse = { userId: number courseId: number dropped: boolean - level: UserCourseLevel + role: string createdAt?: string updatedAt?: string } - -export type UserCourseRole = { - label: string - value: string -} -export const userCourseRoles: UserCourseRole[] = [ - { label: 'Student', value: 'student' }, - { label: 'TA', value: 'ta' }, - { label: 'Instructor', value: 'instructor' }, -] - -export const userCourseLevels = ['student', 'ta', 'instructor'] as const -export type UserCourseLevel = typeof userCourseLevels[number] From 80f6f972ff264814a26a41f49f4e80532f399642 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Wed, 17 Apr 2024 22:50:15 -0400 Subject: [PATCH 017/400] First draft of navbar, added package 'react-router-breadcrumbs-hoc' --- devU-client/package-lock.json | 16 ++++++++++++++ devU-client/package.json | 1 + .../components/misc/breadcrumbsToolbar.scss | 6 ++++++ .../components/misc/breadcrumbsToolbar.tsx | 21 +++++++++++++++++++ .../components/shared/layouts/pageWrapper.tsx | 2 ++ 5 files changed, 46 insertions(+) create mode 100644 devU-client/src/components/misc/breadcrumbsToolbar.scss create mode 100644 devU-client/src/components/misc/breadcrumbsToolbar.tsx diff --git a/devU-client/package-lock.json b/devU-client/package-lock.json index 5727d809..45623b33 100644 --- a/devU-client/package-lock.json +++ b/devU-client/package-lock.json @@ -22,6 +22,7 @@ "react-datepicker": "^4.1.1", "react-dom": "^17.0.1", "react-redux": "^7.2.4", + "react-router-breadcrumbs-hoc": "^4.1.0", "react-router-dom": "^5.2.0", "react-select": "^4.3.1", "react-toggle": "^4.1.2", @@ -7675,6 +7676,15 @@ "react": ">=15" } }, + "node_modules/react-router-breadcrumbs-hoc": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/react-router-breadcrumbs-hoc/-/react-router-breadcrumbs-hoc-4.1.0.tgz", + "integrity": "sha512-HZn352JkMzi/1Mp9H6v78V9f7sjnWRf/dXeFVLDDbxEUK7QJCj1JUBJuEHC+B3tq1+uRCASNm3ofEOaG33tAKw==", + "peerDependencies": { + "react": ">=16.8", + "react-router-dom": ">=5" + } + }, "node_modules/react-router-dom": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", @@ -17294,6 +17304,12 @@ "tiny-warning": "^1.0.0" } }, + "react-router-breadcrumbs-hoc": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/react-router-breadcrumbs-hoc/-/react-router-breadcrumbs-hoc-4.1.0.tgz", + "integrity": "sha512-HZn352JkMzi/1Mp9H6v78V9f7sjnWRf/dXeFVLDDbxEUK7QJCj1JUBJuEHC+B3tq1+uRCASNm3ofEOaG33tAKw==", + "requires": {} + }, "react-router-dom": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", diff --git a/devU-client/package.json b/devU-client/package.json index c43573ad..530e0ef0 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -38,6 +38,7 @@ "react-datepicker": "^4.1.1", "react-dom": "^17.0.1", "react-redux": "^7.2.4", + "react-router-breadcrumbs-hoc": "^4.1.0", "react-router-dom": "^5.2.0", "react-select": "^4.3.1", "react-toggle": "^4.1.2", diff --git a/devU-client/src/components/misc/breadcrumbsToolbar.scss b/devU-client/src/components/misc/breadcrumbsToolbar.scss new file mode 100644 index 00000000..1a1199ad --- /dev/null +++ b/devU-client/src/components/misc/breadcrumbsToolbar.scss @@ -0,0 +1,6 @@ +@import 'variables'; + +.link { + text-decoration: none; + color: $text-color; +} \ No newline at end of file diff --git a/devU-client/src/components/misc/breadcrumbsToolbar.tsx b/devU-client/src/components/misc/breadcrumbsToolbar.tsx new file mode 100644 index 00000000..6e840653 --- /dev/null +++ b/devU-client/src/components/misc/breadcrumbsToolbar.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import withBreadcrumbs from 'react-router-breadcrumbs-hoc' +import { Link } from 'react-router-dom' +import styles from './breadcrumbsToolbar.scss' + +const BreadcrumbsToolbar = ({breadcrumbs}: any) => { + + return ( +
+ {breadcrumbs.map(({breadcrumb, match}: any, index: number) => ( + + {breadcrumb} + {index < (breadcrumbs.length - 1) ? ' > ' : ''} + + ))} +
+ ) + +} + +export default withBreadcrumbs()(BreadcrumbsToolbar) \ No newline at end of file diff --git a/devU-client/src/components/shared/layouts/pageWrapper.tsx b/devU-client/src/components/shared/layouts/pageWrapper.tsx index d251b618..a03daa76 100644 --- a/devU-client/src/components/shared/layouts/pageWrapper.tsx +++ b/devU-client/src/components/shared/layouts/pageWrapper.tsx @@ -1,6 +1,7 @@ import React from 'react' import GlobalToolbar from 'components/misc/globalToolbar' +import BreadcrumbsToolbar from 'components/misc/breadcrumbsToolbar' import styles from './pageWrapper.scss' @@ -12,6 +13,7 @@ type Props = { const PageWrapper = ({ children, className = '' }: Props) => (
+
{children}
) From 8bbe5249cade20021bc8d5b6c9ccbb1bf0331c0d Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Wed, 17 Apr 2024 23:18:32 -0400 Subject: [PATCH 018/400] Renamed navbar files and component --- .../misc/{breadcrumbsToolbar.scss => navbar.scss} | 0 .../components/misc/{breadcrumbsToolbar.tsx => navbar.tsx} | 6 +++--- devU-client/src/components/shared/layouts/pageWrapper.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename devU-client/src/components/misc/{breadcrumbsToolbar.scss => navbar.scss} (100%) rename devU-client/src/components/misc/{breadcrumbsToolbar.tsx => navbar.tsx} (75%) diff --git a/devU-client/src/components/misc/breadcrumbsToolbar.scss b/devU-client/src/components/misc/navbar.scss similarity index 100% rename from devU-client/src/components/misc/breadcrumbsToolbar.scss rename to devU-client/src/components/misc/navbar.scss diff --git a/devU-client/src/components/misc/breadcrumbsToolbar.tsx b/devU-client/src/components/misc/navbar.tsx similarity index 75% rename from devU-client/src/components/misc/breadcrumbsToolbar.tsx rename to devU-client/src/components/misc/navbar.tsx index 6e840653..25b23977 100644 --- a/devU-client/src/components/misc/breadcrumbsToolbar.tsx +++ b/devU-client/src/components/misc/navbar.tsx @@ -1,9 +1,9 @@ import React from 'react' import withBreadcrumbs from 'react-router-breadcrumbs-hoc' import { Link } from 'react-router-dom' -import styles from './breadcrumbsToolbar.scss' +import styles from './navbar.scss' -const BreadcrumbsToolbar = ({breadcrumbs}: any) => { +const Navbar = ({breadcrumbs}: any) => { return (
@@ -18,4 +18,4 @@ const BreadcrumbsToolbar = ({breadcrumbs}: any) => { } -export default withBreadcrumbs()(BreadcrumbsToolbar) \ No newline at end of file +export default withBreadcrumbs()(Navbar) \ No newline at end of file diff --git a/devU-client/src/components/shared/layouts/pageWrapper.tsx b/devU-client/src/components/shared/layouts/pageWrapper.tsx index a03daa76..e54f2705 100644 --- a/devU-client/src/components/shared/layouts/pageWrapper.tsx +++ b/devU-client/src/components/shared/layouts/pageWrapper.tsx @@ -1,7 +1,7 @@ import React from 'react' import GlobalToolbar from 'components/misc/globalToolbar' -import BreadcrumbsToolbar from 'components/misc/breadcrumbsToolbar' +import Navbar from 'components/misc/navbar' import styles from './pageWrapper.scss' @@ -13,7 +13,7 @@ type Props = { const PageWrapper = ({ children, className = '' }: Props) => (
- +
{children}
) From ecedf475f4428e7cf2bf05fa5804255116bc8509 Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 18 Apr 2024 01:52:29 -0400 Subject: [PATCH 019/400] added role toggle to navbar with redux --- .../src/components/misc/globalToolbar.tsx | 2 + .../src/components/utils/roleToggle.tsx | 28 +++++++++++++ devU-client/src/redux/initialState/index.ts | 1 + devU-client/src/redux/reducers/index.ts | 2 + devU-client/src/redux/role.redux.ts | 41 +++++++++++++++++++ devU-client/src/redux/store.ts | 4 +- 6 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 devU-client/src/components/utils/roleToggle.tsx create mode 100644 devU-client/src/redux/role.redux.ts diff --git a/devU-client/src/components/misc/globalToolbar.tsx b/devU-client/src/components/misc/globalToolbar.tsx index c191aeaf..52774df8 100644 --- a/devU-client/src/components/misc/globalToolbar.tsx +++ b/devU-client/src/components/misc/globalToolbar.tsx @@ -8,6 +8,7 @@ import UserOptionsDropdown from 'components/utils/userOptionsDropdown' import { useAppSelector } from 'redux/hooks' import styles from './globalToolbar.scss' +import RoleToggle from '../utils/roleToggle' const GlobalToolbar = () => { const userId = useAppSelector((store) => store.user.id) @@ -25,6 +26,7 @@ const GlobalToolbar = () => { {/* Turns into a sidebar via css on mobile */}
+ Courses diff --git a/devU-client/src/components/utils/roleToggle.tsx b/devU-client/src/components/utils/roleToggle.tsx new file mode 100644 index 00000000..8e7b2e95 --- /dev/null +++ b/devU-client/src/components/utils/roleToggle.tsx @@ -0,0 +1,28 @@ +import Switch from '@mui/material/Switch' +import FormControlLabel from '@mui/material/FormControlLabel' +import { updateUserRole } from '../../redux/role.redux' +import { useDispatch, useSelector } from 'react-redux' +import { RootState } from '../../redux/store' +import React from 'react' + +const RoleToggle = () => { + const dispatch = useDispatch(); + const userRole = useSelector((state: RootState) => state.roleMode.userRole); + + const handleToggle = () => { + const newRole = userRole === 'Student' ? 'Instructor' : 'Student'; + dispatch(updateUserRole(newRole)); + }; + + return ( + } + /> + ) +} + +export default RoleToggle diff --git a/devU-client/src/redux/initialState/index.ts b/devU-client/src/redux/initialState/index.ts index 0903d5db..974e81d7 100644 --- a/devU-client/src/redux/initialState/index.ts +++ b/devU-client/src/redux/initialState/index.ts @@ -4,4 +4,5 @@ import user from './user.initialState' export default { active, user, + } diff --git a/devU-client/src/redux/reducers/index.ts b/devU-client/src/redux/reducers/index.ts index 8bb7b35f..dfb90f5c 100644 --- a/devU-client/src/redux/reducers/index.ts +++ b/devU-client/src/redux/reducers/index.ts @@ -2,10 +2,12 @@ import { combineReducers } from 'redux' import active from './active.reducer' import user from './user.reducer' +import roleMode from '../role.redux' const reducers = combineReducers({ active, user, + roleMode, }) export default reducers diff --git a/devU-client/src/redux/role.redux.ts b/devU-client/src/redux/role.redux.ts new file mode 100644 index 00000000..de4e9d19 --- /dev/null +++ b/devU-client/src/redux/role.redux.ts @@ -0,0 +1,41 @@ +// Define types for actions +type UpdateUserRoleAction = { + type: 'UPDATE_USER_ROLE'; + payload: string; +}; + +// Action Types +type UserActionTypes = UpdateUserRoleAction; + +// Action Creators +export const updateUserRole = (newRole: string): UpdateUserRoleAction => ({ + type: 'UPDATE_USER_ROLE', + payload: newRole, +}); + +// Define interface for the initial state +interface UserState { + userRole: string; +} + +// Load initial state from localStorage if available +const initialState: UserState = { + userRole: localStorage.getItem('userRole') || 'Student', // Default role is 'student' +}; + +// Reducer +const reducer = (state: UserState = initialState, action: UserActionTypes): UserState => { + switch (action.type) { + case 'UPDATE_USER_ROLE': + // Save to localStorage when role is updated + localStorage.setItem('userRole', action.payload); + return { + ...state, + userRole: action.payload, + }; + default: + return state; + } +}; + +export default reducer; diff --git a/devU-client/src/redux/store.ts b/devU-client/src/redux/store.ts index ca08d184..97b5cdb6 100644 --- a/devU-client/src/redux/store.ts +++ b/devU-client/src/redux/store.ts @@ -7,9 +7,7 @@ import rootReducer from './reducers/index' export const composeEnhancers = window && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ function configureStore() { - const store = createStore(rootReducer, initialState, composeWithDevTools()) - - return store + return createStore(rootReducer, initialState, composeWithDevTools()) } const store = configureStore() From 564f478914dfe6299bceb2c485496574ad9cc1a8 Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 18 Apr 2024 02:27:17 -0400 Subject: [PATCH 020/400] added toggle in creating courses and assignments buttons Also added isInstructor function to roletype --- .../pages/courseAssignmentsListPage.tsx | 94 ++++++++++--------- devU-client/src/components/pages/homePage.tsx | 4 +- .../components/pages/userCoursesListPage.tsx | 11 ++- devU-client/src/redux/role.redux.ts | 18 ++-- 4 files changed, 70 insertions(+), 57 deletions(-) diff --git a/devU-client/src/components/pages/courseAssignmentsListPage.tsx b/devU-client/src/components/pages/courseAssignmentsListPage.tsx index 604a3b8b..dfd806ea 100644 --- a/devU-client/src/components/pages/courseAssignmentsListPage.tsx +++ b/devU-client/src/components/pages/courseAssignmentsListPage.tsx @@ -1,62 +1,66 @@ -import React,{useEffect,useState} from 'react' +import React, { useEffect, useState } from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' -import {Assignment} from 'devu-shared-modules' +import { Assignment } from 'devu-shared-modules' import RequestService from 'services/request.service' import ErrorPage from './errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import { Link, useParams } from 'react-router-dom' import styles from './courseAssignmentsListPage.scss' +import { useAppSelector } from '../../redux/hooks' const CourseAssignmentsListPage = () => { - const { courseId } = useParams<{courseId: string}>() - const [error, setError] = useState(null) - const [loading, setLoading] = useState(true) - const [assignments, setAssignments] = useState(new Array()) - - useEffect(() => { - fetchData() - }, []) - - const fetchData = async () => { - try { - const assignments = await RequestService.get(`/api/assignments/course/${courseId}`) - setAssignments(assignments) - }catch(error){ - setError(error) - }finally{ - setLoading(false) - } + const role = useAppSelector((store) => store.roleMode) + const { courseId } = useParams<{ courseId: string }>() + const [error, setError] = useState(null) + const [loading, setLoading] = useState(true) + const [assignments, setAssignments] = useState(new Array()) + + useEffect(() => { + fetchData() + }, []) + + const fetchData = async () => { + try { + const assignments = await RequestService.get(`/api/assignments/course/${courseId}`) + setAssignments(assignments) + } catch (error) { + setError(error) + } finally { + setLoading(false) } + } + + if (loading) return + if (error) return + + return ( + +
+

Course Assignments List Page

+ +
+ {( role.isInstructor() && + Add Assignments + )} +
+
+ - if (loading) return - if (error) return - - return( - -
-

Course Assignments List Page

- -
- Add Assignments -
-
- - - {assignments.map(assignment => ( -
- - {assignment.name} -
- ))} -
- ) + {assignments.map(assignment => ( +
+ + {assignment.name} +
+ ))} +
+ ) } export default CourseAssignmentsListPage diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index f48a01a3..48b40781 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -25,6 +25,7 @@ import { Course, UserCourse } from 'devu-shared-modules' const HomePage = () => { const history = useHistory() const userId = useAppSelector((store) => store.user.id) + const role = useAppSelector((store) => store.roleMode) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) @@ -59,9 +60,10 @@ const HomePage = () => {

Courses

- + }
diff --git a/devU-client/src/components/pages/userCoursesListPage.tsx b/devU-client/src/components/pages/userCoursesListPage.tsx index 7717fe17..6733177b 100644 --- a/devU-client/src/components/pages/userCoursesListPage.tsx +++ b/devU-client/src/components/pages/userCoursesListPage.tsx @@ -14,6 +14,7 @@ import RequestService from 'services/request.service' import LocalStorageService from 'services/localStorage.service' import styles from './userCoursesListPage.scss' + //import Button from 'components/shared/inputs/button' const FILTER_LOCAL_STORAGE_KEY = 'courses_filter' @@ -29,6 +30,7 @@ const filterOptions: Option[] = [ const UserCoursesListPage = () => { const userId = useAppSelector((store) => store.user.id) + const role = useAppSelector((store) => store.roleMode) const defaultFilter = LocalStorageService.get(FILTER_LOCAL_STORAGE_KEY) || 'active' @@ -81,16 +83,17 @@ const UserCoursesListPage = () => { const defaultOption = filterOptions.find((o) => o.value === filter) - return (

My Courses

- - Add Courses - + {role.isInstructor() && ( + + Add Courses + + )}
diff --git a/devU-client/src/redux/role.redux.ts b/devU-client/src/redux/role.redux.ts index de4e9d19..a1559d2c 100644 --- a/devU-client/src/redux/role.redux.ts +++ b/devU-client/src/redux/role.redux.ts @@ -11,31 +11,35 @@ type UserActionTypes = UpdateUserRoleAction; export const updateUserRole = (newRole: string): UpdateUserRoleAction => ({ type: 'UPDATE_USER_ROLE', payload: newRole, -}); +}) // Define interface for the initial state interface UserState { userRole: string; + isInstructor: () => boolean } // Load initial state from localStorage if available const initialState: UserState = { userRole: localStorage.getItem('userRole') || 'Student', // Default role is 'student' -}; + isInstructor: function() { + return this.userRole === 'Instructor'; + }, +} // Reducer const reducer = (state: UserState = initialState, action: UserActionTypes): UserState => { switch (action.type) { case 'UPDATE_USER_ROLE': // Save to localStorage when role is updated - localStorage.setItem('userRole', action.payload); + localStorage.setItem('userRole', action.payload) return { ...state, userRole: action.payload, - }; + } default: - return state; + return state } -}; +} -export default reducer; +export default reducer From ccab92a54f055f24eea7356160f57b4378dd5f5d Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 18 Apr 2024 02:39:48 -0400 Subject: [PATCH 021/400] added a dev docker compose for local client development --- dev-docker-compose.yml | 97 ++++++++++++++++++++++++++++++++++++++++ devU-client/package.json | 4 +- 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 dev-docker-compose.yml diff --git a/dev-docker-compose.yml b/dev-docker-compose.yml new file mode 100644 index 00000000..9e2c6a46 --- /dev/null +++ b/dev-docker-compose.yml @@ -0,0 +1,97 @@ +name: dev-backend +services: + db: + # Runs the PostgreSQL database + image: postgres + environment: + POSTGRES_DB: typescript_api + POSTGRES_USER: typescript_user + POSTGRES_PASSWORD: password + ports: + - '5432:5432' + expose: + - '5432' + config: + # Generates a config file for the API + build: + context: devU-api/scripts + command: "./generateConfig.sh config/default.yml" + image: ubautograding/devtools + volumes: + - ./devU-api/config:/config + api: + # Runs the API + build: + context: . + dockerfile: api.Dockerfile + environment: + TANGO_KEY: devutangokey # todo load in from env file. for now this is defined in tango section below + WAIT_HOSTS: db:5432 + dev: 0 # value here is irrelevant; just here to make sure dev env exists + depends_on: + db: + condition: service_started + config: + condition: service_completed_successfully + ports: + - '3001:3001' +# nginx: +# # Hosts the front end static files from ./dist/local thorough a web server +# build: +# context: . +# dockerfile: nginx.Dockerfile +# volumes: +# - ./dist/local:/usr/share/nginx/html +# ports: +# - '9000:80' + minio: + image: minio/minio + ports: + - '9002:9000' + - '9001:9001' + expose: + - '9000' + # volumes: + # - /tmp/data:/data + environment: + MINIO_ROOT_USER: typescript_user + MINIO_ROOT_PASSWORD: changeMe + command: server /data --console-address ":9001" + # tango stuff + redis: + image: redis:latest + ports: + - '127.0.0.1:6379:6379' + deploy: + replicas: 1 + restart: unless-stopped + tango: + ports: + - '127.0.0.1:3000:3000' + build: + context: ./tango + environment: + - DOCKER_REDIS_HOSTNAME=redis + - RESTFUL_KEY=devutangokey +# - DOCKER_DEPLOYMENT + # Path to volumes within the Tango container. Does not need to be modified. +# - DOCKER_VOLUME_PATH + # Modify the below to be the path to volumes on your host machine +# - DOCKER_TANGO_HOST_VOLUME_PATH + + depends_on: + - redis + volumes: + - ./tango.config.py:/opt/TangoService/Tango/config.py + - /var/run/docker.sock:/var/run/docker.sock + - ./logs/tango/:/var/log/tango/ + - ./logs/tangonginx:/var/log/nginx +# - ./Tango/volumes:/opt/TangoService/Tango/volumes + +# certbot: +# container_name: certbot +# image: certbot/certbot +# volumes: +# - ./ssl/certbot/conf:/etc/letsencrypt +# - ./ssl/certbot/www:/var/www/certbot + restart: unless-stopped diff --git a/devU-client/package.json b/devU-client/package.json index c43573ad..6b45de23 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -9,7 +9,9 @@ "build-all": "concurrently \"npm run build-development\" \"npm run build\"", "build-local": "cross-env NODE_ENV=local webpack -p --mode=production", "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", - "pre-commit": "lint-staged" + "pre-commit": "lint-staged", + "dev-backend": "docker compose -f ../dev-docker-compose.yml up -d", + "dev-backend-stop": "docker compose -f ../dev-docker-compose.yml stop" }, "lint-staged": { "./**/*.{js,ts,json,md}": [ From 31046241b62aea676eb8e9ce9bf2ee03452bfa8d Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 18 Apr 2024 03:16:23 -0400 Subject: [PATCH 022/400] added documentation --- dev-docker-compose.yml | 19 ++++++++------- devU-client/README.md | 53 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/dev-docker-compose.yml b/dev-docker-compose.yml index 9e2c6a46..42e0ad1f 100644 --- a/dev-docker-compose.yml +++ b/dev-docker-compose.yml @@ -1,3 +1,13 @@ +################################################## +################################################## +# For development use only +# sets up the full backend stack for frontend development +# Usage: +# cd devU-client +# npm run dev-backend +################################################## +################################################## + name: dev-backend services: db: @@ -35,15 +45,6 @@ services: condition: service_completed_successfully ports: - '3001:3001' -# nginx: -# # Hosts the front end static files from ./dist/local thorough a web server -# build: -# context: . -# dockerfile: nginx.Dockerfile -# volumes: -# - ./dist/local:/usr/share/nginx/html -# ports: -# - '9000:80' minio: image: minio/minio ports: diff --git a/devU-client/README.md b/devU-client/README.md index 51b5826f..c2551e4b 100644 --- a/devU-client/README.md +++ b/devU-client/README.md @@ -12,18 +12,63 @@ This project is the client for the DevU autograder. It's intent is to act as a w - [Node](https://nodejs.org/en/) - Used to run webpack. ## Running the Application +All commands below assume you are in client directory: ```devU-client``` -Firstly install this projects dependancies by installing node. Once node is installed we can continue with running the application. +We are using an older version of node, +so you might need to use a node version manager to switch to an older version. -Firstly we'll have to install the project dependancies +Below are instructions for [NVM](https://github.com/nvm-sh/nvm), but you are free to use whatever version manager you prefer. + +Install [NVM](https://github.com/nvm-sh/nvm). +For Windows users use [NVM-windows](https://github.com/coreybutler/nvm-windows) + +As of writing, we are using node version 16, so run + +**Warning this will change the node version globally +so for other projects you need to change node version again.** + +``` +nvm use 16 +``` + +To switch back to latest version run + +``` +nvm install latest # if latest version is not installed +nvm use latest +``` + +To verify node version + +``` +node -v # make sure it ouputs v16.X.X +``` + +Next install the project dependencies ``` npm install ``` -Because this project lives almost entirely behind auth, this project requires [its API](https://github.com/UBAutograding/devU-api) to be running in order to operate correctly. Under normal circumstances you'd be able to hit the development or production APIs without having to run your own local API. But because those don't yet exist for now if you want to run this project you'll have to run a local version of the api to use this client. +Because this project lives almost entirely behind auth, this project requires [its API](../devU-api/README.md) to be running in order to operate correctly. +Under normal circumstances you'd be able to hit the development or production APIs without having to run your own local API. +But because those don't yet exist for now if you want to run this project you'll have to run a local version of the api to use this client. + +You can start a local api in docker using + +``` +npm run dev-backend +``` + +This will start all backend services required for the app. + +To stop the api use + +``` +npm run dev-backend-stop +``` -Once there project dependancies are installed and an api is running, we can run the project with a few different commands +Once there project dependencies are installed and an api is running, we can run the project with a few different commands ``` npm run local # points to local api From caec411a645628b484a986432ce674c4cdc758a1 Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 18 Apr 2024 04:02:56 -0400 Subject: [PATCH 023/400] switched to using profiles and added commands to api package json --- dev-docker-compose.yml | 98 ---------------------------------------- devU-api/package.json | 4 +- devU-client/package.json | 4 +- docker-compose.yml | 11 ++++- 4 files changed, 15 insertions(+), 102 deletions(-) delete mode 100644 dev-docker-compose.yml diff --git a/dev-docker-compose.yml b/dev-docker-compose.yml deleted file mode 100644 index 42e0ad1f..00000000 --- a/dev-docker-compose.yml +++ /dev/null @@ -1,98 +0,0 @@ -################################################## -################################################## -# For development use only -# sets up the full backend stack for frontend development -# Usage: -# cd devU-client -# npm run dev-backend -################################################## -################################################## - -name: dev-backend -services: - db: - # Runs the PostgreSQL database - image: postgres - environment: - POSTGRES_DB: typescript_api - POSTGRES_USER: typescript_user - POSTGRES_PASSWORD: password - ports: - - '5432:5432' - expose: - - '5432' - config: - # Generates a config file for the API - build: - context: devU-api/scripts - command: "./generateConfig.sh config/default.yml" - image: ubautograding/devtools - volumes: - - ./devU-api/config:/config - api: - # Runs the API - build: - context: . - dockerfile: api.Dockerfile - environment: - TANGO_KEY: devutangokey # todo load in from env file. for now this is defined in tango section below - WAIT_HOSTS: db:5432 - dev: 0 # value here is irrelevant; just here to make sure dev env exists - depends_on: - db: - condition: service_started - config: - condition: service_completed_successfully - ports: - - '3001:3001' - minio: - image: minio/minio - ports: - - '9002:9000' - - '9001:9001' - expose: - - '9000' - # volumes: - # - /tmp/data:/data - environment: - MINIO_ROOT_USER: typescript_user - MINIO_ROOT_PASSWORD: changeMe - command: server /data --console-address ":9001" - # tango stuff - redis: - image: redis:latest - ports: - - '127.0.0.1:6379:6379' - deploy: - replicas: 1 - restart: unless-stopped - tango: - ports: - - '127.0.0.1:3000:3000' - build: - context: ./tango - environment: - - DOCKER_REDIS_HOSTNAME=redis - - RESTFUL_KEY=devutangokey -# - DOCKER_DEPLOYMENT - # Path to volumes within the Tango container. Does not need to be modified. -# - DOCKER_VOLUME_PATH - # Modify the below to be the path to volumes on your host machine -# - DOCKER_TANGO_HOST_VOLUME_PATH - - depends_on: - - redis - volumes: - - ./tango.config.py:/opt/TangoService/Tango/config.py - - /var/run/docker.sock:/var/run/docker.sock - - ./logs/tango/:/var/log/tango/ - - ./logs/tangonginx:/var/log/nginx -# - ./Tango/volumes:/opt/TangoService/Tango/volumes - -# certbot: -# container_name: certbot -# image: certbot/certbot -# volumes: -# - ./ssl/certbot/conf:/etc/letsencrypt -# - ./ssl/certbot/www:/var/www/certbot - restart: unless-stopped diff --git a/devU-api/package.json b/devU-api/package.json index 647ccabe..6613c68a 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -14,7 +14,9 @@ "pre-commit": "lint-staged", "generate-config": "docker run --pull always -v $(pwd)/config:/config --user $(id -u):$(id -g) --rm ubautograding/devtools /generateConfig.sh config/default.yml", "populate-db": "ts-node-dev ./scripts/populate-db.ts", - "tango": "ts-node-dev ./src/tango/tests/tango.endpoint.test.ts" + "tango": "ts-node-dev ./src/tango/tests/tango.endpoint.test.ts", + "api-services": "docker compose -f ../docker-compose.yml --profile dev-api up -d", + "api-services-stop": "docker compose -f ../docker-compose.yml --profile dev-api stop" }, "lint-staged": { "./**/*.{js,ts,json,md}": [ diff --git a/devU-client/package.json b/devU-client/package.json index 6b45de23..aef9c006 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -10,8 +10,8 @@ "build-local": "cross-env NODE_ENV=local webpack -p --mode=production", "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", "pre-commit": "lint-staged", - "dev-backend": "docker compose -f ../dev-docker-compose.yml up -d", - "dev-backend-stop": "docker compose -f ../dev-docker-compose.yml stop" + "dev-backend": "docker compose -f ../docker-compose.yml --profile dev-client up -d", + "dev-backend-stop": "docker compose -f ../docker-compose.yml --profile dev-client stop" }, "lint-staged": { "./**/*.{js,ts,json,md}": [ diff --git a/docker-compose.yml b/docker-compose.yml index e6519ed1..feaf0af2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' +name: devu services: db: # Runs the PostgreSQL database @@ -35,6 +35,9 @@ services: condition: service_completed_successfully ports: - '3001:3001' + profiles: + - '' # so it starts with normal docker compose + - 'dev-client' client: # Builds the front end and exports static files to ./dist build: @@ -42,6 +45,9 @@ services: context: . volumes: - ./dist:/out + profiles: + - '' # so it starts with normal docker compose + - 'dev-api' # start when developing api nginx: # Hosts the front end static files from ./dist/local thorough a web server build: @@ -51,6 +57,9 @@ services: - ./dist/local:/usr/share/nginx/html ports: - '9000:80' + profiles: + - '' # so it starts with normal docker compose + - 'dev-api' # start when developing api not when developing client minio: image: minio/minio # ports: From fa7c52e65423150d9c248dc5723d2a82d9e7c586 Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 18 Apr 2024 04:03:19 -0400 Subject: [PATCH 024/400] removed tango key requirement for local development --- devU-api/src/environment.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-api/src/environment.ts b/devU-api/src/environment.ts index dde83375..f1743f01 100644 --- a/devU-api/src/environment.ts +++ b/devU-api/src/environment.ts @@ -51,7 +51,7 @@ const refreshTokenBuffer = load('auth.jwt.refreshTokenExpirationBufferSeconds') // if it is undefined it is running on dev machine const isDocker = !(process.env.dev === undefined) -if (process.env.TANGO_KEY === undefined){ +if (isDocker && process.env.TANGO_KEY === undefined){ throw Error('Tango key not found.\nMake sure to set environment variable TANGO_KEY in the api service in docker-compose') } From ea970399482e91b7508671b4188f8964bb1a4ecf Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 18 Apr 2024 04:10:52 -0400 Subject: [PATCH 025/400] added docs for api --- devU-api/README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/devU-api/README.md b/devU-api/README.md index c976ec94..704824ea 100644 --- a/devU-api/README.md +++ b/devU-api/README.md @@ -29,6 +29,34 @@ to This will probably be fixed in the future but for now the above steps are necessary +#### Using docker compose + +We use [docker compose profiles](https://docs.docker.com/compose/profiles/) to selectively start services in the main docker-compose when developing. + +Assuming you are in api dir ```devU-api```, To start all api services except the api run + +``` +npm run api-services +``` + +To stop the services + +``` +npm run api-services-stop +``` + +Then install dependencies using +``` +npm install +``` + +Once you've got all the dependencies installed you can run the project via +``` +npm start +``` + +#### Manually: + ``` docker run \ --name typeorm-postgres \ From 2cbe5e00bdf3d06427d06579aed3160660547a4e Mon Sep 17 00:00:00 2001 From: SantarinX Date: Thu, 18 Apr 2024 15:38:50 -0400 Subject: [PATCH 026/400] Create functions only handle ValidationErrors and pair given style together. --- devU-client/src/utils/textField.utils.ts | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 devU-client/src/utils/textField.utils.ts diff --git a/devU-client/src/utils/textField.utils.ts b/devU-client/src/utils/textField.utils.ts new file mode 100644 index 00000000..6b67c399 --- /dev/null +++ b/devU-client/src/utils/textField.utils.ts @@ -0,0 +1,47 @@ +import {ExpressValidationError} from "../../devu-shared-modules"; + + +export function applyClassToField(allFields: Map, errField: string, className: string) { + if (allFields.has(errField)) { + allFields.set(errField, className) + } + return allFields +} + + +export function applyClassToMultipleFields(allField: Map, errField: string[], className: string) { + let newMap = new Map(allField); + for (const i in errField) { + newMap = applyClassToField(allField, errField[i], className) + } + return newMap +} + + +export function initializeFieldClasses(data: any) { + return new Map(new Map(Object.keys(data).map((key) => [key, '']))) +} + + +export function extractErrorFields(err: ExpressValidationError[] | Error) { + if (err instanceof Error) { + return [] + } else { + return err.map((e) => e.param) + } +} + + +export function removeClassFromField(allFields: Map, errField: string) { + if (allFields.has(errField)) { + allFields.set(errField, '') + } + return allFields +} + + +export function applyStylesToErrorFields(error: ExpressValidationError[] | Error, data: any, styles: any) { + const sections = extractErrorFields(error) + const allFields = initializeFieldClasses(data) + return applyClassToMultipleFields(allFields, sections, styles) +} \ No newline at end of file From e012488edcf99cc566a8d407b9f0e027603a5b16 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Thu, 18 Apr 2024 18:01:32 -0400 Subject: [PATCH 027/400] Highlight all the validation errors, and color will return to normal once modify the error field. #95 --- .../components/pages/assignmentFormPage.tsx | 47 ++++++++++------ .../components/pages/assignmentUpdatePage.tsx | 56 +++++++++++++------ .../pages/containerAutoGraderForm.tsx | 33 +++++++---- .../src/components/pages/courseUpdatePage.tsx | 24 +++++--- .../src/components/pages/coursesFormPage.tsx | 27 ++++++--- .../pages/nonContainerAutoGraderForm.tsx | 35 ++++++++---- .../components/shared/inputs/textField.scss | 8 +++ 7 files changed, 161 insertions(+), 69 deletions(-) diff --git a/devU-client/src/components/pages/assignmentFormPage.tsx b/devU-client/src/components/pages/assignmentFormPage.tsx index cb1968cc..1a8be840 100644 --- a/devU-client/src/components/pages/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/assignmentFormPage.tsx @@ -1,18 +1,23 @@ -import React,{useState} from 'react' +import React, {useState} from 'react' import DatePicker from 'react-datepicker' -import { ExpressValidationError } from 'devu-shared-modules' +import {ExpressValidationError} from 'devu-shared-modules' import 'react-datepicker/dist/react-datepicker.css' import PageWrapper from 'components/shared/layouts/pageWrapper' import RequestService from 'services/request.service' -import { useActionless } from 'redux/hooks' +import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import Button from 'components/shared/inputs/button' -import { SET_ALERT } from 'redux/types/active.types' +import {SET_ALERT} from 'redux/types/active.types' + +import styles from '../shared/inputs/textField.scss' +import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; +import {useParams} from 'react-router-dom' const AssignmentCreatePage = () => { const [setAlert] = useActionless(SET_ALERT) + const {courseId} = useParams<{ courseId: string }>() const [formData, setFormData] = useState({ courseId: 0, @@ -27,11 +32,15 @@ const AssignmentCreatePage = () => { const [endDate, setEndDate] = useState(new Date()) const [dueDate, setDueDate] = useState(new Date()) const [startDate, setStartDate] = useState(new Date()) + const [invalidFields, setInvalidFields] = useState(new Map()) const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id - + + const newInvalidFields = removeClassFromField(invalidFields, key) + setInvalidFields(newInvalidFields) + setFormData(prevState => ({...prevState,[key] : value})) } @@ -41,7 +50,7 @@ const AssignmentCreatePage = () => { const handleSubmit = () => { const finalFormData = { - courseId: formData.courseId, + courseId: courseId, name: formData.name, startDate : startDate.toISOString(), dueDate: dueDate.toISOString(), @@ -52,7 +61,7 @@ const AssignmentCreatePage = () => { maxSubmissions: formData.maxSubmissions, disableHandins: formData.disableHandins, } - + setLoading(true) RequestService.post('/api/assignments/', finalFormData) .then(() => { @@ -60,7 +69,9 @@ const AssignmentCreatePage = () => { }) .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message + const newFields = applyStylesToErrorFields(err, formData, styles.errorField) + setInvalidFields(newFields) setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => setLoading(false)) @@ -70,16 +81,20 @@ const AssignmentCreatePage = () => { return(

Assignment Form

- - - - + + + - - - - - + + + + + diff --git a/devU-client/src/components/pages/assignmentUpdatePage.tsx b/devU-client/src/components/pages/assignmentUpdatePage.tsx index 5f586d22..654aa6a5 100644 --- a/devU-client/src/components/pages/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/assignmentUpdatePage.tsx @@ -1,18 +1,20 @@ import React, {useState} from 'react' -import { ExpressValidationError } from 'devu-shared-modules' +import {ExpressValidationError} from 'devu-shared-modules' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' -import { useParams } from 'react-router-dom' +import {useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' import RequestService from 'services/request.service' -import { useActionless } from 'redux/hooks' +import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import Button from 'components/shared/inputs/button' -import { SET_ALERT } from 'redux/types/active.types' +import {SET_ALERT} from 'redux/types/active.types' +import styles from 'components/shared/inputs/textField.scss' +import {applyStylesToErrorFields, removeClassFromField} from 'utils/textField.utils' type UrlParams = { assignmentId: string @@ -26,6 +28,7 @@ const AssignmentUpdatePage = () => { problemName: '', maxScore: '', }) + const [problemInvalidFields, setProblemInvalidFields] = useState(new Map()) const toggleProblemForm = () => { setToggleForm(!toggleForm) } @@ -43,7 +46,9 @@ const AssignmentUpdatePage = () => { }) .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message + const newFields = applyStylesToErrorFields(err, problemFormData, styles.errorField) + setProblemInvalidFields(newFields) setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => { @@ -58,6 +63,8 @@ const AssignmentUpdatePage = () => { const handleProblemChange = (value : String, e : React.ChangeEvent) => { const key = e.target.id + const newInvalidFields = removeClassFromField(problemInvalidFields, key) + setProblemInvalidFields(newInvalidFields) setProblemFormData(prevState => ({...prevState,[key] : value})) } @@ -77,11 +84,14 @@ const AssignmentUpdatePage = () => { const [endDate, setEndDate] = useState(new Date()) const [dueDate, setDueDate] = useState(new Date()) const [loading, setLoading] = useState(false) + const [invalidFields, setInvalidFields] = useState(new Map()) const { assignmentId } = useParams() as UrlParams const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id + const newInvalidFields = removeClassFromField(invalidFields, key) + setInvalidFields(newInvalidFields) setFormData(prevState => ({...prevState,[key] : value})) } @@ -112,7 +122,9 @@ const AssignmentUpdatePage = () => { }) .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message + const newFields = applyStylesToErrorFields(err, formData, styles.errorField) + setInvalidFields(newFields) setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => setLoading(false)) @@ -127,25 +139,35 @@ const AssignmentUpdatePage = () => { {toggleForm && (


- - - + + +
)}



- - - - + + + + - - - - - - + + + + +
) diff --git a/devU-client/src/components/pages/containerAutoGraderForm.tsx b/devU-client/src/components/pages/containerAutoGraderForm.tsx index c5be5109..13a0b83a 100644 --- a/devU-client/src/components/pages/containerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/containerAutoGraderForm.tsx @@ -1,11 +1,14 @@ -import React,{useState} from 'react' +import React, {useState} from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' import styles from './nonContainerAutoGraderForm.scss' import TextField from 'components/shared/inputs/textField' import Button from 'components/shared/inputs/button' -import { useActionless } from 'redux/hooks' -import { SET_ALERT } from 'redux/types/active.types' +import {useActionless} from 'redux/hooks' +import {SET_ALERT} from 'redux/types/active.types' import RequestService from 'services/request.service' +import {ExpressValidationError} from "../../../devu-shared-modules"; +import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; +import textStyles from "../shared/inputs/textField.scss"; const ContainerAutoGraderForm = () => { const [setAlert] = useActionless(SET_ALERT) @@ -17,9 +20,12 @@ const ContainerAutoGraderForm = () => { autogradingImage: '', timeout: '', }) + const [invalidFields, setInvalidFields] = useState(new Map()) const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id + const newInvalidFields = removeClassFromField(invalidFields, key) + setInvalidFields(newInvalidFields) setFormData(prevState => ({...prevState,[key] : value})) } //This is janky but it works and I'm too tired to come up with a better solution @@ -44,10 +50,13 @@ const ContainerAutoGraderForm = () => { .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Container Auto-Grader Added' }) }) - .catch((err: Error) => { - console.log(err.message) - setAlert({ autoDelete: false, type: 'error', message: err.message }) - }) + .catch((err: ExpressValidationError[] | Error) => { + const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message + const newFields = applyStylesToErrorFields(err, formData, textStyles.errorField) + setInvalidFields(newFields) + + setAlert({autoDelete: false, type: 'error', message: message}) + }) setFormData({ @@ -62,9 +71,13 @@ const ContainerAutoGraderForm = () => {

Non Container Auto Grader Form

Add a Non-Container Auto Grader

- - - + + +
diff --git a/devU-client/src/components/pages/courseUpdatePage.tsx b/devU-client/src/components/pages/courseUpdatePage.tsx index 4ca83e9f..a7965bf2 100644 --- a/devU-client/src/components/pages/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/courseUpdatePage.tsx @@ -1,5 +1,5 @@ import React, {useState} from 'react' -import { useParams } from 'react-router-dom' +import {useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -7,12 +7,14 @@ import RequestService from 'services/request.service' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' -import { ExpressValidationError } from 'devu-shared-modules' +import {ExpressValidationError} from 'devu-shared-modules' -import { useActionless } from 'redux/hooks' +import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import Button from 'components/shared/inputs/button' -import { SET_ALERT } from 'redux/types/active.types' +import {SET_ALERT} from 'redux/types/active.types' +import styles from '../shared/inputs/textField.scss' +import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; type UrlParams = { courseId: string @@ -30,11 +32,14 @@ const CourseUpdatePage = ({}) => { const [startDate, setStartDate] = useState(new Date()) const [endDate, setEndDate] = useState(new Date()) const [loading, setLoading] = useState(false) - + const [invalidFields, setInvalidFields] = useState(new Map()) + const { courseId } = useParams() as UrlParams const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id + const newInvalidFields = removeClassFromField(invalidFields, key) + setInvalidFields(newInvalidFields) setFormData(prevState => ({...prevState,[key] : value})) } const handleStartDateChange = (date : Date) => {setStartDate(date)} @@ -57,6 +62,8 @@ const CourseUpdatePage = ({}) => { }) .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message + const newInvalidFields = applyStylesToErrorFields(err, formData, styles.errorField) + setInvalidFields(newInvalidFields) setAlert({ autoDelete: false, type: 'error', message }) }) @@ -67,9 +74,10 @@ const CourseUpdatePage = ({}) => { return (

Course Detail Update

- - - + + + diff --git a/devU-client/src/components/pages/coursesFormPage.tsx b/devU-client/src/components/pages/coursesFormPage.tsx index 44d2ded4..d858dd9f 100644 --- a/devU-client/src/components/pages/coursesFormPage.tsx +++ b/devU-client/src/components/pages/coursesFormPage.tsx @@ -1,18 +1,19 @@ -import React,{useState} from 'react' +import React, {useState} from 'react' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' -import { ExpressValidationError } from 'devu-shared-modules' +import {ExpressValidationError} from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' import RequestService from 'services/request.service' -import { useActionless } from 'redux/hooks' +import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import Button from 'components/shared/inputs/button' -import { SET_ALERT } from 'redux/types/active.types' - +import {SET_ALERT} from 'redux/types/active.types' +import styles from '../shared/inputs/textField.scss' +import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; const EditCourseFormPage = () => { const [setAlert] = useActionless(SET_ALERT) @@ -25,10 +26,14 @@ const EditCourseFormPage = () => { const [startDate, setStartDate] = useState(new Date()) const [endDate, setEndDate] = useState(new Date()) const [loading, setLoading] = useState(false) + const [invalidFields, setInvalidFields] = useState(new Map()) const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id setFormData(prevState => ({...prevState,[key] : value})) + + const newInvalidFields = removeClassFromField(invalidFields, key) + setInvalidFields(newInvalidFields) } const handleStartDateChange = (date : Date) => {setStartDate(date)} const handleEndDateChange = (date : Date) => {setEndDate(date)} @@ -52,6 +57,9 @@ const EditCourseFormPage = () => { .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message + const newFields = applyStylesToErrorFields(err, formData, styles.errorField) + setInvalidFields(newFields); + setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => setLoading(false)) @@ -60,9 +68,12 @@ const EditCourseFormPage = () => { return (

Course Form

- - - + + + diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx index fa11e835..30c2e802 100644 --- a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx @@ -1,14 +1,19 @@ -import React,{useState} from 'react' +import React, {useState} from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' import styles from './nonContainerAutoGraderForm.scss' import TextField from 'components/shared/inputs/textField' import Button from 'components/shared/inputs/button' -import { useActionless } from 'redux/hooks' -import { SET_ALERT } from 'redux/types/active.types' +import {useActionless} from 'redux/hooks' +import {SET_ALERT} from 'redux/types/active.types' import RequestService from 'services/request.service' +import textStyles from '../shared/inputs/textField.scss' +import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; +import {ExpressValidationError} from "../../../devu-shared-modules"; + const NonContainerAutoGraderForm = () => { const [setAlert] = useActionless(SET_ALERT) + const [invalidFields, setInvalidFields] = useState(new Map()) const [formData,setFormData] = useState({ assignmentId: '', @@ -26,6 +31,9 @@ const NonContainerAutoGraderForm = () => { const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id + + const newInvalidFields = removeClassFromField(invalidFields, key) + setInvalidFields(newInvalidFields) setFormData(prevState => ({...prevState,[key] : value})) } @@ -51,9 +59,12 @@ const NonContainerAutoGraderForm = () => { .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Non-Container Auto-Grader Added' }) }) - .catch((err: Error) => { - console.log(err.message) - setAlert({ autoDelete: false, type: 'error', message: err.message }) + .catch((err: ExpressValidationError[] | Error) => { + const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message + const newFields = applyStylesToErrorFields(err, formData, textStyles.errorField) + setInvalidFields(newFields) + + setAlert({autoDelete: false, type: 'error', message: message}) }) @@ -71,10 +82,14 @@ const NonContainerAutoGraderForm = () => {

Non Container Auto Grader Form

Add a Non-Container Auto Grader

- - - - + + + +



diff --git a/devU-client/src/components/shared/inputs/textField.scss b/devU-client/src/components/shared/inputs/textField.scss index e6e67d3a..6bb76987 100644 --- a/devU-client/src/components/shared/inputs/textField.scss +++ b/devU-client/src/components/shared/inputs/textField.scss @@ -9,3 +9,11 @@ .input { padding: 5px; } + +.errorField { + color: red; + + .input { + border-color: red; + } +} From 18011eebf5bb309a5d03a09929468bf24ade4d6e Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Thu, 18 Apr 2024 23:38:40 -0400 Subject: [PATCH 028/400] Navbar functional, though unloaded route details are visible for a moment, unsure how to fix --- devU-client/src/components/misc/navbar.tsx | 58 +++++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/devU-client/src/components/misc/navbar.tsx b/devU-client/src/components/misc/navbar.tsx index 25b23977..dd2c32d4 100644 --- a/devU-client/src/components/misc/navbar.tsx +++ b/devU-client/src/components/misc/navbar.tsx @@ -1,7 +1,61 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import withBreadcrumbs from 'react-router-breadcrumbs-hoc' import { Link } from 'react-router-dom' import styles from './navbar.scss' +import RequestService from 'services/request.service' +import { Assignment, Course, User } from 'devu-shared-modules' + +const UserBreadcrumb = ({ match }: any) => { + const [userName, setUserName] = useState('') + + useEffect(() => { + getUser() + }, []) + + const getUser = async () => { + const user = await RequestService.get( `/api/users/${match.params.userId}` ) + setUserName(user.externalId) + } + + return (<>{userName}) +} + +const CourseBreadcrumb = ({ match }: any) => { + const [courseName, setCourseName] = useState('') + + useEffect(() => { + getCourse() + }, []) + + const getCourse = async () => { + const course = await RequestService.get( `/api/courses/${match.params.courseId}` ) + setCourseName(course.name) + } + + return (<>{courseName}) +} + +const AssignmentBreadcrumb = ({ match }: any) => { + const [assignmentName, setAssignmentName] = useState('') + + useEffect(() => { + getAssignment() + }, []) + + const getAssignment = async () => { + const assignment = await RequestService.get( `/api/assignments/${match.params.assignmentId}` ) + setAssignmentName(assignment.name) + } + + return (<>{assignmentName}) +} + +const routes = [ + { path: '/', breadcrumb: 'Home' }, + { path: '/users/:userId', breadcrumb: UserBreadcrumb}, + { path: '/courses/:courseId', breadcrumb: CourseBreadcrumb}, + { path: '/courses/:courseId/assignments/:assignmentId', breadcrumb: AssignmentBreadcrumb}, +] const Navbar = ({breadcrumbs}: any) => { @@ -18,4 +72,4 @@ const Navbar = ({breadcrumbs}: any) => { } -export default withBreadcrumbs()(Navbar) \ No newline at end of file +export default withBreadcrumbs(routes, { disableDefaults: true })(Navbar) \ No newline at end of file From 7cf36bc31618b818a638a2a9ade3cc63d34f76f8 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Thu, 18 Apr 2024 23:46:30 -0400 Subject: [PATCH 029/400] Updated some frontend routes to work with navbar --- devU-client/src/components/authenticatedRouter.tsx | 10 +++++----- devU-client/src/components/misc/navbar.tsx | 1 + .../src/components/pages/assignmentDetailPage.tsx | 9 +++++---- .../src/components/pages/courseAssignmentsListPage.tsx | 2 +- devU-client/src/components/pages/courseDetailPage.tsx | 2 +- .../src/components/pages/submissionDetailPage.tsx | 4 ++-- .../src/components/pages/submissionFeedbackPage.tsx | 4 ++-- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index b360047c..96cf48ef 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -35,18 +35,18 @@ const AuthenticatedRouter = () => ( - - - + - - + + + + diff --git a/devU-client/src/components/misc/navbar.tsx b/devU-client/src/components/misc/navbar.tsx index dd2c32d4..08c3bb9e 100644 --- a/devU-client/src/components/misc/navbar.tsx +++ b/devU-client/src/components/misc/navbar.tsx @@ -54,6 +54,7 @@ const routes = [ { path: '/', breadcrumb: 'Home' }, { path: '/users/:userId', breadcrumb: UserBreadcrumb}, { path: '/courses/:courseId', breadcrumb: CourseBreadcrumb}, + { path: '/courses/:courseId/gradebook', breadcrumb: 'Gradebook'}, { path: '/courses/:courseId/assignments/:assignmentId', breadcrumb: AssignmentBreadcrumb}, ] diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index bb28c149..95c674e5 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -128,9 +128,9 @@ const AssignmentDetailPage = () => { Update Assignment


- Add Non-Container Auto-Graders + Add Non-Container Auto-Graders


- Add Container Auto-Grader + Add Container Auto-Grader {/**Assignment Problems & Submission */} {assignmentProblems.map(assignmentProblem => ( @@ -165,6 +165,7 @@ type SubmissionProps = { assignmentProblems: AssignmentProblem[], } const SubmissionComponent = ({index, submission, submissionScore, submissionProblemScores, assignmentProblems}: SubmissionProps) => { + const { assignmentId, courseId } = useParams<{assignmentId: string, courseId: string}>() return (

Submission {index}:

@@ -181,8 +182,8 @@ const SubmissionComponent = ({index, submission, submissionScore, submissionProb
- Submission Details - Submission Feedback + Submission Details + Submission Feedback


) diff --git a/devU-client/src/components/pages/courseAssignmentsListPage.tsx b/devU-client/src/components/pages/courseAssignmentsListPage.tsx index 604a3b8b..f5288b94 100644 --- a/devU-client/src/components/pages/courseAssignmentsListPage.tsx +++ b/devU-client/src/components/pages/courseAssignmentsListPage.tsx @@ -41,7 +41,7 @@ const CourseAssignmentsListPage = () => {

Course Assignments List Page

- Add Assignments + Add Assignments
diff --git a/devU-client/src/components/pages/courseDetailPage.tsx b/devU-client/src/components/pages/courseDetailPage.tsx index c0cdb278..96c235c0 100644 --- a/devU-client/src/components/pages/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courseDetailPage.tsx @@ -64,7 +64,7 @@ const CourseDetailPage = () => { history.push(`/courses/${courseId}/gradebook`) }}>Gradebook
))} - View Submission Details + View Submission Details ) } From 2ed239529e302b43c77e6ba9de735b4d66ae77af Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Fri, 19 Apr 2024 00:07:40 -0400 Subject: [PATCH 030/400] Made changes according to Saiwang's code review --- .../containerAutoGrader/containerAutoGrader.controller.ts | 6 +++--- .../containerAutoGrader/containerAutoGrader.model.ts | 2 +- .../containerAutoGrader/containerAutoGrader.router.ts | 2 +- .../containerAutoGrader/containerAutoGrader.service.ts | 4 ++-- devU-api/src/entities/grader/grader.service.ts | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts index fa60b5ce..fa4c1961 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts @@ -30,10 +30,10 @@ export async function detail(req: Request, res: Response, next: NextFunction) { } } -export async function getByAssignment(req: Request, res: Response, next: NextFunction) { +export async function getObjectByAssignment(req: Request, res: Response, next: NextFunction) { try { const assignmentId = parseInt(req.params.id) - const containerAutoGrader = await ContainerAutoGraderService.listByAssignmentId(assignmentId) + const containerAutoGrader = await ContainerAutoGraderService.getGraderObjectByAssignmentId(assignmentId) res.status(200).json(containerAutoGrader.map(serialize)) } catch (err) { @@ -110,5 +110,5 @@ export default { post, put, _delete, - getByAssignment, + getObjectByAssignment, } \ No newline at end of file diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts index 0f012bf3..720f3892 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts @@ -22,7 +22,7 @@ export default class ContainerAutoGraderModel { * schemas: * ContainerAutoGrader: * type: object - * required: [assignmentId, autogradingImage, timeout, graderFile, makefileFile] + * required: [assignmentId, autogradingImage, timeout, graderFile] * properties: * assignmentId: * type: integer diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts index 923dc15d..73e84bd3 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts @@ -58,7 +58,7 @@ Router.get('/:id', asInt(), ContainerAutoGraderController.detail); * schema: * type: integer */ -Router.get('/assignment/:id', asInt(), ContainerAutoGraderController.getByAssignment); +Router.get('/assignment/:id', asInt(), ContainerAutoGraderController.getObjectByAssignment); /** * @swagger diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index 314b496f..1e295220 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -85,7 +85,7 @@ export async function list() { //The grader has not changed to the new function, so the fake function keep here for now to avoid error //But need to be deleted when the grader entity changed to the getGraderByAssignmentId -export async function listByAssignmentId(assignmentId: number) { +export async function getGraderObjectByAssignmentId(assignmentId: number) { if (!assignmentId) throw new Error('Missing AssignmentId') return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) } @@ -115,5 +115,5 @@ export default { _delete, list, getGraderByAssignmentId, - listByAssignmentId, + getGraderObjectByAssignmentId, } \ No newline at end of file diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index b6433645..779c1bac 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -23,7 +23,7 @@ export async function grade(submissionId: number) { const filepaths = content.filepaths //Using the field name that was written on the whiteboard for now const nonContainerAutograders = await nonContainerAutograderService.listByAssignmentId(assignmentId) - const containerAutograders = await containerAutograderService.listByAssignmentId(assignmentId) + const containerAutograders = await containerAutograderService.getGraderObjectByAssignmentId(assignmentId) //No longer called in proper implementation but fixed to prevent compilation error const assignmentProblems = await assignmentProblemService.list(assignmentId) From c846abcb02b1db281550440a8e8890ffb14ce124 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Fri, 19 Apr 2024 01:15:50 -0400 Subject: [PATCH 031/400] Form Passover Updated forms across the UI to be more user accessible/usable. --- devU-client/src/assets/variables.scss | 3 +- .../src/components/authenticatedRouter.tsx | 2 +- .../components/pages/assignmentDetailPage.tsx | 2 +- .../components/pages/assignmentFormPage.tsx | 29 +++++++++++---- .../components/pages/assignmentUpdatePage.tsx | 36 +++++++++++++------ .../src/components/pages/courseUpdatePage.tsx | 11 ++++-- .../src/components/pages/coursesFormPage.tsx | 11 ++++-- .../pages/nonContainerAutoGraderForm.tsx | 8 +++-- 8 files changed, 75 insertions(+), 27 deletions(-) diff --git a/devU-client/src/assets/variables.scss b/devU-client/src/assets/variables.scss index 8ef49958..f93a7d50 100644 --- a/devU-client/src/assets/variables.scss +++ b/devU-client/src/assets/variables.scss @@ -52,7 +52,8 @@ $font-family: 'Open Sans', 'Helvetica', 'Arial', sans-serif; $medium: 1000px; $small: 600px; -$extreme: 320px; +// $extreme: 320px; +$extreme: 700px; @mixin ellipsis { overflow: hidden; diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index a3a6ac40..d1e9932e 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -44,7 +44,7 @@ const AuthenticatedRouter = () => ( - + diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index 9c8e6e5f..151bc8c4 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -109,7 +109,7 @@ const AssignmentDetailPage = () => { Update Assignment


- Add Non-Container Auto-Graders + Add Non-Container Auto-Graders {/**Assignment Problems & Submission */} {assignmentProblems.map(assignmentProblem => ( diff --git a/devU-client/src/components/pages/assignmentFormPage.tsx b/devU-client/src/components/pages/assignmentFormPage.tsx index cb1968cc..fbc336e7 100644 --- a/devU-client/src/components/pages/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/assignmentFormPage.tsx @@ -10,12 +10,14 @@ import TextField from 'components/shared/inputs/textField' import Button from 'components/shared/inputs/button' import { SET_ALERT } from 'redux/types/active.types' +import { useParams } from 'react-router-dom' const AssignmentCreatePage = () => { const [setAlert] = useActionless(SET_ALERT) + const {courseId} = useParams<{courseId : string}>() const [formData, setFormData] = useState({ - courseId: 0, + courseId: courseId, name: '', categoryName: null, description: null, @@ -35,6 +37,10 @@ const AssignmentCreatePage = () => { setFormData(prevState => ({...prevState,[key] : value})) } + const handleCheckbox = (e: React.ChangeEvent) => { + setFormData(prevState => ({...prevState,disableHandins : e.target.checked})) + } + const handleStartDateChange = (date : Date) => {setStartDate(date)} const handleEndDateChange = (date : Date) => {setEndDate(date)} const handleDueDateChange = (date: Date) => {setDueDate(date)} @@ -70,16 +76,27 @@ const AssignmentCreatePage = () => { return(

Assignment Form

- - - - + + +
+ +
+ +
+ +
+ +
+ - + + + +
diff --git a/devU-client/src/components/pages/assignmentUpdatePage.tsx b/devU-client/src/components/pages/assignmentUpdatePage.tsx index 5f586d22..2f90f987 100644 --- a/devU-client/src/components/pages/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/assignmentUpdatePage.tsx @@ -19,10 +19,13 @@ type UrlParams = { } const AssignmentUpdatePage = () => { + const { assignmentId } = useParams() as UrlParams + const { courseId } = useParams<{courseId : string}>() + const [toggleForm,setToggleForm] = useState(false) const [problemFormData,setProblemFormData] = useState({ - assignmentId: '', + assignmentId: assignmentId, problemName: '', maxScore: '', }) @@ -49,7 +52,7 @@ const AssignmentUpdatePage = () => { .finally(() => { setLoading(false) setProblemFormData({ - assignmentId: '', + assignmentId: assignmentId, problemName: '', maxScore: '', }) @@ -65,7 +68,7 @@ const AssignmentUpdatePage = () => { const [setAlert] = useActionless(SET_ALERT) const [formData, setFormData] = useState({ - courseId: 0, + courseId: courseId, name: '', categoryName: null, description: null, @@ -78,13 +81,14 @@ const AssignmentUpdatePage = () => { const [dueDate, setDueDate] = useState(new Date()) const [loading, setLoading] = useState(false) - const { assignmentId } = useParams() as UrlParams - const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id setFormData(prevState => ({...prevState,[key] : value})) } + const handleCheckbox = (e: React.ChangeEvent) => { + setFormData(prevState => ({...prevState,disableHandins : e.target.checked})) + } const handleStartDateChange = (date : Date) => {setStartDate(date)} const handleEndDateChange = (date : Date) => {setEndDate(date)} const handleDueDateChange = (date: Date) => {setDueDate(date)} @@ -127,7 +131,6 @@ const AssignmentUpdatePage = () => { {toggleForm && (


- @@ -135,16 +138,27 @@ const AssignmentUpdatePage = () => { )}



- - - - + + +
+ +
+ +
+ +
+ +
+ - + + + +
diff --git a/devU-client/src/components/pages/courseUpdatePage.tsx b/devU-client/src/components/pages/courseUpdatePage.tsx index 4ca83e9f..65b63777 100644 --- a/devU-client/src/components/pages/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/courseUpdatePage.tsx @@ -70,8 +70,15 @@ const CourseUpdatePage = ({}) => { - - + + +
+ +
+ +
+ +
diff --git a/devU-client/src/components/pages/coursesFormPage.tsx b/devU-client/src/components/pages/coursesFormPage.tsx index 44d2ded4..b26ea03e 100644 --- a/devU-client/src/components/pages/coursesFormPage.tsx +++ b/devU-client/src/components/pages/coursesFormPage.tsx @@ -63,8 +63,15 @@ const EditCourseFormPage = () => { - - + + +
+ +
+ +
+ +
diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx index fa11e835..6e72bb0a 100644 --- a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx @@ -7,11 +7,14 @@ import { useActionless } from 'redux/hooks' import { SET_ALERT } from 'redux/types/active.types' import RequestService from 'services/request.service' +import { useParams } from 'react-router-dom' + const NonContainerAutoGraderForm = () => { const [setAlert] = useActionless(SET_ALERT) + const { assignmentId } = useParams<{ assignmentId: string }>() const [formData,setFormData] = useState({ - assignmentId: '', + assignmentId: assignmentId, question: '', correctString: '', score: '', @@ -58,7 +61,7 @@ const NonContainerAutoGraderForm = () => { setFormData({ - assignmentId: '', + assignmentId: assignmentId, question: '', correctString: '', score: '', @@ -71,7 +74,6 @@ const NonContainerAutoGraderForm = () => {

Non Container Auto Grader Form

Add a Non-Container Auto Grader

- From d7fd3215b48e08fe55b58961b189d69940ad7144 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Fri, 19 Apr 2024 01:20:39 -0400 Subject: [PATCH 032/400] Updated Container Autograder Form Updated Container Autograder creation form to not need user input of assignmnet id --- .../src/components/pages/containerAutoGraderForm.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devU-client/src/components/pages/containerAutoGraderForm.tsx b/devU-client/src/components/pages/containerAutoGraderForm.tsx index c5be5109..24c7a723 100644 --- a/devU-client/src/components/pages/containerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/containerAutoGraderForm.tsx @@ -6,14 +6,16 @@ import Button from 'components/shared/inputs/button' import { useActionless } from 'redux/hooks' import { SET_ALERT } from 'redux/types/active.types' import RequestService from 'services/request.service' +import { useParams } from 'react-router-dom' const ContainerAutoGraderForm = () => { const [setAlert] = useActionless(SET_ALERT) + const { assignmentId } = useParams<{ assignmentId: string }>() const [graderFile, setGraderFile] = useState() const [makefile, setMakefile] = useState() const [formData,setFormData] = useState({ - assignmentId: '', + assignmentId: assignmentId, autogradingImage: '', timeout: '', }) @@ -45,13 +47,12 @@ const ContainerAutoGraderForm = () => { setAlert({ autoDelete: true, type: 'success', message: 'Container Auto-Grader Added' }) }) .catch((err: Error) => { - console.log(err.message) setAlert({ autoDelete: false, type: 'error', message: err.message }) }) setFormData({ - assignmentId: '', + assignmentId: assignmentId, autogradingImage: '', timeout: '', }) @@ -62,7 +63,6 @@ const ContainerAutoGraderForm = () => {

Non Container Auto Grader Form

Add a Non-Container Auto Grader

- From 048de857059ca531de5f20d38674ee84e014e694 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Fri, 19 Apr 2024 09:35:08 -0400 Subject: [PATCH 033/400] Fixing some outdated tests; deny all permissions if a user dropped a course --- .../tests/login.developer.controller.test.ts | 2 +- devU-api/src/authorization/authorization.middleware.ts | 2 +- .../tests/nonContainerAutoGrader.grader.test.ts | 10 +++++----- .../src/entities/role/tests/role.controller.test.ts | 3 +++ .../src/entities/role/tests/role.serializer.test.ts | 3 +++ .../entities/role/tests/userCourse.controller.test.ts | 1 - .../entities/role/tests/userCourse.serializer.test.ts | 1 - 7 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 devU-api/src/entities/role/tests/role.controller.test.ts create mode 100644 devU-api/src/entities/role/tests/role.serializer.test.ts delete mode 100644 devU-api/src/entities/role/tests/userCourse.controller.test.ts delete mode 100644 devU-api/src/entities/role/tests/userCourse.serializer.test.ts diff --git a/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts b/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts index babcdea8..6220550d 100644 --- a/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts +++ b/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts @@ -42,7 +42,7 @@ describe('LoginDeveloperController', () => { test('Returns refreshToken as cookie', () => expect(res.cookie).toBeCalledWith('refreshToken', refreshToken, refreshCookieOptions)) test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) - test('Generic response returned ', () => expect(res.json).toBeCalledWith({ message: 'Login successful' })) + test('Generic response returned ', () => expect(res.json).toBeCalledWith({ message: 'Login successful', 'userId': 1 })) }) describe('400 - Bad Request', () => { diff --git a/devU-api/src/authorization/authorization.middleware.ts b/devU-api/src/authorization/authorization.middleware.ts index 4847f7d9..2216d599 100644 --- a/devU-api/src/authorization/authorization.middleware.ts +++ b/devU-api/src/authorization/authorization.middleware.ts @@ -29,7 +29,7 @@ export function isAuthorized(permission: string, permissionIfSelf?: string) { // If the course doesn't exist or the user is not enrolled, return the same error, so we don't leak information // about what courses exist - if (!userCourse) { + if (!userCourse || userCourse.dropped) { return res.status(404).json(NotFound) } diff --git a/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.grader.test.ts b/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.grader.test.ts index d5994437..dfe2e0bd 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.grader.test.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.grader.test.ts @@ -22,14 +22,14 @@ describe('NonContainerAutoGrader grader', () => { const studentAnswer = '42' const expectedResult = checkAnswer(studentAnswer, mockQuestionWithoutRegex) - expect(expectedResult).toEqual(mockQuestionWithoutRegex.score) + expect(expectedResult[0]).toEqual(mockQuestionWithoutRegex.score) }) test('Student messed up', () => { const studentAnswer = '69' const expectedResult = checkAnswer(studentAnswer, mockQuestionWithoutRegex) - expect(expectedResult).toEqual(0) + expect(expectedResult[0]).toEqual(0) }) }) @@ -52,21 +52,21 @@ describe('NonContainerAutoGrader grader', () => { const studentAnswer = 'So long, and thanks for all the fish.' const expectedResult = checkAnswer(studentAnswer, mockQuestionWithRegex) - expect(expectedResult).toEqual(mockQuestionWithRegex.score) + expect(expectedResult[0]).toEqual(mockQuestionWithRegex.score) }) test('Incorrect answer', () => { const studentAnswer = '~~dolphin noises~~' const expectedResult = checkAnswer(studentAnswer, mockQuestionWithRegex) - expect(expectedResult).toEqual(0) + expect(expectedResult[0]).toEqual(0) }) test('correct answer but incorrect formatting', () => { const studentAnswer = 'So long, and thanks for all the fish.'.toUpperCase() const expectedResult = checkAnswer(studentAnswer, mockQuestionWithRegex) - expect(expectedResult).toEqual(0) + expect(expectedResult[0]).toEqual(0) }) }) }) \ No newline at end of file diff --git a/devU-api/src/entities/role/tests/role.controller.test.ts b/devU-api/src/entities/role/tests/role.controller.test.ts new file mode 100644 index 00000000..aafcfc95 --- /dev/null +++ b/devU-api/src/entities/role/tests/role.controller.test.ts @@ -0,0 +1,3 @@ +// TODO: Role testing. Lots of it! + +describe('this is where tests will go', () => {}) \ No newline at end of file diff --git a/devU-api/src/entities/role/tests/role.serializer.test.ts b/devU-api/src/entities/role/tests/role.serializer.test.ts new file mode 100644 index 00000000..aafcfc95 --- /dev/null +++ b/devU-api/src/entities/role/tests/role.serializer.test.ts @@ -0,0 +1,3 @@ +// TODO: Role testing. Lots of it! + +describe('this is where tests will go', () => {}) \ No newline at end of file diff --git a/devU-api/src/entities/role/tests/userCourse.controller.test.ts b/devU-api/src/entities/role/tests/userCourse.controller.test.ts deleted file mode 100644 index fb96d0f9..00000000 --- a/devU-api/src/entities/role/tests/userCourse.controller.test.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO: Role testing. Lots of it! diff --git a/devU-api/src/entities/role/tests/userCourse.serializer.test.ts b/devU-api/src/entities/role/tests/userCourse.serializer.test.ts deleted file mode 100644 index fb96d0f9..00000000 --- a/devU-api/src/entities/role/tests/userCourse.serializer.test.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO: Role testing. Lots of it! From cef7d7893b8e8cdc52c2e8908ee8cf38e700f869 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 21 Apr 2024 00:20:26 -0400 Subject: [PATCH 034/400] renames some vars --- devU-api/src/tango/tango.service.ts | 54 +++++++++++++++++------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/devU-api/src/tango/tango.service.ts b/devU-api/src/tango/tango.service.ts index 59c335fe..cf720a53 100644 --- a/devU-api/src/tango/tango.service.ts +++ b/devU-api/src/tango/tango.service.ts @@ -1,56 +1,57 @@ import './tango.types' +import fetch from "node-fetch"; -const tangoHost = `http://${(process.env.TANGO_KEY ?? 'localhost:3000')}` +const tangoHost = `http://127.0.0.1:3000` const tangoKey = process.env.TANGO_KEY ?? 'test' // for more info https://docs.autolabproject.com/tango-rest/ /** - * Opens a directory for a given course and lab. - * @param courselab - The combination of the course name and the lab name. + * Opens a directory for a given course. + * @param course - The course name. */ -export async function openDirectory(courselab: string): Promise { - const url = `${tangoHost}/open/${tangoKey}/${courselab}/` +export async function createCourse(course: string): Promise { + const url = `${tangoHost}/open/${tangoKey}/${course}/` const response = await fetch(url, { method: 'GET' }) - return response.ok ? await response.json() : null + return response.ok ? await response.json() as OpenResponse : null } /** - * Uploads a file to the server for a given course and lab. - * @param courselab - The combination of the course name and the lab name. + * Uploads a file to the server for a given course. + * @param course - The course name. * @param fileName - The file name, used to identify the file when uploaded * @param file - The file to be uploaded. */ -export async function uploadFile(courselab: string, file: File, fileName: string): Promise { - const url = `${tangoHost}/upload/${tangoKey}/${courselab}/` +export async function uploadFile(course: string, file: File, fileName: string): Promise { + const url = `${tangoHost}/upload/${tangoKey}/${course}/` const formData = new FormData() formData.append('file', file) const response = await fetch(url, { method: 'POST', body: formData, headers: { 'filename': fileName } }) - return response.ok ? await response.json() : null + return response.ok ? await response.json() as UploadResponse : null } /** - * Adds a job to the server for a given course and lab. - * @param courselab - The combination of the course name and the lab name. + * Adds a job to the server for a given course. + * @param course - The course name. * @param job - The job request object. */ -export async function addJob(courselab: string, job: AddJobRequest): Promise { - const url = `${tangoHost}/addJob/${tangoKey}/${courselab}/` +export async function addJob(course: string, job: AddJobRequest): Promise { + const url = `${tangoHost}/addJob/${tangoKey}/${course}/` const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(job), }) - return response.ok ? await response.json() : null + return response.ok ? await response.json() as AddJobResponse : null } /** * Polls the server for the status of a job. - * @param courselab - The combination of the course name and the lab name. + * @param course - The course name. * @param outputFile - The name of the output file. */ -export async function pollJob(courselab: string, outputFile: string): Promise { - const url = `${tangoHost}/poll/${tangoKey}/${courselab}/${outputFile}/` +export async function pollJob(course: string, outputFile: string): Promise { + const url = `${tangoHost}/poll/${tangoKey}/${course}/${outputFile}/` const response = await fetch(url, { method: 'GET' }) const data = await response.json() return response.ok ? @@ -58,13 +59,22 @@ export async function pollJob(courselab: string, outputFile: string): Promise { + const url = `${tangoHost}/` + const response = await fetch(url, { method: 'GET' }) + return response.ok +} + /** * Retrieves information about the Tango service. */ export async function getInfo(): Promise { const url = `${tangoHost}/info/${tangoKey}/` const response = await fetch(url, { method: 'GET' }) - return response.ok ? await response.json() : null + return response.ok ? await response.json() as InfoResponse: null } /** @@ -74,7 +84,7 @@ export async function getInfo(): Promise { export async function getPoolInfo(image: string): Promise { const url = `${tangoHost}/pool/${tangoKey}/${image}/` const response = await fetch(url, { method: 'GET' }) - return response.ok ? await response.json() : null + return response.ok ? await response.json() as Object: null } /** @@ -90,7 +100,7 @@ export async function preallocateInstances(image: string, num: number, request: headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }) - return response.ok ? await response.json() : null + return response.ok ? await response.json() as PreallocResponse: null } /** From f70141097bedaded9075eb0e914e2f87c6480321 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 21 Apr 2024 00:21:31 -0400 Subject: [PATCH 035/400] added test files --- devU-api/src/tango/tests/test_files/Makefile | 4 + .../src/tango/tests/test_files/autograde.tar | Bin 0 -> 10240 bytes .../tango/tests/test_files/create_grader.sh | 3 + devU-api/src/tango/tests/test_files/grader.py | 98 ++++++++++++++++++ devU-api/src/tango/tests/test_files/grader.sh | 3 + devU-api/src/tango/tests/test_files/handin.py | 10 ++ .../tests/test_files/handin_incorrect1.py | 8 ++ .../tests/test_files/handin_incorrect2.py | 8 ++ .../tests/test_files/handin_incorrect3.py | 8 ++ .../tests/test_files/handin_incorrect4.py | 8 ++ 10 files changed, 150 insertions(+) create mode 100644 devU-api/src/tango/tests/test_files/Makefile create mode 100644 devU-api/src/tango/tests/test_files/autograde.tar create mode 100644 devU-api/src/tango/tests/test_files/create_grader.sh create mode 100644 devU-api/src/tango/tests/test_files/grader.py create mode 100644 devU-api/src/tango/tests/test_files/grader.sh create mode 100644 devU-api/src/tango/tests/test_files/handin.py create mode 100644 devU-api/src/tango/tests/test_files/handin_incorrect1.py create mode 100644 devU-api/src/tango/tests/test_files/handin_incorrect2.py create mode 100644 devU-api/src/tango/tests/test_files/handin_incorrect3.py create mode 100644 devU-api/src/tango/tests/test_files/handin_incorrect4.py diff --git a/devU-api/src/tango/tests/test_files/Makefile b/devU-api/src/tango/tests/test_files/Makefile new file mode 100644 index 00000000..629e3e13 --- /dev/null +++ b/devU-api/src/tango/tests/test_files/Makefile @@ -0,0 +1,4 @@ +all: + tar -xf autograde.tar + chmod +x grader.sh + ./grader.sh \ No newline at end of file diff --git a/devU-api/src/tango/tests/test_files/autograde.tar b/devU-api/src/tango/tests/test_files/autograde.tar new file mode 100644 index 0000000000000000000000000000000000000000..295b59c02881804510015d27f6857219bfe3f702 GIT binary patch literal 10240 zcmeHK-EZ135a&66#g#n_R7UMMAuSS*)3o>QsjW~b1~P$@Ii><3{`Z|@=L4wvF%5N- z><2hIzPn$3_rWoj@s!Jcwr!nn5nNFeweX8ZA!&Z=M?n}yEgB4nPgxL!L5ol_h=LZ7 z^FgXDuX7bkfR`=7;pcgXomFc#&2$z+3Y<}@Oi9~Cq7m@S9zuUr$eB|3?A1q z^BZ#G4kPus-?&d3=d5w17PS!%L1k&46rj@S^avPUVFnAD2#pjDAu>`lgc~DizZNvr zW~~y{j;$I+J!%yx(U(YhixB+`E3kI}hd*&~gagMzU|@;~D~fl<&_NI%13&kVYoU5_m8#O*4R;*VO zE8sCds%sWhAC=(G3tQ>V` zsf0viYXo0F?E&9p{83?@VU4Vp3ZGVbCNBL%@l{@ypS?ALmk8)IU6^W1W>Bh*j(r+( zba9({yVG^{0GA_7U45#TsnJ>cMYp@A-+dQ7s^J0haQ6LtE;O zt2Xc<;q_g3Uk|Ttjei{9y03mnfSC($8DQ*u@p9R@JLqjA>}{l1_d8t>$7hiRkMF^2~3=KQ|NVArI&Od~rr(ddPoa{wIVDPUio> zck}-lLsFUkba6FF#MLCu7x;^}Rf|+Gut{;&KZO+nCpZQi1C9a5fMdWh;23ZWI0hU8 ijseGjW56-s7;p?Y1{?#90mp!2z%k$$a18uS27Uu;5ElIa literal 0 HcmV?d00001 diff --git a/devU-api/src/tango/tests/test_files/create_grader.sh b/devU-api/src/tango/tests/test_files/create_grader.sh new file mode 100644 index 00000000..9becea49 --- /dev/null +++ b/devU-api/src/tango/tests/test_files/create_grader.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +tar -cf autograde.tar grader.py grader.sh \ No newline at end of file diff --git a/devU-api/src/tango/tests/test_files/grader.py b/devU-api/src/tango/tests/test_files/grader.py new file mode 100644 index 00000000..d425e66c --- /dev/null +++ b/devU-api/src/tango/tests/test_files/grader.py @@ -0,0 +1,98 @@ +import json + + +def finished_grading(): + print("\nGrader finished") + print(json.dumps({"scores": scores})) + exit(0) + + +print("Grader starting") + +scores = { + "q1": 0, + "q2": 0, + "q3": 0, +} + +negative_tests = { + -9999987654321: 1, + -99: 1, + -32: 1, + -31: 1, + -30: 1, + -29: 1, + -10: 1, + -3: 1, + -2: 1, + -1: 1 +} + +positive_odd_tests = { + 1: 3, + 3: 5, + 5: 7, + 7: 9, + 9: 11, + 11: 13, + 13: 15, + 15: 17, + 101: 103, + 1005: 1007, +} + +positive_even_tests = { + 2: 3, + 4: 5, + 6: 7, + 8: 9, + 10: 11, + 100: 101, + 1000: 1001, + 12345678: 12345679, + 2222: 2223, + 3336: 3337, +} + +try: + from handin import get_next_positive_odd_number +except ImportError: + print("Unable to import get_next_positive_odd_number from handin.py") + finished_grading() + +print("\nTesting negative numbers") +for input_number, expected_result in negative_tests.items(): + result = get_next_positive_odd_number(input_number) + if result == expected_result: + scores["q1"] += 1 + print(f"get_next_positive_odd_number({input_number}) returned {result}, and that's correct! (+1)") + else: + print( + f"get_next_positive_odd_number({input_number}) incorrectly returned {result}, expected {expected_result}. (+0)") + +print("\nTesting positive odd numbers") +for input_number, expected_result in positive_odd_tests.items(): + result = get_next_positive_odd_number(input_number) + if result == expected_result: + scores["q2"] += 1 + print(f"get_next_positive_odd_number({input_number}) returned {result}, and that's correct! (+1)") + else: + print( + f"get_next_positive_odd_number({input_number}) incorrectly returned {result}, expected {expected_result}. (+0)") + +print("\nTesting positive even numbers") +for input_number, expected_result in positive_even_tests.items(): + result = get_next_positive_odd_number(input_number) + if result == expected_result: + scores["q3"] += 1 + print(f"get_next_positive_odd_number({input_number}) returned {result}, and that's correct! (+1)") + else: + print( + f"get_next_positive_odd_number({input_number}) incorrectly returned {result}, expected {expected_result}. (+0)") + +if all([scores["q1"] == 10, scores["q2"] == 10, scores["q3"] == 10]): + print("\nGreat job!") +else: + print("\nTry again.") + +finished_grading() diff --git a/devU-api/src/tango/tests/test_files/grader.sh b/devU-api/src/tango/tests/test_files/grader.sh new file mode 100644 index 00000000..c1aa59e8 --- /dev/null +++ b/devU-api/src/tango/tests/test_files/grader.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +python3 grader.py \ No newline at end of file diff --git a/devU-api/src/tango/tests/test_files/handin.py b/devU-api/src/tango/tests/test_files/handin.py new file mode 100644 index 00000000..978677d4 --- /dev/null +++ b/devU-api/src/tango/tests/test_files/handin.py @@ -0,0 +1,10 @@ +# This is an example of a correct solution. +# It can be helpful to test your grader with the correct solution in the same directory where the student's handin +# will end up. You don't need to include the correct solution in the grader archive. + +def get_next_positive_odd_number(number: int) -> int: + # Given `number`, return the next positive odd number. + # If `number` is negative, return 1. + # If `number` is positive and odd, return `number` + 2. + # If `number` is positive and even, return `number` + 1. + return max(1, number + (number % 2) + 1) diff --git a/devU-api/src/tango/tests/test_files/handin_incorrect1.py b/devU-api/src/tango/tests/test_files/handin_incorrect1.py new file mode 100644 index 00000000..f44068cb --- /dev/null +++ b/devU-api/src/tango/tests/test_files/handin_incorrect1.py @@ -0,0 +1,8 @@ +# This is an example of an incorrect solution. + +def get_next_positive_odd_number(number: int) -> int: + # Given `number`, return the next positive odd number. + # If `number` is negative, return 1. + # If `number` is positive and odd, return `number` + 2. + # If `number` is positive and even, return `number` + 1. + return max(1, number + (number % 2)) diff --git a/devU-api/src/tango/tests/test_files/handin_incorrect2.py b/devU-api/src/tango/tests/test_files/handin_incorrect2.py new file mode 100644 index 00000000..dabbdbdd --- /dev/null +++ b/devU-api/src/tango/tests/test_files/handin_incorrect2.py @@ -0,0 +1,8 @@ +# This is an example of an incorrect solution. + +def get_next_positive_odd_number(number: int) -> int: + # Given `number`, return the next positive odd number. + # If `number` is negative, return 1. + # If `number` is positive and odd, return `number` + 2. + # If `number` is positive and even, return `number` + 1. + return max(1, number + (number % 3) + 1) diff --git a/devU-api/src/tango/tests/test_files/handin_incorrect3.py b/devU-api/src/tango/tests/test_files/handin_incorrect3.py new file mode 100644 index 00000000..995dd7c2 --- /dev/null +++ b/devU-api/src/tango/tests/test_files/handin_incorrect3.py @@ -0,0 +1,8 @@ +# This is an example of an incorrect solution. + +def get_next_positive_odd_number(number: int) -> int: + # Given `number`, return the next positive odd number. + # If `number` is negative, return 1. + # If `number` is positive and odd, return `number` + 2. + # If `number` is positive and even, return `number` + 1. + return min(1, number + (number % 3) + 1) diff --git a/devU-api/src/tango/tests/test_files/handin_incorrect4.py b/devU-api/src/tango/tests/test_files/handin_incorrect4.py new file mode 100644 index 00000000..9ecae225 --- /dev/null +++ b/devU-api/src/tango/tests/test_files/handin_incorrect4.py @@ -0,0 +1,8 @@ +# This is an example of an incorrect solution because the function name is incorrect. + +def incorrect_function_name(number: int) -> int: + # Given `number`, return the next positive odd number. + # If `number` is negative, return 1. + # If `number` is positive and odd, return `number` + 2. + # If `number` is positive and even, return `number` + 1. + return max(1, number + (number % 2) + 1) From 041feef60ac23cb8f930528b6c5d16b0d6525c5d Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 21 Apr 2024 00:24:21 -0400 Subject: [PATCH 036/400] added env vars needed for tango --- docker-compose.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index feaf0af2..f1816242 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -91,11 +91,7 @@ services: environment: - DOCKER_REDIS_HOSTNAME=redis - RESTFUL_KEY=devutangokey -# - DOCKER_DEPLOYMENT - # Path to volumes within the Tango container. Does not need to be modified. -# - DOCKER_VOLUME_PATH - # Modify the below to be the path to volumes on your host machine -# - DOCKER_TANGO_HOST_VOLUME_PATH + - DOCKER_TANGO_HOST_VOLUME_PATH= #TODO add the absolute path to tango_files here before running depends_on: - redis @@ -104,7 +100,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ./logs/tango/:/var/log/tango/ - ./logs/tangonginx:/var/log/nginx -# - ./Tango/volumes:/opt/TangoService/Tango/volumes + - ./tango_files/volumes:/opt/TangoService/Tango/volumes # certbot: # container_name: certbot From 15442c6dda47cf2381fd1242cb60449de6266bd9 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 21 Apr 2024 00:26:56 -0400 Subject: [PATCH 037/400] added tango status --- devU-api/src/status/status.controller.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/devU-api/src/status/status.controller.ts b/devU-api/src/status/status.controller.ts index e3a1c3c8..1c1bff4a 100644 --- a/devU-api/src/status/status.controller.ts +++ b/devU-api/src/status/status.controller.ts @@ -1,5 +1,16 @@ import { Request, Response, NextFunction } from 'express' +import { getInfo, tangoHelloWorld } from '../tango/tango.service' export async function get(req: Request, res: Response, next: NextFunction) { - res.status(200).send() + let tango = false + let tangoStatus; + try { + tango = await tangoHelloWorld() + tangoStatus = await getInfo() + console.log(tangoStatus) + } catch (e) { + console.log('failed to start tango') + console.log(e) + } + res.status(200).send(`Api is online.\n Tango is ${tango ? `Online` : 'Offline'}`) } From 951da07e2e676e8742316d575f3843614edcf68d Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 21 Apr 2024 00:59:36 -0400 Subject: [PATCH 038/400] added node-fetch --- devU-api/package-lock.json | 39 ++++++++++++++++++++++++++++++++++++++ devU-api/package.json | 1 + 2 files changed, 40 insertions(+) diff --git a/devU-api/package-lock.json b/devU-api/package-lock.json index 927af045..8d145fe4 100644 --- a/devU-api/package-lock.json +++ b/devU-api/package-lock.json @@ -21,6 +21,7 @@ "minio": "^7.0.18", "morgan": "^1.10.0", "multer": "^1.4.2", + "node-fetch": "^2.7.0", "passport": "^0.6.0", "passport-saml": "^3.2.2", "pg": "^8.4.0", @@ -9271,6 +9272,44 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/devU-api/package.json b/devU-api/package.json index 6613c68a..1064985a 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -61,6 +61,7 @@ "body-parser": "^1.19.0", "colors": "^1.4.0", "config": "^3.3.6", + "node-fetch": "^2.7.0", "cookie-parser": "^1.4.5", "cors": "^2.8.5", "devu-shared-modules": "./devu-shared-modules", From 7a5704e3618a82ed2c70724989ddd9e830ccd3e8 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Mon, 22 Apr 2024 12:56:48 -0400 Subject: [PATCH 039/400] Add endpoint to get a role in a course by name --- devU-api/src/entities/course/course.router.ts | 2 +- devU-api/src/entities/role/role.controller.ts | 17 ++++++++++++++- devU-api/src/entities/role/role.defaults.ts | 2 -- devU-api/src/entities/role/role.router.ts | 21 +++++++++++++++++++ .../submissionProblemScore.router.ts | 2 ++ .../tests/userCourse.controller.test.ts | 2 +- .../entities/userCourse/userCourse.router.ts | 8 ++++--- 7 files changed, 46 insertions(+), 8 deletions(-) diff --git a/devU-api/src/entities/course/course.router.ts b/devU-api/src/entities/course/course.router.ts index 292dd10d..714f0114 100644 --- a/devU-api/src/entities/course/course.router.ts +++ b/devU-api/src/entities/course/course.router.ts @@ -82,7 +82,7 @@ Router.post('/', /*isAuthorized('admin..'),*/ validator, CourseController.post) /** * @swagger - * /courses: + * /courses/instructor: * post: * summary: Creates a course and adds the requester as an instructor in the course. Intended to be used during * development only. Production will have an admin who can create courses and add the first instructor diff --git a/devU-api/src/entities/role/role.controller.ts b/devU-api/src/entities/role/role.controller.ts index de1a66e3..ec691169 100644 --- a/devU-api/src/entities/role/role.controller.ts +++ b/devU-api/src/entities/role/role.controller.ts @@ -43,6 +43,21 @@ export async function detail(req: Request, res: Response, next: NextFunction) { next(err) } } +export async function detailByName(req: Request, res: Response, next: NextFunction) { + try { + const courseId = parseInt(req.params.courseId) + const roleName = req.params.roleName + const role = await RoleService.retrieveByCourseAndName(courseId, roleName) + + if (!role) return res.status(404).json(NotFound) + + const response = serialize(role) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} export async function post(req: Request, res: Response, next: NextFunction) { try { @@ -81,4 +96,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { getByCourse, getAll, detail, post, put, _delete } +export default { getByCourse, getAll, detail, detailByName, post, put, _delete } diff --git a/devU-api/src/entities/role/role.defaults.ts b/devU-api/src/entities/role/role.defaults.ts index d46e3207..b4bbcddb 100644 --- a/devU-api/src/entities/role/role.defaults.ts +++ b/devU-api/src/entities/role/role.defaults.ts @@ -2,7 +2,6 @@ import {Role} from 'devu-shared-modules' const student: Role = { - name: 'student', assignmentViewAll: false, assignmentEditAll: false, @@ -24,7 +23,6 @@ const student: Role = { } const instructor: Role = { - name: 'instructor', assignmentViewAll: true, assignmentEditAll: true, diff --git a/devU-api/src/entities/role/role.router.ts b/devU-api/src/entities/role/role.router.ts index 6eecc96e..60625a7b 100644 --- a/devU-api/src/entities/role/role.router.ts +++ b/devU-api/src/entities/role/role.router.ts @@ -68,6 +68,27 @@ Router.get('/:id', isAuthorized('roleViewAll'), asInt(), RoleController.detail) // TODO: make sure all details are checking the courseId. Add matching 'detailByCourse' for each and only admin can hit 'detail'? +/** + * @swagger + * /course/:courseId/roles/{name}: + * get: + * summary: Retrieve a single role by name + * tags: + * - Roles + * responses: + * '200': + * description: OK + * parameters: + * - name: id + * description: Enter role id + * in: path + * required: true + * schema: + * type: integer + */ +Router.get('/:roleName', isAuthorized('enrolled'), RoleController.detailByName) + + /** * @swagger * /course/:courseId/roles: diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts index a6a4952b..be746f7c 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts @@ -12,6 +12,8 @@ import SubmissionProblemScoreController from '../submissionProblemScore/submissi const Router = express.Router() +// TODO: get by assignment and user + /** * @swagger * /course/:courseId/assignment/:assignmentId/submission/:{submissionId}/submission-problem-scores: diff --git a/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts b/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts index 1ce39d6f..4f14825c 100644 --- a/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts +++ b/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts @@ -47,7 +47,7 @@ describe('UserCourseController', () => { expectedDbResult = {} as UpdateResult }) - describe('GET - /user-course', () => { + describe('GET - /user-courses', () => { describe('200 - Ok', () => { beforeEach(async () => { UserCourseService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedUserCourses)) diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index d4dd8abe..8432bb3c 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -90,7 +90,9 @@ Router.get('/user/:userId', extractOwnerByPathParam('userId'), isAuthorized('cou * schema: * $ref: '#/components/schemas/UserCourse' */ -Router.post('/', isAuthorized('userCourseEditAll'), validator, UserCourseController.post) +Router.post('/', validator, UserCourseController.post) +// TODO: userCourseEditAll eventually. For now, allow self enroll + /** * @swagger @@ -133,6 +135,6 @@ Router.put('/:id', isAuthorized('userCourseEditAll'), asInt(), validator, UserCo * schema: * type: integer */ -Router.delete('/:id', isAuthorized('userCourseEditAll'), asInt(), UserCourseController._delete) - +Router.delete('/:id', asInt(), UserCourseController._delete) +// TODO: eventually add authorization to this. For now, everyone can remove anyone export default Router From 91dcb42d361f9eb1097a9acc141ca4e24314b8f5 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Mon, 22 Apr 2024 13:08:58 -0400 Subject: [PATCH 040/400] Generate roles migration; map MinIO ports for local testing --- .../entities/assignment/assignment.router.ts | 2 +- devU-api/src/migration/1713805438440-roles.ts | 24 +++++++++++++++++++ docker-compose.yml | 6 ++--- 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 devU-api/src/migration/1713805438440-roles.ts diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index 9867a0cb..85b1f22f 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -84,7 +84,7 @@ Router.get('/:id', asInt(), isAuthorizedByAssignmentStatus, AssignmentsControlle /** * @swagger - * /course/:courseId/assignments + * /course/:courseId/assignments: * post: * summary: Create an assignment * tags: diff --git a/devU-api/src/migration/1713805438440-roles.ts b/devU-api/src/migration/1713805438440-roles.ts new file mode 100644 index 00000000..1bd51209 --- /dev/null +++ b/devU-api/src/migration/1713805438440-roles.ts @@ -0,0 +1,24 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class roles1713805438440 implements MigrationInterface { + name = 'roles1713805438440' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user_courses" RENAME COLUMN "type" TO "role"`); + await queryRunner.query(`ALTER TYPE "public"."user_courses_type_enum" RENAME TO "user_courses_role_enum"`); + await queryRunner.query(`CREATE TABLE "role" ("id" SERIAL NOT NULL, "course_id" integer NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "name" character varying NOT NULL, "enrolled" boolean NOT NULL, "course_edit" boolean NOT NULL, "course_view_all" boolean NOT NULL, "assignment_view_all" boolean NOT NULL, "assignment_edit_all" boolean NOT NULL, "assignment_view_release" boolean NOT NULL, "scores_view_all" boolean NOT NULL, "scores_edit_all" boolean NOT NULL, "scores_view_self_released" boolean NOT NULL, "role_edit_all" boolean NOT NULL, "role_view_all" boolean NOT NULL, "role_view_self" boolean NOT NULL, "submission_change_state" boolean NOT NULL, "submission_create_all" boolean NOT NULL, "submission_create_self" boolean NOT NULL, "submission_view_all" boolean NOT NULL, "user_course_edit_all" boolean NOT NULL, CONSTRAINT "PK_b36bcfe02fc8de3c57a8b2391c2" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "user_courses" DROP COLUMN "role"`); + await queryRunner.query(`ALTER TABLE "user_courses" ADD "role" character varying NOT NULL`); + await queryRunner.query(`ALTER TABLE "role" ADD CONSTRAINT "FK_3c1c31ab1941f90200d36a236b3" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "role" DROP CONSTRAINT "FK_3c1c31ab1941f90200d36a236b3"`); + await queryRunner.query(`ALTER TABLE "user_courses" DROP COLUMN "role"`); + await queryRunner.query(`ALTER TABLE "user_courses" ADD "role" "user_courses_role_enum" NOT NULL`); + await queryRunner.query(`DROP TABLE "role"`); + await queryRunner.query(`ALTER TYPE "user_courses_role_enum" RENAME TO "user_courses_type_enum"`); + await queryRunner.query(`ALTER TABLE "user_courses" RENAME COLUMN "role" TO "type"`); + } + +} diff --git a/docker-compose.yml b/docker-compose.yml index f23ee09b..094b2909 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,9 +62,9 @@ services: - 'dev-api' # start when developing api not when developing client minio: image: minio/minio - # ports: - # - '9002:9000' - # - '9001:9001' + ports: + - '9002:9000' + - '9001:9001' expose: - '9000' # volumes: From e6458304f05d711e8eb3044407605aa4a883a6f6 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Mon, 22 Apr 2024 13:10:55 -0400 Subject: [PATCH 041/400] Capital U on devU-shared.. it's been bugging me --- api.Dockerfile | 2 +- client.Dockerfile | 2 +- {devu-shared => devU-shared}/.dockerignore | 0 {devu-shared => devU-shared}/.gitignore | 0 {devu-shared => devU-shared}/.husky/.gitignore | 0 {devu-shared => devU-shared}/.husky/pre-commit | 0 {devu-shared => devU-shared}/.prettierignore | 0 {devu-shared => devU-shared}/.prettierrc.json | 0 {devu-shared => devU-shared}/.vscode/launch.json | 0 {devu-shared => devU-shared}/README.md | 2 +- {devu-shared => devU-shared}/jest.config.ts | 0 {devu-shared => devU-shared}/package-lock.json | 0 {devu-shared => devU-shared}/package.json | 0 {devu-shared => devU-shared}/src/index.ts | 0 {devu-shared => devU-shared}/src/types/assignment.types.ts | 0 .../src/types/assignmentProblem.types.ts | 0 {devu-shared => devU-shared}/src/types/assignmentScore.types.ts | 0 {devu-shared => devU-shared}/src/types/auth.types.ts | 0 {devu-shared => devU-shared}/src/types/category.types.ts | 0 {devu-shared => devU-shared}/src/types/categoryScore.types.ts | 0 {devu-shared => devU-shared}/src/types/codeAssignment.types.ts | 0 .../src/types/containerAutoGrader.types.ts | 0 {devu-shared => devU-shared}/src/types/course.types.ts | 0 {devu-shared => devU-shared}/src/types/courseScore.types.ts | 0 .../src/types/deadlineExtensions.types.ts | 0 {devu-shared => devU-shared}/src/types/fileUpload.types.ts | 0 {devu-shared => devU-shared}/src/types/grader.types.ts | 0 .../src/types/nonContainerAutoGrader.types.ts | 0 {devu-shared => devU-shared}/src/types/role.types.ts | 0 {devu-shared => devU-shared}/src/types/submission.types.ts | 0 .../src/types/submissionProblemScore.types.ts | 0 {devu-shared => devU-shared}/src/types/submissionScore.types.ts | 0 {devu-shared => devU-shared}/src/types/user.types.ts | 0 {devu-shared => devU-shared}/src/types/userCourse.types.ts | 0 {devu-shared => devU-shared}/src/types/validation.types.ts | 0 {devu-shared => devU-shared}/src/utils/object.utils.ts | 0 {devu-shared => devU-shared}/src/utils/string.utils.ts | 0 .../src/utils/tests/object.utils.test.ts | 0 .../src/utils/tests/string.utils.test.ts | 0 {devu-shared => devU-shared}/tsconfig.json | 0 40 files changed, 3 insertions(+), 3 deletions(-) rename {devu-shared => devU-shared}/.dockerignore (100%) rename {devu-shared => devU-shared}/.gitignore (100%) rename {devu-shared => devU-shared}/.husky/.gitignore (100%) rename {devu-shared => devU-shared}/.husky/pre-commit (100%) rename {devu-shared => devU-shared}/.prettierignore (100%) rename {devu-shared => devU-shared}/.prettierrc.json (100%) rename {devu-shared => devU-shared}/.vscode/launch.json (100%) rename {devu-shared => devU-shared}/README.md (94%) rename {devu-shared => devU-shared}/jest.config.ts (100%) rename {devu-shared => devU-shared}/package-lock.json (100%) rename {devu-shared => devU-shared}/package.json (100%) rename {devu-shared => devU-shared}/src/index.ts (100%) rename {devu-shared => devU-shared}/src/types/assignment.types.ts (100%) rename {devu-shared => devU-shared}/src/types/assignmentProblem.types.ts (100%) rename {devu-shared => devU-shared}/src/types/assignmentScore.types.ts (100%) rename {devu-shared => devU-shared}/src/types/auth.types.ts (100%) rename {devu-shared => devU-shared}/src/types/category.types.ts (100%) rename {devu-shared => devU-shared}/src/types/categoryScore.types.ts (100%) rename {devu-shared => devU-shared}/src/types/codeAssignment.types.ts (100%) rename {devu-shared => devU-shared}/src/types/containerAutoGrader.types.ts (100%) rename {devu-shared => devU-shared}/src/types/course.types.ts (100%) rename {devu-shared => devU-shared}/src/types/courseScore.types.ts (100%) rename {devu-shared => devU-shared}/src/types/deadlineExtensions.types.ts (100%) rename {devu-shared => devU-shared}/src/types/fileUpload.types.ts (100%) rename {devu-shared => devU-shared}/src/types/grader.types.ts (100%) rename {devu-shared => devU-shared}/src/types/nonContainerAutoGrader.types.ts (100%) rename {devu-shared => devU-shared}/src/types/role.types.ts (100%) rename {devu-shared => devU-shared}/src/types/submission.types.ts (100%) rename {devu-shared => devU-shared}/src/types/submissionProblemScore.types.ts (100%) rename {devu-shared => devU-shared}/src/types/submissionScore.types.ts (100%) rename {devu-shared => devU-shared}/src/types/user.types.ts (100%) rename {devu-shared => devU-shared}/src/types/userCourse.types.ts (100%) rename {devu-shared => devU-shared}/src/types/validation.types.ts (100%) rename {devu-shared => devU-shared}/src/utils/object.utils.ts (100%) rename {devu-shared => devU-shared}/src/utils/string.utils.ts (100%) rename {devu-shared => devU-shared}/src/utils/tests/object.utils.test.ts (100%) rename {devu-shared => devU-shared}/src/utils/tests/string.utils.test.ts (100%) rename {devu-shared => devU-shared}/tsconfig.json (100%) diff --git a/api.Dockerfile b/api.Dockerfile index 316fcebc..bf4aabff 100644 --- a/api.Dockerfile +++ b/api.Dockerfile @@ -2,7 +2,7 @@ FROM node:16 as module_builder WORKDIR /tmp -COPY ./devu-shared . +COPY devU-shared . RUN npm install && \ npm run clean-directory && \ diff --git a/client.Dockerfile b/client.Dockerfile index 497f3274..eec9df6e 100644 --- a/client.Dockerfile +++ b/client.Dockerfile @@ -2,7 +2,7 @@ FROM node:16 as module_builder WORKDIR /tmp -COPY ./devu-shared . +COPY devU-shared . RUN npm install && \ npm run clean-directory && \ diff --git a/devu-shared/.dockerignore b/devU-shared/.dockerignore similarity index 100% rename from devu-shared/.dockerignore rename to devU-shared/.dockerignore diff --git a/devu-shared/.gitignore b/devU-shared/.gitignore similarity index 100% rename from devu-shared/.gitignore rename to devU-shared/.gitignore diff --git a/devu-shared/.husky/.gitignore b/devU-shared/.husky/.gitignore similarity index 100% rename from devu-shared/.husky/.gitignore rename to devU-shared/.husky/.gitignore diff --git a/devu-shared/.husky/pre-commit b/devU-shared/.husky/pre-commit similarity index 100% rename from devu-shared/.husky/pre-commit rename to devU-shared/.husky/pre-commit diff --git a/devu-shared/.prettierignore b/devU-shared/.prettierignore similarity index 100% rename from devu-shared/.prettierignore rename to devU-shared/.prettierignore diff --git a/devu-shared/.prettierrc.json b/devU-shared/.prettierrc.json similarity index 100% rename from devu-shared/.prettierrc.json rename to devU-shared/.prettierrc.json diff --git a/devu-shared/.vscode/launch.json b/devU-shared/.vscode/launch.json similarity index 100% rename from devu-shared/.vscode/launch.json rename to devU-shared/.vscode/launch.json diff --git a/devu-shared/README.md b/devU-shared/README.md similarity index 94% rename from devu-shared/README.md rename to devU-shared/README.md index 56f1fcf9..c64faa52 100644 --- a/devu-shared/README.md +++ b/devU-shared/README.md @@ -2,7 +2,7 @@ This project exists as a shared resource between the DevU api and client projects -For usage [see](/devu-shared/README.md#devu-shared-modules) +For usage [see](/devU-shared/README.md#devu-shared-modules) ## Project Dependencies (install these) diff --git a/devu-shared/jest.config.ts b/devU-shared/jest.config.ts similarity index 100% rename from devu-shared/jest.config.ts rename to devU-shared/jest.config.ts diff --git a/devu-shared/package-lock.json b/devU-shared/package-lock.json similarity index 100% rename from devu-shared/package-lock.json rename to devU-shared/package-lock.json diff --git a/devu-shared/package.json b/devU-shared/package.json similarity index 100% rename from devu-shared/package.json rename to devU-shared/package.json diff --git a/devu-shared/src/index.ts b/devU-shared/src/index.ts similarity index 100% rename from devu-shared/src/index.ts rename to devU-shared/src/index.ts diff --git a/devu-shared/src/types/assignment.types.ts b/devU-shared/src/types/assignment.types.ts similarity index 100% rename from devu-shared/src/types/assignment.types.ts rename to devU-shared/src/types/assignment.types.ts diff --git a/devu-shared/src/types/assignmentProblem.types.ts b/devU-shared/src/types/assignmentProblem.types.ts similarity index 100% rename from devu-shared/src/types/assignmentProblem.types.ts rename to devU-shared/src/types/assignmentProblem.types.ts diff --git a/devu-shared/src/types/assignmentScore.types.ts b/devU-shared/src/types/assignmentScore.types.ts similarity index 100% rename from devu-shared/src/types/assignmentScore.types.ts rename to devU-shared/src/types/assignmentScore.types.ts diff --git a/devu-shared/src/types/auth.types.ts b/devU-shared/src/types/auth.types.ts similarity index 100% rename from devu-shared/src/types/auth.types.ts rename to devU-shared/src/types/auth.types.ts diff --git a/devu-shared/src/types/category.types.ts b/devU-shared/src/types/category.types.ts similarity index 100% rename from devu-shared/src/types/category.types.ts rename to devU-shared/src/types/category.types.ts diff --git a/devu-shared/src/types/categoryScore.types.ts b/devU-shared/src/types/categoryScore.types.ts similarity index 100% rename from devu-shared/src/types/categoryScore.types.ts rename to devU-shared/src/types/categoryScore.types.ts diff --git a/devu-shared/src/types/codeAssignment.types.ts b/devU-shared/src/types/codeAssignment.types.ts similarity index 100% rename from devu-shared/src/types/codeAssignment.types.ts rename to devU-shared/src/types/codeAssignment.types.ts diff --git a/devu-shared/src/types/containerAutoGrader.types.ts b/devU-shared/src/types/containerAutoGrader.types.ts similarity index 100% rename from devu-shared/src/types/containerAutoGrader.types.ts rename to devU-shared/src/types/containerAutoGrader.types.ts diff --git a/devu-shared/src/types/course.types.ts b/devU-shared/src/types/course.types.ts similarity index 100% rename from devu-shared/src/types/course.types.ts rename to devU-shared/src/types/course.types.ts diff --git a/devu-shared/src/types/courseScore.types.ts b/devU-shared/src/types/courseScore.types.ts similarity index 100% rename from devu-shared/src/types/courseScore.types.ts rename to devU-shared/src/types/courseScore.types.ts diff --git a/devu-shared/src/types/deadlineExtensions.types.ts b/devU-shared/src/types/deadlineExtensions.types.ts similarity index 100% rename from devu-shared/src/types/deadlineExtensions.types.ts rename to devU-shared/src/types/deadlineExtensions.types.ts diff --git a/devu-shared/src/types/fileUpload.types.ts b/devU-shared/src/types/fileUpload.types.ts similarity index 100% rename from devu-shared/src/types/fileUpload.types.ts rename to devU-shared/src/types/fileUpload.types.ts diff --git a/devu-shared/src/types/grader.types.ts b/devU-shared/src/types/grader.types.ts similarity index 100% rename from devu-shared/src/types/grader.types.ts rename to devU-shared/src/types/grader.types.ts diff --git a/devu-shared/src/types/nonContainerAutoGrader.types.ts b/devU-shared/src/types/nonContainerAutoGrader.types.ts similarity index 100% rename from devu-shared/src/types/nonContainerAutoGrader.types.ts rename to devU-shared/src/types/nonContainerAutoGrader.types.ts diff --git a/devu-shared/src/types/role.types.ts b/devU-shared/src/types/role.types.ts similarity index 100% rename from devu-shared/src/types/role.types.ts rename to devU-shared/src/types/role.types.ts diff --git a/devu-shared/src/types/submission.types.ts b/devU-shared/src/types/submission.types.ts similarity index 100% rename from devu-shared/src/types/submission.types.ts rename to devU-shared/src/types/submission.types.ts diff --git a/devu-shared/src/types/submissionProblemScore.types.ts b/devU-shared/src/types/submissionProblemScore.types.ts similarity index 100% rename from devu-shared/src/types/submissionProblemScore.types.ts rename to devU-shared/src/types/submissionProblemScore.types.ts diff --git a/devu-shared/src/types/submissionScore.types.ts b/devU-shared/src/types/submissionScore.types.ts similarity index 100% rename from devu-shared/src/types/submissionScore.types.ts rename to devU-shared/src/types/submissionScore.types.ts diff --git a/devu-shared/src/types/user.types.ts b/devU-shared/src/types/user.types.ts similarity index 100% rename from devu-shared/src/types/user.types.ts rename to devU-shared/src/types/user.types.ts diff --git a/devu-shared/src/types/userCourse.types.ts b/devU-shared/src/types/userCourse.types.ts similarity index 100% rename from devu-shared/src/types/userCourse.types.ts rename to devU-shared/src/types/userCourse.types.ts diff --git a/devu-shared/src/types/validation.types.ts b/devU-shared/src/types/validation.types.ts similarity index 100% rename from devu-shared/src/types/validation.types.ts rename to devU-shared/src/types/validation.types.ts diff --git a/devu-shared/src/utils/object.utils.ts b/devU-shared/src/utils/object.utils.ts similarity index 100% rename from devu-shared/src/utils/object.utils.ts rename to devU-shared/src/utils/object.utils.ts diff --git a/devu-shared/src/utils/string.utils.ts b/devU-shared/src/utils/string.utils.ts similarity index 100% rename from devu-shared/src/utils/string.utils.ts rename to devU-shared/src/utils/string.utils.ts diff --git a/devu-shared/src/utils/tests/object.utils.test.ts b/devU-shared/src/utils/tests/object.utils.test.ts similarity index 100% rename from devu-shared/src/utils/tests/object.utils.test.ts rename to devU-shared/src/utils/tests/object.utils.test.ts diff --git a/devu-shared/src/utils/tests/string.utils.test.ts b/devU-shared/src/utils/tests/string.utils.test.ts similarity index 100% rename from devu-shared/src/utils/tests/string.utils.test.ts rename to devU-shared/src/utils/tests/string.utils.test.ts diff --git a/devu-shared/tsconfig.json b/devU-shared/tsconfig.json similarity index 100% rename from devu-shared/tsconfig.json rename to devU-shared/tsconfig.json From 79318d903026199a6978775e2919da5fb9756a9b Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Tue, 23 Apr 2024 09:49:02 -0400 Subject: [PATCH 042/400] Run the formatter... apparently it's been a long time --- devU-api/README.md | 25 +- devU-api/docs/SCHEMA.md | 23 +- devU-api/package.json | 4 +- devU-api/scripts/populate-db.ts | 440 +++++++++--------- .../tests/login.developer.controller.test.ts | 3 +- .../authorization/authorization.middleware.ts | 158 +++---- devU-api/src/database.ts | 20 +- .../assignment/assignment.controller.ts | 5 +- .../entities/assignment/assignment.model.ts | 180 +++---- .../entities/assignment/assignment.router.ts | 9 +- .../entities/assignment/assignment.service.ts | 101 ++-- .../assignmentProblem.router.ts | 5 +- .../assignmentScore.controller.ts | 144 +++--- .../assignmentScore/assignmentScore.model.ts | 69 ++- .../assignmentScore/assignmentScore.router.ts | 24 +- .../assignmentScore.serializer.ts | 18 +- .../assignmentScore.service.ts | 41 +- .../assignmentScore.validator.ts | 2 +- .../tests/assignmentScore.controller.test.ts | 276 ++++++----- .../tests/assignmentScore.serializer.test.ts | 59 ++- .../entities/category/category.controller.ts | 3 +- .../src/entities/category/category.model.ts | 15 +- .../src/entities/category/category.router.ts | 9 +- .../entities/category/category.serializer.ts | 2 +- .../src/entities/category/category.service.ts | 5 +- .../entities/category/category.validator.ts | 2 +- .../tests/category.controller.test.ts | 2 +- .../tests/category.serializer.test.ts | 2 +- .../categoryScore/categoryScore.controller.ts | 100 ++-- .../categoryScore/categoryScore.model.ts | 103 ++-- .../categoryScore/categoryScore.router.ts | 7 +- .../categoryScore/categoryScore.serializer.ts | 16 +- .../categoryScore/categoryScore.service.ts | 33 +- .../tests/categoryScore.serializer.test.ts | 14 +- .../containerAutoGrader.controller.ts | 24 +- .../containerAutoGrader.model.ts | 65 ++- .../containerAutoGrader.router.ts | 41 +- .../containerAutoGrader.serializer.ts | 22 +- .../containerAutoGrader.service.ts | 173 ++++--- .../containerAutoGrader.validator.ts | 36 +- .../containerAutoGrade.controller.test.ts | 354 +++++++------- .../containerAutoGrader.serializer.test.ts | 20 +- .../src/entities/course/course.controller.ts | 6 +- devU-api/src/entities/course/course.model.ts | 2 +- devU-api/src/entities/course/course.router.ts | 6 +- .../src/entities/course/course.service.ts | 2 - .../courseScore/courseScore.controller.ts | 82 ++-- .../entities/courseScore/courseScore.model.ts | 99 ++-- .../courseScore/courseScore.router.ts | 3 +- .../courseScore/courseScore.serializer.ts | 16 +- .../deadlineExtensions.model.ts | 135 +++--- .../deadlineExtensions.router.ts | 2 +- .../deadlineExtensions.service.ts | 10 +- .../deadlineExtensions.validator.ts | 8 +- .../deadlineExtensions.controller.test.ts | 4 +- .../src/entities/grader/grader.controller.ts | 18 +- devU-api/src/entities/grader/grader.router.ts | 6 +- .../src/entities/grader/grader.serializer.ts | 2 +- .../src/entities/grader/grader.service.ts | 195 ++++---- .../grader/tests/grader.controller.test.ts | 87 ++-- .../grader/tests/grader.serializer.test.ts | 22 +- .../nonContainerAutoGrader.controller.ts | 1 - .../nonContainerAutoGrader.grader.ts | 12 +- .../nonContainerAutoGrader.model.ts | 2 +- .../nonContainerAutoGrader.router.ts | 5 +- .../nonContainerAutoGrader.service.ts | 1 - .../nonContainerAutoGrader.validator.ts | 8 +- .../nonContainerAutoGrader.controller.test.ts | 23 +- .../nonContainerAutoGrader.grader.test.ts | 5 +- .../nonContainerAutoGrader.serializer.test.ts | 2 +- devU-api/src/entities/role/role.controller.ts | 1 - devU-api/src/entities/role/role.defaults.ts | 75 ++- devU-api/src/entities/role/role.model.ts | 160 ++++--- devU-api/src/entities/role/role.router.ts | 8 +- devU-api/src/entities/role/role.service.ts | 80 +++- devU-api/src/entities/role/role.validator.ts | 28 +- .../role/tests/role.controller.test.ts | 2 +- .../role/tests/role.serializer.test.ts | 2 +- .../submission/submission.controller.ts | 6 +- .../entities/submission/submission.model.ts | 1 - .../entities/submission/submission.router.ts | 9 +- .../entities/submission/submission.service.ts | 16 +- .../submission/submission.validator.ts | 15 +- .../submissionProblemScore.model.ts | 3 +- .../submissionProblemScore.router.ts | 13 +- .../submissionProblemScore.service.ts | 31 +- .../submissionProblemScore.serializer.test.ts | 2 +- .../submissionScore.controller.ts | 2 +- .../submissionScore/submissionScore.model.ts | 2 +- .../submissionScore/submissionScore.router.ts | 13 +- .../submissionScore.service.ts | 14 +- devU-api/src/entities/user/user.controller.ts | 5 +- devU-api/src/entities/user/user.model.ts | 2 +- devU-api/src/entities/user/user.router.ts | 5 +- devU-api/src/entities/user/user.service.ts | 6 +- .../userCourse/userCourse.controller.ts | 139 +++--- .../entities/userCourse/userCourse.model.ts | 3 +- .../entities/userCourse/userCourse.router.ts | 15 +- .../entities/userCourse/userCourse.service.ts | 8 +- devU-api/src/environment.ts | 13 +- devU-api/src/fileStorage.ts | 17 +- .../src/fileUpload/fileUpload.controller.ts | 99 ++-- devU-api/src/fileUpload/fileUpload.model.ts | 83 ++-- devU-api/src/fileUpload/fileUpload.router.ts | 27 +- .../src/fileUpload/fileUpload.serializer.ts | 15 +- devU-api/src/fileUpload/fileUpload.service.ts | 106 ++--- .../src/fileUpload/fileUpload.validator.ts | 21 +- .../test/fileUpload.controller.test.ts | 147 +++--- .../test/fileUpload.serializer.test.ts | 57 ++- devU-api/src/index.ts | 42 +- .../migration/1708215731032-entityFixUp.ts | 289 ++++++++---- .../1709413878786-addDeadlineExtensions.ts | 36 +- .../src/migration/1709541636414-fileUpload.ts | 37 +- devU-api/src/migration/1713805438440-roles.ts | 41 +- devU-api/src/model/index.ts | 36 +- devU-api/src/router/courseData.router.ts | 12 +- devU-api/src/router/index.ts | 5 +- devU-api/src/tango/tango.service.ts | 21 +- devU-api/src/tango/tango.types.ts | 73 ++- .../src/tango/tests/tango.endpoint.test.ts | 4 +- devU-api/src/utils/fileUpload.utils.ts | 23 +- devU-api/src/utils/swagger.utils.ts | 28 +- 122 files changed, 2755 insertions(+), 2561 deletions(-) diff --git a/devU-api/README.md b/devU-api/README.md index 704824ea..5c1a1cb3 100644 --- a/devU-api/README.md +++ b/devU-api/README.md @@ -17,15 +17,15 @@ Once you've got these installed, we can build our container and run it #### Note to run the postgres container locally using the command below -You have to modify ``devU-api/src/environment.ts`` +You have to modify `devU-api/src/environment.ts` change -``dbHost: (load('database.host') || 'localhost') as string`` +`dbHost: (load('database.host') || 'localhost') as string` to -``dbHost: 'localhost'`` +`dbHost: 'localhost'` This will probably be fixed in the future but for now the above steps are necessary @@ -33,7 +33,7 @@ This will probably be fixed in the future but for now the above steps are necess We use [docker compose profiles](https://docs.docker.com/compose/profiles/) to selectively start services in the main docker-compose when developing. -Assuming you are in api dir ```devU-api```, To start all api services except the api run +Assuming you are in api dir `devU-api`, To start all api services except the api run ``` npm run api-services @@ -46,11 +46,13 @@ npm run api-services-stop ``` Then install dependencies using + ``` npm install ``` Once you've got all the dependencies installed you can run the project via + ``` npm start ``` @@ -68,6 +70,7 @@ docker run \ ``` Install all node dependencies. All of the database environment variables can change, and can be set as environment variables on your machine if you want to overwrite the defaults + ``` docker run \ --name minio \ @@ -194,30 +197,30 @@ To generate types for local development run the following commands (all commands 1. ```bash cd ./devu-shared - ``` + ``` 2. ```bash npm install - ``` + ``` 3. ```bash npm run build-local ``` -This will create a ``devu-shared-modules`` folder in ``devU-api`` and ``devU-client`` dir +This will create a `devu-shared-modules` folder in `devU-api` and `devU-client` dir When developing if you need to create a new type, -1. create the type file in ``devu-shared/src/types/example.type.ts`` -2. export the new type in ``devu-shared/src/index.ts`` +1. create the type file in `devu-shared/src/types/example.type.ts` +2. export the new type in `devu-shared/src/index.ts` 3. rerun ```bash npm run build-local ``` -This will update the types in ``devU-api`` and ``devU-client`` folders. +This will update the types in `devU-api` and `devU-client` folders. **Note if the types are not being detected by your IDE** -**Go to the ``devU-api/`` and ``devU-client/`` and run ``npm install`` in each folder to update the shared modules.** +**Go to the `devU-api/` and `devU-client/` and run `npm install` in each folder to update the shared modules.** ### Testing diff --git a/devU-api/docs/SCHEMA.md b/devU-api/docs/SCHEMA.md index f75b55f4..a2187309 100644 --- a/devU-api/docs/SCHEMA.md +++ b/devU-api/docs/SCHEMA.md @@ -12,12 +12,13 @@ - [ ] [CategoryScore](#categoryscore) - [ ] [Category](#category) - [ ] [CourseScore](#coursescore) -# Entity Details +# Entity Details ### User _Student user for devu_ + - id: number - createdAt: Date - updatedAt: Date @@ -25,8 +26,10 @@ _Student user for devu_ - email: string - externalId: foreign_key - preferredName: string -- +- + ### Course + - id: number primary_key - name: string - semester: string @@ -40,12 +43,13 @@ _Student user for devu_ ### UserCourse _Links a user to a course_ + - id: number - createdAt: Date - updatedAt: Date - deletedAt: ?Date - userId: number foreign_key ManyToOne -> User -- courseId: number foreign_key ManyToOne -> Course +- courseId: number foreign_key ManyToOne -> Course - level: UserCourseLevel - dropped: boolean @@ -63,7 +67,7 @@ _Links a user to a course_ - maxFileSize: number - maxSubmissions: number | null - disableHandins: boolean -- createdAt: Date +- createdAt: Date - updatedAt: Date - deletedAt: ?Date @@ -93,7 +97,8 @@ _Links a user to a course_ - deletedAt: ?Date ### SubmissionProblemScore -- submissionProblemScoreId: number (primary_key) + +- submissionProblemScoreId: number (primary_key) - submissionId: foreign_key ManyToOne -> submission - assignmentProblemId: foreign_key ManyToOne -> AssignmentProblem - createdAt: Date @@ -114,7 +119,6 @@ _Links a user to a course_ - feedback: string | null - releasedAt: ?Date - ### Category - id: number primary_key @@ -133,20 +137,21 @@ _Links a user to a course_ - updatedAt: Date - DeletedAt: ?Date - ### CategoryScore + - if: number - createdAt: Date - updatedAt: Date - deletedAt: ?Date -- courseId: number foreign_key ManyToOne -> Course +- courseId: number foreign_key ManyToOne -> Course - userId: number foreign_key ManyToOne -> Category - category: string - score: number - letterGrade: LetterGrade ### CourseScore -- id: number + +- id: number - courseId: number foreign_key ManyToOne -> Course - score: number - letterGrade: string diff --git a/devU-api/package.json b/devU-api/package.json index 6613c68a..b0379a50 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -14,7 +14,7 @@ "pre-commit": "lint-staged", "generate-config": "docker run --pull always -v $(pwd)/config:/config --user $(id -u):$(id -g) --rm ubautograding/devtools /generateConfig.sh config/default.yml", "populate-db": "ts-node-dev ./scripts/populate-db.ts", - "tango": "ts-node-dev ./src/tango/tests/tango.endpoint.test.ts", + "tango": "ts-node-dev ./src/tango/tests/tango.endpoint.test.ts", "api-services": "docker compose -f ../docker-compose.yml --profile dev-api up -d", "api-services-stop": "docker compose -f ../docker-compose.yml --profile dev-api stop" }, @@ -27,7 +27,7 @@ "devu-shared-modules": "file:/devu-shared-modules" }, "engines": { - "node": ">=16" + "node": ">=20" }, "devDependencies": { "@types/config": "^0.0.38", diff --git a/devU-api/scripts/populate-db.ts b/devU-api/scripts/populate-db.ts index 19ce17d4..c90bc0f3 100644 --- a/devU-api/scripts/populate-db.ts +++ b/devU-api/scripts/populate-db.ts @@ -1,246 +1,266 @@ const API_URL = 'http://localhost:3001' -let apiToken:{[key:string]:string} = {} +let apiToken: { [key: string]: string } = {} let content = '' -let file; -let makefile; - -async function fetchToken( email: string, externalId:string) { - const urlencoded = new URLSearchParams() - urlencoded.append('email', email) - urlencoded.append('externalId', externalId) - - const options = { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: urlencoded, - } - - const res = await fetch(API_URL + '/login/developer', options) - const responseBody = await res.json() - const tmp = res.headers.get('Set-Cookie')?.split('=')[1] - - if (tmp === undefined) { - throw Error('Api token not found') - } - - apiToken[externalId]= tmp.split(';')[0] - return responseBody.userId +let file +let makefile + +async function fetchToken(email: string, externalId: string) { + const urlencoded = new URLSearchParams() + urlencoded.append('email', email) + urlencoded.append('externalId', externalId) + + const options = { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: urlencoded, + } + + const res = await fetch(API_URL + '/login/developer', options) + const responseBody = await res.json() + const tmp = res.headers.get('Set-Cookie')?.split('=')[1] + + if (tmp === undefined) { + throw Error('Api token not found') + } + + apiToken[externalId] = tmp.split(';')[0] + return responseBody.userId } -async function initAdmin(){ - await fetchToken('admin@admin.admin', 'admin') +async function initAdmin() { + await fetchToken('admin@admin.admin', 'admin') } - //Returns the ID of the newly created entry -async function SendPOST(path: string, requestBody: string|FormData , externalId:string) { - const headers = new Headers() - headers.append('Authorization', `Bearer ${apiToken[externalId]}`) - headers.append('Content-Type', 'application/json') - if (requestBody instanceof FormData) { - headers.delete('Content-Type') - }else if (typeof requestBody === 'object') { - requestBody = JSON.stringify(requestBody) - } - - let response = await fetch(API_URL + path, { - method: 'POST', - headers:headers, - body: requestBody, - }) - - return await response.json() +async function SendPOST(path: string, requestBody: string | FormData, externalId: string) { + const headers = new Headers() + headers.append('Authorization', `Bearer ${apiToken[externalId]}`) + headers.append('Content-Type', 'application/json') + if (requestBody instanceof FormData) { + headers.delete('Content-Type') + } else if (typeof requestBody === 'object') { + requestBody = JSON.stringify(requestBody) + } + + let response = await fetch(API_URL + path, { + method: 'POST', + headers: headers, + body: requestBody, + }) + + return await response.json() } - -async function CreateCourse(name:string, number:string, semester:string) { - const courseData = { +async function CreateCourse(name: string, number: string, semester: string) { + const courseData = { name: name, semester: semester, number: number, startDate: '2024-01-24T00:00:00-0500', endDate: '2024-05-10T23:59:59-0500', - }; - console.log("Creating course: ", courseData.name) - return await SendPOST('/courses', JSON.stringify(courseData), 'admin'); + } + console.log('Creating course: ', courseData.name) + return await SendPOST('/courses/instructor', JSON.stringify(courseData), 'admin') } +async function createAssignment(courseId: number, name: string, categoryName: string) { + const time = new Date().getTime() + const startDate = new Date(time + 60 * 1000).toISOString() + const dueDate = new Date(time + 7 * 24 * 60 * 60 * 1000).toISOString() + const endDate = new Date(time + 14 * 24 * 60 * 60 * 1000).toISOString() -async function createAssignment(courseId:number, name:string, categoryName:string) { - const time = new Date().getTime(); - const startDate = new Date(time + 60 * 1000).toISOString(); - const dueDate = new Date(time + 7 * 24 * 60 * 60 * 1000).toISOString(); - const endDate = new Date(time + 14 * 24 * 60 * 60 * 1000).toISOString(); - - const assignmentData = { - courseId: courseId, - name: name, - startDate: startDate, - dueDate: dueDate, - endDate: endDate, - categoryName: categoryName, - description: "This is a test assignment for course Id:."+courseId, - maxFileSize: 1024*20, - disableHandins: false - }; - console.log(`Creating assignment for Course Id: ${courseId}, Name: ${name}`) - return await SendPOST('/assignments', JSON.stringify(assignmentData), 'admin'); + const assignmentData = { + courseId: courseId, + name: name, + startDate: startDate, + dueDate: dueDate, + endDate: endDate, + categoryName: categoryName, + description: 'This is a test assignment for course Id:.' + courseId, + maxFileSize: 1024 * 20, + disableHandins: false, + } + console.log(`Creating assignment for Course Id: ${courseId}, Name: ${name}`) + return await SendPOST('/assignments', JSON.stringify(assignmentData), 'admin') } - -async function createNonContainerAutoGrader(assignmentId:number, problemName:string, Score:number, Regex:string, isRegex:boolean){ - const problemData = { - assignmentId: assignmentId, - question: problemName, - score: Score, - correctString: Regex, - isRegex: isRegex - }; - console.log("Creating NonContainerAutoGrader for Assignment Id: ",assignmentId ) - return await SendPOST('/nonContainerAutoGrader', JSON.stringify(problemData), 'admin'); +async function createNonContainerAutoGrader( + assignmentId: number, + problemName: string, + Score: number, + Regex: string, + isRegex: boolean +) { + const problemData = { + assignmentId: assignmentId, + question: problemName, + score: Score, + correctString: Regex, + isRegex: isRegex, + } + console.log('Creating NonContainerAutoGrader for Assignment Id: ', assignmentId) + return await SendPOST('/nonContainerAutoGrader', JSON.stringify(problemData), 'admin') } - -async function createContainerAutoGrader(assignmentId:number, imageName:string, timeout:number, graderFile:File, makefile?:File){ - const formData = new FormData(); - formData.append('assignmentId', assignmentId.toString()); - formData.append('autogradingImage', imageName); - formData.append('graderFile', graderFile); - formData.append('timeout', timeout.toString()); - if (makefile) { - formData.append('makefileFile', makefile); - } - console.log("Creating ContainerAutoGrader for Assignment Id: ",assignmentId ) - return await SendPOST('/container-auto-graders', formData, 'admin'); +async function createContainerAutoGrader( + assignmentId: number, + imageName: string, + timeout: number, + graderFile: File, + makefile?: File +) { + const formData = new FormData() + formData.append('assignmentId', assignmentId.toString()) + formData.append('autogradingImage', imageName) + formData.append('graderFile', graderFile) + formData.append('timeout', timeout.toString()) + if (makefile) { + formData.append('makefileFile', makefile) + } + console.log('Creating ContainerAutoGrader for Assignment Id: ', assignmentId) + return await SendPOST('/container-auto-graders', formData, 'admin') } - -async function createSubmission(courseId:number, assignmentId:number, userId:number, externalId:string, content?:string, file?:File) { - const time = new Date().toISOString(); - - content = content || `This is a test submission for assignment Id: ${assignmentId}`; - const fullContent = `{"form":${JSON.stringify(content)}}`; - - let response; - console.log("Creating submission for assignment Id: ", assignmentId) - if (file) { - const formData = new FormData(); - formData.append('content', fullContent); - formData.append('createdAt', time); - formData.append('updatedAt', time); - formData.append('courseId', courseId.toString()); - formData.append('assignmentId', assignmentId.toString()); - formData.append('userId', userId.toString()); - formData.append('files', file); - - response = await SendPOST('/submissions', formData, externalId) - }else{ - const submissionData = { - createdAt: time, - updatedAt: time, - courseId: courseId, - assignmentId: assignmentId, - userId: userId, - content: fullContent - }; - //@ts-ignore - response = await SendPOST('/submissions', submissionData, externalId) +async function createSubmission( + courseId: number, + assignmentId: number, + userId: number, + externalId: string, + content?: string, + file?: File +) { + const time = new Date().toISOString() + + content = content || `This is a test submission for assignment Id: ${assignmentId}` + const fullContent = `{"form":${JSON.stringify(content)}}` + + let response + console.log('Creating submission for assignment Id: ', assignmentId) + if (file) { + const formData = new FormData() + formData.append('content', fullContent) + formData.append('createdAt', time) + formData.append('updatedAt', time) + formData.append('courseId', courseId.toString()) + formData.append('assignmentId', assignmentId.toString()) + formData.append('userId', userId.toString()) + formData.append('files', file) + + response = await SendPOST('/submissions', formData, externalId) + } else { + const submissionData = { + createdAt: time, + updatedAt: time, + courseId: courseId, + assignmentId: assignmentId, + userId: userId, + content: fullContent, } - return response + //@ts-ignore + response = await SendPOST('/submissions', submissionData, externalId) + } + return response } - -async function createAssignmentProblem(assignmentId:number, problemName:string, maxScore:number){ - const problemData = { - assignmentId: assignmentId, - problemName: problemName, - maxScore: maxScore - }; -return await SendPOST('/assignment-problems', JSON.stringify(problemData), 'admin'); +async function createAssignmentProblem(assignmentId: number, problemName: string, maxScore: number) { + const problemData = { + assignmentId: assignmentId, + problemName: problemName, + maxScore: maxScore, + } + return await SendPOST('/assignment-problems', JSON.stringify(problemData), 'admin') } - -async function gradeSubmission(submissionId:number){ - return await SendPOST(`/grade/${submissionId}`, '', 'admin') +async function gradeSubmission(submissionId: number) { + return await SendPOST(`/grade/${submissionId}`, '', 'admin') } async function runCourseAndSubmission() { - try { - //Create users - const billy= (await fetchToken('billy@buffalo.edu', 'billy')) - const bob = (await fetchToken('bob@buffalo.edu', 'bob')) - - //Create courses - const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024')).id - const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id - - //Create enroll students - await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId1}, role:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId2}, role:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId1}, role:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId2}, role:student, dropped:false}`, 'admin') - - //Create assignments - const assignmentId1 = (await createAssignment(courseId1, 'Course1 Assignment 1', 'Quiz')).id - const assignmentId2 =(await createAssignment(courseId1, 'Course1 Assignment 2', 'Homework')).id - - const assignmentId3 = (await createAssignment(courseId2, 'Course2 Assignment 1', 'Quiz')).id - const assignmentId4 =(await createAssignment(courseId2, 'Course2 Assignment 2', 'Homework')).id - - const problemName1 = (await createAssignmentProblem(assignmentId1, 'Please answer A', 10)).problemName - const problemName2 = (await createAssignmentProblem(assignmentId1, 'Please answer B', 10)).problemName - - const problemName3 = (await createAssignmentProblem(assignmentId3, 'Please NOT answer A', 10)).problemName - const problemName4 = (await createAssignmentProblem(assignmentId3, 'Please NOT answer B', 10)).problemName - - //NonContainerAutoGrader - await createNonContainerAutoGrader(assignmentId1, problemName1, 10, 'A', false) - await createNonContainerAutoGrader(assignmentId1, problemName2, 10, 'B', false) - await createNonContainerAutoGrader(assignmentId3, problemName1, 10, '/^[^Aa]+$/', true) - await createNonContainerAutoGrader(assignmentId3, problemName2, 10, '/^[^Bb]+$/', true) - - //ContainerAutoGrader - file = new File(['This is a test grader file'], 'grader.code'); - await createContainerAutoGrader(assignmentId2, 'NewestImageInTheWorld', 300, file) - - file = new File(['This is another test grader file'], 'grader.code'); - makefile = new File(['This is a test makefile'], 'makefile'); - await createContainerAutoGrader(assignmentId4, 'OldestImageInTheWorld', 300, file, makefile) - - //Create submissions - content = `{"${problemName1}": "A", "${problemName2}": "B"}` - const submissionId1 = (await createSubmission(courseId1, assignmentId1, billy, 'billy',content)).id - - content = `{"${problemName1}": "C", "${problemName2}": "D"}` - const submissionId2 = (await createSubmission(courseId1, assignmentId1, bob, 'bob',content)).id - - content = `{"${problemName3}": "A", "${problemName4}": "B"}` - const submissionId3 = (await createSubmission(courseId2, assignmentId3, billy, 'billy',content)).id - - content = `{"${problemName3}": "C", "${problemName4}": "D"}` - const submissionId4 = (await createSubmission(courseId2, assignmentId3, bob, 'bob',content)).id - - file = new File(['This is a test file1'], 'test.txt'); - const submissionId5 = (await createSubmission(courseId1, assignmentId2, billy, 'billy',undefined, file)).id - const submissionId6 = (await createSubmission(courseId1, assignmentId2, bob, 'bob',undefined, file)).id - - file = new File(['These are lines of codes'], 'code.code'); - const submissionId7 = (await createSubmission(courseId1, assignmentId4, billy, 'billy',undefined, file)).id - const submissionId8 = (await createSubmission(courseId1, assignmentId4, bob, 'bob',undefined, file)).id - - //Grade the submissions - for (const submissionId of [submissionId1, submissionId2, submissionId3, submissionId4, submissionId5, submissionId6, submissionId7, submissionId8]){ - console.log('Grading submission: ', submissionId) - await gradeSubmission(submissionId) - } - - console.log('Script completed successfully!') - } catch (e) { - console.error(e) + try { + //Create users + const billy = await fetchToken('billy@buffalo.edu', 'billy') + const bob = await fetchToken('bob@buffalo.edu', 'bob') + + //Create courses + const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024')).id + const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id + + //Create enroll students + await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId1}, role:student, dropped:false}`, 'admin') + await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId2}, role:student, dropped:false}`, 'admin') + await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId1}, role:student, dropped:false}`, 'admin') + await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId2}, role:student, dropped:false}`, 'admin') + + //Create assignments + const assignment1 = await createAssignment(courseId1, 'Course1 Assignment 1', 'Quiz') + const assignmentId1 = assignment1.id + const assignmentId2 = (await createAssignment(courseId1, 'Course1 Assignment 2', 'Homework')).id + + const assignmentId3 = (await createAssignment(courseId2, 'Course2 Assignment 1', 'Quiz')).id + const assignmentId4 = (await createAssignment(courseId2, 'Course2 Assignment 2', 'Homework')).id + + const problemName1 = (await createAssignmentProblem(assignmentId1, 'Please answer A', 10)).problemName + const problemName2 = (await createAssignmentProblem(assignmentId1, 'Please answer B', 10)).problemName + + const problemName3 = (await createAssignmentProblem(assignmentId3, 'Please NOT answer A', 10)).problemName + const problemName4 = (await createAssignmentProblem(assignmentId3, 'Please NOT answer B', 10)).problemName + + //NonContainerAutoGrader + await createNonContainerAutoGrader(assignmentId1, problemName1, 10, 'A', false) + await createNonContainerAutoGrader(assignmentId1, problemName2, 10, 'B', false) + await createNonContainerAutoGrader(assignmentId3, problemName1, 10, '/^[^Aa]+$/', true) + await createNonContainerAutoGrader(assignmentId3, problemName2, 10, '/^[^Bb]+$/', true) + + //ContainerAutoGrader + file = new File(['This is a test grader file'], 'grader.code') + await createContainerAutoGrader(assignmentId2, 'NewestImageInTheWorld', 300, file) + + file = new File(['This is another test grader file'], 'grader.code') + makefile = new File(['This is a test makefile'], 'makefile') + await createContainerAutoGrader(assignmentId4, 'OldestImageInTheWorld', 300, file, makefile) + + //Create submissions + content = `{"${problemName1}": "A", "${problemName2}": "B"}` + const submissionId1 = (await createSubmission(courseId1, assignmentId1, billy, 'billy', content)).id + + content = `{"${problemName1}": "C", "${problemName2}": "D"}` + const submissionId2 = (await createSubmission(courseId1, assignmentId1, bob, 'bob', content)).id + + content = `{"${problemName3}": "A", "${problemName4}": "B"}` + const submissionId3 = (await createSubmission(courseId2, assignmentId3, billy, 'billy', content)).id + + content = `{"${problemName3}": "C", "${problemName4}": "D"}` + const submissionId4 = (await createSubmission(courseId2, assignmentId3, bob, 'bob', content)).id + + file = new File(['This is a test file1'], 'test.txt') + const submissionId5 = (await createSubmission(courseId1, assignmentId2, billy, 'billy', undefined, file)).id + const submissionId6 = (await createSubmission(courseId1, assignmentId2, bob, 'bob', undefined, file)).id + + file = new File(['These are lines of codes'], 'code.code') + const submissionId7 = (await createSubmission(courseId1, assignmentId4, billy, 'billy', undefined, file)).id + const submissionId8 = (await createSubmission(courseId1, assignmentId4, bob, 'bob', undefined, file)).id + + //Grade the submissions + for (const submissionId of [ + submissionId1, + submissionId2, + submissionId3, + submissionId4, + submissionId5, + submissionId6, + submissionId7, + submissionId8, + ]) { + console.log('Grading submission: ', submissionId) + await gradeSubmission(submissionId) } -} + console.log('Script completed successfully!') + } catch (e) { + console.error(e) + } +} initAdmin() -runCourseAndSubmission() \ No newline at end of file +runCourseAndSubmission() diff --git a/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts b/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts index 6220550d..70faa051 100644 --- a/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts +++ b/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts @@ -42,7 +42,8 @@ describe('LoginDeveloperController', () => { test('Returns refreshToken as cookie', () => expect(res.cookie).toBeCalledWith('refreshToken', refreshToken, refreshCookieOptions)) test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) - test('Generic response returned ', () => expect(res.json).toBeCalledWith({ message: 'Login successful', 'userId': 1 })) + test('Generic response returned ', () => + expect(res.json).toBeCalledWith({ message: 'Login successful', userId: 1 })) }) describe('400 - Bad Request', () => { diff --git a/devU-api/src/authorization/authorization.middleware.ts b/devU-api/src/authorization/authorization.middleware.ts index 2216d599..f997bad0 100644 --- a/devU-api/src/authorization/authorization.middleware.ts +++ b/devU-api/src/authorization/authorization.middleware.ts @@ -1,11 +1,10 @@ -import {NextFunction, Request, Response} from "express"; -import {NotFound, GenericResponse, Unauthorized} from "../utils/apiResponse.utils"; +import { NextFunction, Request, Response } from 'express' +import { NotFound, GenericResponse, Unauthorized } from '../utils/apiResponse.utils' import AssignmentService from '../entities/assignment/assignment.service' import UserCourseService from '../entities/userCourse/userCourse.service' -import RoleService from "../entities/role/role.service"; -import {serialize} from "../entities/role/role.serializer"; -import {Role} from "../../devu-shared-modules"; - +import RoleService from '../entities/role/role.service' +import { serialize } from '../entities/role/role.serializer' +import { Role } from '../../devu-shared-modules' /** * Are you authorized to access this endpoint? @@ -16,53 +15,53 @@ import {Role} from "../../devu-shared-modules"; * @param ownerId id that must match the user id if using a self permission */ export function isAuthorized(permission: string, permissionIfSelf?: string) { - return async function (req: Request, res: Response, next: NextFunction) { - const courseId = parseInt(req.params.courseId) - const userId = req.currentUser?.userId - - if (!courseId || !userId) { - return res.status(404).json(NotFound) - } - - // Pull userCourse - const userCourse = await UserCourseService.retrieveByCourseAndUser(courseId, userId) - - // If the course doesn't exist or the user is not enrolled, return the same error, so we don't leak information - // about what courses exist - if (!userCourse || userCourse.dropped) { - return res.status(404).json(NotFound) - } - - // Pull role - let role: Role | undefined - const roleModel = await RoleService.retrieveByCourseAndName(courseId, userCourse.role) - - if (!roleModel) { - role = RoleService.retrieveDefaultByName(userCourse.role) - } else { - role = serialize(roleModel) - } - - if (!role) { - return res.status(403).json(new GenericResponse("Invalid role: " + userCourse.role)) - } - - // check for permission - if (role[permission as keyof typeof role]) { - // authorized! - next() - } - - if (permissionIfSelf && req.ownerId && role[permissionIfSelf as keyof typeof role]) { - // can access if they are the owner - if (req.currentUser?.userId && req.currentUser?.userId === parseInt(req.ownerId)) { - // authorized to access their own content - next() - } - } - - return res.status(403).json(Unauthorized) + return async function (req: Request, res: Response, next: NextFunction) { + const courseId = parseInt(req.params.courseId) + const userId = req.currentUser?.userId + + if (!courseId || !userId) { + return res.status(404).json(NotFound) } + + // Pull userCourse + const userCourse = await UserCourseService.retrieveByCourseAndUser(courseId, userId) + + // If the course doesn't exist or the user is not enrolled, return the same error, so we don't leak information + // about what courses exist + if (!userCourse || userCourse.dropped) { + return res.status(404).json(NotFound) + } + + // Pull role + let role: Role | undefined + const roleModel = await RoleService.retrieveByCourseAndName(courseId, userCourse.role) + + if (!roleModel) { + role = RoleService.retrieveDefaultByName(userCourse.role) + } else { + role = serialize(roleModel) + } + + if (!role) { + return res.status(403).json(new GenericResponse('Invalid role: ' + userCourse.role)) + } + + // check for permission + if (role[permission as keyof typeof role]) { + // authorized! + next() + } + + if (permissionIfSelf && req.ownerId && role[permissionIfSelf as keyof typeof role]) { + // can access if they are the owner + if (req.currentUser?.userId && req.currentUser?.userId === parseInt(req.ownerId)) { + // authorized to access their own content + next() + } + } + + return res.status(403).json(Unauthorized) + } } /** @@ -74,14 +73,14 @@ export function isAuthorized(permission: string, permissionIfSelf?: string) { * but not checked to be a valid assignment id */ export async function isAuthorizedByAssignmentStatus(req: Request, res: Response, next: NextFunction) { - const assignmentId = parseInt(req.params['assignmentId']) - const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) - const permissionString = isAssignmentReleased ? 'assignmentViewReleased' : 'assignmentViewAll' - await isAuthorized(permissionString)(req, res, next) - // TODO: Authorization based on released status. This way is bad. It has hard-coded permission strings and won't - // work for sub-entities (eg. submissionProblemScores shouldn't be viewable if the assignment is not released) - // TODO: check if scores are released - // TODO: rework this to be proper middleware, if keeping this at all + const assignmentId = parseInt(req.params['assignmentId']) + const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) + const permissionString = isAssignmentReleased ? 'assignmentViewReleased' : 'assignmentViewAll' + await isAuthorized(permissionString)(req, res, next) + // TODO: Authorization based on released status. This way is bad. It has hard-coded permission strings and won't + // work for sub-entities (eg. submissionProblemScores shouldn't be viewable if the assignment is not released) + // TODO: check if scores are released + // TODO: rework this to be proper middleware, if keeping this at all } // Maybe do this instead @@ -92,31 +91,32 @@ export async function isAuthorizedByAssignmentStatus(req: Request, res: Response // next() // } - export function extractOwnerByPathParam(ownerParam: string) { - return async function (req: Request, res: Response, next: NextFunction) { - req.ownerId = req.params[ownerParam] - next() - } + return async function (req: Request, res: Response, next: NextFunction) { + req.ownerId = req.params[ownerParam] + next() + } } - export function extractOwnerByBodyParam(ownerBodyParam: string) { - return async function (req: Request, res: Response, next: NextFunction) { - req.ownerId = req.body[ownerBodyParam] - next() - } + return async function (req: Request, res: Response, next: NextFunction) { + req.ownerId = req.body[ownerBodyParam] + next() + } } -export function extractOwnerOfCourseContentByResourceField(serviceFunction: (resourceId: number, courseId: number) => Promise, extractOwnerId: (resource: T) => string, resourceIdParam: string) { - return async function (req: Request, res: Response, next: NextFunction) { - const resourceId = parseInt(req.params[resourceIdParam]) - const courseId = parseInt(req.params.courseId) +export function extractOwnerOfCourseContentByResourceField( + serviceFunction: (resourceId: number, courseId: number) => Promise, + extractOwnerId: (resource: T) => string, + resourceIdParam: string +) { + return async function (req: Request, res: Response, next: NextFunction) { + const resourceId = parseInt(req.params[resourceIdParam]) + const courseId = parseInt(req.params.courseId) - const resource = await serviceFunction(resourceId, courseId) - req.ownerId = extractOwnerId(resource) + const resource = await serviceFunction(resourceId, courseId) + req.ownerId = extractOwnerId(resource) - next() - } + next() + } } - diff --git a/devU-api/src/database.ts b/devU-api/src/database.ts index d1deb363..a2dfbc83 100644 --- a/devU-api/src/database.ts +++ b/devU-api/src/database.ts @@ -24,7 +24,6 @@ const typeORMConfiguration: ConnectionOptions = { export default typeORMConfiguration - /* This function is used to group the data by the specified column @param connection: the specific connection to the database @@ -33,21 +32,26 @@ export default typeORMConfiguration @param filter: the filter object @returns the grouped data */ -export async function groupBy( connection: Repository, columnList:string[], query: any, filter: { index: string, value: number }){ +export async function groupBy( + connection: Repository, + columnList: string[], + query: any, + filter: { index: string; value: number } +) { let orders = query // The filteredOrders currently only filters the orders by the columnList, any other orders are removed // and only set to 'ASC' since no input is provided for the order const filteredOrders = Object.entries(orders) - .filter(([key]) => columnList.includes(orders[key])) - .reduce((acc, [key]) => ({ ...acc, [orders[key]]: 'ASC' }), {}); + .filter(([key]) => columnList.includes(orders[key])) + .reduce((acc, [key]) => ({ ...acc, [orders[key]]: 'ASC' }), {}) - orders = Object.keys(filteredOrders).length === 0 ? { id: 'ASC' } : filteredOrders; + orders = Object.keys(filteredOrders).length === 0 ? { id: 'ASC' } : filteredOrders return await connection.find({ where: { - [filter.index]: filter.value + [filter.index]: filter.value, }, order: orders, - withDeleted: false + withDeleted: false, }) -} \ No newline at end of file +} diff --git a/devU-api/src/entities/assignment/assignment.controller.ts b/devU-api/src/entities/assignment/assignment.controller.ts index 930aa5d5..7c540b14 100644 --- a/devU-api/src/entities/assignment/assignment.controller.ts +++ b/devU-api/src/entities/assignment/assignment.controller.ts @@ -26,7 +26,7 @@ export async function getByCourse(req: Request, res: Response, next: NextFunctio try { const courseId = parseInt(req.params.courseId) const assignments = await AssignmentService.listByCourse(courseId) - + const response = assignments.map(serialize) res.status(200).json(response) @@ -84,5 +84,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } - -export default { detail, post, put, _delete, getByCourse, getReleased} +export default { detail, post, put, _delete, getByCourse, getReleased } diff --git a/devU-api/src/entities/assignment/assignment.model.ts b/devU-api/src/entities/assignment/assignment.model.ts index 0c1c2731..654f5403 100644 --- a/devU-api/src/entities/assignment/assignment.model.ts +++ b/devU-api/src/entities/assignment/assignment.model.ts @@ -1,98 +1,98 @@ import { - Entity, - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, - DeleteDateColumn, - ManyToOne, - JoinColumn, + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, + ManyToOne, + JoinColumn, } from 'typeorm' import CourseModel from '../course/course.model' @Entity('assignments') export default class AssignmentModel { - /** - * @swagger - * tags: - * - name: Assignments - * description: - * components: - * schemas: - * Assignment: - * type: object - * required: [courseId, name, categoryName, maxFileSize, disableHandins, startDate, dueDate, endDate] - * properties: - * courseId: - * type: integer - * name: - * type: string - * categoryName: - * type: string - * description: - * type: string - * maxFileSize: - * type: integer - * maxSubmissions: - * type: integer - * disableHandins: - * type: boolean - * startDate: - * type: string - * format: date-time - * description: Must be in ISO 8601 format - * dueDate: - * type: string - * format: date-time - * description: Must be in ISO 8601 format - * endDate: - * type: string - * format: date-time - * description: Must be in ISO 8601 format - */ - - @PrimaryGeneratedColumn() - id: number - - @Column({name: 'course_id'}) - @JoinColumn({name: 'course_id'}) - @ManyToOne(() => CourseModel) - courseId: number - - @Column({length: 128}) - name: string - - @Column({name: 'start_date'}) - startDate: Date - - @Column({name: 'due_date'}) - dueDate: Date - - @Column({name: 'end_date'}) - endDate: Date - - @Column({name: 'category_name', length: 128}) - categoryName: string - - @Column({nullable: true, type: 'text'}) - description: string | null - - @Column({name: 'max_file_size'}) - maxFileSize: number - - @Column({name: 'max_submissions', type: 'int', nullable: true}) - maxSubmissions: number | null - - @Column({name: 'disable_handins'}) - disableHandins: boolean - - @CreateDateColumn({name: 'created_at'}) - createdAt: Date - - @UpdateDateColumn({name: 'updated_at'}) - updatedAt: Date - - @DeleteDateColumn({name: 'deleted_at'}) - deletedAt?: Date + /** + * @swagger + * tags: + * - name: Assignments + * description: + * components: + * schemas: + * Assignment: + * type: object + * required: [courseId, name, categoryName, maxFileSize, disableHandins, startDate, dueDate, endDate] + * properties: + * courseId: + * type: integer + * name: + * type: string + * categoryName: + * type: string + * description: + * type: string + * maxFileSize: + * type: integer + * maxSubmissions: + * type: integer + * disableHandins: + * type: boolean + * startDate: + * type: string + * format: date-time + * description: Must be in ISO 8601 format + * dueDate: + * type: string + * format: date-time + * description: Must be in ISO 8601 format + * endDate: + * type: string + * format: date-time + * description: Must be in ISO 8601 format + */ + + @PrimaryGeneratedColumn() + id: number + + @Column({ name: 'course_id' }) + @JoinColumn({ name: 'course_id' }) + @ManyToOne(() => CourseModel) + courseId: number + + @Column({ length: 128 }) + name: string + + @Column({ name: 'start_date' }) + startDate: Date + + @Column({ name: 'due_date' }) + dueDate: Date + + @Column({ name: 'end_date' }) + endDate: Date + + @Column({ name: 'category_name', length: 128 }) + categoryName: string + + @Column({ nullable: true, type: 'text' }) + description: string | null + + @Column({ name: 'max_file_size' }) + maxFileSize: number + + @Column({ name: 'max_submissions', type: 'int', nullable: true }) + maxSubmissions: number | null + + @Column({ name: 'disable_handins' }) + disableHandins: boolean + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date + + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date } diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index 85b1f22f..3e77a20d 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -3,15 +3,14 @@ import express from 'express' // Middleware import validator from './assignment.validator' -import {asInt} from '../../middleware/validator/generic.validator' +import { asInt } from '../../middleware/validator/generic.validator' // Controller import AssignmentsController from './assignment.controller' -import {isAuthorized, isAuthorizedByAssignmentStatus} from "../../authorization/authorization.middleware"; +import { isAuthorized, isAuthorizedByAssignmentStatus } from '../../authorization/authorization.middleware' const Router = express.Router() - /** * @swagger * /course/:courseId/assignments/released: @@ -32,7 +31,6 @@ const Router = express.Router() */ Router.get('/released', isAuthorized('assignmentViewReleased'), AssignmentsController.getReleased) - /** * @swagger * /course/:courseId/assignments: @@ -53,7 +51,6 @@ Router.get('/released', isAuthorized('assignmentViewReleased'), AssignmentsContr */ Router.get('/', isAuthorized('assignmentViewAll'), AssignmentsController.getByCourse) - /** * @swagger * /course/:courseId/assignments/{id}: @@ -80,8 +77,6 @@ Router.get('/', isAuthorized('assignmentViewAll'), AssignmentsController.getByCo */ Router.get('/:id', asInt(), isAuthorizedByAssignmentStatus, AssignmentsController.detail) - - /** * @swagger * /course/:courseId/assignments: diff --git a/devU-api/src/entities/assignment/assignment.service.ts b/devU-api/src/entities/assignment/assignment.service.ts index e69758b1..b3b2fc9e 100644 --- a/devU-api/src/entities/assignment/assignment.service.ts +++ b/devU-api/src/entities/assignment/assignment.service.ts @@ -1,86 +1,85 @@ -import {getRepository, IsNull} from 'typeorm' +import { getRepository, IsNull } from 'typeorm' import AssignmentModel from './assignment.model' -import {Assignment} from 'devu-shared-modules' +import { Assignment } from 'devu-shared-modules' const connect = () => getRepository(AssignmentModel) export async function create(assignment: Assignment) { - return await connect().save(assignment) + return await connect().save(assignment) } export async function update(assignment: Assignment) { - const { - id, - name, - startDate, - dueDate, - endDate, - categoryName, - description, - maxFileSize, - maxSubmissions, - disableHandins, - } = assignment - - if (!id) throw new Error('Missing Id') - - return await connect().update(id, { - name, - startDate, - dueDate, - endDate, - categoryName, - description, - maxFileSize, - maxSubmissions, - disableHandins, - }) + const { + id, + name, + startDate, + dueDate, + endDate, + categoryName, + description, + maxFileSize, + maxSubmissions, + disableHandins, + } = assignment + + if (!id) throw new Error('Missing Id') + + return await connect().update(id, { + name, + startDate, + dueDate, + endDate, + categoryName, + description, + maxFileSize, + maxSubmissions, + disableHandins, + }) } export async function _delete(id: number) { - return await connect().softDelete({id, deletedAt: IsNull()}) + return await connect().softDelete({ id, deletedAt: IsNull() }) } export async function retrieve(id: number, courseId: number) { - return await connect().findOne({"id": id, "courseId": courseId, deletedAt: IsNull()}) + return await connect().findOne({ id: id, courseId: courseId, deletedAt: IsNull() }) } export async function list() { - return await connect().find({deletedAt: IsNull()}) + return await connect().find({ deletedAt: IsNull() }) } export async function listByCourse(courseId: number) { - return await connect().find({'courseId': courseId, deletedAt: IsNull()}) + return await connect().find({ courseId: courseId, deletedAt: IsNull() }) } export async function listByCourseReleased(courseId: number) { - // TODO: filter by start date after current time - return await connect().find({'courseId': courseId, deletedAt: IsNull()}) + // TODO: filter by start date after current time + return await connect().find({ courseId: courseId, deletedAt: IsNull() }) } - export async function isReleased(id: number) { - const assignment = await connect().findOne({id, deletedAt: IsNull()}) + const assignment = await connect().findOne({ id, deletedAt: IsNull() }) - if (!assignment) { - return false - } + if (!assignment) { + return false + } - const startDate = assignment?.startDate - const currentDate = new Date(Date.now()) + const startDate = assignment?.startDate + const currentDate = new Date(Date.now()) - return startDate && startDate < currentDate + return startDate && startDate < currentDate } export default { - create, - retrieve, - update, - _delete, - list, - listByCourse, - listByCourseReleased, - isReleased, + create, + retrieve, + update, + _delete, + list, + listByCourse, + listByCourseReleased, + isReleased, } diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts index 8e44b8fa..bb3c2063 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts @@ -3,9 +3,8 @@ import express from 'express' // Middleware import validator from './assignmentProblem.validator' -import {asInt} from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; - +import { asInt } from '../../middleware/validator/generic.validator' +import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import AssignmentProblemController from './assignmentProblem.controller' diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts b/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts index 018d033e..f72da422 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts @@ -7,108 +7,110 @@ import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.util import { serialize } from './assignmentScore.serializer' export async function get(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const assignmentScores = await AssignmentScoreService.list(id) - const response = assignmentScores.map(serialize) - - res.status(200).json(response) - } catch (err) { - next(err) - } + try { + const id = parseInt(req.params.id) + const assignmentScores = await AssignmentScoreService.list(id) + const response = assignmentScores.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function detail(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const assignmentScore = await AssignmentScoreService.retrieve(id) + try { + const id = parseInt(req.params.id) + const assignmentScore = await AssignmentScoreService.retrieve(id) - if (!assignmentScore) return res.status(404).json(NotFound) + if (!assignmentScore) return res.status(404).json(NotFound) - const response = serialize(assignmentScore) + const response = serialize(assignmentScore) - res.status(200).json(response) - } catch (err) { - next(err) - } + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function getByUser(req: Request, res: Response, next: NextFunction) { - try { - const userId = parseInt(req.params.id) - const assignmentScores = await AssignmentScoreService.listByUser(userId) + try { + const userId = parseInt(req.params.id) + const assignmentScores = await AssignmentScoreService.listByUser(userId) - const response = assignmentScores.map(serialize) + const response = assignmentScores.map(serialize) - res.status(200).json(response) - } catch (err) { - next(err) - } + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function detailByUser(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const userId = parseInt(req.params.userId) - const assignmentScore = await AssignmentScoreService.retrieveByUser(id, userId) + try { + const id = parseInt(req.params.id) + const userId = parseInt(req.params.userId) + const assignmentScore = await AssignmentScoreService.retrieveByUser(id, userId) - if (!assignmentScore) return res.status(404).json(NotFound) + if (!assignmentScore) return res.status(404).json(NotFound) - const response = serialize(assignmentScore) + const response = serialize(assignmentScore) - res.status(200).json(response) - } catch (err) { - next(err) - } + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function getByCourse(req: Request, res: Response, next: NextFunction) { - try { - const userId = parseInt(req.params.id) - const assignmentScores = await AssignmentScoreService.listByCourse(userId) - - const response = assignmentScores.map(as => { if (as) return serialize(as) }) - - res.status(200).json(response) - } catch (err) { - next(err) - } + try { + const userId = parseInt(req.params.id) + const assignmentScores = await AssignmentScoreService.listByCourse(userId) + + const response = assignmentScores.map(as => { + if (as) return serialize(as) + }) + + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function post(req: Request, res: Response, next: NextFunction) { - try { - const assignmentScore = await AssignmentScoreService.create(req.body) - const response = serialize(assignmentScore) - - res.status(201).json(response) - } catch (err) { - res.status(400).json(new GenericResponse(err.message)) - } + try { + const assignmentScore = await AssignmentScoreService.create(req.body) + const response = serialize(assignmentScore) + + res.status(201).json(response) + } catch (err) { + res.status(400).json(new GenericResponse(err.message)) + } } export async function put(req: Request, res: Response, next: NextFunction) { - try { - req.body.id = parseInt(req.params.id) - const results = await AssignmentScoreService.update(req.body) + try { + req.body.id = parseInt(req.params.id) + const results = await AssignmentScoreService.update(req.body) - if (!results.affected) return res.status(404).json(NotFound) + if (!results.affected) return res.status(404).json(NotFound) - res.status(200).json(Updated) - } catch (err) { - next(err) - } + res.status(200).json(Updated) + } catch (err) { + next(err) + } } export async function _delete(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const results = await AssignmentScoreService._delete(id) + try { + const id = parseInt(req.params.id) + const results = await AssignmentScoreService._delete(id) - if (!results.affected) return res.status(404).json(NotFound) + if (!results.affected) return res.status(404).json(NotFound) - res.status(204).send() - } catch (err) { - next(err) - } + res.status(204).send() + } catch (err) { + next(err) + } } -export default { get, detail, post, put, _delete, getByUser, detailByUser, getByCourse } \ No newline at end of file +export default { get, detail, post, put, _delete, getByUser, detailByUser, getByCourse } diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.model.ts b/devU-api/src/entities/assignmentScore/assignmentScore.model.ts index c399fe0f..d63aebaf 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.model.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.model.ts @@ -1,12 +1,12 @@ import { - Entity, - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, - DeleteDateColumn, - ManyToOne, - JoinColumn, + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, + ManyToOne, + JoinColumn, } from 'typeorm' import AssignmentModel from '../assignment/assignment.model' @@ -18,7 +18,7 @@ export default class AssignmentScoreModel { * @swagger * tags: * - name: AssignmentScore - * description: + * description: * components: * schemas: * AssignmentScore: @@ -33,29 +33,28 @@ export default class AssignmentScoreModel { * type: number */ - @PrimaryGeneratedColumn() - id: number - - @Column({ name: 'assignment_id'}) - @JoinColumn({ name: 'assignment_id'}) - @ManyToOne( () => AssignmentModel) - assignmentId: number - - @Column({ name: 'user_id'}) - @JoinColumn({ name: 'user_id'}) - @ManyToOne( () => UserModel) - userId: number - - @Column({ name: 'score', nullable: true}) - score: number - - @CreateDateColumn({ name: 'created_at' }) - createdAt: Date - - @UpdateDateColumn({ name: 'updated_at' }) - updatedAt: Date - - @DeleteDateColumn({ name: 'deleted_at' }) - deletedAt?: Date - -} \ No newline at end of file + @PrimaryGeneratedColumn() + id: number + + @Column({ name: 'assignment_id' }) + @JoinColumn({ name: 'assignment_id' }) + @ManyToOne(() => AssignmentModel) + assignmentId: number + + @Column({ name: 'user_id' }) + @JoinColumn({ name: 'user_id' }) + @ManyToOne(() => UserModel) + userId: number + + @Column({ name: 'score', nullable: true }) + score: number + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date + + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date +} diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts index 4b4264c3..bb87c20e 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts @@ -3,8 +3,8 @@ import express from 'express' //Middleware import validator from './assignmentScore.validator' -import {asInt} from '../../middleware/validator/generic.validator' -import {extractOwnerByPathParam, isAuthorized} from "../../authorization/authorization.middleware"; +import { asInt } from '../../middleware/validator/generic.validator' +import { extractOwnerByPathParam, isAuthorized } from '../../authorization/authorization.middleware' //Controller import AssignmentScoreController from './assignmentScore.controller' @@ -69,7 +69,13 @@ Router.get('/:id', isAuthorized('scoresViewAll'), asInt(), AssignmentScoreContro * schema: * type: integer */ -Router.get('/user/:id', extractOwnerByPathParam('userId'), isAuthorized('scoresViewAll', 'scoresViewSelfReleased'), asInt(), AssignmentScoreController.getByUser) +Router.get( + '/user/:id', + extractOwnerByPathParam('userId'), + isAuthorized('scoresViewAll', 'scoresViewSelfReleased'), + asInt(), + AssignmentScoreController.getByUser +) /** * @swagger @@ -95,8 +101,14 @@ Router.get('/user/:id', extractOwnerByPathParam('userId'), isAuthorized('scoresV * schema: * type: integer */ -Router.get('/detail/:id/:userId', asInt(), asInt('userId'), extractOwnerByPathParam('userId'), isAuthorized('scoresViewAll', 'scoresViewSelfReleased'), AssignmentScoreController.detailByUser) - +Router.get( + '/detail/:id/:userId', + asInt(), + asInt('userId'), + extractOwnerByPathParam('userId'), + isAuthorized('scoresViewAll', 'scoresViewSelfReleased'), + AssignmentScoreController.detailByUser +) /** * @swagger @@ -159,4 +171,4 @@ Router.put('/:id', isAuthorized('scoresEditAll'), asInt(), validator, Assignment */ Router.delete('/:id', isAuthorized('scoresEditAll'), asInt(), AssignmentScoreController._delete) -export default Router \ No newline at end of file +export default Router diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.serializer.ts b/devU-api/src/entities/assignmentScore/assignmentScore.serializer.ts index 7067c441..71c3ee6e 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.serializer.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.serializer.ts @@ -3,12 +3,12 @@ import { AssignmentScore } from 'devu-shared-modules' import AssignmentScoreModel from './assignmentScore.model' export function serialize(assignmentScore: AssignmentScoreModel): AssignmentScore { - return { - id: assignmentScore.id, - assignmentId: assignmentScore.assignmentId, - userId: assignmentScore.userId, - score: assignmentScore.score, - createdAt: assignmentScore.createdAt.toISOString(), - updatedAt: assignmentScore.updatedAt.toISOString(), - } -} \ No newline at end of file + return { + id: assignmentScore.id, + assignmentId: assignmentScore.assignmentId, + userId: assignmentScore.userId, + score: assignmentScore.score, + createdAt: assignmentScore.createdAt.toISOString(), + updatedAt: assignmentScore.updatedAt.toISOString(), + } +} diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.service.ts b/devU-api/src/entities/assignmentScore/assignmentScore.service.ts index a62952c0..97555ca5 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.service.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.service.ts @@ -1,22 +1,22 @@ -import { getRepository, IsNull } from "typeorm"; +import { getRepository, IsNull } from 'typeorm' import AssignmentScoreModel from './assignmentScore.model' import { AssignmentScore } from 'devu-shared-modules' -import AssignmentService from "../assignment/assignment.service"; +import AssignmentService from '../assignment/assignment.service' const connect = () => getRepository(AssignmentScoreModel) export async function create(assignmentScore: AssignmentScore) { - return await connect().save(assignmentScore) + return await connect().save(assignmentScore) } export async function update(assignmentScore: AssignmentScore) { - const { id, assignmentId, userId, score } = assignmentScore + const { id, assignmentId, userId, score } = assignmentScore - if (!id) throw new Error('Missing Id') - return await connect().update(id, { assignmentId, userId, score}) + if (!id) throw new Error('Missing Id') + return await connect().update(id, { assignmentId, userId, score }) } export async function _delete(id: number) { @@ -27,11 +27,13 @@ export async function retrieve(id: number) { return await connect().findOne({ id, deletedAt: IsNull() }) } -export async function list(assignmentId: number) { // TODO: There's no way this is right. Test to verify that it should be 'assignmentId': assignmentId - return await connect().find({ assignmentId, deletedAt: IsNull() }) +export async function list(assignmentId: number) { + // TODO: There's no way this is right. Test to verify that it should be 'assignmentId': assignmentId + return await connect().find({ assignmentId, deletedAt: IsNull() }) } -export async function retrieveByUser(assignmentId: number, userId: number) { //TODO: This can't be right.. can it? +export async function retrieveByUser(assignmentId: number, userId: number) { + //TODO: This can't be right.. can it? return await connect().findOne({ assignmentId, userId, deletedAt: IsNull() }) } @@ -41,20 +43,17 @@ export async function listByUser(userId: number) { export async function listByCourse(courseId: number) { const assignments = await AssignmentService.listByCourse(courseId) - const assignmentScorePromises = assignments.map(a => ( - connect().find({ assignmentId: a.id, deletedAt: IsNull()}) - )) + const assignmentScorePromises = assignments.map(a => connect().find({ assignmentId: a.id, deletedAt: IsNull() })) return (await Promise.all(assignmentScorePromises)).reduce((a, b) => a.concat(b), []) //Must flatten 2D array resulting from array promises } export default { - create, - retrieve, - update, - _delete, - list, - retrieveByUser, - listByUser, - listByCourse, + create, + retrieve, + update, + _delete, + list, + retrieveByUser, + listByUser, + listByCourse, } - diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.validator.ts b/devU-api/src/entities/assignmentScore/assignmentScore.validator.ts index 84ae9124..a99c8a5b 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.validator.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.validator.ts @@ -8,4 +8,4 @@ const score = check('score').isNumeric() const validator = [assignmentId, userId, score, validate] -export default validator \ No newline at end of file +export default validator diff --git a/devU-api/src/entities/assignmentScore/tests/assignmentScore.controller.test.ts b/devU-api/src/entities/assignmentScore/tests/assignmentScore.controller.test.ts index 73c329c5..d0a6296c 100644 --- a/devU-api/src/entities/assignmentScore/tests/assignmentScore.controller.test.ts +++ b/devU-api/src/entities/assignmentScore/tests/assignmentScore.controller.test.ts @@ -25,157 +25,155 @@ let expectedError: Error let expectedDbResult: UpdateResult describe('AssignmentScoreController', () => { - beforeEach(() => { - req = Testing.fakeRequest() - res = Testing.fakeResponse() - next = Testing.fakeNext() + beforeEach(() => { + req = Testing.fakeRequest() + res = Testing.fakeResponse() + next = Testing.fakeNext() - // mockedAssignmentScores = Testing.generateTypeOrmArray(AssignmentScoresModel, 3) - mockedAssignmentScore = Testing.generateTypeOrm(AssignmentScoresModel) + // mockedAssignmentScores = Testing.generateTypeOrmArray(AssignmentScoresModel, 3) + mockedAssignmentScore = Testing.generateTypeOrm(AssignmentScoresModel) - expectedResult = serialize(mockedAssignmentScore) - expectedError = new Error('Expected Error') + expectedResult = serialize(mockedAssignmentScore) + expectedError = new Error('Expected Error') - expectedDbResult = {} as UpdateResult + expectedDbResult = {} as UpdateResult + }) + describe('GET - /assignment-score/:id', () => { + describe('200 - Ok', () => { + beforeEach(async () => { + AssignmentScoreService.retrieve = jest.fn().mockImplementation(() => Promise.resolve(mockedAssignmentScore)) + await controller.detail(req, res, next) + }) + + test('Returns expected assignmentScore', () => expect(res.json).toBeCalledWith(expectedResult)) + test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) }) - describe('GET - /assignment-score/:id', () => { - describe('200 - Ok', () => { - beforeEach(async () => { - AssignmentScoreService.retrieve = jest.fn().mockImplementation(() => Promise.resolve(mockedAssignmentScore)) - await controller.detail(req, res, next) - }) - - test('Returns expected assignmentScore', () => expect(res.json).toBeCalledWith(expectedResult)) - test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) - }) - - describe('404 - Not Found', () => { - beforeEach(async () => { - AssignmentScoreService.retrieve = jest.fn().mockImplementation(() => Promise.resolve()) // No results - await controller.detail(req, res, next) - }) - - test('Status code is 404 on missing assignmentScore', () => expect(res.status).toBeCalledWith(404)) - test('Responds with NotFound on missing assignmentScore', () => expect(res.json).toBeCalledWith(NotFound)) - test('Next not called on missing assignmentScore', () => expect(next).toBeCalledTimes(0)) - }) - - describe('400 - Bad Requesst', () => { - test('Next called with expected error', async () => { - AssignmentScoreService.retrieve = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - - try { - await controller.detail(req, res, next) - - fail('Expected test to throw') - } catch { - expect(next).toBeCalledWith(expectedError) - } - }) - }) + describe('404 - Not Found', () => { + beforeEach(async () => { + AssignmentScoreService.retrieve = jest.fn().mockImplementation(() => Promise.resolve()) // No results + await controller.detail(req, res, next) + }) + + test('Status code is 404 on missing assignmentScore', () => expect(res.status).toBeCalledWith(404)) + test('Responds with NotFound on missing assignmentScore', () => expect(res.json).toBeCalledWith(NotFound)) + test('Next not called on missing assignmentScore', () => expect(next).toBeCalledTimes(0)) }) - describe('Post - /assignment-score', () => { - describe('201 - Created', () => { - beforeEach(async () => { - AssignmentScoreService.create = jest.fn().mockImplementation(() => Promise.resolve(mockedAssignmentScore)) - await controller.post(req, res, next) - }) - - test('Returns expected assignmentScore', () => expect(res.json).toBeCalledWith(expectedResult)) - test('Status code is 201', () => expect(res.status).toBeCalledWith(201)) - }) - - describe('400 - Bad Request', () => { - beforeEach(async () => { - AssignmentScoreService.create = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - - try { - await controller.post(req, res, next) - - fail('Expected test to throw') - } catch { - // continue to tests - } - }) - - test('Status code is 400', () => expect(res.status).toBeCalledWith(400)) - test('Responds with generic error', () => expect(res.json).toBeCalledWith(new GenericResponse(expectedError.message))) - test('Next not called', () => expect(next).toBeCalledTimes(0)) - }) + describe('400 - Bad Requesst', () => { + test('Next called with expected error', async () => { + AssignmentScoreService.retrieve = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + + try { + await controller.detail(req, res, next) + + fail('Expected test to throw') + } catch { + expect(next).toBeCalledWith(expectedError) + } + }) }) + }) + + describe('Post - /assignment-score', () => { + describe('201 - Created', () => { + beforeEach(async () => { + AssignmentScoreService.create = jest.fn().mockImplementation(() => Promise.resolve(mockedAssignmentScore)) + await controller.post(req, res, next) + }) - describe('PUT - /assignment-score/:id', () => { - describe('200 - Ok', () => { - beforeEach(async () => { - expectedDbResult.affected = 1 // mocking serice return shape - AssignmentScoreService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - await controller.put(req, res, next) - }) - - test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) - test('Returns Updated message', () => expect(res.json).toBeCalledWith(Updated)) - test('Next is not called', () => expect(next).toHaveBeenCalledTimes(0)) - }) - - describe('404 - Not Found', () => { - beforeEach(async () => { - expectedDbResult.affected = 0 //No records afected in db - AssignmentScoreService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - await controller.put(req, res, next) - }) - - test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) - test('Returns Not found message', () => expect(res.json).toBeCalledWith(NotFound)) - test('Next is not called', () => expect(next).toHaveBeenCalledTimes(0)) - }) - - describe('400 - Bad Request', () => { - beforeEach(async () => { - AssignmentScoreService.update = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - await controller.put(req, res, next) - }) - - test('Next is called with error', () => expect(next).toBeCalledWith(expectedError)) - }) + test('Returns expected assignmentScore', () => expect(res.json).toBeCalledWith(expectedResult)) + test('Status code is 201', () => expect(res.status).toBeCalledWith(201)) }) - describe('DELETE - /assignment-score/:id', () => { - describe('204 - No Content', () => { - beforeEach(async () => { - expectedDbResult.affected = 1 - AssignmentScoreService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - await controller._delete(req, res, next) - }) - - test('Status coe is 204', () => expect(res.status).toBeCalledWith(204)) - test('Response to have no content', () => expect(res.send).toBeCalledWith()) - test('Next not called', () => expect(next).toBeCalledTimes(0)) - }) - - describe('404 - Not Found', () => { - beforeEach(async () => { - expectedDbResult.affected = 0 - AssignmentScoreService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - await controller._delete(req, res, next) - }) - - test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) - test('Response to have no content', () => expect(res.json).toBeCalledWith(NotFound)) - test('Next not called', () => expect(next).toBeCalledTimes(0)) - }) - - describe('400 - Bad Request', () => { - beforeEach(async () => { - AssignmentScoreService._delete = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - await controller._delete(req, res, next) - }) - - test('Next called with expected error', () => expect(next).toBeCalledWith(expectedError)) - }) + describe('400 - Bad Request', () => { + beforeEach(async () => { + AssignmentScoreService.create = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + + try { + await controller.post(req, res, next) + + fail('Expected test to throw') + } catch { + // continue to tests + } + }) + + test('Status code is 400', () => expect(res.status).toBeCalledWith(400)) + test('Responds with generic error', () => + expect(res.json).toBeCalledWith(new GenericResponse(expectedError.message))) + test('Next not called', () => expect(next).toBeCalledTimes(0)) + }) + }) + + describe('PUT - /assignment-score/:id', () => { + describe('200 - Ok', () => { + beforeEach(async () => { + expectedDbResult.affected = 1 // mocking serice return shape + AssignmentScoreService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + await controller.put(req, res, next) + }) + + test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) + test('Returns Updated message', () => expect(res.json).toBeCalledWith(Updated)) + test('Next is not called', () => expect(next).toHaveBeenCalledTimes(0)) }) -}) + describe('404 - Not Found', () => { + beforeEach(async () => { + expectedDbResult.affected = 0 //No records afected in db + AssignmentScoreService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + await controller.put(req, res, next) + }) + + test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) + test('Returns Not found message', () => expect(res.json).toBeCalledWith(NotFound)) + test('Next is not called', () => expect(next).toHaveBeenCalledTimes(0)) + }) + + describe('400 - Bad Request', () => { + beforeEach(async () => { + AssignmentScoreService.update = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + await controller.put(req, res, next) + }) + + test('Next is called with error', () => expect(next).toBeCalledWith(expectedError)) + }) + }) + + describe('DELETE - /assignment-score/:id', () => { + describe('204 - No Content', () => { + beforeEach(async () => { + expectedDbResult.affected = 1 + AssignmentScoreService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + await controller._delete(req, res, next) + }) + + test('Status coe is 204', () => expect(res.status).toBeCalledWith(204)) + test('Response to have no content', () => expect(res.send).toBeCalledWith()) + test('Next not called', () => expect(next).toBeCalledTimes(0)) + }) + + describe('404 - Not Found', () => { + beforeEach(async () => { + expectedDbResult.affected = 0 + AssignmentScoreService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + await controller._delete(req, res, next) + }) + test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) + test('Response to have no content', () => expect(res.json).toBeCalledWith(NotFound)) + test('Next not called', () => expect(next).toBeCalledTimes(0)) + }) + + describe('400 - Bad Request', () => { + beforeEach(async () => { + AssignmentScoreService._delete = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + await controller._delete(req, res, next) + }) + + test('Next called with expected error', () => expect(next).toBeCalledWith(expectedError)) + }) + }) +}) diff --git a/devU-api/src/entities/assignmentScore/tests/assignmentScore.serializer.test.ts b/devU-api/src/entities/assignmentScore/tests/assignmentScore.serializer.test.ts index 6352d725..b9c275cd 100644 --- a/devU-api/src/entities/assignmentScore/tests/assignmentScore.serializer.test.ts +++ b/devU-api/src/entities/assignmentScore/tests/assignmentScore.serializer.test.ts @@ -6,40 +6,37 @@ import Testing from '../../../utils/testing.utils' let mockAssignmentScore: AssignmentScoreModel - describe('AssignmentScore Serializer', () => { - beforeEach(() => { - mockAssignmentScore = Testing.generateTypeOrm(AssignmentScoreModel) - - mockAssignmentScore.id = 10 - mockAssignmentScore.assignmentId = 123 - mockAssignmentScore.userId = 789 - mockAssignmentScore.score = 75 - mockAssignmentScore.createdAt = new Date() - mockAssignmentScore.updatedAt = new Date() - mockAssignmentScore.deletedAt = new Date() - + beforeEach(() => { + mockAssignmentScore = Testing.generateTypeOrm(AssignmentScoreModel) + + mockAssignmentScore.id = 10 + mockAssignmentScore.assignmentId = 123 + mockAssignmentScore.userId = 789 + mockAssignmentScore.score = 75 + mockAssignmentScore.createdAt = new Date() + mockAssignmentScore.updatedAt = new Date() + mockAssignmentScore.deletedAt = new Date() + }) + + describe('Serializing AssignmentScores', () => { + test('AssignmentScore values exist in the response', () => { + const expectedResult = serialize(mockAssignmentScore) + + expect(expectedResult).toBeDefined() + expect(expectedResult.id).toEqual(mockAssignmentScore.id) + expect(expectedResult.assignmentId).toEqual(mockAssignmentScore.assignmentId) + expect(expectedResult.userId).toEqual(mockAssignmentScore.userId) + expect(expectedResult.score).toEqual(mockAssignmentScore.score) }) - describe('Serializing AssignmentScores', () => { - test('AssignmentScore values exist in the response', () => { - const expectedResult = serialize(mockAssignmentScore) - - expect(expectedResult).toBeDefined() - expect(expectedResult.id).toEqual(mockAssignmentScore.id) - expect(expectedResult.assignmentId).toEqual(mockAssignmentScore.assignmentId) - expect(expectedResult.userId).toEqual(mockAssignmentScore.userId) - expect(expectedResult.score).toEqual(mockAssignmentScore.score) - }) - - test('Dates are returned as ISO strings for all assignments', () => { - const expectedResult = serialize(mockAssignmentScore) - - expect(expectedResult).toBeDefined() + test('Dates are returned as ISO strings for all assignments', () => { + const expectedResult = serialize(mockAssignmentScore) - expect(expectedResult.createdAt).toEqual(mockAssignmentScore.createdAt.toISOString()) - expect(expectedResult.updatedAt).toEqual(mockAssignmentScore.updatedAt.toISOString()) + expect(expectedResult).toBeDefined() - }) + expect(expectedResult.createdAt).toEqual(mockAssignmentScore.createdAt.toISOString()) + expect(expectedResult.updatedAt).toEqual(mockAssignmentScore.updatedAt.toISOString()) }) -}) \ No newline at end of file + }) +}) diff --git a/devU-api/src/entities/category/category.controller.ts b/devU-api/src/entities/category/category.controller.ts index 0b5fff6e..3fb84369 100644 --- a/devU-api/src/entities/category/category.controller.ts +++ b/devU-api/src/entities/category/category.controller.ts @@ -45,7 +45,6 @@ export async function getByCourse(req: Request, res: Response, next: NextFunctio } } - export async function post(req: Request, res: Response, next: NextFunction) { try { const category = await CategoryService.create(req.body) @@ -83,4 +82,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { get, detail, post, put, _delete, getByCourse } \ No newline at end of file +export default { get, detail, post, put, _delete, getByCourse } diff --git a/devU-api/src/entities/category/category.model.ts b/devU-api/src/entities/category/category.model.ts index 6d9242fc..0267c9f7 100644 --- a/devU-api/src/entities/category/category.model.ts +++ b/devU-api/src/entities/category/category.model.ts @@ -5,9 +5,10 @@ import { CreateDateColumn, UpdateDateColumn, DeleteDateColumn, - JoinColumn, ManyToOne + JoinColumn, + ManyToOne, } from 'typeorm' -import CourseModel from "../course/course.model"; +import CourseModel from '../course/course.model' @Entity('category') export default class CategoryModel { @@ -15,14 +16,14 @@ export default class CategoryModel { * @swagger * tags: * - name: Categories - * description: + * description: * components: * schemas: * Category: * type: object * required: [courseId, name] * properties: - * courseId: + * courseId: * type: integer * name: * type: string @@ -30,8 +31,8 @@ export default class CategoryModel { @PrimaryGeneratedColumn() id: number - @Column({name: 'course_id'}) - @JoinColumn({name: 'course_id'}) + @Column({ name: 'course_id' }) + @JoinColumn({ name: 'course_id' }) @ManyToOne(() => CourseModel) courseId: number @@ -46,4 +47,4 @@ export default class CategoryModel { @DeleteDateColumn({ name: 'deleted_at' }) deletedAt?: Date -} \ No newline at end of file +} diff --git a/devU-api/src/entities/category/category.router.ts b/devU-api/src/entities/category/category.router.ts index 6a848eea..d5b3375e 100644 --- a/devU-api/src/entities/category/category.router.ts +++ b/devU-api/src/entities/category/category.router.ts @@ -2,13 +2,12 @@ import express from 'express' import validator from './category.validator' import { asInt } from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import { isAuthorized } from '../../authorization/authorization.middleware' import CategoryController from './category.controller' const Router = express.Router() - /** * @swagger * /course/:courseId/categories/{id}: @@ -64,7 +63,7 @@ Router.get('/', isAuthorized('enrolled'), CategoryController.getByCourse) * application/x-www-form-urlencoded: * schema: * $ref: '#/components/schemas/Category' - */ + */ Router.post('/', isAuthorized('courseEdit'), validator, CategoryController.post) /** @@ -107,7 +106,7 @@ Router.put('/:id', isAuthorized('courseEdit'), asInt(), validator, CategoryContr * required: true * schema: * type: integer - */ + */ Router.delete('/:id', isAuthorized('courseEdit'), asInt(), CategoryController._delete) -export default Router \ No newline at end of file +export default Router diff --git a/devU-api/src/entities/category/category.serializer.ts b/devU-api/src/entities/category/category.serializer.ts index b9954eee..ca90a267 100644 --- a/devU-api/src/entities/category/category.serializer.ts +++ b/devU-api/src/entities/category/category.serializer.ts @@ -10,4 +10,4 @@ export function serialize(category: CategoryModel): Category { createdAt: category.createdAt.toISOString(), updatedAt: category.updatedAt.toISOString(), } -} \ No newline at end of file +} diff --git a/devU-api/src/entities/category/category.service.ts b/devU-api/src/entities/category/category.service.ts index 4a8200eb..219b6c6b 100644 --- a/devU-api/src/entities/category/category.service.ts +++ b/devU-api/src/entities/category/category.service.ts @@ -28,7 +28,8 @@ export async function list() { return await connect().find({ deletedAt: IsNull() }) } -export async function listByCourse(courseId: number) { // TODO? +export async function listByCourse(courseId: number) { + // TODO? return await connect().find({ courseId, deletedAt: IsNull() }) } @@ -39,4 +40,4 @@ export default { _delete, list, listByCourse, -} \ No newline at end of file +} diff --git a/devU-api/src/entities/category/category.validator.ts b/devU-api/src/entities/category/category.validator.ts index f8ee8cae..a44202e2 100644 --- a/devU-api/src/entities/category/category.validator.ts +++ b/devU-api/src/entities/category/category.validator.ts @@ -7,4 +7,4 @@ const courseId = check('courseId').isNumeric() const validator = [name, courseId, validate] -export default validator \ No newline at end of file +export default validator diff --git a/devU-api/src/entities/category/tests/category.controller.test.ts b/devU-api/src/entities/category/tests/category.controller.test.ts index 47259121..e32959ec 100644 --- a/devU-api/src/entities/category/tests/category.controller.test.ts +++ b/devU-api/src/entities/category/tests/category.controller.test.ts @@ -205,4 +205,4 @@ describe('CategoryController', () => { test('Next called with expected error', () => expect(next).toBeCalledWith(expectedError)) }) }) -}) \ No newline at end of file +}) diff --git a/devU-api/src/entities/category/tests/category.serializer.test.ts b/devU-api/src/entities/category/tests/category.serializer.test.ts index 1b222146..616d56ba 100644 --- a/devU-api/src/entities/category/tests/category.serializer.test.ts +++ b/devU-api/src/entities/category/tests/category.serializer.test.ts @@ -35,4 +35,4 @@ describe('Category Serializer', () => { expect(expectedResult.updatedAt).toEqual(mockCategory.updatedAt.toISOString()) }) }) -}) \ No newline at end of file +}) diff --git a/devU-api/src/entities/categoryScore/categoryScore.controller.ts b/devU-api/src/entities/categoryScore/categoryScore.controller.ts index 15c900a6..d528f4ca 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.controller.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.controller.ts @@ -7,77 +7,77 @@ import { NotFound, Updated } from '../../utils/apiResponse.utils' import { serialize } from './categoryScore.serializer' export async function get(req: Request, res: Response, next: NextFunction) { - try { - const categoryScores = await CategoryScoreService.list() - const response = categoryScores.map(serialize) - - res.status(200).json(response) - } catch (err) { - next(err) - } + try { + const categoryScores = await CategoryScoreService.list() + const response = categoryScores.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function getByCourse(req: Request, res: Response, next: NextFunction) { - try { - const courseId = parseInt(req.params.courseId) - const categoryScores = await CategoryScoreService.listByCourse(courseId) - const response = categoryScores.map(serialize) - - res.status(200).json(response) - } catch (err) { - next(err) - } + try { + const courseId = parseInt(req.params.courseId) + const categoryScores = await CategoryScoreService.listByCourse(courseId) + const response = categoryScores.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function detail(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const categoryScore = await CategoryScoreService.retrieve(id) + try { + const id = parseInt(req.params.id) + const categoryScore = await CategoryScoreService.retrieve(id) - if (!categoryScore) return res.status(404).json(NotFound) + if (!categoryScore) return res.status(404).json(NotFound) - const response = serialize(categoryScore) + const response = serialize(categoryScore) - res.status(200).json(response) - } catch (err) { - next(err) - } + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function post(req: Request, res: Response, next: NextFunction) { - try { - const categoryScore = await CategoryScoreService.create(req.body) - const response = serialize(categoryScore) - - res.status(201).json(response) - } catch (err) { - next(err) - } + try { + const categoryScore = await CategoryScoreService.create(req.body) + const response = serialize(categoryScore) + + res.status(201).json(response) + } catch (err) { + next(err) + } } export async function put(req: Request, res: Response, next: NextFunction) { - try { - req.body.id = parseInt(req.params.id) - const results = await CategoryScoreService.update(req.body) + try { + req.body.id = parseInt(req.params.id) + const results = await CategoryScoreService.update(req.body) - if (!results.affected) return res.status(404).json(NotFound) + if (!results.affected) return res.status(404).json(NotFound) - res.status(200).json(Updated) - } catch (err) { - next(err) - } + res.status(200).json(Updated) + } catch (err) { + next(err) + } } export async function _delete(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const results = await CategoryScoreService._delete(id) + try { + const id = parseInt(req.params.id) + const results = await CategoryScoreService._delete(id) - if (!results.affected) return res.status(404).json(NotFound) + if (!results.affected) return res.status(404).json(NotFound) - res.status(204).send() - } catch (err) { - next(err) - } + res.status(204).send() + } catch (err) { + next(err) + } } export default { get, getByCourse, detail, post, put, _delete } diff --git a/devU-api/src/entities/categoryScore/categoryScore.model.ts b/devU-api/src/entities/categoryScore/categoryScore.model.ts index 807bbdb3..16cd01c7 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.model.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.model.ts @@ -1,12 +1,12 @@ import { - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, - DeleteDateColumn, - Column, - JoinColumn, - ManyToOne, - Entity, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, + Column, + JoinColumn, + ManyToOne, + Entity, } from 'typeorm' import CategoryModel from '../category/category.model' @@ -15,54 +15,53 @@ import UserModel from '../user/user.model' @Entity('category_score') export default class CategoryScoreModel { - /** - * @swagger - * tags: - * - name: CategoryScores - * description: Route is currently non-functional, TS2305 error (Issue #34) - * components: - * schemas: - * CategoryScore: - * type: object - * required: [category, courseId, userId, score] - * properties: - * category: - * type: string - * courseId: - * type: integer - * userId: - * type: integer - * score: - * type: number - */ - @PrimaryGeneratedColumn() - id: number + /** + * @swagger + * tags: + * - name: CategoryScores + * description: Route is currently non-functional, TS2305 error (Issue #34) + * components: + * schemas: + * CategoryScore: + * type: object + * required: [category, courseId, userId, score] + * properties: + * category: + * type: string + * courseId: + * type: integer + * userId: + * type: integer + * score: + * type: number + */ + @PrimaryGeneratedColumn() + id: number - @CreateDateColumn({name: 'created_at'}) - createdAt: Date + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date - @UpdateDateColumn({name: 'updated_at'}) - updatedAt: Date + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date - @DeleteDateColumn({name: 'deleted_at'}) - deletedAt?: Date + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date - @Column({name: 'course_id'}) - @JoinColumn({name: 'course_id'}) - @ManyToOne(() => CourseModel) - courseId: number + @Column({ name: 'course_id' }) + @JoinColumn({ name: 'course_id' }) + @ManyToOne(() => CourseModel) + courseId: number - @Column({name: 'user_id'}) - @JoinColumn({name: 'user_id'}) - @ManyToOne(() => UserModel) - userId: number + @Column({ name: 'user_id' }) + @JoinColumn({ name: 'user_id' }) + @ManyToOne(() => UserModel) + userId: number - @Column({name: 'category_id'}) - @JoinColumn({name: 'category_id'}) - @ManyToOne(() => CategoryModel) - categoryId: number + @Column({ name: 'category_id' }) + @JoinColumn({ name: 'category_id' }) + @ManyToOne(() => CategoryModel) + categoryId: number - @Column({name: 'score', type: 'float', nullable: true}) - score: number - -} \ No newline at end of file + @Column({ name: 'score', type: 'float', nullable: true }) + score: number +} diff --git a/devU-api/src/entities/categoryScore/categoryScore.router.ts b/devU-api/src/entities/categoryScore/categoryScore.router.ts index 1677a290..6abdfdec 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.router.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.router.ts @@ -4,7 +4,7 @@ import express from 'express' //validators import validator from './categoryScore.validator' import { asInt } from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import { isAuthorized } from '../../authorization/authorization.middleware' //Controller import CategoryScoreController from './categoryScore.controller' @@ -21,10 +21,9 @@ const Router = express.Router() * responses: * '200': * description: OK - */ + */ Router.get('/', isAuthorized('scoresViewAll'), CategoryScoreController.getByCourse) - /** * @swagger * /course/:courseId/category-score/{id}: @@ -106,4 +105,4 @@ Router.put('/:id', isAuthorized('scoresEditAll'), validator, asInt(), CategorySc */ Router.delete('/:id', isAuthorized('scoresEditAll'), asInt(), CategoryScoreController._delete) -export default Router \ No newline at end of file +export default Router diff --git a/devU-api/src/entities/categoryScore/categoryScore.serializer.ts b/devU-api/src/entities/categoryScore/categoryScore.serializer.ts index f5eea3fc..0780f87e 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.serializer.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.serializer.ts @@ -3,11 +3,11 @@ import { CategoryScore } from 'devu-shared-modules' import CategoryScoreModel from './categoryScore.model' export function serialize(categoryScore: CategoryScoreModel): CategoryScore { - return { - id: categoryScore.id, - userId: categoryScore.userId, - courseId: categoryScore.courseId, - categoryId: categoryScore.categoryId, - score: categoryScore.score - } -} \ No newline at end of file + return { + id: categoryScore.id, + userId: categoryScore.userId, + courseId: categoryScore.courseId, + categoryId: categoryScore.categoryId, + score: categoryScore.score, + } +} diff --git a/devU-api/src/entities/categoryScore/categoryScore.service.ts b/devU-api/src/entities/categoryScore/categoryScore.service.ts index 2f5df621..1196a3ba 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.service.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.service.ts @@ -1,49 +1,48 @@ -import {getRepository, IsNull} from 'typeorm' +import { getRepository, IsNull } from 'typeorm' import CategoryScoreModel from './categoryScore.model' -import {CategoryScore} from 'devu-shared-modules' +import { CategoryScore } from 'devu-shared-modules' const connect = () => getRepository(CategoryScoreModel) export async function create(categoryScore: CategoryScore) { - return await connect().save(categoryScore) + return await connect().save(categoryScore) } export async function update(categoryScore: CategoryScore) { - const {id, courseId, userId, categoryId, score} = categoryScore + const { id, courseId, userId, categoryId, score } = categoryScore - if (!id) throw new Error('Missing Id') + if (!id) throw new Error('Missing Id') - return await connect().update(id, {courseId, userId, categoryId, score}) + return await connect().update(id, { courseId, userId, categoryId, score }) } export async function _delete(id: number) { - return await connect().softDelete({id, deletedAt: IsNull()}) + return await connect().softDelete({ id, deletedAt: IsNull() }) } export async function retrieve(id: number) { - return await connect().findOne({id, deletedAt: IsNull()}) + return await connect().findOne({ id, deletedAt: IsNull() }) } // Retrieve all the categoryScores linked to a particular category (TODO: This endpoint doesn't have a path) // export async function listByCategory(categoryId: number) { // return await connect().find({ categoryId: categoryId, deletedAt: IsNull() }) - export async function list() { - return await connect().find({deletedAt: IsNull()}) + return await connect().find({ deletedAt: IsNull() }) } export async function listByCourse(courseId: number) { - return await connect().find({courseId, deletedAt: IsNull()}) + return await connect().find({ courseId, deletedAt: IsNull() }) } export default { - create, - retrieve, - update, - _delete, - listByCourse, - list, + create, + retrieve, + update, + _delete, + listByCourse, + list, } diff --git a/devU-api/src/entities/categoryScore/tests/categoryScore.serializer.test.ts b/devU-api/src/entities/categoryScore/tests/categoryScore.serializer.test.ts index 48e852bf..6bb5b683 100644 --- a/devU-api/src/entities/categoryScore/tests/categoryScore.serializer.test.ts +++ b/devU-api/src/entities/categoryScore/tests/categoryScore.serializer.test.ts @@ -8,13 +8,13 @@ let mockCategory: CategoryScoreModel describe('Category Serializer', () => { beforeEach(() => { - mockCategory = Testing.generateTypeOrm(CategoryScoreModel) - - mockCategory.id = 10 - mockCategory.courseId = 123 - mockCategory.userId = 50 - mockCategory.categoryId = 1 - mockCategory.score = 100 + mockCategory = Testing.generateTypeOrm(CategoryScoreModel) + + mockCategory.id = 10 + mockCategory.courseId = 123 + mockCategory.userId = 50 + mockCategory.categoryId = 1 + mockCategory.score = 100 }) describe('Serializing category', () => { diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts index fa60b5ce..81bb1664 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts @@ -42,16 +42,16 @@ export async function getByAssignment(req: Request, res: Response, next: NextFun } /* - * for the post method, I changed how the upload is handled. I am now using fields instead of - * single(for the purpose of uploading grader file and makefile). But I set the makefile to be - * optional, since it is not required. I also added the makefile to the create method in the - * ContainerAutoGraderService. -*/ + * for the post method, I changed how the upload is handled. I am now using fields instead of + * single(for the purpose of uploading grader file and makefile). But I set the makefile to be + * optional, since it is not required. I also added the makefile to the create method in the + * ContainerAutoGraderService. + */ export async function post(req: Request, res: Response, next: NextFunction) { try { if (!req.currentUser?.userId) return res.status(400).json(new GenericResponse('Request requires auth')) if (!req.files || !('graderFile' in req.files)) { - return res.status(400).json(new GenericResponse('Container Auto Grader requires file upload for grader')); + return res.status(400).json(new GenericResponse('Container Auto Grader requires file upload for grader')) } const graderFile = req.files['graderFile'][0] const makefile = req.files['makefileFile']?.[0] ?? null @@ -67,13 +67,11 @@ export async function post(req: Request, res: Response, next: NextFunction) { } } - - export async function put(req: Request, res: Response, next: NextFunction) { try { if (!req.currentUser?.userId) return res.status(400).json(new GenericResponse('Request requires auth')) - if (req.files && (!('graderFile' in req.files) && !('makefileFile' in req.files))) { - return res.status(400).json(new GenericResponse('Uploaded files must be grader or makefile')); + if (req.files && !('graderFile' in req.files) && !('makefileFile' in req.files)) { + return res.status(400).json(new GenericResponse('Uploaded files must be grader or makefile')) } const graderFile = req.files?.['graderFile']?.[0] ?? null @@ -86,9 +84,9 @@ export async function put(req: Request, res: Response, next: NextFunction) { if (!results.affected) return res.status(404).json(NotFound) res.status(200).json(Updated) - }catch (err) { + } catch (err) { next(err) - } + } } export async function _delete(req: Request, res: Response, next: NextFunction) { @@ -111,4 +109,4 @@ export default { put, _delete, getByAssignment, -} \ No newline at end of file +} diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts index 0f012bf3..9405fbdd 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.model.ts @@ -1,12 +1,12 @@ import { - JoinColumn, - ManyToOne, - Entity, - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, - DeleteDateColumn, + JoinColumn, + ManyToOne, + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, } from 'typeorm' import AssignmentModel from '../assignment/assignment.model' @@ -17,7 +17,7 @@ export default class ContainerAutoGraderModel { * @swagger * tags: * - name: ContainerAutoGraders - * description: + * description: * components: * schemas: * ContainerAutoGrader: @@ -30,7 +30,7 @@ export default class ContainerAutoGraderModel { * type: string * timeout: * type: integer - * description: Must be a positive integer + * description: Must be a positive integer * graderFile: * type: string * format: binary @@ -39,34 +39,33 @@ export default class ContainerAutoGraderModel { * format: binary */ - @PrimaryGeneratedColumn() - id: number + @PrimaryGeneratedColumn() + id: number - @CreateDateColumn({ name: 'created_at' }) - createdAt: Date + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date - @UpdateDateColumn({ name: 'updated_at' }) - updatedAt: Date + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date - @DeleteDateColumn({ name: 'deleted_at' }) - deletedAt?: Date + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date - @Column({ name: 'assignment_id' }) - @JoinColumn({ name: 'assignment_id' }) - @ManyToOne(() => AssignmentModel) - assignmentId: number + @Column({ name: 'assignment_id' }) + @JoinColumn({ name: 'assignment_id' }) + @ManyToOne(() => AssignmentModel) + assignmentId: number - @Column({ name: 'grader_filename', length: 128 }) - graderFile: string + @Column({ name: 'grader_filename', length: 128 }) + graderFile: string - @Column({ name: 'makefile_filename', type: 'text' , nullable: true }) - makefileFile: string | null + @Column({ name: 'makefile_filename', type: 'text', nullable: true }) + makefileFile: string | null - @Column({ name: 'autograding_image' }) - autogradingImage: string + @Column({ name: 'autograding_image' }) + autogradingImage: string - // timeout should be positive integer - @Column({ name: 'timeout'}) - timeout: number - -} \ No newline at end of file + // timeout should be positive integer + @Column({ name: 'timeout' }) + timeout: number +} diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts index 12f55a03..60fc151a 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts @@ -1,14 +1,14 @@ -import express from 'express'; -import multer from 'multer'; +import express from 'express' +import multer from 'multer' -import validator from './containerAutoGrader.validator'; -import { asInt } from '../../middleware/validator/generic.validator'; -import {isAuthorized} from "../../authorization/authorization.middleware"; +import validator from './containerAutoGrader.validator' +import { asInt } from '../../middleware/validator/generic.validator' +import { isAuthorized } from '../../authorization/authorization.middleware' -import ContainerAutoGraderController from './containerAutoGrader.controller'; +import ContainerAutoGraderController from './containerAutoGrader.controller' -const Router = express.Router(); -const upload = multer(); +const Router = express.Router() +const upload = multer() /** * @swagger @@ -21,7 +21,7 @@ const upload = multer(); * '200': * description: OK */ -Router.get('/', isAuthorized('assignmentViewAll'), ContainerAutoGraderController.get); +Router.get('/', isAuthorized('assignmentViewAll'), ContainerAutoGraderController.get) /** * @swagger @@ -40,7 +40,7 @@ Router.get('/', isAuthorized('assignmentViewAll'), ContainerAutoGraderController * schema: * type: integer */ -Router.get('/:id',isAuthorized('assignmentViewAll'), asInt(), ContainerAutoGraderController.detail); +Router.get('/:id', isAuthorized('assignmentViewAll'), asInt(), ContainerAutoGraderController.detail) /** * @swagger @@ -58,7 +58,13 @@ Router.get('/:id',isAuthorized('assignmentViewAll'), asInt(), ContainerAutoGrad * schema: * $ref: '#/components/schemas/ContainerAutoGrader' */ -Router.post('/', isAuthorized('assignmentEditAll'), upload.fields([{name: 'graderFile'},{name: 'makefileFile'}]), validator, ContainerAutoGraderController.post); +Router.post( + '/', + isAuthorized('assignmentEditAll'), + upload.fields([{ name: 'graderFile' }, { name: 'makefileFile' }]), + validator, + ContainerAutoGraderController.post +) /** * @swagger @@ -82,7 +88,14 @@ Router.post('/', isAuthorized('assignmentEditAll'), upload.fields([{name: 'grade * schema: * $ref: '#/components/schemas/ContainerAutoGrader' */ -Router.put('/:id', isAuthorized('assignmentEditAll'), asInt(), upload.fields([{name: 'graderFile'},{name: 'makefileFile'}]), validator, ContainerAutoGraderController.put); +Router.put( + '/:id', + isAuthorized('assignmentEditAll'), + asInt(), + upload.fields([{ name: 'graderFile' }, { name: 'makefileFile' }]), + validator, + ContainerAutoGraderController.put +) /** * @swagger @@ -101,6 +114,6 @@ Router.put('/:id', isAuthorized('assignmentEditAll'), asInt(), upload.fields([{n * schema: * type: integer */ -Router.delete('/:id', isAuthorized('assignmentEditAll'), asInt(), ContainerAutoGraderController._delete); +Router.delete('/:id', isAuthorized('assignmentEditAll'), asInt(), ContainerAutoGraderController._delete) -export default Router; \ No newline at end of file +export default Router diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.serializer.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.serializer.ts index 417beb5e..856f2e27 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.serializer.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.serializer.ts @@ -3,14 +3,14 @@ import { ContainerAutoGrader } from 'devu-shared-modules' import ContainerAutoGraderModel from './containerAutoGrader.model' export function serialize(containerAutoGrader: ContainerAutoGraderModel): ContainerAutoGrader { - return { - id: containerAutoGrader.id, - assignmentId: containerAutoGrader.assignmentId, - graderFile: containerAutoGrader.graderFile, - makefileFile: containerAutoGrader.makefileFile, - autogradingImage: containerAutoGrader.autogradingImage, - timeout: containerAutoGrader.timeout, - createdAt: containerAutoGrader.createdAt.toISOString(), - updatedAt: containerAutoGrader.updatedAt.toISOString(), - } -} \ No newline at end of file + return { + id: containerAutoGrader.id, + assignmentId: containerAutoGrader.assignmentId, + graderFile: containerAutoGrader.graderFile, + makefileFile: containerAutoGrader.makefileFile, + autogradingImage: containerAutoGrader.autogradingImage, + timeout: containerAutoGrader.timeout, + createdAt: containerAutoGrader.createdAt.toISOString(), + updatedAt: containerAutoGrader.updatedAt.toISOString(), + } +} diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index 314b496f..32c50cf0 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -3,117 +3,136 @@ import { getRepository, IsNull } from 'typeorm' import { ContainerAutoGrader, FileUpload } from 'devu-shared-modules' import ContainerAutoGraderModel from './containerAutoGrader.model' -import FileModel from "../../fileUpload/fileUpload.model"; +import FileModel from '../../fileUpload/fileUpload.model' import { uploadFile, downloadFile } from '../../fileStorage' -import {generateFilename} from "../../utils/fileUpload.utils"; +import { generateFilename } from '../../utils/fileUpload.utils' const connect = () => getRepository(ContainerAutoGraderModel) const fileConn = () => getRepository(FileModel) -async function filesUpload(bucket: string, file: Express.Multer.File, containerAutoGrader: ContainerAutoGrader,filename: string, userId: number) { - const Etag: string = await uploadFile(bucket, file,filename) - const assignmentId = containerAutoGrader.assignmentId - - const fileModel: FileUpload = { - etags: Etag, - fieldName: bucket, - originalName: file.originalname, - filename: filename, - assignmentId: assignmentId, - } - //TODO: This is a temporary fix to get the function to pass. CourseId should be modified in the future - fileModel.courseId = 1 - fileModel.userId = userId - - await fileConn().save(fileModel) - - return Etag +async function filesUpload( + bucket: string, + file: Express.Multer.File, + containerAutoGrader: ContainerAutoGrader, + filename: string, + userId: number +) { + const Etag: string = await uploadFile(bucket, file, filename) + const assignmentId = containerAutoGrader.assignmentId + + const fileModel: FileUpload = { + etags: Etag, + fieldName: bucket, + originalName: file.originalname, + filename: filename, + assignmentId: assignmentId, + } + //TODO: This is a temporary fix to get the function to pass. CourseId should be modified in the future + fileModel.courseId = 1 + fileModel.userId = userId + + await fileConn().save(fileModel) + + return Etag } +export async function create( + containerAutoGrader: ContainerAutoGrader, + graderInputFile: Express.Multer.File, + makefileInputFile: Express.Multer.File | null = null, + userId: number +) { + const existingContainerAutoGrader = await connect().findOne({ + assignmentId: containerAutoGrader.assignmentId, + deletedAt: IsNull(), + }) + if (existingContainerAutoGrader) + throw new Error( + 'Container Auto Grader already exists for this assignment, please update instead of creating a new one' + ) + const bucket: string = 'graders' + const filename: string = generateFilename(graderInputFile.originalname, userId) + await filesUpload(bucket, graderInputFile, containerAutoGrader, filename, userId) + containerAutoGrader.graderFile = filename + + if (makefileInputFile) { + const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) + await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) + containerAutoGrader.makefileFile = makefileFilename + } + + const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader + return await connect().save({ id, assignmentId, graderFile, makefileFile, autogradingImage, timeout }) +} - -export async function create(containerAutoGrader: ContainerAutoGrader, graderInputFile: Express.Multer.File, makefileInputFile: Express.Multer.File | null = null, userId: number) { - const existingContainerAutoGrader = await connect().findOne({ assignmentId: containerAutoGrader.assignmentId, deletedAt: IsNull() }) - if (existingContainerAutoGrader) throw new Error('Container Auto Grader already exists for this assignment, please update instead of creating a new one') +export async function update( + containerAutoGrader: ContainerAutoGrader, + graderInputFile: Express.Multer.File | null = null, + makefileInputFile: Express.Multer.File | null = null, + userId: number +) { + if (!containerAutoGrader.id) throw new Error('Missing Id') + if (graderInputFile) { const bucket: string = 'graders' const filename: string = generateFilename(graderInputFile.originalname, userId) await filesUpload(bucket, graderInputFile, containerAutoGrader, filename, userId) containerAutoGrader.graderFile = filename + } - if (makefileInputFile) { - const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) - await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) - containerAutoGrader.makefileFile = makefileFilename - } + if (makefileInputFile) { + const bucket: string = 'makefiles' + const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) + await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) + containerAutoGrader.makefileFile = makefileFilename + } - const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader - return await connect().save({ id, assignmentId, graderFile, makefileFile, autogradingImage, timeout }) -} - -export async function update(containerAutoGrader: ContainerAutoGrader, graderInputFile: Express.Multer.File | null = null, makefileInputFile: Express.Multer.File | null = null, userId: number) { - if (!containerAutoGrader.id) throw new Error('Missing Id') - if (graderInputFile) { - const bucket: string = 'graders' - const filename: string = generateFilename(graderInputFile.originalname, userId) - await filesUpload(bucket, graderInputFile, containerAutoGrader, filename, userId) - containerAutoGrader.graderFile = filename - } - - if (makefileInputFile) { - const bucket: string = 'makefiles' - const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) - await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) - containerAutoGrader.makefileFile = makefileFilename - } - - const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader - return await connect().update(id, { assignmentId, graderFile, makefileFile, autogradingImage, timeout }) + const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader + return await connect().update(id, { assignmentId, graderFile, makefileFile, autogradingImage, timeout }) } export async function _delete(id: number) { - return await connect().softDelete({ id, deletedAt: IsNull() }) + return await connect().softDelete({ id, deletedAt: IsNull() }) } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOne({ id, deletedAt: IsNull() }) } export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().find({ deletedAt: IsNull() }) } //The grader has not changed to the new function, so the fake function keep here for now to avoid error //But need to be deleted when the grader entity changed to the getGraderByAssignmentId export async function listByAssignmentId(assignmentId: number) { - if (!assignmentId) throw new Error('Missing AssignmentId') - return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) + if (!assignmentId) throw new Error('Missing AssignmentId') + return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) } -export async function getGraderByAssignmentId(assignmentId: number){ - const containerAutoGraders = await connect().findOne({ assignmentId: assignmentId, deletedAt: IsNull() }) - if (!containerAutoGraders) throw new Error('No containerAutoGraders found') +export async function getGraderByAssignmentId(assignmentId: number) { + const containerAutoGraders = await connect().findOne({ assignmentId: assignmentId, deletedAt: IsNull() }) + if (!containerAutoGraders) throw new Error('No containerAutoGraders found') - const { graderFile, makefileFile, autogradingImage, timeout } = containerAutoGraders - const graderData = await downloadFile('graders', graderFile) - let makefileData; + const { graderFile, makefileFile, autogradingImage, timeout } = containerAutoGraders + const graderData = await downloadFile('graders', graderFile) + let makefileData - if (makefileFile){ - makefileData = await downloadFile('makefiles', makefileFile) - }else{ - makefileData = await downloadFile('makefiles', 'defaultMakefile') // Put actual default makefile name here - } + if (makefileFile) { + makefileData = await downloadFile('makefiles', makefileFile) + } else { + makefileData = await downloadFile('makefiles', 'defaultMakefile') // Put actual default makefile name here + } - return {graderData, makefileData, autogradingImage, timeout} + return { graderData, makefileData, autogradingImage, timeout } } - export default { - create, - retrieve, - update, - _delete, - list, - getGraderByAssignmentId, - listByAssignmentId, -} \ No newline at end of file + create, + retrieve, + update, + _delete, + list, + getGraderByAssignmentId, + listByAssignmentId, +} diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts index 638dcdde..5deab6cc 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts @@ -2,35 +2,37 @@ import { check } from 'express-validator' import validate from '../../middleware/validator/generic.validator' - - const assignmentId = check('assignmentId').isNumeric() -const graderFile = check('graderFile').optional({ nullable: true }).custom(({ req }) => { +const graderFile = check('graderFile') + .optional({ nullable: true }) + .custom(({ req }) => { const file = req.files['grader'] if (file !== null) { - if (file.size <= 0) { - throw new Error('File is empty') - } + if (file.size <= 0) { + throw new Error('File is empty') + } } -}) + }) -const makefileFile = check('makefileFile').optional({ nullable: true }).custom(({ req }) => { +const makefileFile = check('makefileFile') + .optional({ nullable: true }) + .custom(({ req }) => { const file = req.files['makefile'] if (file !== null) { - if (file.size <= 0) { - throw new Error('File is empty') - } + if (file.size <= 0) { + throw new Error('File is empty') + } } -}) - + }) const autogradingImage = check('autogradingImage').isString() -const timeout = check('timeout').isNumeric() -.custom((value) => value > 0) -.withMessage('Timeout should be a positive integer') +const timeout = check('timeout') + .isNumeric() + .custom(value => value > 0) + .withMessage('Timeout should be a positive integer') const validator = [assignmentId, graderFile, makefileFile, autogradingImage, timeout, validate] -export default validator \ No newline at end of file +export default validator diff --git a/devU-api/src/entities/containerAutoGrader/test/containerAutoGrade.controller.test.ts b/devU-api/src/entities/containerAutoGrader/test/containerAutoGrade.controller.test.ts index 5a86a04f..5d6bdd24 100644 --- a/devU-api/src/entities/containerAutoGrader/test/containerAutoGrade.controller.test.ts +++ b/devU-api/src/entities/containerAutoGrader/test/containerAutoGrade.controller.test.ts @@ -1,18 +1,17 @@ -import { UpdateResult } from "typeorm" +import { UpdateResult } from 'typeorm' import { ContainerAutoGrader } from 'devu-shared-modules' -import controller from "../containerAutoGrader.controller" +import controller from '../containerAutoGrader.controller' -import ContainerAutoGraderModel from "../containerAutoGrader.model" +import ContainerAutoGraderModel from '../containerAutoGrader.model' -import ContainerAutoGraderService from "../containerAutoGrader.service" +import ContainerAutoGraderService from '../containerAutoGrader.service' -import { serialize } from "../containerAutoGrader.serializer" - -import Testing from "../../../utils/testing.utils" -import { GenericResponse, NotFound, Updated } from "../../../utils/apiResponse.utils" +import { serialize } from '../containerAutoGrader.serializer' +import Testing from '../../../utils/testing.utils' +import { GenericResponse, NotFound, Updated } from '../../../utils/apiResponse.utils' // Testing Globals let req: any @@ -27,198 +26,193 @@ let expectedError: Error let expectedDbResult: UpdateResult -describe("ContainerAutoGraderController", () => { - beforeEach(() => { - req = Testing.fakeRequest() - res = Testing.fakeResponse() - next = Testing.fakeNext() - - mockedContainerAutoGraders = Testing.generateTypeOrmArray(ContainerAutoGraderModel, 3) - mockedContainerAutoGrader = Testing.generateTypeOrm(ContainerAutoGraderModel) - - expectedResults = mockedContainerAutoGraders.map(serialize) - expectedResult = serialize(mockedContainerAutoGrader) - expectedError = new Error("Expected Error") - - expectedDbResult = {} as UpdateResult +describe('ContainerAutoGraderController', () => { + beforeEach(() => { + req = Testing.fakeRequest() + res = Testing.fakeResponse() + next = Testing.fakeNext() + + mockedContainerAutoGraders = Testing.generateTypeOrmArray(ContainerAutoGraderModel, 3) + mockedContainerAutoGrader = Testing.generateTypeOrm(ContainerAutoGraderModel) + + expectedResults = mockedContainerAutoGraders.map(serialize) + expectedResult = serialize(mockedContainerAutoGrader) + expectedError = new Error('Expected Error') + + expectedDbResult = {} as UpdateResult + }) + + describe('GET - /container-auto-graders', () => { + describe('200 - Ok', () => { + beforeEach(async () => { + ContainerAutoGraderService.list = jest + .fn() + .mockImplementation(() => Promise.resolve(mockedContainerAutoGraders)) + await controller.get(req, res, next) // what we're testing + }) + + test('Returns list of containerAutoGraders', () => expect(res.json).toBeCalledWith(expectedResults)) + test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) }) + describe('400 - Bad request', () => { + test('Next called with expected error', async () => { + ContainerAutoGraderService.list = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + try { + await controller.get(req, res, next) - describe("GET - /container-auto-graders", () => { - describe("200 - Ok", () => { - beforeEach(async () => { - ContainerAutoGraderService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedContainerAutoGraders)) - await controller.get(req, res, next) // what we're testing - }) - - test("Returns list of containerAutoGraders", () => expect(res.json).toBeCalledWith(expectedResults)) - test("Status code is 200", () => expect(res.status).toBeCalledWith(200)) - }) - - describe("400 - Bad request", () => { - test("Next called with expected error", async () => { - ContainerAutoGraderService.list = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - - try { - await controller.get(req, res, next) - - fail("Expected test to throw") - } catch { - expect(next).toBeCalledWith(expectedError) - } - }) - }) + fail('Expected test to throw') + } catch { + expect(next).toBeCalledWith(expectedError) + } + }) + }) + }) + + describe('GET - /container-auto-graders/:id', () => { + describe('200 - Ok', () => { + beforeEach(async () => { + ContainerAutoGraderService.retrieve = jest + .fn() + .mockImplementation(() => Promise.resolve(mockedContainerAutoGrader)) + await controller.detail(req, res, next) + }) + + test('Returns containerAutoGrader', () => expect(res.json).toBeCalledWith(expectedResult)) + test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) }) + describe('404 - Not found', () => { + beforeEach(async () => { + ContainerAutoGraderService.retrieve = jest.fn().mockImplementation(() => Promise.resolve()) + await controller.detail(req, res, next) + }) - - describe("GET - /container-auto-graders/:id", () => { - describe("200 - Ok", () => { - beforeEach(async () => { - ContainerAutoGraderService.retrieve = jest.fn().mockImplementation(() => Promise.resolve(mockedContainerAutoGrader)) - await controller.detail(req, res, next) - }) - - test("Returns containerAutoGrader", () => expect(res.json).toBeCalledWith(expectedResult)) - test("Status code is 200", () => expect(res.status).toBeCalledWith(200)) - }) - - describe("404 - Not found", () => { - beforeEach(async () => { - ContainerAutoGraderService.retrieve = jest.fn().mockImplementation(() => Promise.resolve()) - await controller.detail(req, res, next) - }) - - test("Returns notfound", () => expect(res.json).toBeCalledWith(NotFound)) - test("Status code is 404", () => expect(res.status).toBeCalledWith(404)) - test("Next is not called", () => expect(next).toBeCalledTimes(0)) - }) - - describe("400 - Bad request", () => { - test("Next called with expected error", async () => { - ContainerAutoGraderService.retrieve = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - - try { - await controller.detail(req, res, next) - - fail("Expected test to throw") - } catch { - expect(next).toBeCalledWith(expectedError) - } - }) - }) + test('Returns notfound', () => expect(res.json).toBeCalledWith(NotFound)) + test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) + test('Next is not called', () => expect(next).toBeCalledTimes(0)) }) + describe('400 - Bad request', () => { + test('Next called with expected error', async () => { + ContainerAutoGraderService.retrieve = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + try { + await controller.detail(req, res, next) - describe("POST - /container-auto-graders", () => { - describe("201 - Created", () => { - beforeEach(async () => { - ContainerAutoGraderService.create = jest.fn().mockImplementation(() => Promise.resolve(mockedContainerAutoGrader)) - req.files = { graderFile: { name: "graderFile"}, makefileFile: { name: "makefileFile"} } - await controller.post(req, res, next) - }) - - test("Returns containerAutoGrader", () => expect(res.json).toBeCalledWith(expectedResult)) - test("Status code is 201", () => expect(res.status).toBeCalledWith(201)) - }) + fail('Expected test to throw') + } catch { + expect(next).toBeCalledWith(expectedError) + } + }) + }) + }) + + describe('POST - /container-auto-graders', () => { + describe('201 - Created', () => { + beforeEach(async () => { + ContainerAutoGraderService.create = jest + .fn() + .mockImplementation(() => Promise.resolve(mockedContainerAutoGrader)) + req.files = { graderFile: { name: 'graderFile' }, makefileFile: { name: 'makefileFile' } } + await controller.post(req, res, next) + }) + + test('Returns containerAutoGrader', () => expect(res.json).toBeCalledWith(expectedResult)) + test('Status code is 201', () => expect(res.status).toBeCalledWith(201)) + }) - describe("400 - Bad request", () => { - beforeEach(async () => { - ContainerAutoGraderService.create = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - req.files = { graderFile: { name: "graderFile"}, makefileFile: { name: "makefileFile"} } + describe('400 - Bad request', () => { + beforeEach(async () => { + ContainerAutoGraderService.create = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + req.files = { graderFile: { name: 'graderFile' }, makefileFile: { name: 'makefileFile' } } + + try { + await controller.post(req, res, next) + fail('Expected test to throw') + } catch { + // continue to tests + } + }) + + test('Status code is 400', () => expect(res.status).toBeCalledWith(400)) + test('Responds with generic error', () => + expect(res.json).toBeCalledWith(new GenericResponse(expectedError.message))) + test('Next is not called', () => expect(next).toBeCalledTimes(0)) + }) + }) + + describe('PUT - /container-auto-graders/:id', () => { + describe('200 - OK', () => { + beforeEach(async () => { + expectedDbResult.affected = 1 + ContainerAutoGraderService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + req.files = { graderFile: { name: 'graderFile' }, makefileFile: { name: 'makefileFile' } } + await controller.put(req, res, next) + }) + + test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) + test('Returns updated', () => expect(res.json).toBeCalledWith(Updated)) + test('Next is not called', () => expect(next).toBeCalledTimes(0)) + }) - try { - await controller.post(req, res, next) - fail("Expected test to throw") - } catch { + describe('404 - Not found', () => { + beforeEach(async () => { + expectedDbResult.affected = 0 + ContainerAutoGraderService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + req.files = { graderFile: { name: 'graderFile' }, makefileFile: { name: 'makefileFile' } } + await controller.put(req, res, next) + }) + + test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) + test('Returns notfound', () => expect(res.json).toBeCalledWith(NotFound)) + test('Next is not called', () => expect(next).toBeCalledTimes(0)) + }) - // continue to tests - } - }) + describe('400 - Bad request', () => { + beforeEach(async () => { + ContainerAutoGraderService.update = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + req.files = { graderFile: { name: 'graderFile' }, makefileFile: { name: 'makefileFile' } } + await controller.put(req, res, next) + }) - test("Status code is 400", () => expect(res.status).toBeCalledWith(400)) - test("Responds with generic error", () => - expect(res.json).toBeCalledWith(new GenericResponse(expectedError.message))) - test("Next is not called", () => expect(next).toBeCalledTimes(0)) - }) + test('Next is called with error', () => expect(next).toBeCalledWith(expectedError)) + }) + }) + + describe('DELETE - /container-auto-graders/:id', () => { + describe('204 - No content', () => { + beforeEach(async () => { + expectedDbResult.affected = 1 + ContainerAutoGraderService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + await controller._delete(req, res, next) + }) + + test('Status code is 204', () => expect(res.status).toBeCalledWith(204)) + test('Next is not called', () => expect(next).toBeCalledTimes(0)) + test('Response to have no content', () => expect(res.send).toBeCalledWith()) }) + describe('404 - Not found', () => { + beforeEach(async () => { + expectedDbResult.affected = 0 + ContainerAutoGraderService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) + await controller._delete(req, res, next) + }) - - describe("PUT - /container-auto-graders/:id", () => { - describe("200 - OK", () => { - beforeEach(async () => { - expectedDbResult.affected = 1 - ContainerAutoGraderService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - req.files = { graderFile: { name: "graderFile"}, makefileFile: { name: "makefileFile"} } - await controller.put(req, res, next) - }) - - test("Status code is 200", () => expect(res.status).toBeCalledWith(200)) - test("Returns updated", () => expect(res.json).toBeCalledWith(Updated)) - test("Next is not called", () => expect(next).toBeCalledTimes(0)) - }) - - describe("404 - Not found", () => { - beforeEach(async () => { - expectedDbResult.affected = 0 - ContainerAutoGraderService.update = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - req.files = { graderFile: { name: "graderFile"}, makefileFile: { name: "makefileFile"} } - await controller.put(req, res, next) - }) - - test("Status code is 404", () => expect(res.status).toBeCalledWith(404)) - test("Returns notfound", () => expect(res.json).toBeCalledWith(NotFound)) - test("Next is not called", () => expect(next).toBeCalledTimes(0)) - }) - - describe("400 - Bad request", () => { - beforeEach(async () => { - ContainerAutoGraderService.update = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - req.files = { graderFile: { name: "graderFile"}, makefileFile: { name: "makefileFile"} } - await controller.put(req, res, next) - }) - - test("Next is called with error", () => expect(next).toBeCalledWith(expectedError)) - }) + test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) + test('Response to have no content', () => expect(res.json).toBeCalledWith(NotFound)) + test('Next is not called', () => expect(next).toBeCalledTimes(0)) }) + describe('400 - Bad request', () => { + beforeEach(async () => { + ContainerAutoGraderService._delete = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + await controller._delete(req, res, next) + }) - - describe("DELETE - /container-auto-graders/:id", () => { - describe("204 - No content", () => { - beforeEach(async () => { - expectedDbResult.affected = 1 - ContainerAutoGraderService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - await controller._delete(req, res, next) - }) - - test("Status code is 204", () => expect(res.status).toBeCalledWith(204)) - test("Next is not called", () => expect(next).toBeCalledTimes(0)) - test("Response to have no content", () => expect(res.send).toBeCalledWith()) - }) - - describe("404 - Not found", () => { - beforeEach(async () => { - expectedDbResult.affected = 0 - ContainerAutoGraderService._delete = jest.fn().mockImplementation(() => Promise.resolve(expectedDbResult)) - await controller._delete(req, res, next) - }) - - test("Status code is 404", () => expect(res.status).toBeCalledWith(404)) - test("Response to have no content", () => expect(res.json).toBeCalledWith(NotFound)) - test("Next is not called", () => expect(next).toBeCalledTimes(0)) - }) - - describe("400 - Bad request", () => { - beforeEach(async () => { - ContainerAutoGraderService._delete = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - await controller._delete(req, res, next) - }) - - test("Next is called with error", () => expect(next).toBeCalledWith(expectedError)) - }) + test('Next is called with error', () => expect(next).toBeCalledWith(expectedError)) }) + }) }) diff --git a/devU-api/src/entities/containerAutoGrader/test/containerAutoGrader.serializer.test.ts b/devU-api/src/entities/containerAutoGrader/test/containerAutoGrader.serializer.test.ts index 82f75038..ee281256 100644 --- a/devU-api/src/entities/containerAutoGrader/test/containerAutoGrader.serializer.test.ts +++ b/devU-api/src/entities/containerAutoGrader/test/containerAutoGrader.serializer.test.ts @@ -22,15 +22,15 @@ describe('ContainerAutoGrader Serializer', () => { describe('Serializing ContainerAutoGrader', () => { test('ContainerAutoGrader values exist in the response', () => { - const actualResult = serialize(mockContainerAutoGrader) - - expect(actualResult).toBeDefined() - expect(actualResult.id).toEqual(mockContainerAutoGrader.id) - expect(actualResult.assignmentId).toEqual(mockContainerAutoGrader.assignmentId) - expect(actualResult.graderFile).toEqual(mockContainerAutoGrader.graderFile) - expect(actualResult.makefileFile).toEqual(mockContainerAutoGrader.makefileFile) - expect(actualResult.autogradingImage).toEqual(mockContainerAutoGrader.autogradingImage) - expect(actualResult.timeout).toEqual(mockContainerAutoGrader.timeout) + const actualResult = serialize(mockContainerAutoGrader) + + expect(actualResult).toBeDefined() + expect(actualResult.id).toEqual(mockContainerAutoGrader.id) + expect(actualResult.assignmentId).toEqual(mockContainerAutoGrader.assignmentId) + expect(actualResult.graderFile).toEqual(mockContainerAutoGrader.graderFile) + expect(actualResult.makefileFile).toEqual(mockContainerAutoGrader.makefileFile) + expect(actualResult.autogradingImage).toEqual(mockContainerAutoGrader.autogradingImage) + expect(actualResult.timeout).toEqual(mockContainerAutoGrader.timeout) }) test('CreatedAt and ModifiedAt are ISO strings', () => { @@ -41,4 +41,4 @@ describe('ContainerAutoGrader Serializer', () => { expect(actualResult.createdAt).toEqual(mockContainerAutoGrader.createdAt.toISOString()) }) }) -}) \ No newline at end of file +}) diff --git a/devU-api/src/entities/course/course.controller.ts b/devU-api/src/entities/course/course.controller.ts index 0da98033..99617cb4 100644 --- a/devU-api/src/entities/course/course.controller.ts +++ b/devU-api/src/entities/course/course.controller.ts @@ -5,7 +5,7 @@ import CourseService from './course.service' import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' import { serialize } from './course.serializer' -import UserCourseService from "../userCourse/userCourse.service"; +import UserCourseService from '../userCourse/userCourse.service' export async function get(req: Request, res: Response, next: NextFunction) { try { @@ -60,12 +60,12 @@ export async function postAddInstructor(req: Request, res: Response, next: NextF const course = await CourseService.create(req.body) const response = serialize(course) - if(req.currentUser?.userId) { + if (req.currentUser?.userId) { await UserCourseService.create({ userId: req.currentUser?.userId, courseId: course.id, dropped: false, - role: 'instructor' + role: 'instructor', }) } diff --git a/devU-api/src/entities/course/course.model.ts b/devU-api/src/entities/course/course.model.ts index 5dbffff6..3ab6c5a7 100644 --- a/devU-api/src/entities/course/course.model.ts +++ b/devU-api/src/entities/course/course.model.ts @@ -6,7 +6,7 @@ export default class CourseModel { * @swagger * tags: * - name: Courses - * description: + * description: * components: * schemas: * Course: diff --git a/devU-api/src/entities/course/course.router.ts b/devU-api/src/entities/course/course.router.ts index 714f0114..7e4198be 100644 --- a/devU-api/src/entities/course/course.router.ts +++ b/devU-api/src/entities/course/course.router.ts @@ -4,7 +4,7 @@ import express from 'express' // Middleware import validator from './course.validator' import { asInt } from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import CourseController from './course.controller' @@ -26,7 +26,6 @@ const Router = express.Router() Router.get('/', CourseController.get) // TODO: Top-level authorization - /** * @swagger * /courses/user/{userId}: @@ -56,11 +55,10 @@ Router.get('/user/:userId', asInt('userId'), CourseController.getByUser) * in: path * required: true * schema: - * type: integer + * type: integer */ Router.get('/:courseId', isAuthorized('enrolled'), asInt('courseId'), CourseController.detail) - /** * @swagger * /courses: diff --git a/devU-api/src/entities/course/course.service.ts b/devU-api/src/entities/course/course.service.ts index 0008ab97..2d1dbbc9 100644 --- a/devU-api/src/entities/course/course.service.ts +++ b/devU-api/src/entities/course/course.service.ts @@ -5,8 +5,6 @@ import CourseModel from './course.model' import { Course } from 'devu-shared-modules' import { initializeMinio } from '../../fileStorage' - - const connect = () => getRepository(CourseModel) export async function create(course: Course) { diff --git a/devU-api/src/entities/courseScore/courseScore.controller.ts b/devU-api/src/entities/courseScore/courseScore.controller.ts index 661feb3c..a78918fa 100644 --- a/devU-api/src/entities/courseScore/courseScore.controller.ts +++ b/devU-api/src/entities/courseScore/courseScore.controller.ts @@ -7,65 +7,65 @@ import { NotFound, Updated } from '../../utils/apiResponse.utils' import { serialize } from './courseScore.serializer' export async function get(req: Request, res: Response, next: NextFunction) { - try { - const courseScores = await CourseScoreService.list() - const response = courseScores.map(serialize) - - res.status(200).json(response) - } catch (err) { - next(err) - } + try { + const courseScores = await CourseScoreService.list() + const response = courseScores.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function detail(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const courseScore = await CourseScoreService.retrieve(id) + try { + const id = parseInt(req.params.id) + const courseScore = await CourseScoreService.retrieve(id) - if (!courseScore) return res.status(404).json(NotFound) + if (!courseScore) return res.status(404).json(NotFound) - const response = serialize(courseScore) + const response = serialize(courseScore) - res.status(200).json(response) - } catch (err) { - next(err) - } + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function post(req: Request, res: Response, next: NextFunction) { - try { - const courseScore = await CourseScoreService.create(req.body) - const response = serialize(courseScore) - - res.status(201).json(response) - } catch (err) { - next(err) - } + try { + const courseScore = await CourseScoreService.create(req.body) + const response = serialize(courseScore) + + res.status(201).json(response) + } catch (err) { + next(err) + } } export async function put(req: Request, res: Response, next: NextFunction) { - try { - req.body.id = parseInt(req.params.id) - const results = await CourseScoreService.update(req.body) + try { + req.body.id = parseInt(req.params.id) + const results = await CourseScoreService.update(req.body) - if (!results.affected) return res.status(404).json(NotFound) + if (!results.affected) return res.status(404).json(NotFound) - res.status(200).json(Updated) - } catch (err) { - next(err) - } + res.status(200).json(Updated) + } catch (err) { + next(err) + } } export async function _delete(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const results = await CourseScoreService._delete(id) + try { + const id = parseInt(req.params.id) + const results = await CourseScoreService._delete(id) - if (!results.affected) return res.status(404).json(NotFound) + if (!results.affected) return res.status(404).json(NotFound) - res.status(204).send() - } catch (err) { - next(err) - } + res.status(204).send() + } catch (err) { + next(err) + } } export default { get, detail, post, put, _delete } diff --git a/devU-api/src/entities/courseScore/courseScore.model.ts b/devU-api/src/entities/courseScore/courseScore.model.ts index b66d87c1..34e09881 100644 --- a/devU-api/src/entities/courseScore/courseScore.model.ts +++ b/devU-api/src/entities/courseScore/courseScore.model.ts @@ -1,54 +1,59 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, JoinColumn, ManyToOne } from 'typeorm' +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, + JoinColumn, + ManyToOne, +} from 'typeorm' import CourseModel from '../course/course.model' import UserModel from '../user/user.model' @Entity('courseScore') export default class CourseScoreModel { - /** - * @swagger - * tags: - * - name: CourseScores - * description: Route is currently non-functional, TS2305 error - * components: - * schemas: - * CourseScore: - * type: object - * required: [courseId, score] - * properties: - * courseId: - * type: integer - * score: - * type: number - * letterGrade: - * type: string - */ - @PrimaryGeneratedColumn() - id: number - - @Column({ name: 'course_id' }) - @JoinColumn({ name: 'course_id' }) - @ManyToOne(() => CourseModel) - courseId: number - - - @Column({ name: 'user_id' }) - @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => UserModel) - userId: number - - @Column({ name: 'score' }) - score: number - - @CreateDateColumn({ name: 'created_at' }) - createdAt: Date - - @UpdateDateColumn({ name: 'updated_at' }) - updatedAt: Date - - @DeleteDateColumn({ name: 'deleted_at' }) - deletedAt?: Date - - + /** + * @swagger + * tags: + * - name: CourseScores + * description: Route is currently non-functional, TS2305 error + * components: + * schemas: + * CourseScore: + * type: object + * required: [courseId, score] + * properties: + * courseId: + * type: integer + * score: + * type: number + * letterGrade: + * type: string + */ + @PrimaryGeneratedColumn() + id: number + + @Column({ name: 'course_id' }) + @JoinColumn({ name: 'course_id' }) + @ManyToOne(() => CourseModel) + courseId: number + + @Column({ name: 'user_id' }) + @JoinColumn({ name: 'user_id' }) + @ManyToOne(() => UserModel) + userId: number + + @Column({ name: 'score' }) + score: number + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date + + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date } - diff --git a/devU-api/src/entities/courseScore/courseScore.router.ts b/devU-api/src/entities/courseScore/courseScore.router.ts index 5a03c4c1..c9623c72 100644 --- a/devU-api/src/entities/courseScore/courseScore.router.ts +++ b/devU-api/src/entities/courseScore/courseScore.router.ts @@ -4,7 +4,7 @@ import express from 'express' //validators import validator from './courseScore.validator' import { asInt } from '../../middleware/validator/generic.validator' -import { isAuthorized } from "../../authorization/authorization.middleware"; +import { isAuthorized } from '../../authorization/authorization.middleware' //Controller import CourseScoreController from './courseScore.controller' @@ -24,7 +24,6 @@ const Router = express.Router() */ Router.get('/', isAuthorized('scoresViewAll'), CourseScoreController.get) - /** * @swagger * /course/:courseId/course-score/{id}: diff --git a/devU-api/src/entities/courseScore/courseScore.serializer.ts b/devU-api/src/entities/courseScore/courseScore.serializer.ts index e1adfe29..7465311a 100644 --- a/devU-api/src/entities/courseScore/courseScore.serializer.ts +++ b/devU-api/src/entities/courseScore/courseScore.serializer.ts @@ -3,12 +3,12 @@ import { CourseScore } from 'devu-shared-modules' import CourseScoreModel from './courseScore.model' export function serialize(courseScore: CourseScoreModel): CourseScore { - return { - id: courseScore.id, - userId: courseScore.userId, - courseId: courseScore.courseId, - score: courseScore.score, - createdAt: courseScore.createdAt.toISOString(), - updatedAt: courseScore.updatedAt.toISOString(), - } + return { + id: courseScore.id, + userId: courseScore.userId, + courseId: courseScore.courseId, + score: courseScore.score, + createdAt: courseScore.createdAt.toISOString(), + updatedAt: courseScore.updatedAt.toISOString(), + } } diff --git a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.model.ts b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.model.ts index e41a0a5c..1052e6a4 100644 --- a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.model.ts +++ b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.model.ts @@ -1,86 +1,85 @@ import { - Column, - CreateDateColumn, - DeleteDateColumn, - Entity, - JoinColumn, - ManyToOne, - PrimaryGeneratedColumn, - UpdateDateColumn, + Column, + CreateDateColumn, + DeleteDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, } from 'typeorm' import AssignmentModel from '../assignment/assignment.model' import UserModel from '../user/user.model' @Entity('deadline_extensions') export default class DeadlineExtensionsModel { - /** - * @swagger - * tags: - * - name: DeadlineExtensionsTypes - * description: - * components: - * schemas: - * DeadlineExtensionsTypes: - * type: object - * required: [creator_id, user_id, assignment_id, deadline_date] - * properties: - * creator_id: - * type: string - * description: The person who is creating the deadline extension - * user_id: - * type: string - * description: The person receiving the extension - * assignment_id: - * type: string - * description: The assignment id for the deadline - * deadline_date: - * type: string - * description: The deadline date. Must be in ISO 8601 format. - * is_regex: - * type: bool - * description: Default false. Set if correct string is a regex. - * new_start_Date: - * type: Date|null - * description: Optional field to specify new start date for an assignment, if specified this will override assignment start_date. Must be in ISO 8601 format. - * new_end_Date: - * type: Date|null - * description: Optional field to specify new end date for an assignment, if specified this will override assignment end_date. Must be in ISO 8601 format. - */ + /** + * @swagger + * tags: + * - name: DeadlineExtensionsTypes + * description: + * components: + * schemas: + * DeadlineExtensionsTypes: + * type: object + * required: [creator_id, user_id, assignment_id, deadline_date] + * properties: + * creator_id: + * type: string + * description: The person who is creating the deadline extension + * user_id: + * type: string + * description: The person receiving the extension + * assignment_id: + * type: string + * description: The assignment id for the deadline + * deadline_date: + * type: string + * description: The deadline date. Must be in ISO 8601 format. + * is_regex: + * type: bool + * description: Default false. Set if correct string is a regex. + * new_start_Date: + * type: Date|null + * description: Optional field to specify new start date for an assignment, if specified this will override assignment start_date. Must be in ISO 8601 format. + * new_end_Date: + * type: Date|null + * description: Optional field to specify new end date for an assignment, if specified this will override assignment end_date. Must be in ISO 8601 format. + */ - @PrimaryGeneratedColumn() - id: number + @PrimaryGeneratedColumn() + id: number - @Column({name: 'assignment_id'}) - @JoinColumn({name: 'assignment_id'}) - @ManyToOne(() => AssignmentModel) - assignmentId: number + @Column({ name: 'assignment_id' }) + @JoinColumn({ name: 'assignment_id' }) + @ManyToOne(() => AssignmentModel) + assignmentId: number - @Column({name: 'creator_id'}) - @JoinColumn({name: 'creator_id'}) - @ManyToOne(() => UserModel) - creatorId: number + @Column({ name: 'creator_id' }) + @JoinColumn({ name: 'creator_id' }) + @ManyToOne(() => UserModel) + creatorId: number - @Column({name: 'user_id'}) - @JoinColumn({name: 'user_id'}) - @ManyToOne(() => UserModel) - userId: number + @Column({ name: 'user_id' }) + @JoinColumn({ name: 'user_id' }) + @ManyToOne(() => UserModel) + userId: number - @Column({name: 'deadline_date'}) - deadlineDate: Date + @Column({ name: 'deadline_date' }) + deadlineDate: Date + @Column({ name: 'new_end_date', nullable: true }) + newEndDate: Date - @Column({name: 'new_end_date', nullable: true}) - newEndDate: Date + @Column({ name: 'new_start_date', nullable: true }) + newStartDate: Date - @Column({name: 'new_start_date', nullable: true}) - newStartDate: Date + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date - @CreateDateColumn({name: 'created_at'}) - createdAt: Date + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date - @UpdateDateColumn({name: 'updated_at'}) - updatedAt: Date - - @DeleteDateColumn({name: 'deleted_at'}) - deletedAt?: Date + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date } diff --git a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts index 81453433..259cbadd 100644 --- a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts +++ b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts @@ -4,7 +4,7 @@ import express from 'express' // Middleware import validator from './deadlineExtensions.validator' import { asInt } from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import DeadlineExtensionsController from './deadlineExtensions.controller' diff --git a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.service.ts b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.service.ts index 8a4628a8..d218ae52 100644 --- a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.service.ts +++ b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.service.ts @@ -11,15 +11,7 @@ export async function create(assignment: DeadlineExtensions) { } export async function update(assignment: DeadlineExtensions) { - const { - id, - userId, - creatorId, - deadlineDate, - assignmentId, - newEndDate, - newStartDate, - } = assignment + const { id, userId, creatorId, deadlineDate, assignmentId, newEndDate, newStartDate } = assignment if (!id) throw new Error('Missing Id') diff --git a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.validator.ts b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.validator.ts index 65554fa6..3880dbb5 100644 --- a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.validator.ts +++ b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.validator.ts @@ -5,12 +5,6 @@ const userId = check('userId').isNumeric() const assignmentId = check('assignmentId').isNumeric() const deadlineDate = check('deadlineDate').trim().isISO8601().toDate() -const validator = [ - userId, - creatorId, - assignmentId, - deadlineDate, -] - +const validator = [userId, creatorId, assignmentId, deadlineDate] export default validator diff --git a/devU-api/src/entities/deadlineExtensions/tests/deadlineExtensions.controller.test.ts b/devU-api/src/entities/deadlineExtensions/tests/deadlineExtensions.controller.test.ts index b1412785..7d91bea8 100644 --- a/devU-api/src/entities/deadlineExtensions/tests/deadlineExtensions.controller.test.ts +++ b/devU-api/src/entities/deadlineExtensions/tests/deadlineExtensions.controller.test.ts @@ -79,7 +79,9 @@ describe('Deadline Extensions', () => { describe('GET - /deadline-extensions/:id', () => { describe('200 - Ok', () => { beforeEach(async () => { - DeadlineExtensionsService.retrieve = jest.fn().mockImplementation(() => Promise.resolve(mockedDeadlineExtension)) + DeadlineExtensionsService.retrieve = jest + .fn() + .mockImplementation(() => Promise.resolve(mockedDeadlineExtension)) await controller.detail(req, res, next) }) diff --git a/devU-api/src/entities/grader/grader.controller.ts b/devU-api/src/entities/grader/grader.controller.ts index bd766ca0..8cfa8432 100644 --- a/devU-api/src/entities/grader/grader.controller.ts +++ b/devU-api/src/entities/grader/grader.controller.ts @@ -7,17 +7,17 @@ import { GenericResponse, NotFound } from '../../utils/apiResponse.utils' import { serialize } from '../grader/grader.serializer' export async function grade(req: Request, res: Response, next: NextFunction) { - try { - const submissionId = parseInt(req.params.id) - const grade = await GraderService.grade(submissionId) - if (!grade || grade.length === 0) return res.status(404).json(NotFound) + try { + const submissionId = parseInt(req.params.id) + const grade = await GraderService.grade(submissionId) + if (!grade || grade.length === 0) return res.status(404).json(NotFound) - const response = serialize(grade) + const response = serialize(grade) - res.status(200).json(response) - } catch (err) { - res.status(400).json(new GenericResponse(err.message)) - } + res.status(200).json(response) + } catch (err) { + res.status(400).json(new GenericResponse(err.message)) + } } export default { grade } diff --git a/devU-api/src/entities/grader/grader.router.ts b/devU-api/src/entities/grader/grader.router.ts index 35002d20..d3664141 100644 --- a/devU-api/src/entities/grader/grader.router.ts +++ b/devU-api/src/entities/grader/grader.router.ts @@ -4,7 +4,7 @@ import express from 'express' // Middleware //import validator from './grader.validator' import { asInt } from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import GraderController from './grader.controller' @@ -15,7 +15,7 @@ const Router = express.Router() * @swagger * tags: * - name: Grader - * description: + * description: * /course/:courseId/grade/{id}: * post: * summary: Grade a submission, currently only with non-container autograders @@ -39,4 +39,4 @@ Router.post('/:id', isAuthorized('enrolled'), asInt(), GraderController.grade) // -or- do we only let students hit this once per submission. Regrades must be by someone with permission. Then // add a second endpoint that is /regrade -export default Router \ No newline at end of file +export default Router diff --git a/devU-api/src/entities/grader/grader.serializer.ts b/devU-api/src/entities/grader/grader.serializer.ts index 235a797e..36acbbdb 100644 --- a/devU-api/src/entities/grader/grader.serializer.ts +++ b/devU-api/src/entities/grader/grader.serializer.ts @@ -4,6 +4,6 @@ export function serialize(scores: any[]): GraderInfo { const submissionScore = scores.pop() return { submissionScore: submissionScore as SubmissionScore, - submissionProblemScores: scores as SubmissionProblemScore[] + submissionProblemScores: scores as SubmissionProblemScore[], } } diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index b6433645..722e0971 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -13,109 +13,118 @@ import { serialize as serializeContainer } from '../containerAutoGrader/containe import { serialize as serializeAssignmentScore } from '../assignmentScore/assignmentScore.serializer' export async function grade(submissionId: number) { - const submission = await submissionService.retrieve(submissionId) - if (!submission) return null - - const assignmentId = submission.assignmentId - - const content = JSON.parse(submission.content) - const form = content.form - const filepaths = content.filepaths //Using the field name that was written on the whiteboard for now - - const nonContainerAutograders = await nonContainerAutograderService.listByAssignmentId(assignmentId) - const containerAutograders = await containerAutograderService.listByAssignmentId(assignmentId) - const assignmentProblems = await assignmentProblemService.list(assignmentId) - - - let score = 0 - let feedback = '' - let allScores = [] //This is the return value, the serializer parses it into a GraderInfo object for the controller to return - - //Run Non-Container Autograders - for (const question in form) { - const nonContainerGrader = nonContainerAutograders.find(grader => grader.question === question) - const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) - - if (nonContainerGrader && assignmentProblem) { - const [problemScore, problemFeedback] = checkAnswer(form[question], serializeNonContainer(nonContainerGrader)) - score += problemScore - feedback += problemFeedback + '\n' - - const problemScoreObj: SubmissionProblemScore = { - submissionId: submissionId, - assignmentProblemId: assignmentProblem.id, - score: problemScore, - feedback: problemFeedback - } - allScores.push(await submissionProblemScoreService.create(problemScoreObj)) - } - } + const submission = await submissionService.retrieve(submissionId) + if (!submission) return null - //Run Container Autograders - //Mock functionality, this is not finalized!!!! - if (filepaths){ - for (const filepath of filepaths) { - const containerGrader = containerAutograders.find(grader => grader.autogradingImage === filepath) //PLACEHOLDER, I'm just using autogradingImage temporarily to associate graders to files - - if (containerGrader) { - const gradeResults = await mockContainerCheckAnswer(filepath, serializeContainer(containerGrader)) - - for (const result of gradeResults) { - const problemScoreObj: SubmissionProblemScore = { - submissionId: submissionId, - assignmentProblemId: 1, //PLACEHOLDER, an assignmentProblem must exist in the db for this to work - score: result.score, - feedback: result.feedback, - } - allScores.push(await submissionProblemScoreService.create(problemScoreObj)) - score += result.score - feedback += result.feedback + '\n' - } - } - } - } + const assignmentId = submission.assignmentId + + const content = JSON.parse(submission.content) + const form = content.form + const filepaths = content.filepaths //Using the field name that was written on the whiteboard for now + + const nonContainerAutograders = await nonContainerAutograderService.listByAssignmentId(assignmentId) + const containerAutograders = await containerAutograderService.listByAssignmentId(assignmentId) + const assignmentProblems = await assignmentProblemService.list(assignmentId) + + let score = 0 + let feedback = '' + let allScores = [] //This is the return value, the serializer parses it into a GraderInfo object for the controller to return - //Grading is finished. Create SubmissionScore and AssignmentScore and save to db. - const scoreObj: SubmissionScore = { + //Run Non-Container Autograders + for (const question in form) { + const nonContainerGrader = nonContainerAutograders.find(grader => grader.question === question) + const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) + + if (nonContainerGrader && assignmentProblem) { + const [problemScore, problemFeedback] = checkAnswer(form[question], serializeNonContainer(nonContainerGrader)) + score += problemScore + feedback += problemFeedback + '\n' + + const problemScoreObj: SubmissionProblemScore = { submissionId: submissionId, - score: score, //Sum of all SubmissionProblemScore scores - feedback: feedback //Concatination of SubmissionProblemScore feedbacks + assignmentProblemId: assignmentProblem.id, + score: problemScore, + feedback: problemFeedback, + } + allScores.push(await submissionProblemScoreService.create(problemScoreObj)) } - allScores.push(await submissionScoreService.create(scoreObj)) - - //PLACEHOLDER AssignmentScore logic. This should be customizable, but for now AssignmentScore will simply equal the latest SubmissionScore - const assignmentScoreModel = await assignmentScoreService.retrieveByUser(submission.assignmentId, submission.userId) - if (assignmentScoreModel) { //If assignmentScore already exists, update existing entity - const assignmentScore = serializeAssignmentScore(assignmentScoreModel) - assignmentScore.score = score - await assignmentScoreService.update(assignmentScore) - - } else { //Otherwise make a new one - const assignmentScore: AssignmentScore = { - assignmentId: submission.assignmentId, - userId: submission.userId, - score: score, + } + + //Run Container Autograders + //Mock functionality, this is not finalized!!!! + if (filepaths) { + for (const filepath of filepaths) { + const containerGrader = containerAutograders.find(grader => grader.autogradingImage === filepath) //PLACEHOLDER, I'm just using autogradingImage temporarily to associate graders to files + + if (containerGrader) { + const gradeResults = await mockContainerCheckAnswer(filepath, serializeContainer(containerGrader)) + + for (const result of gradeResults) { + const problemScoreObj: SubmissionProblemScore = { + submissionId: submissionId, + assignmentProblemId: 1, //PLACEHOLDER, an assignmentProblem must exist in the db for this to work + score: result.score, + feedback: result.feedback, + } + allScores.push(await submissionProblemScoreService.create(problemScoreObj)) + score += result.score + feedback += result.feedback + '\n' } - await assignmentScoreService.create(assignmentScore) + } + } + } + + //Grading is finished. Create SubmissionScore and AssignmentScore and save to db. + const scoreObj: SubmissionScore = { + submissionId: submissionId, + score: score, //Sum of all SubmissionProblemScore scores + feedback: feedback, //Concatination of SubmissionProblemScore feedbacks + } + allScores.push(await submissionScoreService.create(scoreObj)) + + //PLACEHOLDER AssignmentScore logic. This should be customizable, but for now AssignmentScore will simply equal the latest SubmissionScore + const assignmentScoreModel = await assignmentScoreService.retrieveByUser(submission.assignmentId, submission.userId) + if (assignmentScoreModel) { + //If assignmentScore already exists, update existing entity + const assignmentScore = serializeAssignmentScore(assignmentScoreModel) + assignmentScore.score = score + await assignmentScoreService.update(assignmentScore) + } else { + //Otherwise make a new one + const assignmentScore: AssignmentScore = { + assignmentId: submission.assignmentId, + userId: submission.userId, + score: score, } + await assignmentScoreService.create(assignmentScore) + } - return allScores + return allScores } //Temporary mock function, delete when the container autograder grading function is written export async function mockContainerCheckAnswer(file: string, containerAutoGrader: ContainerAutoGrader) { - let gradeResults = [] - - //SubmissionProblemScore 1 - gradeResults.push({score: 5, feedback: "Grader #" + containerAutoGrader.id + " graded " + file + " problem 1 for 5/5 points"}) - - //SubmissionProblemScore 2 - gradeResults.push({score: 5, feedback: "Grader #" + containerAutoGrader.id + " graded " + file + " problem 2 for 5/5 points"}) - - //SubmissionProblemScore 3 - gradeResults.push({score: 10, feedback: "Grader #" + containerAutoGrader.id + " graded " + file + " problem 3 for 10/10 points"}) - - return gradeResults + let gradeResults = [] + + //SubmissionProblemScore 1 + gradeResults.push({ + score: 5, + feedback: 'Grader #' + containerAutoGrader.id + ' graded ' + file + ' problem 1 for 5/5 points', + }) + + //SubmissionProblemScore 2 + gradeResults.push({ + score: 5, + feedback: 'Grader #' + containerAutoGrader.id + ' graded ' + file + ' problem 2 for 5/5 points', + }) + + //SubmissionProblemScore 3 + gradeResults.push({ + score: 10, + feedback: 'Grader #' + containerAutoGrader.id + ' graded ' + file + ' problem 3 for 10/10 points', + }) + + return gradeResults } -export default { grade } \ No newline at end of file +export default { grade } diff --git a/devU-api/src/entities/grader/tests/grader.controller.test.ts b/devU-api/src/entities/grader/tests/grader.controller.test.ts index ff194992..27ff4b0c 100644 --- a/devU-api/src/entities/grader/tests/grader.controller.test.ts +++ b/devU-api/src/entities/grader/tests/grader.controller.test.ts @@ -11,7 +11,7 @@ import Testing from '../../../utils/testing.utils' import { GenericResponse } from '../../../utils/apiResponse.utils' -//THIS TEST FAILS, +//THIS TEST FAILS, // Testing Globals let req: any @@ -27,50 +27,47 @@ let expectedScore: SubmissionScore let expectedProblemScore1: SubmissionProblemScore let expectedProblemScore2: SubmissionProblemScore - describe('GraderController', () => { - beforeEach(() => { - req = Testing.fakeRequest() - res = Testing.fakeResponse() - next = Testing.fakeNext() - - mockedSubmission = Testing.generateTypeOrm(SubmissionModel) - - expectedReturn.push(expectedProblemScore1) - expectedReturn.push(expectedProblemScore2) - expectedReturn.push(expectedScore) - expectedResult = serialize(expectedReturn) - - expectedError = new Error('Expected Error') - }) - describe('POST - /grade/:id', () => { - describe('200 - Ok', () => { - beforeEach(async () => { - GraderService.grade = jest.fn().mockImplementation(() => Promise.resolve(mockedSubmission)) - await controller.grade(req, res, next) - }) - test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) - test('Returns Grading Info', () => expect(res.json).toBeCalledWith(expectedResult)) - }) - describe('400 - Bad Request', () => { - beforeEach(async () => { - GraderService.grade = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - - try { - await controller.grade(req, res, next) - - fail('Expected test to throw') - } catch { - // continue to tests - } - }) - - test('Status code is 400', () => expect(res.status).toBeCalledWith(400)) - test('Responds with generic error', () => - expect(res.json).toBeCalledWith(new GenericResponse(expectedError.message))) - test('Next not called', () => expect(next).toBeCalledTimes(0)) - }) + beforeEach(() => { + req = Testing.fakeRequest() + res = Testing.fakeResponse() + next = Testing.fakeNext() + + mockedSubmission = Testing.generateTypeOrm(SubmissionModel) + + expectedReturn.push(expectedProblemScore1) + expectedReturn.push(expectedProblemScore2) + expectedReturn.push(expectedScore) + expectedResult = serialize(expectedReturn) + + expectedError = new Error('Expected Error') + }) + describe('POST - /grade/:id', () => { + describe('200 - Ok', () => { + beforeEach(async () => { + GraderService.grade = jest.fn().mockImplementation(() => Promise.resolve(mockedSubmission)) + await controller.grade(req, res, next) + }) + test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) + test('Returns Grading Info', () => expect(res.json).toBeCalledWith(expectedResult)) }) - + describe('400 - Bad Request', () => { + beforeEach(async () => { + GraderService.grade = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + + try { + await controller.grade(req, res, next) -}) \ No newline at end of file + fail('Expected test to throw') + } catch { + // continue to tests + } + }) + + test('Status code is 400', () => expect(res.status).toBeCalledWith(400)) + test('Responds with generic error', () => + expect(res.json).toBeCalledWith(new GenericResponse(expectedError.message))) + test('Next not called', () => expect(next).toBeCalledTimes(0)) + }) + }) +}) diff --git a/devU-api/src/entities/grader/tests/grader.serializer.test.ts b/devU-api/src/entities/grader/tests/grader.serializer.test.ts index 4a90f1a7..30cc2882 100644 --- a/devU-api/src/entities/grader/tests/grader.serializer.test.ts +++ b/devU-api/src/entities/grader/tests/grader.serializer.test.ts @@ -19,25 +19,25 @@ describe('Grader Serializer', () => { mockSubmissionScore.id = 1 mockSubmissionScore.submissionId = 4 mockSubmissionScore.score = 5 - mockSubmissionScore.feedback = "q1: 5/5, q2: 0/5" - mockSubmissionScore.createdAt = new Date - mockSubmissionScore.updatedAt = new Date + mockSubmissionScore.feedback = 'q1: 5/5, q2: 0/5' + mockSubmissionScore.createdAt = new Date() + mockSubmissionScore.updatedAt = new Date() mockSubmissionProblemScore1.id = 1 mockSubmissionProblemScore1.submissionId = 4 mockSubmissionProblemScore1.assignmentProblemId = 1 mockSubmissionProblemScore1.score = 5 - mockSubmissionProblemScore1.feedback = "Correct, 5/5 points" - mockSubmissionProblemScore1.createdAt = new Date - mockSubmissionProblemScore1.updatedAt = new Date - + mockSubmissionProblemScore1.feedback = 'Correct, 5/5 points' + mockSubmissionProblemScore1.createdAt = new Date() + mockSubmissionProblemScore1.updatedAt = new Date() + mockSubmissionProblemScore2.id = 2 mockSubmissionProblemScore2.submissionId = 4 mockSubmissionProblemScore2.assignmentProblemId = 2 mockSubmissionProblemScore2.score = 0 - mockSubmissionProblemScore2.feedback = "Incorrect, 0/5 points" - mockSubmissionProblemScore2.createdAt = new Date - mockSubmissionProblemScore2.updatedAt = new Date + mockSubmissionProblemScore2.feedback = 'Incorrect, 0/5 points' + mockSubmissionProblemScore2.createdAt = new Date() + mockSubmissionProblemScore2.updatedAt = new Date() mockArray.push(mockSubmissionProblemScore1) mockArray.push(mockSubmissionProblemScore2) @@ -54,4 +54,4 @@ describe('Grader Serializer', () => { expect(expectedResult.submissionProblemScores[1]).toEqual(mockSubmissionProblemScore2) }) }) -}) \ No newline at end of file +}) diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts index 313fa3f4..085eb27c 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts @@ -19,7 +19,6 @@ export async function getByAssignmentId(req: Request, res: Response, next: NextF const nonContainerAutoGraders = await NonContainerAutoGraderService.listByAssignmentId(assignmentId) if (!nonContainerAutoGraders) return res.status(404).json(NotFound) - res.status(200).json(nonContainerAutoGraders.map(serialize)) } catch (err) { next(err) diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts index 05cab5e9..c4642f40 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts @@ -11,15 +11,21 @@ export function checkAnswer(studentAnswer: string, nonContainerAutoGrader: NonCo const isMatch: boolean = pattern.test(studentAnswer) if (isMatch) { - return [nonContainerAutoGrader.score, `Grader #${nonContainerAutoGrader.id} graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`] + return [ + nonContainerAutoGrader.score, + `Grader #${nonContainerAutoGrader.id} graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`, + ] } } else { // if no regex is set use normal string matching if (studentAnswer === nonContainerAutoGrader.correctString) { - return [nonContainerAutoGrader.score, `Grader #${nonContainerAutoGrader.id} graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`] + return [ + nonContainerAutoGrader.score, + `Grader #${nonContainerAutoGrader.id} graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`, + ] } } // default value to return if all conditions fail to execute // i.e. the answer is incorrect or improperly formatted return [0, `Grader #${nonContainerAutoGrader.id} graded "${nonContainerAutoGrader.question}" for 0 points`] -} \ No newline at end of file +} diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.model.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.model.ts index 5e0b0798..0287e51e 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.model.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.model.ts @@ -16,7 +16,7 @@ export default class NonContainerAutoGraderModel { * @swagger * tags: * - name: NonContainerAutoGraders - * description: + * description: * components: * schemas: * NonContainerAutoGrader: diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts index af2aa05a..9d7a4626 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts @@ -3,15 +3,14 @@ import express from 'express' // Middleware import validator from './nonContainerAutoGrader.validator' -import {asInt} from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import { asInt } from '../../middleware/validator/generic.validator' +import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import nonContainerQuestions from './nonContainerAutoGrader.controller' const Router = express.Router() - /** * @swagger * /course/:courseId/assignment/:assignmentId/nonContainerAutoGraders: diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts index 1cc3de53..99eab720 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts @@ -33,7 +33,6 @@ export async function list() { return await connect().find({ deletedAt: IsNull() }) } - export default { create, retrieve, diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.validator.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.validator.ts index eef72ad2..eb523ea4 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.validator.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.validator.ts @@ -6,12 +6,6 @@ const question = check('question').isString().trim().isLength({ max: 128 }) const score = check('score').isNumeric() const isRegex = check('isRegex').isBoolean() -const validator = [ - correctString, - validate, - question, - score, - isRegex, -] +const validator = [correctString, validate, question, score, isRegex] export default validator diff --git a/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.controller.test.ts b/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.controller.test.ts index 5aeb2b74..88889390 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.controller.test.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.controller.test.ts @@ -46,7 +46,9 @@ describe('NonContainerAutoGraderController', () => { describe('GET - /nonContainerAutoGrader', () => { describe('200 - Ok', () => { beforeEach(async () => { - NonContainerAutoGraderService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedNonContainerAutoGraders)) + NonContainerAutoGraderService.list = jest + .fn() + .mockImplementation(() => Promise.resolve(mockedNonContainerAutoGraders)) await controller.get(req, res, next) // what we're testing }) @@ -72,7 +74,9 @@ describe('NonContainerAutoGraderController', () => { describe('GET - /nonContainerAutoGrader/byAssignmentID/:assignmentId', () => { describe('200 - Ok', () => { beforeEach(async () => { - NonContainerAutoGraderService.listByAssignmentId = jest.fn().mockImplementation(() => Promise.resolve(mockedNonContainerAutoGraders)) + NonContainerAutoGraderService.listByAssignmentId = jest + .fn() + .mockImplementation(() => Promise.resolve(mockedNonContainerAutoGraders)) await controller.getByAssignmentId(req, res, next) // what we're testing }) @@ -82,21 +86,22 @@ describe('NonContainerAutoGraderController', () => { describe('404 - Not Found', () => { beforeEach(async () => { - NonContainerAutoGraderService.listByAssignmentId = jest.fn().mockImplementation(() => Promise.resolve()) - await controller.getByAssignmentId(req, res, next) + NonContainerAutoGraderService.listByAssignmentId = jest.fn().mockImplementation(() => Promise.resolve()) + await controller.getByAssignmentId(req, res, next) }) test('Status code is 404', () => expect(res.status).toBeCalledWith(404)) test('Returns Not found message', () => expect(res.json).toBeCalledWith(NotFound)) test('Next is not called', () => expect(next).toHaveBeenCalledTimes(0)) - }) }) describe('GET - /nonContainerAutoGrader/byId/:id', () => { describe('200 - Ok', () => { beforeEach(async () => { - NonContainerAutoGraderService.retrieve = jest.fn().mockImplementation(() => Promise.resolve(mockedNonContainerAutoGrader)) + NonContainerAutoGraderService.retrieve = jest + .fn() + .mockImplementation(() => Promise.resolve(mockedNonContainerAutoGrader)) await controller.detail(req, res, next) }) @@ -133,7 +138,9 @@ describe('NonContainerAutoGraderController', () => { describe('POST - /nonContainerAutoGrader/', () => { describe('201 - Created', () => { beforeEach(async () => { - NonContainerAutoGraderService.create = jest.fn().mockImplementation(() => Promise.resolve(mockedNonContainerAutoGrader)) + NonContainerAutoGraderService.create = jest + .fn() + .mockImplementation(() => Promise.resolve(mockedNonContainerAutoGrader)) await controller.post(req, res, next) }) @@ -230,4 +237,4 @@ describe('NonContainerAutoGraderController', () => { test('Next called with expected error', () => expect(next).toBeCalledWith(expectedError)) }) }) -}) \ No newline at end of file +}) diff --git a/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.grader.test.ts b/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.grader.test.ts index dfe2e0bd..7a511fd2 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.grader.test.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.grader.test.ts @@ -2,7 +2,6 @@ import { NonContainerAutoGrader } from 'devu-shared-modules' import { checkAnswer } from '../nonContainerAutoGrader.grader' - describe('NonContainerAutoGrader grader', () => { let mockQuestionWithoutRegex: NonContainerAutoGrader @@ -41,7 +40,7 @@ describe('NonContainerAutoGrader grader', () => { id: 23, question: 'What was the last ever Dolphin message', // regex to match this exact sentence and is case-sensitive - correctString: '/So long, and thanks for all the fish\./gm', + correctString: '/So long, and thanks for all the fish./gm', score: 1, assignmentId: 8, isRegex: true, @@ -69,4 +68,4 @@ describe('NonContainerAutoGrader grader', () => { expect(expectedResult[0]).toEqual(0) }) }) -}) \ No newline at end of file +}) diff --git a/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.serializer.test.ts b/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.serializer.test.ts index dbe7c0d4..f3b9b2ea 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.serializer.test.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/tests/nonContainerAutoGrader.serializer.test.ts @@ -38,4 +38,4 @@ describe('NonContainerAutoGrader Serializer', () => { expect(expectedResult.updatedAt).toEqual(mockQuestion.updatedAt.toISOString()) }) }) -}) \ No newline at end of file +}) diff --git a/devU-api/src/entities/role/role.controller.ts b/devU-api/src/entities/role/role.controller.ts index ec691169..172a592d 100644 --- a/devU-api/src/entities/role/role.controller.ts +++ b/devU-api/src/entities/role/role.controller.ts @@ -7,7 +7,6 @@ import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.util export async function getAll(req: Request, res: Response, next: NextFunction) { try { - const roles = await RoleService.listAll() res.status(200).json(roles.map(serialize)) diff --git a/devU-api/src/entities/role/role.defaults.ts b/devU-api/src/entities/role/role.defaults.ts index b4bbcddb..dbf90ea0 100644 --- a/devU-api/src/entities/role/role.defaults.ts +++ b/devU-api/src/entities/role/role.defaults.ts @@ -1,46 +1,45 @@ -import {Role} from 'devu-shared-modules' - +import { Role } from 'devu-shared-modules' const student: Role = { - name: 'student', - assignmentViewAll: false, - assignmentEditAll: false, - assignmentViewReleased: true, - courseEdit: false, - courseViewAll: false, - enrolled: true, - roleEditAll: false, - roleViewAll: false, - roleViewSelf: true, - scoresEditAll: false, - scoresViewAll: false, - scoresViewSelfReleased: true, - submissionChangeState: false, - submissionCreateAll: false, - submissionCreateSelf: true, - submissionViewAll: false, - userCourseEditAll: false, + name: 'student', + assignmentViewAll: false, + assignmentEditAll: false, + assignmentViewReleased: true, + courseEdit: false, + courseViewAll: false, + enrolled: true, + roleEditAll: false, + roleViewAll: false, + roleViewSelf: true, + scoresEditAll: false, + scoresViewAll: false, + scoresViewSelfReleased: true, + submissionChangeState: false, + submissionCreateAll: false, + submissionCreateSelf: true, + submissionViewAll: false, + userCourseEditAll: false, } const instructor: Role = { - name: 'instructor', - assignmentViewAll: true, - assignmentEditAll: true, - assignmentViewReleased: true, - courseEdit: true, - courseViewAll: true, - enrolled: true, - roleEditAll: true, - roleViewAll: true, - roleViewSelf: true, - scoresEditAll: true, - scoresViewAll: true, - scoresViewSelfReleased: true, - submissionChangeState: true, - submissionCreateAll: true, - submissionCreateSelf: true, - submissionViewAll: true, - userCourseEditAll: true, + name: 'instructor', + assignmentViewAll: true, + assignmentEditAll: true, + assignmentViewReleased: true, + courseEdit: true, + courseViewAll: true, + enrolled: true, + roleEditAll: true, + roleViewAll: true, + roleViewSelf: true, + scoresEditAll: true, + scoresViewAll: true, + scoresViewSelfReleased: true, + submissionChangeState: true, + submissionCreateAll: true, + submissionCreateSelf: true, + submissionViewAll: true, + userCourseEditAll: true, } const defaultRoles: Map = new Map() diff --git a/devU-api/src/entities/role/role.model.ts b/devU-api/src/entities/role/role.model.ts index 307531cf..05a13410 100644 --- a/devU-api/src/entities/role/role.model.ts +++ b/devU-api/src/entities/role/role.model.ts @@ -1,114 +1,110 @@ import { - Entity, - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, - DeleteDateColumn, - JoinColumn, - ManyToOne + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, + JoinColumn, + ManyToOne, } from 'typeorm' import CourseModel from '../course/course.model' @Entity('role') export default class RoleModel { - /** - * @swagger - * tags: - * - name: Role - * components: - * schemas: - * Role: - * type: object - * required: [] - * properties: - * permission: - * type: boolean - */ - @PrimaryGeneratedColumn() - id: number + /** + * @swagger + * tags: + * - name: Role + * components: + * schemas: + * Role: + * type: object + * required: [] + * properties: + * permission: + * type: boolean + */ + @PrimaryGeneratedColumn() + id: number - @Column({name: 'course_id'}) - @JoinColumn({name: 'course_id'}) - @ManyToOne(() => CourseModel) - courseId: number + @Column({ name: 'course_id' }) + @JoinColumn({ name: 'course_id' }) + @ManyToOne(() => CourseModel) + courseId: number - // @Column({name: 'user_id'}) - // @JoinColumn({name: 'user_id'}) - // @ManyToOne(() => UserModel) - // userId: number + // @Column({name: 'user_id'}) + // @JoinColumn({name: 'user_id'}) + // @ManyToOne(() => UserModel) + // userId: number - @CreateDateColumn({name: 'created_at'}) - createdAt: Date + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date - @UpdateDateColumn({name: 'updated_at'}) - updatedAt: Date + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date - @DeleteDateColumn({name: 'deleted_at'}) - deletedAt?: Date + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date - @Column({name: 'name'}) - name: string + @Column({ name: 'name' }) + name: string + // All the permission options // - // All the permission options // + // For default permission that everyone in the course should always have + @Column({ name: 'enrolled' }) + enrolled: boolean - // For default permission that everyone in the course should always have - @Column({name: 'enrolled'}) - enrolled: boolean + @Column({ name: 'course_edit' }) + courseEdit: boolean - @Column({name: 'course_edit'}) - courseEdit: boolean + @Column({ name: 'course_view_all' }) + courseViewAll: boolean - @Column({name: 'course_view_all'}) - courseViewAll: boolean + @Column({ name: 'assignment_view_all' }) + assignmentViewAll: boolean - @Column({name: 'assignment_view_all'}) - assignmentViewAll: boolean + @Column({ name: 'assignment_edit_all' }) // includes category_edit + autograders_edit_all + assignmentEditAll: boolean - @Column({name: 'assignment_edit_all'}) // includes category_edit + autograders_edit_all - assignmentEditAll: boolean + // student perm + @Column({ name: 'assignment_view_release' }) + assignmentViewReleased: boolean - // student perm - @Column({name: 'assignment_view_release'}) - assignmentViewReleased: boolean + @Column({ name: 'scores_view_all' }) + scoresViewAll: boolean - @Column({name: 'scores_view_all'}) - scoresViewAll: boolean + @Column({ name: 'scores_edit_all' }) + scoresEditAll: boolean - @Column({name: 'scores_edit_all'}) - scoresEditAll: boolean + // student perm + @Column({ name: 'scores_view_self_released' }) + scoresViewSelfReleased: boolean - // student perm - @Column({name: 'scores_view_self_released'}) - scoresViewSelfReleased: boolean + @Column({ name: 'role_edit_all' }) // TODO: can only delete a role if no one has that role. Some way of preventing the course from being soft-locked + roleEditAll: boolean - @Column({name: 'role_edit_all'}) // TODO: can only delete a role if no one has that role. Some way of preventing the course from being soft-locked - roleEditAll: boolean + @Column({ name: 'role_view_all' }) + roleViewAll: boolean - @Column({name: 'role_view_all'}) - roleViewAll: boolean + @Column({ name: 'role_view_self' }) // everyone can do this so the front end knows what to render + roleViewSelf: boolean - @Column({name: 'role_view_self'}) // everyone can do this so the front end knows what to render - roleViewSelf: boolean + @Column({ name: 'submission_change_state' }) // For soft-soft delete. Marked as "doesn't count" but can still be viewed by the student + submissionChangeState: boolean + @Column({ name: 'submission_create_all' }) + submissionCreateAll: boolean - @Column({name: 'submission_change_state'}) // For soft-soft delete. Marked as "doesn't count" but can still be viewed by the student - submissionChangeState: boolean + // student perm + @Column({ name: 'submission_create_self' }) + submissionCreateSelf: boolean - @Column({name: 'submission_create_all'}) - submissionCreateAll: boolean - - // student perm - @Column({name: 'submission_create_self'}) - submissionCreateSelf: boolean - - @Column({name: 'submission_view_all'}) - submissionViewAll: boolean - - @Column({name: 'user_course_edit_all'}) // TODO: Don't let the last instructor change their role - userCourseEditAll: boolean + @Column({ name: 'submission_view_all' }) + submissionViewAll: boolean + @Column({ name: 'user_course_edit_all' }) // TODO: Don't let the last instructor change their role + userCourseEditAll: boolean } - diff --git a/devU-api/src/entities/role/role.router.ts b/devU-api/src/entities/role/role.router.ts index 60625a7b..49dbd55d 100644 --- a/devU-api/src/entities/role/role.router.ts +++ b/devU-api/src/entities/role/role.router.ts @@ -3,8 +3,8 @@ import express from 'express' // Middleware import validator from './role.validator' -import {asInt} from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import { asInt } from '../../middleware/validator/generic.validator' +import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import RoleController from './role.controller' @@ -24,8 +24,6 @@ const Router = express.Router() */ Router.get('/', isAuthorized('roleViewAll'), RoleController.getAll) - - /** * @swagger * /course/:courseId/roles: @@ -67,7 +65,6 @@ Router.get('/', isAuthorized('roleViewAll'), RoleController.getByCourse) Router.get('/:id', isAuthorized('roleViewAll'), asInt(), RoleController.detail) // TODO: make sure all details are checking the courseId. Add matching 'detailByCourse' for each and only admin can hit 'detail'? - /** * @swagger * /course/:courseId/roles/{name}: @@ -88,7 +85,6 @@ Router.get('/:id', isAuthorized('roleViewAll'), asInt(), RoleController.detail) */ Router.get('/:roleName', isAuthorized('enrolled'), RoleController.detailByName) - /** * @swagger * /course/:courseId/roles: diff --git a/devU-api/src/entities/role/role.service.ts b/devU-api/src/entities/role/role.service.ts index f28f9d10..79e4da51 100644 --- a/devU-api/src/entities/role/role.service.ts +++ b/devU-api/src/entities/role/role.service.ts @@ -1,6 +1,6 @@ -import {getRepository, IsNull} from 'typeorm' +import { getRepository, IsNull } from 'typeorm' -import {Role as RoleType} from 'devu-shared-modules' +import { Role as RoleType } from 'devu-shared-modules' import Role from './role.model' import Defaults from './role.defaults' @@ -8,49 +8,87 @@ import Defaults from './role.defaults' const connect = () => getRepository(Role) export async function create(role: RoleType) { - return await connect().save(role) + return await connect().save(role) } export async function update(role: RoleType) { - const {id, name, assignmentViewAll, assignmentEditAll, assignmentViewReleased, courseEdit, courseViewAll, enrolled, roleEditAll, roleViewAll, roleViewSelf, scoresEditAll, scoresViewAll, scoresViewSelfReleased, submissionChangeState, submissionCreateAll, submissionCreateSelf, submissionViewAll, userCourseEditAll} = role + const { + id, + name, + assignmentViewAll, + assignmentEditAll, + assignmentViewReleased, + courseEdit, + courseViewAll, + enrolled, + roleEditAll, + roleViewAll, + roleViewSelf, + scoresEditAll, + scoresViewAll, + scoresViewSelfReleased, + submissionChangeState, + submissionCreateAll, + submissionCreateSelf, + submissionViewAll, + userCourseEditAll, + } = role - if (!id) throw new Error('Missing Id') + if (!id) throw new Error('Missing Id') - return await connect().update(id, {name, assignmentViewAll, assignmentEditAll, assignmentViewReleased, courseEdit, courseViewAll, enrolled, roleEditAll, roleViewAll, roleViewSelf, scoresEditAll, scoresViewAll, scoresViewSelfReleased, submissionChangeState, submissionCreateAll, submissionCreateSelf, submissionViewAll, userCourseEditAll}) + return await connect().update(id, { + name, + assignmentViewAll, + assignmentEditAll, + assignmentViewReleased, + courseEdit, + courseViewAll, + enrolled, + roleEditAll, + roleViewAll, + roleViewSelf, + scoresEditAll, + scoresViewAll, + scoresViewSelfReleased, + submissionChangeState, + submissionCreateAll, + submissionCreateSelf, + submissionViewAll, + userCourseEditAll, + }) } export async function _delete(id: number) { - return await connect().softDelete({id, deletedAt: IsNull()}) + return await connect().softDelete({ id, deletedAt: IsNull() }) } export async function retrieve(id: number) { - return await connect().findOne({id, deletedAt: IsNull()}) + return await connect().findOne({ id, deletedAt: IsNull() }) } export async function retrieveByCourseAndName(courseId: number, name: string) { - return await connect().findOne({'courseId': courseId, 'name': name, deletedAt: IsNull()}) + return await connect().findOne({ courseId: courseId, name: name, deletedAt: IsNull() }) } - export function retrieveDefaultByName(name: string) { - return Defaults.get(name) + return Defaults.get(name) } export async function listAll() { - return await connect().find({deletedAt: IsNull()}) + return await connect().find({ deletedAt: IsNull() }) } export async function listByCourse(courseId: number) { - return await connect().find({'courseId': courseId, deletedAt: IsNull()}) + return await connect().find({ courseId: courseId, deletedAt: IsNull() }) } export default { - create, - retrieve, - retrieveByCourseAndName, - retrieveDefaultByName, - update, - _delete, - listAll, - listByCourse, + create, + retrieve, + retrieveByCourseAndName, + retrieveDefaultByName, + update, + _delete, + listAll, + listByCourse, } diff --git a/devU-api/src/entities/role/role.validator.ts b/devU-api/src/entities/role/role.validator.ts index 7c1ae011..4b8e00a9 100644 --- a/devU-api/src/entities/role/role.validator.ts +++ b/devU-api/src/entities/role/role.validator.ts @@ -1,4 +1,4 @@ -import {check} from 'express-validator' +import { check } from 'express-validator' import validate from '../../middleware/validator/generic.validator' @@ -21,10 +21,26 @@ const submissionCreateSelf = check('submissionCreateSelf').isBoolean() const submissionViewAll = check('submissionViewAll').isBoolean() const userCourseEditAll = check('userCourseEditAll').isBoolean() - -const validator = [name, assignmentViewAll, assignmentEditAll, assignmentViewReleased, courseEdit, courseViewAll, - enrolled, roleEditAll, roleViewAll, roleViewSelf, scoresEditAll, scoresViewAll, scoresViewSelfReleased, - submissionChangeState, submissionCreateAll, submissionCreateSelf, submissionViewAll, userCourseEditAll, validate] - +const validator = [ + name, + assignmentViewAll, + assignmentEditAll, + assignmentViewReleased, + courseEdit, + courseViewAll, + enrolled, + roleEditAll, + roleViewAll, + roleViewSelf, + scoresEditAll, + scoresViewAll, + scoresViewSelfReleased, + submissionChangeState, + submissionCreateAll, + submissionCreateSelf, + submissionViewAll, + userCourseEditAll, + validate, +] export default validator diff --git a/devU-api/src/entities/role/tests/role.controller.test.ts b/devU-api/src/entities/role/tests/role.controller.test.ts index aafcfc95..2e09394a 100644 --- a/devU-api/src/entities/role/tests/role.controller.test.ts +++ b/devU-api/src/entities/role/tests/role.controller.test.ts @@ -1,3 +1,3 @@ // TODO: Role testing. Lots of it! -describe('this is where tests will go', () => {}) \ No newline at end of file +describe('this is where tests will go', () => {}) diff --git a/devU-api/src/entities/role/tests/role.serializer.test.ts b/devU-api/src/entities/role/tests/role.serializer.test.ts index aafcfc95..2e09394a 100644 --- a/devU-api/src/entities/role/tests/role.serializer.test.ts +++ b/devU-api/src/entities/role/tests/role.serializer.test.ts @@ -1,3 +1,3 @@ // TODO: Role testing. Lots of it! -describe('this is where tests will go', () => {}) \ No newline at end of file +describe('this is where tests will go', () => {}) diff --git a/devU-api/src/entities/submission/submission.controller.ts b/devU-api/src/entities/submission/submission.controller.ts index c0bc963b..55cd9d40 100644 --- a/devU-api/src/entities/submission/submission.controller.ts +++ b/devU-api/src/entities/submission/submission.controller.ts @@ -51,21 +51,21 @@ export async function post(req: Request, res: Response, next: NextFunction) { const response = serialize(submission) res.status(201).json(response) - } catch (err:any) { + } catch (err: any) { next(err) } } export async function revoke(req: Request, res: Response, next: NextFunction) { try { // TODO: Revoke a submission - } catch (err:any) { + } catch (err: any) { next(err) } } export async function unrevoke(req: Request, res: Response, next: NextFunction) { try { // TODO: Unrevoke a submission - } catch (err:any) { + } catch (err: any) { next(err) } } diff --git a/devU-api/src/entities/submission/submission.model.ts b/devU-api/src/entities/submission/submission.model.ts index 8dae3b14..8fbc0c02 100644 --- a/devU-api/src/entities/submission/submission.model.ts +++ b/devU-api/src/entities/submission/submission.model.ts @@ -80,5 +80,4 @@ export default class SubmissionModel { @JoinColumn({ name: 'submitted_by' }) @ManyToOne(() => UserModel) submittedBy: number - } diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index d254365f..99be1d2d 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -4,8 +4,8 @@ import Multer from 'multer' // Middleware import validator from '../submission/submission.validator' -import {asInt} from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import { asInt } from '../../middleware/validator/generic.validator' +import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import SubmissionController from '../submission/submission.controller' @@ -75,10 +75,9 @@ Router.get('/:id', isAuthorized('enrolled'), asInt(), SubmissionController.detai * schema: * $ref: '#/components/schemas/Submission' */ -Router.post('/', isAuthorized('enrolled'), upload.single("files"), validator, SubmissionController.post) +Router.post('/', isAuthorized('enrolled'), upload.single('files'), validator, SubmissionController.post) // TODO: submissionCreateSelf or submissionCreateAll - /** * @swagger * /course/:courseId/assignment/:assignmentId/submissions/{id}/revoke: @@ -99,7 +98,6 @@ Router.post('/', isAuthorized('enrolled'), upload.single("files"), validator, Su */ Router.put('/:id/revoke', isAuthorized('submissionChangeState'), asInt(), SubmissionController.revoke) - /** * @swagger * /course/:courseId/assignment/:assignmentId/submissions/{id}/unrevoke: @@ -119,7 +117,6 @@ Router.put('/:id/revoke', isAuthorized('submissionChangeState'), asInt(), Submis */ Router.put('/:id/unrevoke', isAuthorized('submissionChangeState'), asInt(), SubmissionController.unrevoke) - /** * @swagger * /course/:courseId/assignment/:assignmentId/submissions/{id}: diff --git a/devU-api/src/entities/submission/submission.service.ts b/devU-api/src/entities/submission/submission.service.ts index 48ccbc71..55a73a50 100644 --- a/devU-api/src/entities/submission/submission.service.ts +++ b/devU-api/src/entities/submission/submission.service.ts @@ -13,12 +13,14 @@ const fileConn = () => getRepository(FileModel) export async function create(submission: Submission, file?: Express.Multer.File | undefined) { if (file) { - const bucket: string = await getRepository(CourseModel).findOne({ id: submission.courseId }).then((course) => { - if (course) { - return (course.number + course.semester + course.id).replace(/ /g, '-').toLowerCase() - } - return 'submission' - }) + const bucket: string = await getRepository(CourseModel) + .findOne({ id: submission.courseId }) + .then(course => { + if (course) { + return (course.number + course.semester + course.id).replace(/ /g, '-').toLowerCase() + } + return 'submission' + }) const filename: string = file.originalname const Etag: string = await uploadFile(bucket, file, filename) @@ -51,8 +53,6 @@ export async function retrieve(id: number) { return await submissionConn().findOne({ id, deletedAt: IsNull() }) } - - export async function list(query: any, id: number) { const OrderByMappings = ['id', 'createdAt', 'updatedAt', 'courseId', 'assignmentId', 'submittedBy'] diff --git a/devU-api/src/entities/submission/submission.validator.ts b/devU-api/src/entities/submission/submission.validator.ts index 1d11e833..4930606f 100644 --- a/devU-api/src/entities/submission/submission.validator.ts +++ b/devU-api/src/entities/submission/submission.validator.ts @@ -7,14 +7,15 @@ const assignmentId = check('assignmentId').isNumeric() const courseId = check('courseId').isNumeric() const content = check('content').isString() -const file = check('file').optional({ nullable: true }).custom ((value, { req }) => { - if(req.file !== null){ - if (req.file.size == 0){ - throw new Error('File is empty') +const file = check('file') + .optional({ nullable: true }) + .custom((value, { req }) => { + if (req.file !== null) { + if (req.file.size == 0) { + throw new Error('File is empty') + } } - } - -}) + }) const validator = [courseId, assignmentId, userId, content, file, validate] diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.model.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.model.ts index 8d668bd9..a8065ad9 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.model.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.model.ts @@ -18,7 +18,7 @@ export default class SubmissionProblemScoreModel { * @swagger * tags: * - name: SubmissionProblemScores - * description: + * description: * components: * schemas: * SubmissionProblemScore: @@ -46,7 +46,6 @@ export default class SubmissionProblemScoreModel { @ManyToOne(() => SubmissionModel) submissionId: number - @Column({ name: 'assignment_problem_id' }) @JoinColumn({ name: 'assignment_problem_id' }) @ManyToOne(() => AssignmentProblemModel) diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts index be746f7c..3893a11c 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts @@ -3,13 +3,12 @@ import express from 'express' // Middleware import validator from '../submissionProblemScore/submissionProblemScore.validator' -import {asInt} from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import { asInt } from '../../middleware/validator/generic.validator' +import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import SubmissionProblemScoreController from '../submissionProblemScore/submissionProblemScore.controller' - const Router = express.Router() // TODO: get by assignment and user @@ -32,10 +31,14 @@ const Router = express.Router() * schema: * type: integer */ -Router.get('/submission/:submissionId', asInt('submissionId'), isAuthorized('enrolled'), SubmissionProblemScoreController.get) +Router.get( + '/submission/:submissionId', + asInt('submissionId'), + isAuthorized('enrolled'), + SubmissionProblemScoreController.get +) // TODO: scoresViewAll or scoresViewSelfReleased by the submission owner - /** * @swagger * /course/:courseId/assignment/:assignmentId/submission-problem-scores/detail/{id}: diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts index 3d5f7b29..2f088109 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts @@ -1,39 +1,40 @@ -import {getRepository, IsNull} from 'typeorm' +import { getRepository, IsNull } from 'typeorm' -import {SubmissionProblemScore} from 'devu-shared-modules' +import { SubmissionProblemScore } from 'devu-shared-modules' import SubmissionProblemScoreModel from './submissionProblemScore.model' const connect = () => getRepository(SubmissionProblemScoreModel) export async function create(submissionProblemScore: SubmissionProblemScore) { - return await connect().save(submissionProblemScore) + return await connect().save(submissionProblemScore) } export async function update(submissionProblemScore: SubmissionProblemScore) { - const {id, submissionId, assignmentProblemId, score, feedback, releasedAt} = submissionProblemScore + const { id, submissionId, assignmentProblemId, score, feedback, releasedAt } = submissionProblemScore - if (!id) throw new Error('Missing Id') + if (!id) throw new Error('Missing Id') - return await connect().update(id, {submissionId, assignmentProblemId, score, feedback, releasedAt}) + return await connect().update(id, { submissionId, assignmentProblemId, score, feedback, releasedAt }) } export async function _delete(id: number) { - return await connect().softDelete({id, deletedAt: IsNull()}) + return await connect().softDelete({ id, deletedAt: IsNull() }) } -export async function retrieve(id: number, courseId: number) { // TODO: Submission Problem Score doesn't know its courseId (neither does submission score). Best way to ensure the request is authorized based on course permissions? - return await connect().findOne({id, deletedAt: IsNull()}) +export async function retrieve(id: number, courseId: number) { + // TODO: Submission Problem Score doesn't know its courseId (neither does submission score). Best way to ensure the request is authorized based on course permissions? + return await connect().findOne({ id, deletedAt: IsNull() }) } export async function list(submissionId: number) { - return await connect().find({submissionId: submissionId, deletedAt: IsNull()}) + return await connect().find({ submissionId: submissionId, deletedAt: IsNull() }) } export default { - create, - retrieve, - update, - _delete, - list, + create, + retrieve, + update, + _delete, + list, } diff --git a/devU-api/src/entities/submissionProblemScore/tests/submissionProblemScore.serializer.test.ts b/devU-api/src/entities/submissionProblemScore/tests/submissionProblemScore.serializer.test.ts index 001fb13c..b5f534b6 100644 --- a/devU-api/src/entities/submissionProblemScore/tests/submissionProblemScore.serializer.test.ts +++ b/devU-api/src/entities/submissionProblemScore/tests/submissionProblemScore.serializer.test.ts @@ -1,6 +1,6 @@ import { serialize } from '../submissionProblemScore.serializer' -import SubmissionProblemScoreModel from "../submissionProblemScore.model" +import SubmissionProblemScoreModel from '../submissionProblemScore.model' import Testing from '../../../utils/testing.utils' diff --git a/devU-api/src/entities/submissionScore/submissionScore.controller.ts b/devU-api/src/entities/submissionScore/submissionScore.controller.ts index 04ac2bc4..bd8506cb 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.controller.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.controller.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from 'express' import submissionScoreService from './submissionScore.service' -import SubmissionScoreService from './submissionScore.service' //why is this twice im not even gonna touch it lmao +import SubmissionScoreService from './submissionScore.service' //why is this twice im not even gonna touch it lmao import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' diff --git a/devU-api/src/entities/submissionScore/submissionScore.model.ts b/devU-api/src/entities/submissionScore/submissionScore.model.ts index 041de4d6..51823e81 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.model.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.model.ts @@ -17,7 +17,7 @@ export default class SubmissionScoreModel { * @swagger * tags: * - name: SubmissionScores - * description: + * description: * components: * schemas: * SubmissionScore: diff --git a/devU-api/src/entities/submissionScore/submissionScore.router.ts b/devU-api/src/entities/submissionScore/submissionScore.router.ts index 17efa28d..93e50be5 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.router.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.router.ts @@ -3,8 +3,8 @@ import express from 'express' // Middleware import validator from './submissionScore.validator' -import {asInt} from '../../middleware/validator/generic.validator' -import {extractOwnerByPathParam, isAuthorized} from "../../authorization/authorization.middleware"; +import { asInt } from '../../middleware/validator/generic.validator' +import { extractOwnerByPathParam, isAuthorized } from '../../authorization/authorization.middleware' // Controller import SubmissionScoreController from './submissionScore.controller' @@ -41,8 +41,13 @@ Router.get('/', isAuthorized('scoresViewAll'), SubmissionScoreController.get) * schema: * type: integer */ -Router.get('/user/:userId', asInt('userId'), extractOwnerByPathParam('userId'), isAuthorized('scoresViewAll', 'scoresViewSelfReleased'), SubmissionScoreController.getByUser) - +Router.get( + '/user/:userId', + asInt('userId'), + extractOwnerByPathParam('userId'), + isAuthorized('scoresViewAll', 'scoresViewSelfReleased'), + SubmissionScoreController.getByUser +) /** * @swagger diff --git a/devU-api/src/entities/submissionScore/submissionScore.service.ts b/devU-api/src/entities/submissionScore/submissionScore.service.ts index ee0a4c3b..d1832a5d 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.service.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.service.ts @@ -11,13 +11,7 @@ export async function create(submissionScore: SubmissionScore) { } export async function update(submissionScore: SubmissionScore) { - const { - id, - submissionId, - score, - feedback, - releasedAt, - } = submissionScore + const { id, submissionId, score, feedback, releasedAt } = submissionScore if (!id) throw new Error('Missing Id') @@ -41,8 +35,8 @@ export async function list(submissionId?: number) { const options: FindManyOptions = { where: { deletedAt: IsNull(), - ...(submissionId !== undefined && {submissionId}), - } + ...(submissionId !== undefined && { submissionId }), + }, } return await connect().find(options) } @@ -53,7 +47,7 @@ export async function listByUser(userId: number, assignmentId: number) { userId: userId, assignmentId: assignmentId, deletedAt: IsNull(), - } + }, } return await connect().find(options) } diff --git a/devU-api/src/entities/user/user.controller.ts b/devU-api/src/entities/user/user.controller.ts index dc312c3c..03d5aa3b 100644 --- a/devU-api/src/entities/user/user.controller.ts +++ b/devU-api/src/entities/user/user.controller.ts @@ -38,7 +38,9 @@ export async function getByCourse(req: Request, res: Response, next: NextFunctio const userRole = req.query.role const users = await UserService.listByCourse(courseId, userRole as string) - const response = users.map(u => { if (u) return serialize(u) }) + const response = users.map(u => { + if (u) return serialize(u) + }) res.status(200).json(response) } catch (err) { @@ -46,7 +48,6 @@ export async function getByCourse(req: Request, res: Response, next: NextFunctio } } - export async function post(req: Request, res: Response, next: NextFunction) { try { const user = await UserService.create(req.body) diff --git a/devU-api/src/entities/user/user.model.ts b/devU-api/src/entities/user/user.model.ts index 3dffae1e..59d0c04a 100644 --- a/devU-api/src/entities/user/user.model.ts +++ b/devU-api/src/entities/user/user.model.ts @@ -6,7 +6,7 @@ export default class UserModel { * @swagger * tags: * - name: Users - * description: + * description: * components: * schemas: * User: diff --git a/devU-api/src/entities/user/user.router.ts b/devU-api/src/entities/user/user.router.ts index fa7f95f5..61bdaf72 100644 --- a/devU-api/src/entities/user/user.router.ts +++ b/devU-api/src/entities/user/user.router.ts @@ -1,8 +1,8 @@ import express from 'express' import validator from './user.validator' -import {asInt} from '../../middleware/validator/generic.validator' -import {isAuthorized} from "../../authorization/authorization.middleware"; +import { asInt } from '../../middleware/validator/generic.validator' +import { isAuthorized } from '../../authorization/authorization.middleware' import UserController from './user.controller' @@ -43,7 +43,6 @@ Router.get('/:id', asInt(), UserController.detail) // self or admin // TODO: Add top level authorization. Currently, all authorization is at the course level - /** * @swagger * /users/course/{course-id}: diff --git a/devU-api/src/entities/user/user.service.ts b/devU-api/src/entities/user/user.service.ts index abc0bb13..09a37d18 100644 --- a/devU-api/src/entities/user/user.service.ts +++ b/devU-api/src/entities/user/user.service.ts @@ -35,10 +35,8 @@ export async function list() { export async function listByCourse(courseId: number, userRole?: string) { const userCourses = await UserCourseService.listByCourse(courseId) const userPromises = userCourses - .filter(uc => !(userRole) || uc.role === userRole) - .map(uc => ( - connect().findOne({ id: uc.userId, deletedAt: IsNull()}) - )) + .filter(uc => !userRole || uc.role === userRole) + .map(uc => connect().findOne({ id: uc.userId, deletedAt: IsNull() })) return await Promise.all(userPromises) } diff --git a/devU-api/src/entities/userCourse/userCourse.controller.ts b/devU-api/src/entities/userCourse/userCourse.controller.ts index b112f828..dde313be 100644 --- a/devU-api/src/entities/userCourse/userCourse.controller.ts +++ b/devU-api/src/entities/userCourse/userCourse.controller.ts @@ -1,111 +1,110 @@ -import {Request, Response, NextFunction} from 'express' +import { Request, Response, NextFunction } from 'express' import UserCourseService from './userCourse.service' -import {serialize} from './userCourse.serializer' +import { serialize } from './userCourse.serializer' -import {GenericResponse, NotFound, Updated} from '../../utils/apiResponse.utils' +import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' export async function getAll(req: Request, res: Response, next: NextFunction) { - try { + try { + const userCourses = await UserCourseService.listAll() - const userCourses = await UserCourseService.listAll() - - res.status(200).json(userCourses.map(serialize)) - } catch (err) { - next(err) - } + res.status(200).json(userCourses.map(serialize)) + } catch (err) { + next(err) + } } export async function get(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const userCourses = await UserCourseService.list(id) - - res.status(200).json(userCourses.map(serialize)) - } catch (err) { - next(err) - } + try { + const id = parseInt(req.params.id) + const userCourses = await UserCourseService.list(id) + + res.status(200).json(userCourses.map(serialize)) + } catch (err) { + next(err) + } } export async function getByCourse(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const userCourses = await UserCourseService.listByCourse(id) + try { + const id = parseInt(req.params.id) + const userCourses = await UserCourseService.listByCourse(id) - const response = userCourses.map(serialize) + const response = userCourses.map(serialize) - res.status(200).json(response) - } catch (err) { - next(err) - } + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function detail(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const userCourse = await UserCourseService.retrieve(id) + try { + const id = parseInt(req.params.id) + const userCourse = await UserCourseService.retrieve(id) - if (!userCourse) return res.status(404).json(NotFound) + if (!userCourse) return res.status(404).json(NotFound) - const response = serialize(userCourse) + const response = serialize(userCourse) - res.status(200).json(response) - } catch (err) { - next(err) - } + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function detailByUser(req: Request, res: Response, next: NextFunction) { - try { - const courseId = parseInt(req.params.courseId) - const userId = parseInt(req.params.userId) - const userCourse = await UserCourseService.retrieveByCourseAndUser(courseId, userId) + try { + const courseId = parseInt(req.params.courseId) + const userId = parseInt(req.params.userId) + const userCourse = await UserCourseService.retrieveByCourseAndUser(courseId, userId) - if (!userCourse) return res.status(404).json(NotFound) + if (!userCourse) return res.status(404).json(NotFound) - const response = serialize(userCourse) + const response = serialize(userCourse) - res.status(200).json(response) - } catch (err) { - next(err) - } + res.status(200).json(response) + } catch (err) { + next(err) + } } export async function post(req: Request, res: Response, next: NextFunction) { - try { - const userCourse = await UserCourseService.create(req.body) - const response = serialize(userCourse) - - res.status(201).json(response) - } catch (err) { - res.status(400).json(new GenericResponse(err.message)) - } + try { + const userCourse = await UserCourseService.create(req.body) + const response = serialize(userCourse) + + res.status(201).json(response) + } catch (err) { + res.status(400).json(new GenericResponse(err.message)) + } } export async function put(req: Request, res: Response, next: NextFunction) { - try { - req.body.id = parseInt(req.params.id) - const results = await UserCourseService.update(req.body) + try { + req.body.id = parseInt(req.params.id) + const results = await UserCourseService.update(req.body) - if (!results.affected) return res.status(404).json(NotFound) + if (!results.affected) return res.status(404).json(NotFound) - res.status(200).json(Updated) - } catch (err) { - next(err) - } + res.status(200).json(Updated) + } catch (err) { + next(err) + } } export async function _delete(req: Request, res: Response, next: NextFunction) { - try { - const id = parseInt(req.params.id) - const results = await UserCourseService._delete(id) + try { + const id = parseInt(req.params.id) + const results = await UserCourseService._delete(id) - if (!results.affected) return res.status(404).json(NotFound) + if (!results.affected) return res.status(404).json(NotFound) - res.status(204).send() - } catch (err) { - next(err) - } + res.status(204).send() + } catch (err) { + next(err) + } } -export default {get, getByCourse, getAll, detail, detailByUser, post, put, _delete} +export default { get, getByCourse, getAll, detail, detailByUser, post, put, _delete } diff --git a/devU-api/src/entities/userCourse/userCourse.model.ts b/devU-api/src/entities/userCourse/userCourse.model.ts index eb5e07f2..76d59122 100644 --- a/devU-api/src/entities/userCourse/userCourse.model.ts +++ b/devU-api/src/entities/userCourse/userCourse.model.ts @@ -9,7 +9,6 @@ import { DeleteDateColumn, } from 'typeorm' - import UserModel from '../user/user.model' import CourseModel from '../course/course.model' @@ -19,7 +18,7 @@ export default class UserCourseModel { * @swagger * tags: * - name: UserCourses - * description: + * description: * components: * schemas: * UserCourse: diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index 8432bb3c..7c8d480a 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -3,15 +3,14 @@ import express from 'express' // Middleware import validator from './userCourse.validator' -import {asInt} from '../../middleware/validator/generic.validator' -import {extractOwnerByPathParam, isAuthorized} from "../../authorization/authorization.middleware"; +import { asInt } from '../../middleware/validator/generic.validator' +import { extractOwnerByPathParam, isAuthorized } from '../../authorization/authorization.middleware' // Controller import UserCourseController from './userCourse.controller' const Router = express.Router() - /** * @swagger * /course/:courseId/user-courses/: @@ -71,8 +70,13 @@ Router.get('/:id', isAuthorized('courseViewAll'), asInt(), UserCourseController. * schema: * type: integer */ -Router.get('/user/:userId', extractOwnerByPathParam('userId'), isAuthorized('courseViewAll', 'enrolled'), asInt('userId'), UserCourseController.detailByUser) - +Router.get( + '/user/:userId', + extractOwnerByPathParam('userId'), + isAuthorized('courseViewAll', 'enrolled'), + asInt('userId'), + UserCourseController.detailByUser +) /** * @swagger @@ -93,7 +97,6 @@ Router.get('/user/:userId', extractOwnerByPathParam('userId'), isAuthorized('cou Router.post('/', validator, UserCourseController.post) // TODO: userCourseEditAll eventually. For now, allow self enroll - /** * @swagger * /course/:courseId/users-courses/{id}: diff --git a/devU-api/src/entities/userCourse/userCourse.service.ts b/devU-api/src/entities/userCourse/userCourse.service.ts index 9453dd50..8372a27d 100644 --- a/devU-api/src/entities/userCourse/userCourse.service.ts +++ b/devU-api/src/entities/userCourse/userCourse.service.ts @@ -27,17 +27,19 @@ export async function retrieve(id: number) { } export async function retrieveByCourseAndUser(courseId: number, userId: number) { - return await connect().findOne({ 'courseId': courseId, 'userId': userId, deletedAt: IsNull() }) + return await connect().findOne({ courseId: courseId, userId: userId, deletedAt: IsNull() }) } -export async function list(userId: number) { // TODO: look into/test this +export async function list(userId: number) { + // TODO: look into/test this return await connect().find({ userId, deletedAt: IsNull() }) } export async function listAll() { return await connect().find({ deletedAt: IsNull() }) } -export async function listByCourse(courseId: number) { // TODO: look into/test this +export async function listByCourse(courseId: number) { + // TODO: look into/test this return await connect().find({ courseId, deletedAt: IsNull() }) } diff --git a/devU-api/src/environment.ts b/devU-api/src/environment.ts index f1743f01..d287dddc 100644 --- a/devU-api/src/environment.ts +++ b/devU-api/src/environment.ts @@ -51,8 +51,10 @@ const refreshTokenBuffer = load('auth.jwt.refreshTokenExpirationBufferSeconds') // if it is undefined it is running on dev machine const isDocker = !(process.env.dev === undefined) -if (isDocker && process.env.TANGO_KEY === undefined){ - throw Error('Tango key not found.\nMake sure to set environment variable TANGO_KEY in the api service in docker-compose') +if (isDocker && process.env.TANGO_KEY === undefined) { + throw Error( + 'Tango key not found.\nMake sure to set environment variable TANGO_KEY in the api service in docker-compose' + ) } const environment = { @@ -61,7 +63,7 @@ const environment = { clientUrl: (process.env.CLIENT_URL || load('api.clientUrl') || 'http://localhost:9000') as string, // Database settings - dbHost: isDocker ? load('database.host') : 'localhost' as string, + dbHost: isDocker ? load('database.host') : ('localhost' as string), dbUsername: (load('database.username') || 'typescript_user') as string, dbPassword: (load('database.password') || 'password') as string, database: (load('database.name') || 'typescript_api') as string, @@ -73,10 +75,9 @@ const environment = { // dbPassword: ('password') as string, // database: ('typescript_api') as string, - // MinIO setting - minioHost: isDocker ? load('minio.host') : 'localhost' as string, - minioPort: isDocker ? load('minio.port') : 9002 as number, + minioHost: isDocker ? load('minio.host') : ('localhost' as string), + minioPort: isDocker ? load('minio.port') : (9002 as number), minioUsername: (load('minio.username') || 'typescript_user') as string, minioPassword: (load('minio.password') || 'changeMe') as string, diff --git a/devU-api/src/fileStorage.ts b/devU-api/src/fileStorage.ts index 00d9bd19..26a7f30d 100644 --- a/devU-api/src/fileStorage.ts +++ b/devU-api/src/fileStorage.ts @@ -30,12 +30,12 @@ export async function initializeMinio(inputBucketName?: string) { } }) return - }else{ + } else { for (const bucketName of Object.values(BucketNames)) { const bucketExists = await minioClient.bucketExists(bucketName) - + if (bucketExists) continue - + minioClient.makeBucket(bucketName, 'us-east-1', function (err) { if (err) { throw new Error(`Error creating MinIO bucket '${bucketName}'`) @@ -45,12 +45,11 @@ export async function initializeMinio(inputBucketName?: string) { } } - -export async function uploadFile(bucketName: string, file: Express.Multer.File, filename:string): Promise { +export async function uploadFile(bucketName: string, file: Express.Multer.File, filename: string): Promise { return new Promise((resolve, reject) => { minioClient.putObject(bucketName, filename, file.buffer, (err, etag) => { if (err) { - reject(new Error('File failed to upload because'+err.message)) + reject(new Error('File failed to upload because' + err.message)) } else { resolve(etag.etag) } @@ -64,10 +63,10 @@ export async function downloadFile(bucketName: string, filename: string): Promis minioClient.getObject(bucketName, filename, (err, dataStream) => { if (err) { - reject(new Error('File failed to download from MinIO because'+err.message)) + reject(new Error('File failed to download from MinIO because' + err.message)) } - dataStream.on('data', (chunk:any) => { + dataStream.on('data', (chunk: any) => { fileData.push(chunk) }) @@ -76,4 +75,4 @@ export async function downloadFile(bucketName: string, filename: string): Promis }) }) }) -} \ No newline at end of file +} diff --git a/devU-api/src/fileUpload/fileUpload.controller.ts b/devU-api/src/fileUpload/fileUpload.controller.ts index 2663b065..344ae486 100644 --- a/devU-api/src/fileUpload/fileUpload.controller.ts +++ b/devU-api/src/fileUpload/fileUpload.controller.ts @@ -1,39 +1,37 @@ -import {NextFunction, Request, Response} from 'express' +import { NextFunction, Request, Response } from 'express' import FileUploadService from './fileUpload.service' -import {serialize} from './fileUpload.serializer' -import {fileUploadTypes} from '../../devu-shared-modules' +import { serialize } from './fileUpload.serializer' +import { fileUploadTypes } from '../../devu-shared-modules' -import {GenericResponse, NotFound} from '../utils/apiResponse.utils' -import {mappingFieldWithBucket} from '../utils/fileUpload.utils' +import { GenericResponse, NotFound } from '../utils/apiResponse.utils' +import { mappingFieldWithBucket } from '../utils/fileUpload.utils' export async function get(req: Request, res: Response, next: NextFunction) { - try { - const bucketName = req.params.bucketName - const fileList = await FileUploadService.list(bucketName) - - res.status(200).json(fileList) - } catch (err) { - next(err) - } + try { + const bucketName = req.params.bucketName + const fileList = await FileUploadService.list(bucketName) + + res.status(200).json(fileList) + } catch (err) { + next(err) + } } - export async function detail(req: Request, res: Response, next: NextFunction) { - try { - const bucketName = req.params.bucketName - const fileName = req.params.fileName - const file: Buffer = await FileUploadService.retrieve(bucketName, fileName) + try { + const bucketName = req.params.bucketName + const fileName = req.params.fileName + const file: Buffer = await FileUploadService.retrieve(bucketName, fileName) - if (!file) return res.status(404).json(NotFound) + if (!file) return res.status(404).json(NotFound) - res.status(200).json(file) - } catch (err) { - next(err) - } + res.status(200).json(file) + } catch (err) { + next(err) + } } - /* This function is deciding which bucket to upload the file however, it only supports one bucket at the moment. @@ -42,34 +40,31 @@ export async function detail(req: Request, res: Response, next: NextFunction) { Marking for discussion */ export async function post(req: Request, res: Response, next: NextFunction) { - try { - if (!req.currentUser?.userId) return res.status(400).json(new GenericResponse('Request requires auth')) - if (!req.files) return res.status(400).json(new GenericResponse('No file uploaded')); - - const files = req.files as { [fileType: string]: Express.Multer.File[] }; - if (!files) return res.status(400).json( - new GenericResponse('Must upload files as an object wih the filetype as keys') - ); - // Input field name needs to update to adjust new pattern of bucket names - const inputFileField = fileUploadTypes.find(type => files[type]) - if (inputFileField === undefined) return res.status(403).json(new GenericResponse('File type not supported')) - const bucketName = mappingFieldWithBucket(inputFileField) - const userId = req.currentUser?.userId - - const fileUpload = await FileUploadService.create(files[inputFileField], bucketName, userId) - - const response = serialize(fileUpload) - - res.status(201).json(response) - } catch (err: any) { - res.status(400).json(new GenericResponse(err.message)) - } + try { + if (!req.currentUser?.userId) return res.status(400).json(new GenericResponse('Request requires auth')) + if (!req.files) return res.status(400).json(new GenericResponse('No file uploaded')) + + const files = req.files as { [fileType: string]: Express.Multer.File[] } + if (!files) + return res.status(400).json(new GenericResponse('Must upload files as an object wih the filetype as keys')) + // Input field name needs to update to adjust new pattern of bucket names + const inputFileField = fileUploadTypes.find(type => files[type]) + if (inputFileField === undefined) return res.status(403).json(new GenericResponse('File type not supported')) + const bucketName = mappingFieldWithBucket(inputFileField) + const userId = req.currentUser?.userId + + const fileUpload = await FileUploadService.create(files[inputFileField], bucketName, userId) + + const response = serialize(fileUpload) + + res.status(201).json(response) + } catch (err: any) { + res.status(400).json(new GenericResponse(err.message)) + } } - - export default { - get, - detail, - post, -} \ No newline at end of file + get, + detail, + post, +} diff --git a/devU-api/src/fileUpload/fileUpload.model.ts b/devU-api/src/fileUpload/fileUpload.model.ts index a4590843..ccca8ce8 100644 --- a/devU-api/src/fileUpload/fileUpload.model.ts +++ b/devU-api/src/fileUpload/fileUpload.model.ts @@ -1,55 +1,50 @@ import { - JoinColumn, - ManyToOne, - Entity, - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, - DeleteDateColumn, + JoinColumn, + ManyToOne, + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, } from 'typeorm' import AssignmentModel from '../entities/assignment/assignment.model' import CourseModel from '../entities/course/course.model' import UserModel from '../entities/user/user.model' - @Entity('FilesAuth') - export default class FileModel { - @PrimaryGeneratedColumn() - id: number - - @CreateDateColumn({ name: 'created_at' }) - createdAt: Date - - @UpdateDateColumn({ name: 'updated_at' }) - updatedAt: Date - - @DeleteDateColumn({ name: 'deleted_at' }) - deletedAt?: Date - - @Column({ name: 'course_id' }) - @JoinColumn({ name: 'course_id' }) - @ManyToOne(() => CourseModel) - courseId: number - - @Column({ name: 'assignment_id' }) - @JoinColumn({ name: 'assignment_id' }) - @ManyToOne(() => AssignmentModel) - assignmentId: number - - @Column({ name: 'user_id' }) - @JoinColumn({ name: 'user_id' }) - @ManyToOne(() => UserModel) - userId: number - - @Column({ name: 'filename', length: 128 }) - filename: string - - @Column({ name: 'bucket', length: 64 }) - fieldName: string - + @PrimaryGeneratedColumn() + id: number -} + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date + + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date + @Column({ name: 'course_id' }) + @JoinColumn({ name: 'course_id' }) + @ManyToOne(() => CourseModel) + courseId: number + + @Column({ name: 'assignment_id' }) + @JoinColumn({ name: 'assignment_id' }) + @ManyToOne(() => AssignmentModel) + assignmentId: number + + @Column({ name: 'user_id' }) + @JoinColumn({ name: 'user_id' }) + @ManyToOne(() => UserModel) + userId: number + + @Column({ name: 'filename', length: 128 }) + filename: string + + @Column({ name: 'bucket', length: 64 }) + fieldName: string +} diff --git a/devU-api/src/fileUpload/fileUpload.router.ts b/devU-api/src/fileUpload/fileUpload.router.ts index 81752a24..7e00d61c 100644 --- a/devU-api/src/fileUpload/fileUpload.router.ts +++ b/devU-api/src/fileUpload/fileUpload.router.ts @@ -1,14 +1,14 @@ -import express from 'express'; -import multer, {Field} from 'multer' +import express from 'express' +import multer, { Field } from 'multer' -import validator from './fileUpload.validator'; +import validator from './fileUpload.validator' -import FileUploadController from './fileUpload.controller'; -import {fileUploadTypes} from '../../devu-shared-modules'; -import {isAuthorized} from "../authorization/authorization.middleware"; +import FileUploadController from './fileUpload.controller' +import { fileUploadTypes } from '../../devu-shared-modules' +import { isAuthorized } from '../authorization/authorization.middleware' -const Router = express.Router(); -const upload = multer(); +const Router = express.Router() +const upload = multer() /* * This is a list of all the fields that can be uploaded @@ -21,14 +21,14 @@ const upload = multer(); return {name} }) */ -const fields: Field[] = fileUploadTypes.map(name => ({name})) +const fields: Field[] = fileUploadTypes.map(name => ({ name })) /** * @swagger * /course/:courseId/file-upload/{bucketName}: * get: * summary: Retrieve a list of all files in the bucket */ -Router.get('/:bucketName', isAuthorized('courseViewAll'), FileUploadController.get); +Router.get('/:bucketName', isAuthorized('courseViewAll'), FileUploadController.get) /** * @swagger @@ -36,7 +36,7 @@ Router.get('/:bucketName', isAuthorized('courseViewAll'), FileUploadController.g * get: * summary: Retrieve a single file from the bucket */ -Router.get('/:bucketName/:fileName', isAuthorized('courseViewAll'), FileUploadController.detail); +Router.get('/:bucketName/:fileName', isAuthorized('courseViewAll'), FileUploadController.detail) /** * @swagger @@ -44,7 +44,6 @@ Router.get('/:bucketName/:fileName', isAuthorized('courseViewAll'), FileUploadCo * post: * summary: Upload a new file to the bucket */ -Router.post('/', isAuthorized('enrolled'), upload.fields(fields), validator, FileUploadController.post); +Router.post('/', isAuthorized('enrolled'), upload.fields(fields), validator, FileUploadController.post) - -export default Router; \ No newline at end of file +export default Router diff --git a/devU-api/src/fileUpload/fileUpload.serializer.ts b/devU-api/src/fileUpload/fileUpload.serializer.ts index 0b66634e..1aa70a33 100644 --- a/devU-api/src/fileUpload/fileUpload.serializer.ts +++ b/devU-api/src/fileUpload/fileUpload.serializer.ts @@ -1,11 +1,10 @@ -import {FileUpload} from "../../devu-shared-modules" - +import { FileUpload } from '../../devu-shared-modules' export function serialize(file: FileUpload): FileUpload { - return { - fieldName: file.fieldName, - originalName: file.originalName, - filename: file.filename, - etags: file.etags - } + return { + fieldName: file.fieldName, + originalName: file.originalName, + filename: file.filename, + etags: file.etags, + } } diff --git a/devU-api/src/fileUpload/fileUpload.service.ts b/devU-api/src/fileUpload/fileUpload.service.ts index 428762f3..98b10c04 100644 --- a/devU-api/src/fileUpload/fileUpload.service.ts +++ b/devU-api/src/fileUpload/fileUpload.service.ts @@ -4,78 +4,78 @@ import { generateFilename } from '../utils/fileUpload.utils' import { minioClient, uploadFile } from '../fileStorage' export async function create(files: Express.Multer.File[], bucketName: string, userId: number) { - try { - let fileName: string = "" - let originalName: string = "" + try { + let fileName: string = '' + let originalName: string = '' let fieldName: string = files[0].fieldname - let etags: string = "" + let etags: string = '' - await Promise.all(files.map(async (file) => { + await Promise.all( + files.map(async file => { const generatedFileName = generateFilename(file.originalname, userId) const etag = await uploadFile(bucketName, file, generatedFileName) etags = etags ? etags + ', ' + etag : etag fileName = fileName ? fileName + ', ' + generatedFileName : generatedFileName originalName = originalName ? originalName + ', ' + file.originalname : file.originalname - })) + }) + ) let fileUpload: FileUpload = { - fieldName: fieldName, - originalName: originalName, - filename: fileName, - etags: etags, + fieldName: fieldName, + originalName: originalName, + filename: fileName, + etags: etags, } return fileUpload - } catch (error: any) { - throw new Error('Error uploading files: ' + error.message) - } + } catch (error: any) { + throw new Error('Error uploading files: ' + error.message) + } } - export async function retrieve(bucketName: string, fileName: string): Promise { - return new Promise((resolve, reject) => { - minioClient.getObject(bucketName, fileName, function(err, dataStream) { - if (err) { - reject(err) - } - - let file: Buffer[] = [] - dataStream.on('data', function(chunk: Buffer) { - file.push(chunk) - }) - - dataStream.on('end', function() { - resolve(Buffer.concat(file)) - }) - - dataStream.on('error', function(err: Error) { - reject(err) - }) - }) + return new Promise((resolve, reject) => { + minioClient.getObject(bucketName, fileName, function (err, dataStream) { + if (err) { + reject(err) + } + + let file: Buffer[] = [] + dataStream.on('data', function (chunk: Buffer) { + file.push(chunk) + }) + + dataStream.on('end', function () { + resolve(Buffer.concat(file)) + }) + + dataStream.on('error', function (err: Error) { + reject(err) + }) }) + }) } export async function list(bucketName: string) { - return new Promise((resolve, reject) => { - const stream = minioClient.listObjects(bucketName) - let files: object[] = [] - - stream.on('data', function(obj) { - files.push(obj) - }) - - stream.on('error', function(err) { - reject(err) - }) - - stream.on('end', function() { - resolve(files) - }) + return new Promise((resolve, reject) => { + const stream = minioClient.listObjects(bucketName) + let files: object[] = [] + + stream.on('data', function (obj) { + files.push(obj) + }) + + stream.on('error', function (err) { + reject(err) }) -} + stream.on('end', function () { + resolve(files) + }) + }) +} export default { - create, - retrieve, - list, -} \ No newline at end of file + create, + retrieve, + list, +} diff --git a/devU-api/src/fileUpload/fileUpload.validator.ts b/devU-api/src/fileUpload/fileUpload.validator.ts index d4074046..a7d0b819 100644 --- a/devU-api/src/fileUpload/fileUpload.validator.ts +++ b/devU-api/src/fileUpload/fileUpload.validator.ts @@ -3,19 +3,18 @@ import { check } from 'express-validator' import validate from '../middleware/validator/generic.validator' /* - * This file is a validator for the file upload routes. - * It now only checks if the file is uploaded - * It does not check the file type or size because this entity handles multiple files uploads - * This can be added in the future + * This file is a validator for the file upload routes. + * It now only checks if the file is uploaded + * It does not check the file type or size because this entity handles multiple files uploads + * This can be added in the future */ -const fileUploaded = check('files') - .custom((_value, { req }) =>{ - if (!req.files) { - throw new Error('No file uploaded') - } - return true - }) +const fileUploaded = check('files').custom((_value, { req }) => { + if (!req.files) { + throw new Error('No file uploaded') + } + return true +}) const validator = [fileUploaded, validate] diff --git a/devU-api/src/fileUpload/test/fileUpload.controller.test.ts b/devU-api/src/fileUpload/test/fileUpload.controller.test.ts index 7410e0fe..52b5b071 100644 --- a/devU-api/src/fileUpload/test/fileUpload.controller.test.ts +++ b/devU-api/src/fileUpload/test/fileUpload.controller.test.ts @@ -1,4 +1,4 @@ -import {FileUpload} from '../../../devu-shared-modules' +import { FileUpload } from '../../../devu-shared-modules' import controller from '../fileUpload.controller' import FileUploadService from '../fileUpload.service' import Testing from '../../utils/testing.utils' @@ -12,84 +12,79 @@ let mockFile: any let mockedFileUpload: FileUpload let expectedError: Error - describe('FileUploadController', () => { - beforeEach(() => { - req = Testing.fakeRequest() - res = Testing.fakeResponse() - next = Testing.fakeNext() - mockFile = { - studentSubmission: [ - { - fieldname: 'studentSubmission', - originalname: 'test.txt', - encoding: '7bit', - mimetype: 'text/plain', - buffer: Buffer.from('Hello, world!', 'utf-8'), - size: 13, - } - ] - }; - expectedError = new Error('Expected Error') - }) - describe('GET - /file-upload/:bucketName', () => { - describe('200 - Ok', () => { - beforeEach(async () => { - FileUploadService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedFileUpload)) - req.files = mockFile - await controller.get(req, res, next) - }) - - test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) - }) + beforeEach(() => { + req = Testing.fakeRequest() + res = Testing.fakeResponse() + next = Testing.fakeNext() + mockFile = { + studentSubmission: [ + { + fieldname: 'studentSubmission', + originalname: 'test.txt', + encoding: '7bit', + mimetype: 'text/plain', + buffer: Buffer.from('Hello, world!', 'utf-8'), + size: 13, + }, + ], + } + expectedError = new Error('Expected Error') + }) + describe('GET - /file-upload/:bucketName', () => { + describe('200 - Ok', () => { + beforeEach(async () => { + FileUploadService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedFileUpload)) + req.files = mockFile + await controller.get(req, res, next) + }) - describe('400 - Bad request', () => { - test('Next called with expected error', async () => { - FileUploadService.list = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - await controller.get(req, res, next) // what we're testing - expect(next).toBeCalledWith(expectedError) - }) - }) + test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) }) - describe('POST - /file-upload', () => { - describe('201 - Created', () => { - beforeEach(async () => { - req.files = mockFile - await controller.post(req, res, next) - }) - test('Status code is 201', () => { - expect(res.status).toBeCalledWith(201) - }) - }) - describe('400 - Bad request', () => { - beforeEach(async () => { - req.files = undefined - await controller.post(req, res, next) - }) - test('Status code is 400', () => expect(res.status).toBeCalledWith(400)) - }) - - describe('403 - Wrong file type', () => { - beforeEach(async () => { - - req.files = { - WrongType: [ - { - fieldName: 'WrongType', - originalName: 'test.txt', - encoding: '7bit', - mimetype: 'text/plain', - buffer: Buffer.from('Hello, world!', 'utf-8'), - size: 13, - } - ] - }; - await controller.post(req, res, next) - }) - test('Status code is 403', () => expect(res.status).toBeCalledWith(403)) - }) - + describe('400 - Bad request', () => { + test('Next called with expected error', async () => { + FileUploadService.list = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + await controller.get(req, res, next) // what we're testing + expect(next).toBeCalledWith(expectedError) + }) + }) + }) + describe('POST - /file-upload', () => { + describe('201 - Created', () => { + beforeEach(async () => { + req.files = mockFile + await controller.post(req, res, next) + }) + test('Status code is 201', () => { + expect(res.status).toBeCalledWith(201) + }) + }) + describe('400 - Bad request', () => { + beforeEach(async () => { + req.files = undefined + await controller.post(req, res, next) + }) + test('Status code is 400', () => expect(res.status).toBeCalledWith(400)) }) -}) \ No newline at end of file + describe('403 - Wrong file type', () => { + beforeEach(async () => { + req.files = { + WrongType: [ + { + fieldName: 'WrongType', + originalName: 'test.txt', + encoding: '7bit', + mimetype: 'text/plain', + buffer: Buffer.from('Hello, world!', 'utf-8'), + size: 13, + }, + ], + } + await controller.post(req, res, next) + }) + test('Status code is 403', () => expect(res.status).toBeCalledWith(403)) + }) + }) +}) diff --git a/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts b/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts index a62eb147..2ca1e818 100644 --- a/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts +++ b/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts @@ -1,36 +1,33 @@ -import {serialize} from '../fileUpload.serializer'; +import { serialize } from '../fileUpload.serializer' - -import {FileUpload} from '../../../devu-shared-modules' +import { FileUpload } from '../../../devu-shared-modules' let fakeFileUpload: FileUpload describe('fileUpload.serializer', () => { - beforeEach(() => { - let fieldName: string = "submissions" - // const originalNames: string[] = ["test1.txt", "test2.txt", "test3.txt"] - // const fileNames: string[] = ["modified1.txt", "modified2.txt", "modified3.txt"] - // const etags: string[] = ["etag1", "etag2", "etag3"] - const originalName: string = "test1.txt" - const filename: string = "modified1.txt" - const etag: string = "etag1" - - fakeFileUpload = { - fieldName: fieldName, - originalName: originalName, - filename: filename, - etags: etag - } - }); - describe('Serializing fileUpload', () => { - test('fileUpload values exist in the response', () => { - const actualResult = serialize(fakeFileUpload) - expect(actualResult).toBeDefined() - expect(actualResult.fieldName).toEqual(fakeFileUpload.fieldName) - expect(actualResult.originalName).toEqual(fakeFileUpload.originalName) - expect(actualResult.filename).toEqual(fakeFileUpload.filename) - expect(actualResult.etags).toEqual(fakeFileUpload.etags) - }) + beforeEach(() => { + let fieldName: string = 'submissions' + // const originalNames: string[] = ["test1.txt", "test2.txt", "test3.txt"] + // const fileNames: string[] = ["modified1.txt", "modified2.txt", "modified3.txt"] + // const etags: string[] = ["etag1", "etag2", "etag3"] + const originalName: string = 'test1.txt' + const filename: string = 'modified1.txt' + const etag: string = 'etag1' + fakeFileUpload = { + fieldName: fieldName, + originalName: originalName, + filename: filename, + etags: etag, + } + }) + describe('Serializing fileUpload', () => { + test('fileUpload values exist in the response', () => { + const actualResult = serialize(fakeFileUpload) + expect(actualResult).toBeDefined() + expect(actualResult.fieldName).toEqual(fakeFileUpload.fieldName) + expect(actualResult.originalName).toEqual(fakeFileUpload.originalName) + expect(actualResult.filename).toEqual(fakeFileUpload.filename) + expect(actualResult.etags).toEqual(fakeFileUpload.etags) }) - -}); \ No newline at end of file + }) +}) diff --git a/devU-api/src/index.ts b/devU-api/src/index.ts index 5d1b1a1d..f5cd0034 100644 --- a/devU-api/src/index.ts +++ b/devU-api/src/index.ts @@ -6,14 +6,14 @@ import bodyParser from 'body-parser' import cors from 'cors' import helmet from 'helmet' import morgan from 'morgan' -import {createConnection} from 'typeorm' +import { createConnection } from 'typeorm' import cookieParser from 'cookie-parser' import passport from 'passport' import environment from './environment' import connectionInfo from './database' -import {initializeMinio} from './fileStorage' +import { initializeMinio } from './fileStorage' // Middleware import router from './router' @@ -25,21 +25,23 @@ import './utils/passport.utils' const app = express() initializeMinio() - .then(() => createConnection(connectionInfo)) - .then(_connection => { - app.use(helmet()) - app.use(bodyParser.urlencoded({extended: true})) - app.use(bodyParser.json()) - app.use(cookieParser()) - app.use(cors({origin: environment.clientUrl, credentials: true})) - app.use(morgan('combined')) - app.use(passport.initialize()) - - // Middleware; - app.use('/', router) - app.use(errorHandler) - - app.listen(environment.port, () => console.log(`API listening at port - ${environment.port}\n - If you are running the full app, the front end should be accessible at http://localhost:9000`)) - }) - .catch(err => console.log('TypeORM connection error:', err)) + .then(() => createConnection(connectionInfo)) + .then(_connection => { + app.use(helmet()) + app.use(bodyParser.urlencoded({ extended: true })) + app.use(bodyParser.json()) + app.use(cookieParser()) + app.use(cors({ origin: environment.clientUrl, credentials: true })) + app.use(morgan('combined')) + app.use(passport.initialize()) + + // Middleware; + app.use('/', router) + app.use(errorHandler) + + app.listen(environment.port, () => + console.log(`API listening at port - ${environment.port}\n + If you are running the full app, the front end should be accessible at http://localhost:9000`) + ) + }) + .catch(err => console.log('TypeORM connection error:', err)) diff --git a/devU-api/src/migration/1708215731032-entityFixUp.ts b/devU-api/src/migration/1708215731032-entityFixUp.ts index 823345bb..cdf889c1 100644 --- a/devU-api/src/migration/1708215731032-entityFixUp.ts +++ b/devU-api/src/migration/1708215731032-entityFixUp.ts @@ -1,102 +1,201 @@ import { MigrationInterface, QueryRunner } from 'typeorm' export class entityFixUp1708215731032 implements MigrationInterface { - name = 'entityFixUp1708215731032' + name = 'entityFixUp1708215731032' - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "assignments" DROP CONSTRAINT "assignments_to_courses_foreign_key_constraint"`); - await queryRunner.query(`ALTER TABLE "assignment_problems" DROP CONSTRAINT "assignment_problems_to_assignment_id_foreign_key_constraint"`); - await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "submissions_to_original_submission_id_foreign_key_constraint"`); - await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "submissions_to_submitted_by_user_id_foreign_key_constraint"`); - await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "submissions_to_user_id_foreign_key_constraint"`); - await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "submissions_to_assignment_id_foreign_key_constraint"`); - await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "submissions_to_course_id_foreign_key_constraint"`); - await queryRunner.query(`ALTER TABLE "submission_problem_scores" DROP CONSTRAINT "submission_problem_scores_to_assignment_problem_id_foreign_key_"`); - await queryRunner.query(`ALTER TABLE "submission_problem_scores" DROP CONSTRAINT "submission_problem_scores_to_submission_id_foreign_key_constrai"`); - await queryRunner.query(`ALTER TABLE "submission_scores" DROP CONSTRAINT "submission_scores_to_submission_id_foreign_key_constraint"`); - await queryRunner.query(`ALTER TABLE "user_courses" DROP CONSTRAINT "user_courses_to_course_id_foreign_key_constraint"`); - await queryRunner.query(`ALTER TABLE "user_courses" DROP CONSTRAINT "user_courses_to_user_id_foreign_key_constraint"`); - await queryRunner.query(`CREATE TABLE "assignment_scores" ("id" SERIAL NOT NULL, "assignment_id" integer NOT NULL, "user_id" integer NOT NULL, "score" integer, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, CONSTRAINT "PK_8b251f15adfa96a86b0f5f8556f" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "category" ("id" SERIAL NOT NULL, "course_id" integer NOT NULL, "name" character varying(128) NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, CONSTRAINT "PK_9c4e4a89e3674fc9f382d733f03" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "category_score" ("id" SERIAL NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "course_id" integer NOT NULL, "user_id" integer NOT NULL, "category_id" integer NOT NULL, "score" double precision, CONSTRAINT "PK_ad3547334f4540fcb508aa55cd2" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "container_auto_grader" ("id" SERIAL NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "assignment_id" integer NOT NULL, "grader_filename" character varying(128) NOT NULL, "makefile_filename" text, "autograding_image" character varying NOT NULL, "timeout" integer NOT NULL, CONSTRAINT "PK_d675941b6e755fb495b5a5fa7d0" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "courseScore" ("id" SERIAL NOT NULL, "course_id" integer NOT NULL, "user_id" integer NOT NULL, "score" integer NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, CONSTRAINT "PK_6abff3c90b96cef828a38135c7f" PRIMARY KEY ("id"))`); - await queryRunner.query(`CREATE TABLE "nonContainerAutoGrader" ("id" SERIAL NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "assignment_id" integer NOT NULL, "question" character varying(128) NOT NULL, "score" integer NOT NULL, "correct_string" character varying(128) NOT NULL, CONSTRAINT "PK_e4bee0253f704dad3db68b60df7" PRIMARY KEY ("id"))`); - await queryRunner.query(`ALTER TABLE "assignments" DROP COLUMN "grading_type"`); - await queryRunner.query(`DROP TYPE "public"."assignments_grading_type_enum"`); - await queryRunner.query(`ALTER TABLE "submissions" DROP COLUMN "original_submission_id"`); - await queryRunner.query(`ALTER TABLE "submissions" DROP COLUMN "type"`); - await queryRunner.query(`DROP TYPE "public"."submissions_type_enum"`); - await queryRunner.query(`ALTER TABLE "assignments" ADD CONSTRAINT "FK_33f833f305070d2d4e6305d8a0c" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "assignment_problems" ADD CONSTRAINT "FK_939580320b59824a3b8ee61e1f7" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "assignment_scores" ADD CONSTRAINT "FK_cffbea35d0c9f6588a641eacf17" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "assignment_scores" ADD CONSTRAINT "FK_673ea1a5ccc2f6612fb1310ec1c" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "category" ADD CONSTRAINT "FK_becefa4788db15281f585822095" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "category_score" ADD CONSTRAINT "FK_c88ba5a4f5d548138457cb6c967" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "category_score" ADD CONSTRAINT "FK_0a3a3e1ab14b8690e52939da390" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "category_score" ADD CONSTRAINT "FK_41d5d4b618bbb9c9a2a0a4392a8" FOREIGN KEY ("category_id") REFERENCES "category"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "container_auto_grader" ADD CONSTRAINT "FK_ab00685bcc7b626e483870ebd73" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "courseScore" ADD CONSTRAINT "FK_7911c9a858be7b4836154eea7ae" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "courseScore" ADD CONSTRAINT "FK_6b89c87198bb05ada5900364ac8" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "nonContainerAutoGrader" ADD CONSTRAINT "FK_7f4461171c55020ab0680ecbcd0" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submissions" ADD CONSTRAINT "FK_6fc42b2f2983dd099fec7978444" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submissions" ADD CONSTRAINT "FK_8723840b9b0464206640c268abc" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submissions" ADD CONSTRAINT "FK_fca12c4ddd646dea4572c6815a9" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submissions" ADD CONSTRAINT "FK_d89b2ee682c9475006762b666ef" FOREIGN KEY ("submitted_by") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submission_problem_scores" ADD CONSTRAINT "FK_0b66632ab30113691b7044719e3" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submission_problem_scores" ADD CONSTRAINT "FK_5a40a69e4a9c4adf229c5eb3d84" FOREIGN KEY ("assignment_problem_id") REFERENCES "assignment_problems"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submission_scores" ADD CONSTRAINT "FK_60b6432533ce75c224598af6bf5" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "user_courses" ADD CONSTRAINT "FK_7ecb10d15b858768c36d37727f9" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "user_courses" ADD CONSTRAINT "FK_d65a2771413a10753d76937b3d6" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "nonContainerAutoGrader" + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "assignments" DROP CONSTRAINT "assignments_to_courses_foreign_key_constraint"`) + await queryRunner.query( + `ALTER TABLE "assignment_problems" DROP CONSTRAINT "assignment_problems_to_assignment_id_foreign_key_constraint"` + ) + await queryRunner.query( + `ALTER TABLE "submissions" DROP CONSTRAINT "submissions_to_original_submission_id_foreign_key_constraint"` + ) + await queryRunner.query( + `ALTER TABLE "submissions" DROP CONSTRAINT "submissions_to_submitted_by_user_id_foreign_key_constraint"` + ) + await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "submissions_to_user_id_foreign_key_constraint"`) + await queryRunner.query( + `ALTER TABLE "submissions" DROP CONSTRAINT "submissions_to_assignment_id_foreign_key_constraint"` + ) + await queryRunner.query( + `ALTER TABLE "submissions" DROP CONSTRAINT "submissions_to_course_id_foreign_key_constraint"` + ) + await queryRunner.query( + `ALTER TABLE "submission_problem_scores" DROP CONSTRAINT "submission_problem_scores_to_assignment_problem_id_foreign_key_"` + ) + await queryRunner.query( + `ALTER TABLE "submission_problem_scores" DROP CONSTRAINT "submission_problem_scores_to_submission_id_foreign_key_constrai"` + ) + await queryRunner.query( + `ALTER TABLE "submission_scores" DROP CONSTRAINT "submission_scores_to_submission_id_foreign_key_constraint"` + ) + await queryRunner.query( + `ALTER TABLE "user_courses" DROP CONSTRAINT "user_courses_to_course_id_foreign_key_constraint"` + ) + await queryRunner.query( + `ALTER TABLE "user_courses" DROP CONSTRAINT "user_courses_to_user_id_foreign_key_constraint"` + ) + await queryRunner.query( + `CREATE TABLE "assignment_scores" ("id" SERIAL NOT NULL, "assignment_id" integer NOT NULL, "user_id" integer NOT NULL, "score" integer, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, CONSTRAINT "PK_8b251f15adfa96a86b0f5f8556f" PRIMARY KEY ("id"))` + ) + await queryRunner.query( + `CREATE TABLE "category" ("id" SERIAL NOT NULL, "course_id" integer NOT NULL, "name" character varying(128) NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, CONSTRAINT "PK_9c4e4a89e3674fc9f382d733f03" PRIMARY KEY ("id"))` + ) + await queryRunner.query( + `CREATE TABLE "category_score" ("id" SERIAL NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "course_id" integer NOT NULL, "user_id" integer NOT NULL, "category_id" integer NOT NULL, "score" double precision, CONSTRAINT "PK_ad3547334f4540fcb508aa55cd2" PRIMARY KEY ("id"))` + ) + await queryRunner.query( + `CREATE TABLE "container_auto_grader" ("id" SERIAL NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "assignment_id" integer NOT NULL, "grader_filename" character varying(128) NOT NULL, "makefile_filename" text, "autograding_image" character varying NOT NULL, "timeout" integer NOT NULL, CONSTRAINT "PK_d675941b6e755fb495b5a5fa7d0" PRIMARY KEY ("id"))` + ) + await queryRunner.query( + `CREATE TABLE "courseScore" ("id" SERIAL NOT NULL, "course_id" integer NOT NULL, "user_id" integer NOT NULL, "score" integer NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, CONSTRAINT "PK_6abff3c90b96cef828a38135c7f" PRIMARY KEY ("id"))` + ) + await queryRunner.query( + `CREATE TABLE "nonContainerAutoGrader" ("id" SERIAL NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "assignment_id" integer NOT NULL, "question" character varying(128) NOT NULL, "score" integer NOT NULL, "correct_string" character varying(128) NOT NULL, CONSTRAINT "PK_e4bee0253f704dad3db68b60df7" PRIMARY KEY ("id"))` + ) + await queryRunner.query(`ALTER TABLE "assignments" DROP COLUMN "grading_type"`) + await queryRunner.query(`DROP TYPE "public"."assignments_grading_type_enum"`) + await queryRunner.query(`ALTER TABLE "submissions" DROP COLUMN "original_submission_id"`) + await queryRunner.query(`ALTER TABLE "submissions" DROP COLUMN "type"`) + await queryRunner.query(`DROP TYPE "public"."submissions_type_enum"`) + await queryRunner.query( + `ALTER TABLE "assignments" ADD CONSTRAINT "FK_33f833f305070d2d4e6305d8a0c" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "assignment_problems" ADD CONSTRAINT "FK_939580320b59824a3b8ee61e1f7" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "assignment_scores" ADD CONSTRAINT "FK_cffbea35d0c9f6588a641eacf17" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "assignment_scores" ADD CONSTRAINT "FK_673ea1a5ccc2f6612fb1310ec1c" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "category" ADD CONSTRAINT "FK_becefa4788db15281f585822095" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "category_score" ADD CONSTRAINT "FK_c88ba5a4f5d548138457cb6c967" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "category_score" ADD CONSTRAINT "FK_0a3a3e1ab14b8690e52939da390" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "category_score" ADD CONSTRAINT "FK_41d5d4b618bbb9c9a2a0a4392a8" FOREIGN KEY ("category_id") REFERENCES "category"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "container_auto_grader" ADD CONSTRAINT "FK_ab00685bcc7b626e483870ebd73" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "courseScore" ADD CONSTRAINT "FK_7911c9a858be7b4836154eea7ae" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "courseScore" ADD CONSTRAINT "FK_6b89c87198bb05ada5900364ac8" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "nonContainerAutoGrader" ADD CONSTRAINT "FK_7f4461171c55020ab0680ecbcd0" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submissions" ADD CONSTRAINT "FK_6fc42b2f2983dd099fec7978444" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submissions" ADD CONSTRAINT "FK_8723840b9b0464206640c268abc" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submissions" ADD CONSTRAINT "FK_fca12c4ddd646dea4572c6815a9" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submissions" ADD CONSTRAINT "FK_d89b2ee682c9475006762b666ef" FOREIGN KEY ("submitted_by") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submission_problem_scores" ADD CONSTRAINT "FK_0b66632ab30113691b7044719e3" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submission_problem_scores" ADD CONSTRAINT "FK_5a40a69e4a9c4adf229c5eb3d84" FOREIGN KEY ("assignment_problem_id") REFERENCES "assignment_problems"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submission_scores" ADD CONSTRAINT "FK_60b6432533ce75c224598af6bf5" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "user_courses" ADD CONSTRAINT "FK_7ecb10d15b858768c36d37727f9" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "user_courses" ADD CONSTRAINT "FK_d65a2771413a10753d76937b3d6" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query(`ALTER TABLE "nonContainerAutoGrader" ADD "is_regex" boolean NOT NULL;`) - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "user_courses" DROP CONSTRAINT "FK_d65a2771413a10753d76937b3d6"`); - await queryRunner.query(`ALTER TABLE "user_courses" DROP CONSTRAINT "FK_7ecb10d15b858768c36d37727f9"`); - await queryRunner.query(`ALTER TABLE "submission_scores" DROP CONSTRAINT "FK_60b6432533ce75c224598af6bf5"`); - await queryRunner.query(`ALTER TABLE "submission_problem_scores" DROP CONSTRAINT "FK_5a40a69e4a9c4adf229c5eb3d84"`); - await queryRunner.query(`ALTER TABLE "submission_problem_scores" DROP CONSTRAINT "FK_0b66632ab30113691b7044719e3"`); - await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "FK_d89b2ee682c9475006762b666ef"`); - await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "FK_fca12c4ddd646dea4572c6815a9"`); - await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "FK_8723840b9b0464206640c268abc"`); - await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "FK_6fc42b2f2983dd099fec7978444"`); - await queryRunner.query(`ALTER TABLE "nonContainerAutoGrader" DROP CONSTRAINT "FK_7f4461171c55020ab0680ecbcd0"`); - await queryRunner.query(`ALTER TABLE "courseScore" DROP CONSTRAINT "FK_6b89c87198bb05ada5900364ac8"`); - await queryRunner.query(`ALTER TABLE "courseScore" DROP CONSTRAINT "FK_7911c9a858be7b4836154eea7ae"`); - await queryRunner.query(`ALTER TABLE "container_auto_grader" DROP CONSTRAINT "FK_ab00685bcc7b626e483870ebd73"`); - await queryRunner.query(`ALTER TABLE "category_score" DROP CONSTRAINT "FK_41d5d4b618bbb9c9a2a0a4392a8"`); - await queryRunner.query(`ALTER TABLE "category_score" DROP CONSTRAINT "FK_0a3a3e1ab14b8690e52939da390"`); - await queryRunner.query(`ALTER TABLE "category_score" DROP CONSTRAINT "FK_c88ba5a4f5d548138457cb6c967"`); - await queryRunner.query(`ALTER TABLE "category" DROP CONSTRAINT "FK_becefa4788db15281f585822095"`); - await queryRunner.query(`ALTER TABLE "assignment_scores" DROP CONSTRAINT "FK_673ea1a5ccc2f6612fb1310ec1c"`); - await queryRunner.query(`ALTER TABLE "assignment_scores" DROP CONSTRAINT "FK_cffbea35d0c9f6588a641eacf17"`); - await queryRunner.query(`ALTER TABLE "assignment_problems" DROP CONSTRAINT "FK_939580320b59824a3b8ee61e1f7"`); - await queryRunner.query(`ALTER TABLE "assignments" DROP CONSTRAINT "FK_33f833f305070d2d4e6305d8a0c"`); - await queryRunner.query(`CREATE TYPE "public"."submissions_type_enum" AS ENUM('filepath', 'json', 'text')`); - await queryRunner.query(`ALTER TABLE "submissions" ADD "type" "submissions_type_enum" NOT NULL`); - await queryRunner.query(`ALTER TABLE "submissions" ADD "original_submission_id" integer`); - await queryRunner.query(`CREATE TYPE "public"."assignments_grading_type_enum" AS ENUM('code', 'non-code', 'manual')`); - await queryRunner.query(`ALTER TABLE "assignments" ADD "grading_type" "assignments_grading_type_enum" NOT NULL`); - await queryRunner.query(`DROP TABLE "nonContainerAutoGrader"`); - await queryRunner.query(`DROP TABLE "courseScore"`); - await queryRunner.query(`DROP TABLE "container_auto_grader"`); - await queryRunner.query(`DROP TABLE "category_score"`); - await queryRunner.query(`DROP TABLE "category"`); - await queryRunner.query(`DROP TABLE "assignment_scores"`); - await queryRunner.query(`ALTER TABLE "user_courses" ADD CONSTRAINT "user_courses_to_user_id_foreign_key_constraint" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "user_courses" ADD CONSTRAINT "user_courses_to_course_id_foreign_key_constraint" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submission_scores" ADD CONSTRAINT "submission_scores_to_submission_id_foreign_key_constraint" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submission_problem_scores" ADD CONSTRAINT "submission_problem_scores_to_submission_id_foreign_key_constrai" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submission_problem_scores" ADD CONSTRAINT "submission_problem_scores_to_assignment_problem_id_foreign_key_" FOREIGN KEY ("assignment_problem_id") REFERENCES "assignment_problems"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submissions" ADD CONSTRAINT "submissions_to_course_id_foreign_key_constraint" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submissions" ADD CONSTRAINT "submissions_to_assignment_id_foreign_key_constraint" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submissions" ADD CONSTRAINT "submissions_to_user_id_foreign_key_constraint" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submissions" ADD CONSTRAINT "submissions_to_submitted_by_user_id_foreign_key_constraint" FOREIGN KEY ("submitted_by") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "submissions" ADD CONSTRAINT "submissions_to_original_submission_id_foreign_key_constraint" FOREIGN KEY ("original_submission_id") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "assignment_problems" ADD CONSTRAINT "assignment_problems_to_assignment_id_foreign_key_constraint" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "assignments" ADD CONSTRAINT "assignments_to_courses_foreign_key_constraint" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - } + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user_courses" DROP CONSTRAINT "FK_d65a2771413a10753d76937b3d6"`) + await queryRunner.query(`ALTER TABLE "user_courses" DROP CONSTRAINT "FK_7ecb10d15b858768c36d37727f9"`) + await queryRunner.query(`ALTER TABLE "submission_scores" DROP CONSTRAINT "FK_60b6432533ce75c224598af6bf5"`) + await queryRunner.query(`ALTER TABLE "submission_problem_scores" DROP CONSTRAINT "FK_5a40a69e4a9c4adf229c5eb3d84"`) + await queryRunner.query(`ALTER TABLE "submission_problem_scores" DROP CONSTRAINT "FK_0b66632ab30113691b7044719e3"`) + await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "FK_d89b2ee682c9475006762b666ef"`) + await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "FK_fca12c4ddd646dea4572c6815a9"`) + await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "FK_8723840b9b0464206640c268abc"`) + await queryRunner.query(`ALTER TABLE "submissions" DROP CONSTRAINT "FK_6fc42b2f2983dd099fec7978444"`) + await queryRunner.query(`ALTER TABLE "nonContainerAutoGrader" DROP CONSTRAINT "FK_7f4461171c55020ab0680ecbcd0"`) + await queryRunner.query(`ALTER TABLE "courseScore" DROP CONSTRAINT "FK_6b89c87198bb05ada5900364ac8"`) + await queryRunner.query(`ALTER TABLE "courseScore" DROP CONSTRAINT "FK_7911c9a858be7b4836154eea7ae"`) + await queryRunner.query(`ALTER TABLE "container_auto_grader" DROP CONSTRAINT "FK_ab00685bcc7b626e483870ebd73"`) + await queryRunner.query(`ALTER TABLE "category_score" DROP CONSTRAINT "FK_41d5d4b618bbb9c9a2a0a4392a8"`) + await queryRunner.query(`ALTER TABLE "category_score" DROP CONSTRAINT "FK_0a3a3e1ab14b8690e52939da390"`) + await queryRunner.query(`ALTER TABLE "category_score" DROP CONSTRAINT "FK_c88ba5a4f5d548138457cb6c967"`) + await queryRunner.query(`ALTER TABLE "category" DROP CONSTRAINT "FK_becefa4788db15281f585822095"`) + await queryRunner.query(`ALTER TABLE "assignment_scores" DROP CONSTRAINT "FK_673ea1a5ccc2f6612fb1310ec1c"`) + await queryRunner.query(`ALTER TABLE "assignment_scores" DROP CONSTRAINT "FK_cffbea35d0c9f6588a641eacf17"`) + await queryRunner.query(`ALTER TABLE "assignment_problems" DROP CONSTRAINT "FK_939580320b59824a3b8ee61e1f7"`) + await queryRunner.query(`ALTER TABLE "assignments" DROP CONSTRAINT "FK_33f833f305070d2d4e6305d8a0c"`) + await queryRunner.query(`CREATE TYPE "public"."submissions_type_enum" AS ENUM('filepath', 'json', 'text')`) + await queryRunner.query(`ALTER TABLE "submissions" ADD "type" "submissions_type_enum" NOT NULL`) + await queryRunner.query(`ALTER TABLE "submissions" ADD "original_submission_id" integer`) + await queryRunner.query( + `CREATE TYPE "public"."assignments_grading_type_enum" AS ENUM('code', 'non-code', 'manual')` + ) + await queryRunner.query(`ALTER TABLE "assignments" ADD "grading_type" "assignments_grading_type_enum" NOT NULL`) + await queryRunner.query(`DROP TABLE "nonContainerAutoGrader"`) + await queryRunner.query(`DROP TABLE "courseScore"`) + await queryRunner.query(`DROP TABLE "container_auto_grader"`) + await queryRunner.query(`DROP TABLE "category_score"`) + await queryRunner.query(`DROP TABLE "category"`) + await queryRunner.query(`DROP TABLE "assignment_scores"`) + await queryRunner.query( + `ALTER TABLE "user_courses" ADD CONSTRAINT "user_courses_to_user_id_foreign_key_constraint" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "user_courses" ADD CONSTRAINT "user_courses_to_course_id_foreign_key_constraint" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submission_scores" ADD CONSTRAINT "submission_scores_to_submission_id_foreign_key_constraint" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submission_problem_scores" ADD CONSTRAINT "submission_problem_scores_to_submission_id_foreign_key_constrai" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submission_problem_scores" ADD CONSTRAINT "submission_problem_scores_to_assignment_problem_id_foreign_key_" FOREIGN KEY ("assignment_problem_id") REFERENCES "assignment_problems"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submissions" ADD CONSTRAINT "submissions_to_course_id_foreign_key_constraint" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submissions" ADD CONSTRAINT "submissions_to_assignment_id_foreign_key_constraint" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submissions" ADD CONSTRAINT "submissions_to_user_id_foreign_key_constraint" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submissions" ADD CONSTRAINT "submissions_to_submitted_by_user_id_foreign_key_constraint" FOREIGN KEY ("submitted_by") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "submissions" ADD CONSTRAINT "submissions_to_original_submission_id_foreign_key_constraint" FOREIGN KEY ("original_submission_id") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "assignment_problems" ADD CONSTRAINT "assignment_problems_to_assignment_id_foreign_key_constraint" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "assignments" ADD CONSTRAINT "assignments_to_courses_foreign_key_constraint" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + } } diff --git a/devU-api/src/migration/1709413878786-addDeadlineExtensions.ts b/devU-api/src/migration/1709413878786-addDeadlineExtensions.ts index 5e02c8a2..d497e709 100644 --- a/devU-api/src/migration/1709413878786-addDeadlineExtensions.ts +++ b/devU-api/src/migration/1709413878786-addDeadlineExtensions.ts @@ -1,19 +1,27 @@ -import {MigrationInterface, QueryRunner} from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm' export class addDeadlineExtensions1709413878786 implements MigrationInterface { - name = 'addDeadlineExtensions1709413878786' + name = 'addDeadlineExtensions1709413878786' - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE "deadline_extensions" ("id" SERIAL NOT NULL, "assignment_id" integer NOT NULL, "creator_id" integer NOT NULL, "user_id" integer NOT NULL, "deadline_date" TIMESTAMP NOT NULL, "new_end_date" TIMESTAMP, "new_start_date" TIMESTAMP, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, CONSTRAINT "PK_7f77bdaaa612f494d52177c7b59" PRIMARY KEY ("id"))`); - await queryRunner.query(`ALTER TABLE "deadline_extensions" ADD CONSTRAINT "FK_733bb5fb011e3d6d59823765422" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "deadline_extensions" ADD CONSTRAINT "FK_7cb47489122a342a16a29a5340a" FOREIGN KEY ("creator_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "deadline_extensions" ADD CONSTRAINT "FK_d5b5ea708f3fa09453f327dcbfc" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "deadline_extensions" ("id" SERIAL NOT NULL, "assignment_id" integer NOT NULL, "creator_id" integer NOT NULL, "user_id" integer NOT NULL, "deadline_date" TIMESTAMP NOT NULL, "new_end_date" TIMESTAMP, "new_start_date" TIMESTAMP, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, CONSTRAINT "PK_7f77bdaaa612f494d52177c7b59" PRIMARY KEY ("id"))` + ) + await queryRunner.query( + `ALTER TABLE "deadline_extensions" ADD CONSTRAINT "FK_733bb5fb011e3d6d59823765422" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "deadline_extensions" ADD CONSTRAINT "FK_7cb47489122a342a16a29a5340a" FOREIGN KEY ("creator_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "deadline_extensions" ADD CONSTRAINT "FK_d5b5ea708f3fa09453f327dcbfc" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "deadline_extensions" DROP CONSTRAINT "FK_d5b5ea708f3fa09453f327dcbfc"`); - await queryRunner.query(`ALTER TABLE "deadline_extensions" DROP CONSTRAINT "FK_7cb47489122a342a16a29a5340a"`); - await queryRunner.query(`ALTER TABLE "deadline_extensions" DROP CONSTRAINT "FK_733bb5fb011e3d6d59823765422"`); - await queryRunner.query(`DROP TABLE "deadline_extensions"`); - } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "deadline_extensions" DROP CONSTRAINT "FK_d5b5ea708f3fa09453f327dcbfc"`) + await queryRunner.query(`ALTER TABLE "deadline_extensions" DROP CONSTRAINT "FK_7cb47489122a342a16a29a5340a"`) + await queryRunner.query(`ALTER TABLE "deadline_extensions" DROP CONSTRAINT "FK_733bb5fb011e3d6d59823765422"`) + await queryRunner.query(`DROP TABLE "deadline_extensions"`) + } } diff --git a/devU-api/src/migration/1709541636414-fileUpload.ts b/devU-api/src/migration/1709541636414-fileUpload.ts index cc4a1031..0050da4e 100644 --- a/devU-api/src/migration/1709541636414-fileUpload.ts +++ b/devU-api/src/migration/1709541636414-fileUpload.ts @@ -1,20 +1,27 @@ -import {MigrationInterface, QueryRunner} from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm' export class fileUpload1709541636414 implements MigrationInterface { - name = 'fileUpload1709541636414' + name = 'fileUpload1709541636414' - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE "FilesAuth" ("id" SERIAL NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "course_id" integer NOT NULL, "assignment_id" integer NOT NULL, "user_id" integer NOT NULL, "filename" character varying(128) NOT NULL, "bucket" character varying(64) NOT NULL, CONSTRAINT "PK_1f230e6f691b8fbdf0fb4b6cb79" PRIMARY KEY ("id"))`); - await queryRunner.query(`ALTER TABLE "FilesAuth" ADD CONSTRAINT "FK_8a72cf4a87234d581d04e34e5fe" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "FilesAuth" ADD CONSTRAINT "FK_a906cfad4f214af17efcdcc4a85" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - await queryRunner.query(`ALTER TABLE "FilesAuth" ADD CONSTRAINT "FK_06a0cc4fd4dd414b312ca3de0ef" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "FilesAuth" DROP CONSTRAINT "FK_06a0cc4fd4dd414b312ca3de0ef"`); - await queryRunner.query(`ALTER TABLE "FilesAuth" DROP CONSTRAINT "FK_a906cfad4f214af17efcdcc4a85"`); - await queryRunner.query(`ALTER TABLE "FilesAuth" DROP CONSTRAINT "FK_8a72cf4a87234d581d04e34e5fe"`); - await queryRunner.query(`DROP TABLE "FilesAuth"`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "FilesAuth" ("id" SERIAL NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "course_id" integer NOT NULL, "assignment_id" integer NOT NULL, "user_id" integer NOT NULL, "filename" character varying(128) NOT NULL, "bucket" character varying(64) NOT NULL, CONSTRAINT "PK_1f230e6f691b8fbdf0fb4b6cb79" PRIMARY KEY ("id"))` + ) + await queryRunner.query( + `ALTER TABLE "FilesAuth" ADD CONSTRAINT "FK_8a72cf4a87234d581d04e34e5fe" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "FilesAuth" ADD CONSTRAINT "FK_a906cfad4f214af17efcdcc4a85" FOREIGN KEY ("assignment_id") REFERENCES "assignments"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + await queryRunner.query( + `ALTER TABLE "FilesAuth" ADD CONSTRAINT "FK_06a0cc4fd4dd414b312ca3de0ef" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "FilesAuth" DROP CONSTRAINT "FK_06a0cc4fd4dd414b312ca3de0ef"`) + await queryRunner.query(`ALTER TABLE "FilesAuth" DROP CONSTRAINT "FK_a906cfad4f214af17efcdcc4a85"`) + await queryRunner.query(`ALTER TABLE "FilesAuth" DROP CONSTRAINT "FK_8a72cf4a87234d581d04e34e5fe"`) + await queryRunner.query(`DROP TABLE "FilesAuth"`) + } } diff --git a/devU-api/src/migration/1713805438440-roles.ts b/devU-api/src/migration/1713805438440-roles.ts index 1bd51209..3128210d 100644 --- a/devU-api/src/migration/1713805438440-roles.ts +++ b/devU-api/src/migration/1713805438440-roles.ts @@ -1,24 +1,27 @@ -import {MigrationInterface, QueryRunner} from "typeorm"; +import { MigrationInterface, QueryRunner } from 'typeorm' export class roles1713805438440 implements MigrationInterface { - name = 'roles1713805438440' + name = 'roles1713805438440' - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "user_courses" RENAME COLUMN "type" TO "role"`); - await queryRunner.query(`ALTER TYPE "public"."user_courses_type_enum" RENAME TO "user_courses_role_enum"`); - await queryRunner.query(`CREATE TABLE "role" ("id" SERIAL NOT NULL, "course_id" integer NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "name" character varying NOT NULL, "enrolled" boolean NOT NULL, "course_edit" boolean NOT NULL, "course_view_all" boolean NOT NULL, "assignment_view_all" boolean NOT NULL, "assignment_edit_all" boolean NOT NULL, "assignment_view_release" boolean NOT NULL, "scores_view_all" boolean NOT NULL, "scores_edit_all" boolean NOT NULL, "scores_view_self_released" boolean NOT NULL, "role_edit_all" boolean NOT NULL, "role_view_all" boolean NOT NULL, "role_view_self" boolean NOT NULL, "submission_change_state" boolean NOT NULL, "submission_create_all" boolean NOT NULL, "submission_create_self" boolean NOT NULL, "submission_view_all" boolean NOT NULL, "user_course_edit_all" boolean NOT NULL, CONSTRAINT "PK_b36bcfe02fc8de3c57a8b2391c2" PRIMARY KEY ("id"))`); - await queryRunner.query(`ALTER TABLE "user_courses" DROP COLUMN "role"`); - await queryRunner.query(`ALTER TABLE "user_courses" ADD "role" character varying NOT NULL`); - await queryRunner.query(`ALTER TABLE "role" ADD CONSTRAINT "FK_3c1c31ab1941f90200d36a236b3" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "role" DROP CONSTRAINT "FK_3c1c31ab1941f90200d36a236b3"`); - await queryRunner.query(`ALTER TABLE "user_courses" DROP COLUMN "role"`); - await queryRunner.query(`ALTER TABLE "user_courses" ADD "role" "user_courses_role_enum" NOT NULL`); - await queryRunner.query(`DROP TABLE "role"`); - await queryRunner.query(`ALTER TYPE "user_courses_role_enum" RENAME TO "user_courses_type_enum"`); - await queryRunner.query(`ALTER TABLE "user_courses" RENAME COLUMN "role" TO "type"`); - } + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user_courses" RENAME COLUMN "type" TO "role"`) + await queryRunner.query(`ALTER TYPE "public"."user_courses_type_enum" RENAME TO "user_courses_role_enum"`) + await queryRunner.query( + `CREATE TABLE "role" ("id" SERIAL NOT NULL, "course_id" integer NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "name" character varying NOT NULL, "enrolled" boolean NOT NULL, "course_edit" boolean NOT NULL, "course_view_all" boolean NOT NULL, "assignment_view_all" boolean NOT NULL, "assignment_edit_all" boolean NOT NULL, "assignment_view_release" boolean NOT NULL, "scores_view_all" boolean NOT NULL, "scores_edit_all" boolean NOT NULL, "scores_view_self_released" boolean NOT NULL, "role_edit_all" boolean NOT NULL, "role_view_all" boolean NOT NULL, "role_view_self" boolean NOT NULL, "submission_change_state" boolean NOT NULL, "submission_create_all" boolean NOT NULL, "submission_create_self" boolean NOT NULL, "submission_view_all" boolean NOT NULL, "user_course_edit_all" boolean NOT NULL, CONSTRAINT "PK_b36bcfe02fc8de3c57a8b2391c2" PRIMARY KEY ("id"))` + ) + await queryRunner.query(`ALTER TABLE "user_courses" DROP COLUMN "role"`) + await queryRunner.query(`ALTER TABLE "user_courses" ADD "role" character varying NOT NULL`) + await queryRunner.query( + `ALTER TABLE "role" ADD CONSTRAINT "FK_3c1c31ab1941f90200d36a236b3" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ) + } + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "role" DROP CONSTRAINT "FK_3c1c31ab1941f90200d36a236b3"`) + await queryRunner.query(`ALTER TABLE "user_courses" DROP COLUMN "role"`) + await queryRunner.query(`ALTER TABLE "user_courses" ADD "role" "user_courses_role_enum" NOT NULL`) + await queryRunner.query(`DROP TABLE "role"`) + await queryRunner.query(`ALTER TYPE "user_courses_role_enum" RENAME TO "user_courses_type_enum"`) + await queryRunner.query(`ALTER TABLE "user_courses" RENAME COLUMN "role" TO "type"`) + } } diff --git a/devU-api/src/model/index.ts b/devU-api/src/model/index.ts index 91eefc92..0e2a286b 100644 --- a/devU-api/src/model/index.ts +++ b/devU-api/src/model/index.ts @@ -13,25 +13,25 @@ import SubmissionScoreModel from '../entities/submissionScore/submissionScore.mo import UserModel from '../entities/user/user.model' import UserCourseModel from '../entities/userCourse/userCourse.model' import FileModel from '../fileUpload/fileUpload.model' -import DeadlineExtensionsModel from "../entities/deadlineExtensions/deadlineExtensions.model"; +import DeadlineExtensionsModel from '../entities/deadlineExtensions/deadlineExtensions.model' type Models = - | AssignmentModel - | AssignmentProblemModel - | AssignmentScoreModel - | CategoryModel - | CategoryScoreModel - | ContainerAutoGraderModel - | CourseModel - | CourseScoreModel - | NonContainerAutoGraderModel - | SubmissionModel - | SubmissionProblemScoreModel - | SubmissionScoreModel - | UserModel - | UserCourseModel - | CategoryModel - | FileModel - | DeadlineExtensionsModel + | AssignmentModel + | AssignmentProblemModel + | AssignmentScoreModel + | CategoryModel + | CategoryScoreModel + | ContainerAutoGraderModel + | CourseModel + | CourseScoreModel + | NonContainerAutoGraderModel + | SubmissionModel + | SubmissionProblemScoreModel + | SubmissionScoreModel + | UserModel + | UserCourseModel + | CategoryModel + | FileModel + | DeadlineExtensionsModel export default Models diff --git a/devU-api/src/router/courseData.router.ts b/devU-api/src/router/courseData.router.ts index b2d97cf0..45371009 100644 --- a/devU-api/src/router/courseData.router.ts +++ b/devU-api/src/router/courseData.router.ts @@ -1,6 +1,5 @@ import express from 'express' - import userCourse from '../entities/userCourse/userCourse.router' import assignments from '../entities/assignment/assignment.router' import submissions from '../entities/submission/submission.router' @@ -8,7 +7,7 @@ import submissionScore from '../entities/submissionScore/submissionScore.router' import containerAutoGrader from '../entities/containerAutoGrader/containerAutoGrader.router' import assignmentProblem from '../entities/assignmentProblem/assignmentProblem.router' import submissionProblemScore from '../entities/submissionProblemScore/submissionProblemScore.router' -import deadlineExtensions from "../entities/deadlineExtensions/deadlineExtensions.router"; +import deadlineExtensions from '../entities/deadlineExtensions/deadlineExtensions.router' import fileUpload from '../fileUpload/fileUpload.router' import grader from '../entities/grader/grader.router' import categories from '../entities/category/category.router' @@ -17,12 +16,11 @@ import courseScores from '../entities/courseScore/courseScore.router' import assignmentScore from '../entities/assignmentScore/assignmentScore.router' import role from '../entities/role/role.router' +import nonContainerAutoGraderRouter from '../entities/nonContainerAutoGrader/nonContainerAutoGrader.router' -import nonContainerAutoGraderRouter from "../entities/nonContainerAutoGrader/nonContainerAutoGrader.router"; - -import {asInt} from "../middleware/validator/generic.validator"; +import { asInt } from '../middleware/validator/generic.validator' -const assignmentRouter = express.Router(); +const assignmentRouter = express.Router() assignmentRouter.use('/assignment-problems', assignmentProblem) assignmentRouter.use('/container-auto-graders', containerAutoGrader) assignmentRouter.use('/deadline-extensions', deadlineExtensions) @@ -31,7 +29,6 @@ assignmentRouter.use('/submissions', submissions) assignmentRouter.use('/submission-problem-scores', submissionProblemScore) assignmentRouter.use('/submission-scores', submissionScore) - const Router = express.Router() Router.use('/assignment/:assignmentId/', asInt('assignmentId'), assignmentRouter) Router.use('/a/:assignmentId/', asInt('assignmentId'), assignmentRouter) @@ -46,5 +43,4 @@ Router.use('/grade', grader) Router.use('/roles', role) Router.use('/user-courses', userCourse) - export default Router diff --git a/devU-api/src/router/index.ts b/devU-api/src/router/index.ts index a82f3963..c742b60b 100644 --- a/devU-api/src/router/index.ts +++ b/devU-api/src/router/index.ts @@ -13,12 +13,10 @@ import courseRoutes from './courseData.router' import { isAuthenticated } from '../authentication/authentication.middleware' import { NotFound } from '../utils/apiResponse.utils' -import {asInt} from "../middleware/validator/generic.validator"; +import { asInt } from '../middleware/validator/generic.validator' const Router = express.Router() - - // TODO: Decide if we want to pull the course object (And return a 404 if not found) in middleware here or let it // happen later... do this check in isAuthorized @@ -32,7 +30,6 @@ Router.use('/users', isAuthenticated, users) Router.use('/courses', isAuthenticated, courses) // TODO: Courses by user - Router.use('/login', login) Router.use('/logout', logout) diff --git a/devU-api/src/tango/tango.service.ts b/devU-api/src/tango/tango.service.ts index 59c335fe..103b91fc 100644 --- a/devU-api/src/tango/tango.service.ts +++ b/devU-api/src/tango/tango.service.ts @@ -1,6 +1,6 @@ import './tango.types' -const tangoHost = `http://${(process.env.TANGO_KEY ?? 'localhost:3000')}` +const tangoHost = `http://${process.env.TANGO_KEY ?? 'localhost:3000'}` const tangoKey = process.env.TANGO_KEY ?? 'test' // for more info https://docs.autolabproject.com/tango-rest/ @@ -25,7 +25,7 @@ export async function uploadFile(courselab: string, file: File, fileName: string const url = `${tangoHost}/upload/${tangoKey}/${courselab}/` const formData = new FormData() formData.append('file', file) - const response = await fetch(url, { method: 'POST', body: formData, headers: { 'filename': fileName } }) + const response = await fetch(url, { method: 'POST', body: formData, headers: { filename: fileName } }) return response.ok ? await response.json() : null } @@ -49,13 +49,14 @@ export async function addJob(courselab: string, job: AddJobRequest): Promise { +export async function pollJob( + courselab: string, + outputFile: string +): Promise { const url = `${tangoHost}/poll/${tangoKey}/${courselab}/${outputFile}/` const response = await fetch(url, { method: 'GET' }) const data = await response.json() - return response.ok ? - data as PollSuccessResponse - : data as PollFailureResponse + return response.ok ? (data as PollSuccessResponse) : (data as PollFailureResponse) } /** @@ -83,7 +84,11 @@ export async function getPoolInfo(image: string): Promise { * @param num - The number of instances to pre-allocate. * @param request - The request object. */ -export async function preallocateInstances(image: string, num: number, request: PreallocRequest): Promise { +export async function preallocateInstances( + image: string, + num: number, + request: PreallocRequest +): Promise { const url = `${tangoHost}/prealloc/${tangoKey}/${image}/${num}/` const response = await fetch(url, { method: 'POST', @@ -102,4 +107,4 @@ export async function getJobs(deadjobs: number): Promise<{} | null> { const url = `${tangoHost}/jobs/${tangoKey}/${deadjobs}/` const response = await fetch(url, { method: 'POST' }) return response.ok ? {} : null -} \ No newline at end of file +} diff --git a/devU-api/src/tango/tango.types.ts b/devU-api/src/tango/tango.types.ts index 04c0434d..759dbd1a 100644 --- a/devU-api/src/tango/tango.types.ts +++ b/devU-api/src/tango/tango.types.ts @@ -2,47 +2,47 @@ * Represents the response body for the `/open` request. */ interface OpenResponse { - statusMsg: string; - statusId: number; - files: { [fileName: string]: string }; + statusMsg: string + statusId: number + files: { [fileName: string]: string } } /** * Represents the response body for the `/upload` request. */ interface UploadResponse { - statusMsg: string; - statusId: number; + statusMsg: string + statusId: number } /** * Represents a single file information object with `localFile` and `destFile` properties. */ interface FileInfo { - localFile: string; - destFile: string; + localFile: string + destFile: string } /** * Represents the request body for the `/addJob` request. */ interface AddJobRequest { - image: string; - files: FileInfo[]; - jobName: string; - output_file: string; - timeout?: number; - max_kb?: number; - callback_url?: string; + image: string + files: FileInfo[] + jobName: string + output_file: string + timeout?: number + max_kb?: number + callback_url?: string } /** * Represents the response body for the `/addJob` request. */ interface AddJobResponse { - statusMsg: string; - statusId: number; - jobId: number; + statusMsg: string + statusId: number + jobId: number } /** @@ -56,8 +56,8 @@ interface PollSuccessResponse { * Represents the response body for the `/poll` request when the job is not completed. */ interface PollFailureResponse { - statusMsg: string; - statusId: number; + statusMsg: string + statusId: number } /** @@ -65,40 +65,39 @@ interface PollFailureResponse { */ interface InfoResponse { info: { - num_threads: number; - job_requests: number; - waitvm_timeouts: number; - runjob_timeouts: number; - elapsed_secs: number; - runjob_errors: number; - job_retries: number; - copyin_errors: number; - copyout_errors: number; - }; - statusMsg: string; - statusId: number; + num_threads: number + job_requests: number + waitvm_timeouts: number + runjob_timeouts: number + elapsed_secs: number + runjob_errors: number + job_retries: number + copyin_errors: number + copyout_errors: number + } + statusMsg: string + statusId: number } - /** * Represents the request body for the `/prealloc` request. */ interface PreallocRequest { - vmms: string; - cores: number; - memory: number; + vmms: string + cores: number + memory: number } /** * Represents the response body for the `/prealloc` request. */ interface PreallocResponse { - status: string; + status: string } /** * Represents the request body for the `/jobs` request. */ interface JobsRequest { - deadjobs: number; + deadjobs: number } diff --git a/devU-api/src/tango/tests/tango.endpoint.test.ts b/devU-api/src/tango/tests/tango.endpoint.test.ts index 5cc24f56..e62a420c 100644 --- a/devU-api/src/tango/tests/tango.endpoint.test.ts +++ b/devU-api/src/tango/tests/tango.endpoint.test.ts @@ -1,6 +1,6 @@ import { getInfo, preallocateInstances } from '../tango.service' -async function main(){ +async function main() { const res = await getInfo() const pre = preallocateInstances() console.log(res) @@ -8,4 +8,4 @@ async function main(){ main().then(value => { console.log('main complete') -}) \ No newline at end of file +}) diff --git a/devU-api/src/utils/fileUpload.utils.ts b/devU-api/src/utils/fileUpload.utils.ts index 0c81dfb7..4963ccf0 100644 --- a/devU-api/src/utils/fileUpload.utils.ts +++ b/devU-api/src/utils/fileUpload.utils.ts @@ -1,20 +1,19 @@ -import { BucketNames} from '../fileStorage' +import { BucketNames } from '../fileStorage' import { fileUploadTypes } from '../../devu-shared-modules' -import crypto from 'crypto' +import crypto from 'crypto' /* - * generateFilename currently only use sha256 to hash the originalName with a timestamp - * This can be updated to use a different hashing algorithm in the future - * Marking for discussion + * generateFilename currently only use sha256 to hash the originalName with a timestamp + * This can be updated to use a different hashing algorithm in the future + * Marking for discussion */ -export function generateFilename(originalName: string,id: number): string { +export function generateFilename(originalName: string, id: number): string { const hash = crypto.createHash('sha256') - const timestamp = Date.now(); - hash.update(originalName+id+timestamp) + const timestamp = Date.now() + hash.update(originalName + id + timestamp) return hash.digest('hex') } - /* This function is deciding which bucket to upload the file however, it only specifies the bucket for graderFile and makefileFile @@ -24,10 +23,10 @@ export function generateFilename(originalName: string,id: number): string { */ export type fileUploadType = typeof fileUploadTypes[number] -export function mappingFieldWithBucket( input: fileUploadType ){ +export function mappingFieldWithBucket(input: fileUploadType) { const specialPair: Partial> = { graderFile: BucketNames.GRADERS, - makefileFile: BucketNames.MAKEFILES + makefileFile: BucketNames.MAKEFILES, } return specialPair[input] || BucketNames.SUBMISSIONS -} \ No newline at end of file +} diff --git a/devU-api/src/utils/swagger.utils.ts b/devU-api/src/utils/swagger.utils.ts index 32c115cd..19d2b519 100644 --- a/devU-api/src/utils/swagger.utils.ts +++ b/devU-api/src/utils/swagger.utils.ts @@ -2,7 +2,7 @@ import swaggerJSDoc from 'swagger-jsdoc' const swaggerOptioner = { definition: { - openapi: '3.0.0', + openapi: '3.0.0', info: { title: 'DevU API Documentation', version: '1.0.0', @@ -14,20 +14,26 @@ const swaggerOptioner = { type: 'http', scheme: 'bearer', name: 'authorization', - } - } + }, + }, }, - security: [{ - Authorization: [], - }], + security: [ + { + Authorization: [], + }, + ], }, apis: [ - './src/router/*.ts', - './src/entities/*/*.router.ts', './src/entities/*/*.model.ts', - './src/fileUpload/*.router.ts', './src/fileUpload/*.model.ts', - './src/authentication/*/*.router.ts', './src/authentication/*/*.model.ts'], + './src/router/*.ts', + './src/entities/*/*.router.ts', + './src/entities/*/*.model.ts', + './src/fileUpload/*.router.ts', + './src/fileUpload/*.model.ts', + './src/authentication/*/*.router.ts', + './src/authentication/*/*.model.ts', + ], } const swaggerSpec = swaggerJSDoc(swaggerOptioner) -export default swaggerSpec \ No newline at end of file +export default swaggerSpec From 759fbe31c91edb115ecaa5ec78c0bfb27a1c029f Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Tue, 23 Apr 2024 11:02:44 -0400 Subject: [PATCH 043/400] Bug Fixes: add mergeParas to routers to inherit parent router params; debug statements in populate-db --- devU-api/scripts/populate-db.ts | 132 +++++++++++------- .../entities/assignment/assignment.router.ts | 2 +- .../assignmentProblem.router.ts | 2 +- .../assignmentScore/assignmentScore.router.ts | 2 +- .../src/entities/category/category.router.ts | 2 +- .../categoryScore/categoryScore.router.ts | 2 +- .../containerAutoGrader.router.ts | 2 +- .../courseScore/courseScore.router.ts | 2 +- .../deadlineExtensions.router.ts | 2 +- devU-api/src/entities/grader/grader.router.ts | 2 +- .../nonContainerAutoGrader.router.ts | 2 +- devU-api/src/entities/role/role.router.ts | 2 +- .../entities/submission/submission.router.ts | 2 +- .../submissionProblemScore.router.ts | 2 +- .../submissionScore/submissionScore.router.ts | 2 +- .../entities/userCourse/userCourse.router.ts | 2 +- devU-api/src/fileUpload/fileUpload.router.ts | 2 +- devU-api/src/router/courseData.router.ts | 4 +- 18 files changed, 100 insertions(+), 68 deletions(-) diff --git a/devU-api/scripts/populate-db.ts b/devU-api/scripts/populate-db.ts index c90bc0f3..92fd0fcc 100644 --- a/devU-api/scripts/populate-db.ts +++ b/devU-api/scripts/populate-db.ts @@ -33,9 +33,11 @@ async function initAdmin() { } //Returns the ID of the newly created entry -async function SendPOST(path: string, requestBody: string | FormData, externalId: string) { +async function SendPOST(path: string, requestBody: string | FormData, requesterExternalId: string) { + console.log(path) + console.log(requestBody) const headers = new Headers() - headers.append('Authorization', `Bearer ${apiToken[externalId]}`) + headers.append('Authorization', `Bearer ${apiToken[requesterExternalId]}`) headers.append('Content-Type', 'application/json') if (requestBody instanceof FormData) { headers.delete('Content-Type') @@ -48,8 +50,9 @@ async function SendPOST(path: string, requestBody: string | FormData, externalId headers: headers, body: requestBody, }) - - return await response.json() + const responseBody = await response.json() + console.log(responseBody) + return responseBody } async function CreateCourse(name: string, number: string, semester: string) { @@ -82,10 +85,11 @@ async function createAssignment(courseId: number, name: string, categoryName: st disableHandins: false, } console.log(`Creating assignment for Course Id: ${courseId}, Name: ${name}`) - return await SendPOST('/assignments', JSON.stringify(assignmentData), 'admin') + return await SendPOST(`/course/${courseId}/assignments`, JSON.stringify(assignmentData), 'admin') } async function createNonContainerAutoGrader( + courseId: number, assignmentId: number, problemName: string, Score: number, @@ -100,10 +104,15 @@ async function createNonContainerAutoGrader( isRegex: isRegex, } console.log('Creating NonContainerAutoGrader for Assignment Id: ', assignmentId) - return await SendPOST('/nonContainerAutoGrader', JSON.stringify(problemData), 'admin') + return await SendPOST( + `/course/${courseId}/assignment/${assignmentId}/nonContainerAutoGrader`, + JSON.stringify(problemData), + 'admin' + ) } async function createContainerAutoGrader( + courseId: number, assignmentId: number, imageName: string, timeout: number, @@ -119,7 +128,7 @@ async function createContainerAutoGrader( formData.append('makefileFile', makefile) } console.log('Creating ContainerAutoGrader for Assignment Id: ', assignmentId) - return await SendPOST('/container-auto-graders', formData, 'admin') + return await SendPOST(`/course/${courseId}/assignment/${assignmentId}/container-auto-graders`, formData, 'admin') } async function createSubmission( @@ -147,7 +156,7 @@ async function createSubmission( formData.append('userId', userId.toString()) formData.append('files', file) - response = await SendPOST('/submissions', formData, externalId) + response = await SendPOST(`/course/${courseId}/assignment/${assignmentId}/submissions`, formData, externalId) } else { const submissionData = { createdAt: time, @@ -158,26 +167,30 @@ async function createSubmission( content: fullContent, } //@ts-ignore - response = await SendPOST('/submissions', submissionData, externalId) + response = await SendPOST(`/course/${courseId}/assignment/${assignmentId}/submissions`, submissionData, externalId) } return response } -async function createAssignmentProblem(assignmentId: number, problemName: string, maxScore: number) { +async function createAssignmentProblem(courseId: number, assignmentId: number, problemName: string, maxScore: number) { const problemData = { assignmentId: assignmentId, problemName: problemName, maxScore: maxScore, } - return await SendPOST('/assignment-problems', JSON.stringify(problemData), 'admin') + return await SendPOST( + `/course/${courseId}/assignment/${assignmentId}/assignment-problems`, + JSON.stringify(problemData), + 'admin' + ) } -async function gradeSubmission(submissionId: number) { - return await SendPOST(`/grade/${submissionId}`, '', 'admin') +async function gradeSubmission(courseId: number, submissionId: number) { + return await SendPOST(`/course/${courseId}/grade/${submissionId}`, '', 'admin') } async function runCourseAndSubmission() { - try { + // try { //Create users const billy = await fetchToken('billy@buffalo.edu', 'billy') const bob = await fetchToken('bob@buffalo.edu', 'bob') @@ -187,10 +200,27 @@ async function runCourseAndSubmission() { const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id //Create enroll students - await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId1}, role:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId2}, role:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId1}, role:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId2}, role:student, dropped:false}`, 'admin') + const response = await SendPOST( + `/course/${courseId1}/user-courses`, + JSON.stringify({userId:billy, courseId:courseId1, role:"student", dropped:false}), + 'admin' + ) + console.log(response) + await SendPOST( + `/course/${courseId2}/user-courses`, + JSON.stringify({userId:billy, courseId:courseId2, role:"student", dropped:false}), + 'admin' + ) + await SendPOST( + `/course/${courseId1}/user-courses`, + JSON.stringify({userId:bob, courseId:courseId1, role:"student", dropped:false}), + 'admin' + ) + await SendPOST( + `/course/${courseId2}/user-courses`, + JSON.stringify({userId:bob, courseId:courseId2, role:"student", dropped:false}), + 'admin' + ) //Create assignments const assignment1 = await createAssignment(courseId1, 'Course1 Assignment 1', 'Quiz') @@ -200,66 +230,68 @@ async function runCourseAndSubmission() { const assignmentId3 = (await createAssignment(courseId2, 'Course2 Assignment 1', 'Quiz')).id const assignmentId4 = (await createAssignment(courseId2, 'Course2 Assignment 2', 'Homework')).id - const problemName1 = (await createAssignmentProblem(assignmentId1, 'Please answer A', 10)).problemName - const problemName2 = (await createAssignmentProblem(assignmentId1, 'Please answer B', 10)).problemName + const problemName1 = (await createAssignmentProblem(courseId1, assignmentId1, 'Please answer A', 10)).problemName + const problemName2 = (await createAssignmentProblem(courseId1, assignmentId1, 'Please answer B', 10)).problemName - const problemName3 = (await createAssignmentProblem(assignmentId3, 'Please NOT answer A', 10)).problemName - const problemName4 = (await createAssignmentProblem(assignmentId3, 'Please NOT answer B', 10)).problemName + const problemName3 = (await createAssignmentProblem(courseId2, assignmentId3, 'Please NOT answer A', 10)) + .problemName + const problemName4 = (await createAssignmentProblem(courseId2, assignmentId3, 'Please NOT answer B', 10)) + .problemName //NonContainerAutoGrader - await createNonContainerAutoGrader(assignmentId1, problemName1, 10, 'A', false) - await createNonContainerAutoGrader(assignmentId1, problemName2, 10, 'B', false) - await createNonContainerAutoGrader(assignmentId3, problemName1, 10, '/^[^Aa]+$/', true) - await createNonContainerAutoGrader(assignmentId3, problemName2, 10, '/^[^Bb]+$/', true) + await createNonContainerAutoGrader(courseId1, assignmentId1, problemName1, 10, 'A', false) + await createNonContainerAutoGrader(courseId1, assignmentId1, problemName2, 10, 'B', false) + await createNonContainerAutoGrader(courseId2, assignmentId3, problemName1, 10, '/^[^Aa]+$/', true) + await createNonContainerAutoGrader(courseId2, assignmentId3, problemName2, 10, '/^[^Bb]+$/', true) //ContainerAutoGrader file = new File(['This is a test grader file'], 'grader.code') - await createContainerAutoGrader(assignmentId2, 'NewestImageInTheWorld', 300, file) + await createContainerAutoGrader(courseId1, assignmentId2, 'NewestImageInTheWorld', 300, file) file = new File(['This is another test grader file'], 'grader.code') makefile = new File(['This is a test makefile'], 'makefile') - await createContainerAutoGrader(assignmentId4, 'OldestImageInTheWorld', 300, file, makefile) + await createContainerAutoGrader(courseId2, assignmentId4, 'OldestImageInTheWorld', 300, file, makefile) //Create submissions content = `{"${problemName1}": "A", "${problemName2}": "B"}` - const submissionId1 = (await createSubmission(courseId1, assignmentId1, billy, 'billy', content)).id + const submission1 = await createSubmission(courseId1, assignmentId1, billy, 'billy', content) content = `{"${problemName1}": "C", "${problemName2}": "D"}` - const submissionId2 = (await createSubmission(courseId1, assignmentId1, bob, 'bob', content)).id + const submission2 = await createSubmission(courseId1, assignmentId1, bob, 'bob', content) content = `{"${problemName3}": "A", "${problemName4}": "B"}` - const submissionId3 = (await createSubmission(courseId2, assignmentId3, billy, 'billy', content)).id + const submission3 = await createSubmission(courseId2, assignmentId3, billy, 'billy', content) content = `{"${problemName3}": "C", "${problemName4}": "D"}` - const submissionId4 = (await createSubmission(courseId2, assignmentId3, bob, 'bob', content)).id + const submission4 = await createSubmission(courseId2, assignmentId3, bob, 'bob', content) file = new File(['This is a test file1'], 'test.txt') - const submissionId5 = (await createSubmission(courseId1, assignmentId2, billy, 'billy', undefined, file)).id - const submissionId6 = (await createSubmission(courseId1, assignmentId2, bob, 'bob', undefined, file)).id + const submission5 = await createSubmission(courseId1, assignmentId2, billy, 'billy', undefined, file) + const submission6 = await createSubmission(courseId1, assignmentId2, bob, 'bob', undefined, file) file = new File(['These are lines of codes'], 'code.code') - const submissionId7 = (await createSubmission(courseId1, assignmentId4, billy, 'billy', undefined, file)).id - const submissionId8 = (await createSubmission(courseId1, assignmentId4, bob, 'bob', undefined, file)).id + const submission7 = await createSubmission(courseId1, assignmentId4, billy, 'billy', undefined, file) + const submission8 = await createSubmission(courseId1, assignmentId4, bob, 'bob', undefined, file) //Grade the submissions - for (const submissionId of [ - submissionId1, - submissionId2, - submissionId3, - submissionId4, - submissionId5, - submissionId6, - submissionId7, - submissionId8, + for (const submission of [ + submission1, + submission2, + submission3, + submission4, + submission5, + submission6, + submission7, + submission8, ]) { - console.log('Grading submission: ', submissionId) - await gradeSubmission(submissionId) + console.log('Grading submission: ', submission) + await gradeSubmission(submission.courseId, submission.id) } console.log('Script completed successfully!') - } catch (e) { - console.error(e) - } + // } catch (e) { + // console.error(e) + // } } initAdmin() diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index 3e77a20d..65d36a21 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -9,7 +9,7 @@ import { asInt } from '../../middleware/validator/generic.validator' import AssignmentsController from './assignment.controller' import { isAuthorized, isAuthorizedByAssignmentStatus } from '../../authorization/authorization.middleware' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts index bb3c2063..a90f2d01 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import AssignmentProblemController from './assignmentProblem.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts index bb87c20e..fb5eda57 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts @@ -9,7 +9,7 @@ import { extractOwnerByPathParam, isAuthorized } from '../../authorization/autho //Controller import AssignmentScoreController from './assignmentScore.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/entities/category/category.router.ts b/devU-api/src/entities/category/category.router.ts index d5b3375e..90eeaf51 100644 --- a/devU-api/src/entities/category/category.router.ts +++ b/devU-api/src/entities/category/category.router.ts @@ -6,7 +6,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' import CategoryController from './category.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/entities/categoryScore/categoryScore.router.ts b/devU-api/src/entities/categoryScore/categoryScore.router.ts index 6abdfdec..f9e21f87 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.router.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' //Controller import CategoryScoreController from './categoryScore.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts index 60fc151a..bcecbb8f 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts @@ -7,7 +7,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' import ContainerAutoGraderController from './containerAutoGrader.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) const upload = multer() /** diff --git a/devU-api/src/entities/courseScore/courseScore.router.ts b/devU-api/src/entities/courseScore/courseScore.router.ts index c9623c72..53f299d5 100644 --- a/devU-api/src/entities/courseScore/courseScore.router.ts +++ b/devU-api/src/entities/courseScore/courseScore.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' //Controller import CourseScoreController from './courseScore.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts index 259cbadd..7ff6ac92 100644 --- a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts +++ b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import DeadlineExtensionsController from './deadlineExtensions.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/entities/grader/grader.router.ts b/devU-api/src/entities/grader/grader.router.ts index d3664141..21a03d4f 100644 --- a/devU-api/src/entities/grader/grader.router.ts +++ b/devU-api/src/entities/grader/grader.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import GraderController from './grader.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts index 9d7a4626..4de96db3 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import nonContainerQuestions from './nonContainerAutoGrader.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/entities/role/role.router.ts b/devU-api/src/entities/role/role.router.ts index 49dbd55d..18ef09ec 100644 --- a/devU-api/src/entities/role/role.router.ts +++ b/devU-api/src/entities/role/role.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import RoleController from './role.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index 99be1d2d..6f6437bc 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -10,7 +10,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import SubmissionController from '../submission/submission.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) const upload = Multer() /** diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts index 3893a11c..8603a51f 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import SubmissionProblemScoreController from '../submissionProblemScore/submissionProblemScore.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) // TODO: get by assignment and user diff --git a/devU-api/src/entities/submissionScore/submissionScore.router.ts b/devU-api/src/entities/submissionScore/submissionScore.router.ts index 93e50be5..7b6c2132 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.router.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.router.ts @@ -9,7 +9,7 @@ import { extractOwnerByPathParam, isAuthorized } from '../../authorization/autho // Controller import SubmissionScoreController from './submissionScore.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index 7c8d480a..d07962f1 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -9,7 +9,7 @@ import { extractOwnerByPathParam, isAuthorized } from '../../authorization/autho // Controller import UserCourseController from './userCourse.controller' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) /** * @swagger diff --git a/devU-api/src/fileUpload/fileUpload.router.ts b/devU-api/src/fileUpload/fileUpload.router.ts index 7e00d61c..78dd961c 100644 --- a/devU-api/src/fileUpload/fileUpload.router.ts +++ b/devU-api/src/fileUpload/fileUpload.router.ts @@ -7,7 +7,7 @@ import FileUploadController from './fileUpload.controller' import { fileUploadTypes } from '../../devu-shared-modules' import { isAuthorized } from '../authorization/authorization.middleware' -const Router = express.Router() +const Router = express.Router({mergeParams: true}) const upload = multer() /* diff --git a/devU-api/src/router/courseData.router.ts b/devU-api/src/router/courseData.router.ts index 45371009..fed71087 100644 --- a/devU-api/src/router/courseData.router.ts +++ b/devU-api/src/router/courseData.router.ts @@ -20,7 +20,7 @@ import nonContainerAutoGraderRouter from '../entities/nonContainerAutoGrader/non import { asInt } from '../middleware/validator/generic.validator' -const assignmentRouter = express.Router() +const assignmentRouter = express.Router({mergeParams: true}) assignmentRouter.use('/assignment-problems', assignmentProblem) assignmentRouter.use('/container-auto-graders', containerAutoGrader) assignmentRouter.use('/deadline-extensions', deadlineExtensions) @@ -29,7 +29,7 @@ assignmentRouter.use('/submissions', submissions) assignmentRouter.use('/submission-problem-scores', submissionProblemScore) assignmentRouter.use('/submission-scores', submissionScore) -const Router = express.Router() +const Router = express.Router({mergeParams: true}) Router.use('/assignment/:assignmentId/', asInt('assignmentId'), assignmentRouter) Router.use('/a/:assignmentId/', asInt('assignmentId'), assignmentRouter) From a26573c217fa7099be9424ff9e932ffbea5b8dad Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Tue, 23 Apr 2024 11:16:57 -0400 Subject: [PATCH 044/400] Bug fix with type of role and not returning on next(); formatting --- devU-api/scripts/populate-db.ts | 200 +++++++++--------- .../authorization/authorization.middleware.ts | 8 +- .../entities/assignment/assignment.router.ts | 2 +- .../assignmentProblem.router.ts | 2 +- .../assignmentScore/assignmentScore.router.ts | 2 +- .../src/entities/category/category.router.ts | 2 +- .../categoryScore/categoryScore.router.ts | 2 +- .../containerAutoGrader.router.ts | 2 +- .../courseScore/courseScore.router.ts | 2 +- .../deadlineExtensions.router.ts | 2 +- devU-api/src/entities/grader/grader.router.ts | 2 +- .../nonContainerAutoGrader.router.ts | 2 +- devU-api/src/entities/role/role.router.ts | 2 +- .../entities/submission/submission.router.ts | 2 +- .../submissionProblemScore.router.ts | 2 +- .../submissionScore/submissionScore.router.ts | 2 +- .../entities/userCourse/userCourse.router.ts | 2 +- devU-api/src/fileUpload/fileUpload.router.ts | 2 +- devU-api/src/router/courseData.router.ts | 4 +- 19 files changed, 122 insertions(+), 122 deletions(-) diff --git a/devU-api/scripts/populate-db.ts b/devU-api/scripts/populate-db.ts index 92fd0fcc..bc432549 100644 --- a/devU-api/scripts/populate-db.ts +++ b/devU-api/scripts/populate-db.ts @@ -34,8 +34,8 @@ async function initAdmin() { //Returns the ID of the newly created entry async function SendPOST(path: string, requestBody: string | FormData, requesterExternalId: string) { - console.log(path) - console.log(requestBody) + console.log(path) + console.log(requestBody) const headers = new Headers() headers.append('Authorization', `Bearer ${apiToken[requesterExternalId]}`) headers.append('Content-Type', 'application/json') @@ -50,8 +50,8 @@ async function SendPOST(path: string, requestBody: string | FormData, requesterE headers: headers, body: requestBody, }) - const responseBody = await response.json() - console.log(responseBody) + const responseBody = await response.json() + console.log(responseBody) return responseBody } @@ -191,104 +191,102 @@ async function gradeSubmission(courseId: number, submissionId: number) { async function runCourseAndSubmission() { // try { - //Create users - const billy = await fetchToken('billy@buffalo.edu', 'billy') - const bob = await fetchToken('bob@buffalo.edu', 'bob') - - //Create courses - const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024')).id - const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id - - //Create enroll students - const response = await SendPOST( - `/course/${courseId1}/user-courses`, - JSON.stringify({userId:billy, courseId:courseId1, role:"student", dropped:false}), - 'admin' - ) - console.log(response) - await SendPOST( - `/course/${courseId2}/user-courses`, - JSON.stringify({userId:billy, courseId:courseId2, role:"student", dropped:false}), - 'admin' - ) - await SendPOST( - `/course/${courseId1}/user-courses`, - JSON.stringify({userId:bob, courseId:courseId1, role:"student", dropped:false}), - 'admin' - ) - await SendPOST( - `/course/${courseId2}/user-courses`, - JSON.stringify({userId:bob, courseId:courseId2, role:"student", dropped:false}), - 'admin' - ) - - //Create assignments - const assignment1 = await createAssignment(courseId1, 'Course1 Assignment 1', 'Quiz') - const assignmentId1 = assignment1.id - const assignmentId2 = (await createAssignment(courseId1, 'Course1 Assignment 2', 'Homework')).id - - const assignmentId3 = (await createAssignment(courseId2, 'Course2 Assignment 1', 'Quiz')).id - const assignmentId4 = (await createAssignment(courseId2, 'Course2 Assignment 2', 'Homework')).id - - const problemName1 = (await createAssignmentProblem(courseId1, assignmentId1, 'Please answer A', 10)).problemName - const problemName2 = (await createAssignmentProblem(courseId1, assignmentId1, 'Please answer B', 10)).problemName - - const problemName3 = (await createAssignmentProblem(courseId2, assignmentId3, 'Please NOT answer A', 10)) - .problemName - const problemName4 = (await createAssignmentProblem(courseId2, assignmentId3, 'Please NOT answer B', 10)) - .problemName - - //NonContainerAutoGrader - await createNonContainerAutoGrader(courseId1, assignmentId1, problemName1, 10, 'A', false) - await createNonContainerAutoGrader(courseId1, assignmentId1, problemName2, 10, 'B', false) - await createNonContainerAutoGrader(courseId2, assignmentId3, problemName1, 10, '/^[^Aa]+$/', true) - await createNonContainerAutoGrader(courseId2, assignmentId3, problemName2, 10, '/^[^Bb]+$/', true) - - //ContainerAutoGrader - file = new File(['This is a test grader file'], 'grader.code') - await createContainerAutoGrader(courseId1, assignmentId2, 'NewestImageInTheWorld', 300, file) - - file = new File(['This is another test grader file'], 'grader.code') - makefile = new File(['This is a test makefile'], 'makefile') - await createContainerAutoGrader(courseId2, assignmentId4, 'OldestImageInTheWorld', 300, file, makefile) - - //Create submissions - content = `{"${problemName1}": "A", "${problemName2}": "B"}` - const submission1 = await createSubmission(courseId1, assignmentId1, billy, 'billy', content) - - content = `{"${problemName1}": "C", "${problemName2}": "D"}` - const submission2 = await createSubmission(courseId1, assignmentId1, bob, 'bob', content) - - content = `{"${problemName3}": "A", "${problemName4}": "B"}` - const submission3 = await createSubmission(courseId2, assignmentId3, billy, 'billy', content) - - content = `{"${problemName3}": "C", "${problemName4}": "D"}` - const submission4 = await createSubmission(courseId2, assignmentId3, bob, 'bob', content) - - file = new File(['This is a test file1'], 'test.txt') - const submission5 = await createSubmission(courseId1, assignmentId2, billy, 'billy', undefined, file) - const submission6 = await createSubmission(courseId1, assignmentId2, bob, 'bob', undefined, file) - - file = new File(['These are lines of codes'], 'code.code') - const submission7 = await createSubmission(courseId1, assignmentId4, billy, 'billy', undefined, file) - const submission8 = await createSubmission(courseId1, assignmentId4, bob, 'bob', undefined, file) - - //Grade the submissions - for (const submission of [ - submission1, - submission2, - submission3, - submission4, - submission5, - submission6, - submission7, - submission8, - ]) { - console.log('Grading submission: ', submission) - await gradeSubmission(submission.courseId, submission.id) - } + //Create users + const billy = await fetchToken('billy@buffalo.edu', 'billy') + const bob = await fetchToken('bob@buffalo.edu', 'bob') + + //Create courses + const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024')).id + const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id + + //Create enroll students + const response = await SendPOST( + `/course/${courseId1}/user-courses`, + JSON.stringify({ userId: billy, courseId: courseId1, role: 'student', dropped: false }), + 'admin' + ) + console.log(response) + await SendPOST( + `/course/${courseId2}/user-courses`, + JSON.stringify({ userId: billy, courseId: courseId2, role: 'student', dropped: false }), + 'admin' + ) + await SendPOST( + `/course/${courseId1}/user-courses`, + JSON.stringify({ userId: bob, courseId: courseId1, role: 'student', dropped: false }), + 'admin' + ) + await SendPOST( + `/course/${courseId2}/user-courses`, + JSON.stringify({ userId: bob, courseId: courseId2, role: 'student', dropped: false }), + 'admin' + ) + + //Create assignments + const assignment1 = await createAssignment(courseId1, 'Course1 Assignment 1', 'Quiz') + const assignmentId1 = assignment1.id + const assignmentId2 = (await createAssignment(courseId1, 'Course1 Assignment 2', 'Homework')).id + + const assignmentId3 = (await createAssignment(courseId2, 'Course2 Assignment 1', 'Quiz')).id + const assignmentId4 = (await createAssignment(courseId2, 'Course2 Assignment 2', 'Homework')).id + + const problemName1 = (await createAssignmentProblem(courseId1, assignmentId1, 'Please answer A', 10)).problemName + const problemName2 = (await createAssignmentProblem(courseId1, assignmentId1, 'Please answer B', 10)).problemName + + const problemName3 = (await createAssignmentProblem(courseId2, assignmentId3, 'Please NOT answer A', 10)).problemName + const problemName4 = (await createAssignmentProblem(courseId2, assignmentId3, 'Please NOT answer B', 10)).problemName + + //NonContainerAutoGrader + await createNonContainerAutoGrader(courseId1, assignmentId1, problemName1, 10, 'A', false) + await createNonContainerAutoGrader(courseId1, assignmentId1, problemName2, 10, 'B', false) + await createNonContainerAutoGrader(courseId2, assignmentId3, problemName1, 10, '/^[^Aa]+$/', true) + await createNonContainerAutoGrader(courseId2, assignmentId3, problemName2, 10, '/^[^Bb]+$/', true) + + //ContainerAutoGrader + file = new File(['This is a test grader file'], 'grader.code') + await createContainerAutoGrader(courseId1, assignmentId2, 'NewestImageInTheWorld', 300, file) + + file = new File(['This is another test grader file'], 'grader.code') + makefile = new File(['This is a test makefile'], 'makefile') + await createContainerAutoGrader(courseId2, assignmentId4, 'OldestImageInTheWorld', 300, file, makefile) + + //Create submissions + content = `{"${problemName1}": "A", "${problemName2}": "B"}` + const submission1 = await createSubmission(courseId1, assignmentId1, billy, 'billy', content) + + content = `{"${problemName1}": "C", "${problemName2}": "D"}` + const submission2 = await createSubmission(courseId1, assignmentId1, bob, 'bob', content) + + content = `{"${problemName3}": "A", "${problemName4}": "B"}` + const submission3 = await createSubmission(courseId2, assignmentId3, billy, 'billy', content) + + content = `{"${problemName3}": "C", "${problemName4}": "D"}` + const submission4 = await createSubmission(courseId2, assignmentId3, bob, 'bob', content) + + file = new File(['This is a test file1'], 'test.txt') + const submission5 = await createSubmission(courseId1, assignmentId2, billy, 'billy', undefined, file) + const submission6 = await createSubmission(courseId1, assignmentId2, bob, 'bob', undefined, file) + + file = new File(['These are lines of codes'], 'code.code') + const submission7 = await createSubmission(courseId1, assignmentId4, billy, 'billy', undefined, file) + const submission8 = await createSubmission(courseId1, assignmentId4, bob, 'bob', undefined, file) + + //Grade the submissions + for (const submission of [ + submission1, + submission2, + submission3, + submission4, + submission5, + submission6, + submission7, + submission8, + ]) { + console.log('Grading submission: ', submission) + await gradeSubmission(submission.courseId, submission.id) + } - console.log('Script completed successfully!') + console.log('Script completed successfully!') // } catch (e) { // console.error(e) // } diff --git a/devU-api/src/authorization/authorization.middleware.ts b/devU-api/src/authorization/authorization.middleware.ts index f997bad0..87fd9ac0 100644 --- a/devU-api/src/authorization/authorization.middleware.ts +++ b/devU-api/src/authorization/authorization.middleware.ts @@ -39,24 +39,26 @@ export function isAuthorized(permission: string, permissionIfSelf?: string) { if (!roleModel) { role = RoleService.retrieveDefaultByName(userCourse.role) } else { - role = serialize(roleModel) + role = serialize(roleModel) as Role } if (!role) { return res.status(403).json(new GenericResponse('Invalid role: ' + userCourse.role)) } + const x = role[permission as keyof typeof role] + console.log(x) // check for permission if (role[permission as keyof typeof role]) { // authorized! - next() + return next() } if (permissionIfSelf && req.ownerId && role[permissionIfSelf as keyof typeof role]) { // can access if they are the owner if (req.currentUser?.userId && req.currentUser?.userId === parseInt(req.ownerId)) { // authorized to access their own content - next() + return next() } } diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index 65d36a21..437a2462 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -9,7 +9,7 @@ import { asInt } from '../../middleware/validator/generic.validator' import AssignmentsController from './assignment.controller' import { isAuthorized, isAuthorizedByAssignmentStatus } from '../../authorization/authorization.middleware' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts index a90f2d01..ca51b142 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import AssignmentProblemController from './assignmentProblem.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts index fb5eda57..21c6caaa 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts @@ -9,7 +9,7 @@ import { extractOwnerByPathParam, isAuthorized } from '../../authorization/autho //Controller import AssignmentScoreController from './assignmentScore.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/entities/category/category.router.ts b/devU-api/src/entities/category/category.router.ts index 90eeaf51..791364d2 100644 --- a/devU-api/src/entities/category/category.router.ts +++ b/devU-api/src/entities/category/category.router.ts @@ -6,7 +6,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' import CategoryController from './category.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/entities/categoryScore/categoryScore.router.ts b/devU-api/src/entities/categoryScore/categoryScore.router.ts index f9e21f87..e9b28895 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.router.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' //Controller import CategoryScoreController from './categoryScore.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts index bcecbb8f..a6f89971 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts @@ -7,7 +7,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' import ContainerAutoGraderController from './containerAutoGrader.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) const upload = multer() /** diff --git a/devU-api/src/entities/courseScore/courseScore.router.ts b/devU-api/src/entities/courseScore/courseScore.router.ts index 53f299d5..0a0a46d1 100644 --- a/devU-api/src/entities/courseScore/courseScore.router.ts +++ b/devU-api/src/entities/courseScore/courseScore.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' //Controller import CourseScoreController from './courseScore.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts index 7ff6ac92..1de90fc1 100644 --- a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts +++ b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import DeadlineExtensionsController from './deadlineExtensions.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/entities/grader/grader.router.ts b/devU-api/src/entities/grader/grader.router.ts index 21a03d4f..7db825f5 100644 --- a/devU-api/src/entities/grader/grader.router.ts +++ b/devU-api/src/entities/grader/grader.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import GraderController from './grader.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts index 4de96db3..88696f15 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import nonContainerQuestions from './nonContainerAutoGrader.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/entities/role/role.router.ts b/devU-api/src/entities/role/role.router.ts index 18ef09ec..c6bd2152 100644 --- a/devU-api/src/entities/role/role.router.ts +++ b/devU-api/src/entities/role/role.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import RoleController from './role.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index 6f6437bc..943c8d9b 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -10,7 +10,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import SubmissionController from '../submission/submission.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) const upload = Multer() /** diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts index 8603a51f..ab8da4c6 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.router.ts @@ -9,7 +9,7 @@ import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import SubmissionProblemScoreController from '../submissionProblemScore/submissionProblemScore.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) // TODO: get by assignment and user diff --git a/devU-api/src/entities/submissionScore/submissionScore.router.ts b/devU-api/src/entities/submissionScore/submissionScore.router.ts index 7b6c2132..c532ac06 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.router.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.router.ts @@ -9,7 +9,7 @@ import { extractOwnerByPathParam, isAuthorized } from '../../authorization/autho // Controller import SubmissionScoreController from './submissionScore.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index d07962f1..4a63b09c 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -9,7 +9,7 @@ import { extractOwnerByPathParam, isAuthorized } from '../../authorization/autho // Controller import UserCourseController from './userCourse.controller' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) /** * @swagger diff --git a/devU-api/src/fileUpload/fileUpload.router.ts b/devU-api/src/fileUpload/fileUpload.router.ts index 78dd961c..8a872e2e 100644 --- a/devU-api/src/fileUpload/fileUpload.router.ts +++ b/devU-api/src/fileUpload/fileUpload.router.ts @@ -7,7 +7,7 @@ import FileUploadController from './fileUpload.controller' import { fileUploadTypes } from '../../devu-shared-modules' import { isAuthorized } from '../authorization/authorization.middleware' -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) const upload = multer() /* diff --git a/devU-api/src/router/courseData.router.ts b/devU-api/src/router/courseData.router.ts index fed71087..4026c768 100644 --- a/devU-api/src/router/courseData.router.ts +++ b/devU-api/src/router/courseData.router.ts @@ -20,7 +20,7 @@ import nonContainerAutoGraderRouter from '../entities/nonContainerAutoGrader/non import { asInt } from '../middleware/validator/generic.validator' -const assignmentRouter = express.Router({mergeParams: true}) +const assignmentRouter = express.Router({ mergeParams: true }) assignmentRouter.use('/assignment-problems', assignmentProblem) assignmentRouter.use('/container-auto-graders', containerAutoGrader) assignmentRouter.use('/deadline-extensions', deadlineExtensions) @@ -29,7 +29,7 @@ assignmentRouter.use('/submissions', submissions) assignmentRouter.use('/submission-problem-scores', submissionProblemScore) assignmentRouter.use('/submission-scores', submissionScore) -const Router = express.Router({mergeParams: true}) +const Router = express.Router({ mergeParams: true }) Router.use('/assignment/:assignmentId/', asInt('assignmentId'), assignmentRouter) Router.use('/a/:assignmentId/', asInt('assignmentId'), assignmentRouter) From f2cc0638c9d4fada4d1ed354c606510a7c2f1396 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 14:27:36 -0400 Subject: [PATCH 045/400] add courseId and assignmentId for submissionComponent to handle to new path links --- .../src/components/pages/assignmentDetailPage.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index bb28c149..99e1c915 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -42,7 +42,7 @@ const AssignmentDetailPage = () => { const assignmentProblemsReq = await RequestService.get(`/api/assignment-problems/${assignmentId}`) setAssignmentProblems(assignmentProblemsReq) - const submissionsReq = await RequestService.get(`/api/submissions?assignmentId=${assignmentId}&userId=${userId}`) + const submissionsReq = await RequestService.get(`/api/submissions/assignments/${assignmentId}`) submissionsReq.sort((a, b) => (Date.parse(b.createdAt ?? '') - Date.parse(a.createdAt ?? ''))) setSubmissions(submissionsReq) @@ -163,8 +163,18 @@ type SubmissionProps = { submissionScore?: SubmissionScore, submissionProblemScores: SubmissionProblemScore[], assignmentProblems: AssignmentProblem[], + courseId: string + assignmentId: string } -const SubmissionComponent = ({index, submission, submissionScore, submissionProblemScores, assignmentProblems}: SubmissionProps) => { +const SubmissionComponent = ({ + index, + submission, + submissionScore, + submissionProblemScores, + assignmentProblems, + courseId, + assignmentId + }: SubmissionProps) => { return (

Submission {index}:

From 7a58d4421b7d335a82fbb8149ce997064a250bc0 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 14:35:29 -0400 Subject: [PATCH 046/400] update paths links and update the code format for better view --- .../src/components/authenticatedRouter.tsx | 19 +++-- .../src/components/misc/globalToolbar.tsx | 10 +-- .../components/pages/assignmentDetailPage.tsx | 84 +++++++++++-------- .../components/pages/userCoursesListPage.tsx | 8 +- 4 files changed, 67 insertions(+), 54 deletions(-) diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index b360047c..eba3344a 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -30,13 +30,15 @@ const AuthenticatedRouter = () => ( - + {/* Just reuse the homepage here, for now this is fine. we might want to change this in the future though which is why they exist as separate routes */} - - - - - + + + + + @@ -45,8 +47,9 @@ const AuthenticatedRouter = () => ( - - + + diff --git a/devU-client/src/components/misc/globalToolbar.tsx b/devU-client/src/components/misc/globalToolbar.tsx index c191aeaf..d0e394e9 100644 --- a/devU-client/src/components/misc/globalToolbar.tsx +++ b/devU-client/src/components/misc/globalToolbar.tsx @@ -1,16 +1,14 @@ import React from 'react' -import { Link } from 'react-router-dom' +import {Link} from 'react-router-dom' import DarkModeToggle from 'components/utils/darkModeToggle' import FaIcon from 'components/shared/icons/faIcon' import UserOptionsDropdown from 'components/utils/userOptionsDropdown' -import { useAppSelector } from 'redux/hooks' import styles from './globalToolbar.scss' const GlobalToolbar = () => { - const userId = useAppSelector((store) => store.user.id) return (
@@ -26,13 +24,13 @@ const GlobalToolbar = () => { {/* Turns into a sidebar via css on mobile */}
- + Courses - + Assignments - + Submissions
diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index 99e1c915..dafc438a 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -1,15 +1,21 @@ -import React,{ useState, useEffect } from 'react' -import {Link} from 'react-router-dom' +import React, {useEffect, useState} from 'react' +import {Link, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' -import { AssignmentProblem, Submission, SubmissionScore, SubmissionProblemScore, Assignment, ContainerAutoGrader } from 'devu-shared-modules' +import { + Assignment, + AssignmentProblem, + ContainerAutoGrader, + Submission, + SubmissionProblemScore, + SubmissionScore +} from 'devu-shared-modules' import RequestService from 'services/request.service' import ErrorPage from './errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import TextField from 'components/shared/inputs/textField' import Button from 'components/shared/inputs/button' -import { useAppSelector,useActionless } from 'redux/hooks' -import { SET_ALERT } from 'redux/types/active.types' -import { useParams } from 'react-router-dom' +import {useActionless, useAppSelector} from 'redux/hooks' +import {SET_ALERT} from 'redux/types/active.types' import styles from './assignmentDetailPage.scss' @@ -30,42 +36,42 @@ const AssignmentDetailPage = () => { const [containerAutograder, setContainerAutograder] = useState() useEffect(() => { - fetchData() + fetchData() }, []); const fetchData = async () => { - try { - const assignment = await RequestService.get( `/api/assignments/${assignmentId}` ) - setAssignment(assignment) + try { + const assignment = await RequestService.get(`/api/assignments/${assignmentId}`) + setAssignment(assignment) - const assignmentProblemsReq = await RequestService.get(`/api/assignment-problems/${assignmentId}`) - setAssignmentProblems(assignmentProblemsReq) + const assignmentProblemsReq = await RequestService.get(`/api/assignment-problems/${assignmentId}`) + setAssignmentProblems(assignmentProblemsReq) - const submissionsReq = await RequestService.get(`/api/submissions/assignments/${assignmentId}`) - submissionsReq.sort((a, b) => (Date.parse(b.createdAt ?? '') - Date.parse(a.createdAt ?? ''))) - setSubmissions(submissionsReq) + const submissionsReq = await RequestService.get(`/api/submissions/assignments/${assignmentId}`) + submissionsReq.sort((a, b) => (Date.parse(b.createdAt ?? '') - Date.parse(a.createdAt ?? ''))) + setSubmissions(submissionsReq) - const submissionScoresPromises = submissionsReq.map(s => { + const submissionScoresPromises = submissionsReq.map(s => { return RequestService.get(`/api/submission-scores?submission=${s.id}`) - }) - const submissionScoresReq = (await Promise.all(submissionScoresPromises)).reduce((a, b) => a.concat(b), []) - setSubmissionScores(submissionScoresReq) + }) + const submissionScoresReq = (await Promise.all(submissionScoresPromises)).reduce((a, b) => a.concat(b), []) + setSubmissionScores(submissionScoresReq) - const submissionProblemScoresPromises = submissionsReq.map(s => { + const submissionProblemScoresPromises = submissionsReq.map(s => { return RequestService.get(`/api/submission-problem-scores/${s.id}`) - }) - const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) - setSubmissionProblemScores(submissionProblemScoresReq) - - const containerAutograder = (await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`)).pop() ?? null - setContainerAutograder(containerAutograder) - - } catch(error) { - setError(error) - } finally { - setLoading(false) - } + }) + const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) + setSubmissionProblemScores(submissionProblemScoresReq) + + const containerAutograder = (await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`)).pop() ?? null + setContainerAutograder(containerAutograder) + + } catch (error) { + setError(error) + } finally { + setLoading(false) + } } if (loading) return @@ -128,9 +134,11 @@ const AssignmentDetailPage = () => { Update Assignment


- Add Non-Container Auto-Graders + Add + Non-Container Auto-Graders


- Add Container Auto-Grader + Add + Container Auto-Grader {/**Assignment Problems & Submission */} {assignmentProblems.map(assignmentProblem => ( @@ -151,6 +159,8 @@ const AssignmentDetailPage = () => { submissionScore={submissionScores.find(ss => ss.submissionId === s.id)} submissionProblemScores={submissionProblemScores.filter(sps => sps.submissionId === s.id)} assignmentProblems={assignmentProblems} + courseId={courseId} + assignmentId={assignmentId} /> ))} @@ -191,8 +201,10 @@ const SubmissionComponent = ({
- Submission Details - Submission Feedback + Submission Details + Submission Feedback


) diff --git a/devU-client/src/components/pages/userCoursesListPage.tsx b/devU-client/src/components/pages/userCoursesListPage.tsx index 7717fe17..3737421f 100644 --- a/devU-client/src/components/pages/userCoursesListPage.tsx +++ b/devU-client/src/components/pages/userCoursesListPage.tsx @@ -85,10 +85,10 @@ const UserCoursesListPage = () => { return (
-

My Courses

+

All Courses

- + Add Courses
@@ -106,13 +106,13 @@ const UserCoursesListPage = () => { {userCourses.map((userCourse) => ( ))} {allCourses.map(course => (
- {course.name} + {course.name}
))} From 881da42397f8660793e777475ac57eea7b2f9500 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 14:40:44 -0400 Subject: [PATCH 047/400] add a course preview page for user to see the description of the course and will be able to join the course by themselves. Also add drop course button in course detail page for user to drop the course (temporary function) --- .../src/components/authenticatedRouter.tsx | 30 +++--- .../src/components/pages/courseDetailPage.tsx | 29 +++++- .../components/pages/coursePreviewPage.tsx | 93 +++++++++++++++++++ 3 files changed, 134 insertions(+), 18 deletions(-) create mode 100644 devU-client/src/components/pages/coursePreviewPage.tsx diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index eba3344a..0c20108a 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Switch, Route } from 'react-router-dom' +import {Route, Switch} from 'react-router-dom' import AssignmentDetailPage from 'components/pages/assignmentDetailPage' @@ -25,20 +25,21 @@ import GradebookStudentPage from './pages/gradebookStudentPage' import GradebookInstructorPage from './pages/gradebookInstructorPage' import SubmissionFeedbackPage from './pages/submissionFeedbackPage' import ContainerAutoGraderForm from './pages/containerAutoGraderForm' +import CoursePreviewPage from './pages/coursePreviewPage' const AuthenticatedRouter = () => ( - + - + {/* Just reuse the homepage here, for now this is fine. we might want to change this in the future though which is why they exist as separate routes */} - - - - - + + + + + @@ -47,13 +48,14 @@ const AuthenticatedRouter = () => ( - - + + + - + ) export default AuthenticatedRouter diff --git a/devU-client/src/components/pages/courseDetailPage.tsx b/devU-client/src/components/pages/courseDetailPage.tsx index c0cdb278..ba58de62 100644 --- a/devU-client/src/components/pages/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courseDetailPage.tsx @@ -1,7 +1,7 @@ -import React,{ useState,useEffect } from 'react' -import { useParams, useHistory } from 'react-router-dom' +import React, {useEffect, useState} from 'react' +import {useHistory, useParams} from 'react-router-dom' import RequestService from 'services/request.service' -import { Course, Assignment } from 'devu-shared-modules' +import {Assignment, Course} from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -17,12 +17,16 @@ import Stack from '@mui/material/Stack' import styles from './courseDetailPage.scss' +import {SET_ALERT} from "../../redux/types/active.types"; +import {useActionless} from "../../redux/hooks"; const CourseDetailPage = () => { const history = useHistory() const { courseId } = useParams<{courseId: string}>() + const [courseInfo, setCourseInfo] = useState(null) const [categoryMap, setCategoryMap] = useState>({}) + const [setAlert] = useActionless(SET_ALERT) const fetchCourseInfo = async () => { RequestService.get(`/api/courses/${courseId}`) @@ -41,9 +45,24 @@ const CourseDetailPage = () => { categoryMap[assignment.categoryName] = [assignment] } }) - console.log(categoryMap) setCategoryMap(categoryMap) }) + + } + + + const handleDropCourse = () => { + + RequestService.delete(`/api/user-courses/${courseId}`).then(() => { + setAlert({autoDelete: true, type: 'success', message: 'Course Dropped'}) + setTimeout(() => { + history.push('/courses') + }, 3000) + + }).catch((error: Error) => { + const message = error.message + setAlert({autoDelete: false, type: 'error', message}) + }) } useEffect(() => { @@ -69,6 +88,8 @@ const CourseDetailPage = () => { +
diff --git a/devU-client/src/components/pages/coursePreviewPage.tsx b/devU-client/src/components/pages/coursePreviewPage.tsx new file mode 100644 index 00000000..8767f00a --- /dev/null +++ b/devU-client/src/components/pages/coursePreviewPage.tsx @@ -0,0 +1,93 @@ +import React, {useEffect, useState} from 'react' + +import {Course, UserCourse} from 'devu-shared-modules' + + +import PageWrapper from 'components/shared/layouts/pageWrapper' +import {useActionless, useAppSelector} from "../../redux/hooks" +import RequestService from "../../services/request.service" +import {SET_ALERT} from "../../redux/types/active.types" +import {useHistory, useParams} from "react-router-dom" +import LoadingOverlay from "../shared/loaders/loadingOverlay" +import Button from '@mui/material/Button' +import styles from "./courseDetailPage.scss" + + +const CoursePreviewPage = () => { + + const [loading, setLoading] = useState(true) + const [setAlert] = useActionless(SET_ALERT) + const {courseId} = useParams<{ courseId: string }>() + const [enrolled, setEnrolled] = useState(false) + const [course, setCourse] = useState() + const [userCourses, setUserCourses] = useState() + const userId = useAppSelector((store) => store.user.id) + + + const handleCheckEnroll = async () => { + RequestService.get(`/api/user-courses/user/courses/${courseId}`) + .then((response) => { + setUserCourses(response); + }) + + } + + useEffect(() => { + handleCheckEnroll() + }, []); + + const handleCourse = () => { + if (userCourses) { + setEnrolled(true) + } else { + RequestService.get(`/api/courses/${courseId}`) + .then((course) => { + setCourse(course) + }).catch((error) => { + setAlert({autoDelete: false, type: 'error', message: error.message}); + }).finally(() => setLoading(false)); + } + } + + useEffect(() => { + handleCourse() + }, [userCourses]); + + + if (loading) return + + if (enrolled) { + useHistory().push(`/courses/${courseId}`) + } + + const handleJoinCourse = () => { + const userCourseData = { + userId: userId, + courseId: courseId, + level: 'student', + dropped: false + }; + + RequestService.post('/api/user-courses', userCourseData) + .catch((error: Error) => { + const message = error.message + setAlert({autoDelete: false, type: 'error', message}) + }).finally(() => { + setAlert({autoDelete: true, type: 'success', message: 'Course Joined'}) + }) + } + + + return ( + +

Course Preview Page

+

{course?.name}

+

{course?.number}

+

{course?.semester}

+ +
+ ) +} + + +export default CoursePreviewPage From c2a987683ab482bb875fa80cfb27abfef028f02b Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 14:46:15 -0400 Subject: [PATCH 048/400] update ListItemWrapper to allow more custom style for container and tag, and add a new AssignmentListItem to only show assignment name, category, and due day for userCourseList to use. --- .../listItems/simpleAssignmentListItem.scss | 76 +++++++++++++++++++ .../listItems/simpleAssignmentListItem.tsx | 24 ++++++ .../listItems/userCourseListItem.scss | 53 ++++++++++++- .../listItems/userCourseListItem.tsx | 10 ++- .../components/pages/userCoursesListPage.tsx | 13 ++-- .../shared/layouts/listItemWrapper.tsx | 11 +-- 6 files changed, 169 insertions(+), 18 deletions(-) create mode 100644 devU-client/src/components/listItems/simpleAssignmentListItem.scss create mode 100644 devU-client/src/components/listItems/simpleAssignmentListItem.tsx diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.scss b/devU-client/src/components/listItems/simpleAssignmentListItem.scss new file mode 100644 index 00000000..108bdc37 --- /dev/null +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.scss @@ -0,0 +1,76 @@ +@import 'variables'; + +.something1 { + text-decoration: none; + + display: flex; + flex-direction: row; + + + border-radius: 0.6rem; + + justify-content: space-between; + + transition: background-color 0.2s linear; + + color: $text-color; + + gap: 1rem; + +} + +.something2 { + padding: 10px; + display: flex; + flex-direction: row; + + border-radius: 0.6rem; + + justify-content: space-between; + + transition: background-color 0.2s linear; + + +} + +.meta { + display: flex; + text-decoration: none; + gap: 1rem; +} + +.subText { + font-size: 1rem; + display: grid; + justify-content: space-between; + gap: 1.5rem; + width: 100%; +} + +.tag { + display: flex; + min-width: 8px; + background-color: $primary; + margin-top: 10px; +} + +.container { + text-decoration: none; + + display: flex; + flex-direction: column; + + background: $list-item-background; + border-radius: 0.6rem; + + padding: 0.5rem; + + transition: background-color 0.2s linear; + + color: $text-color; + + &:hover, + &:focus { + background: $list-item-background-hover; + } +} \ No newline at end of file diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.tsx b/devU-client/src/components/listItems/simpleAssignmentListItem.tsx new file mode 100644 index 00000000..9ec2f1c8 --- /dev/null +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import ListItemWrapper from 'components/shared/layouts/listItemWrapper' +import {prettyPrintDate} from 'utils/date.utils' +import styles from './simpleAssignmentListItem.scss' +import {Assignment} from 'devu-shared-modules' +// import Card from '@mui/material/Card' + + +type Props = { + assignment: Assignment +} + +const SimpleAssignmentListItem = ({assignment}: Props) => ( + +
{assignment.name}
+
{assignment.categoryName}
+
Due At: {prettyPrintDate(assignment.dueDate)}
+ +
+) + + +export default SimpleAssignmentListItem \ No newline at end of file diff --git a/devU-client/src/components/listItems/userCourseListItem.scss b/devU-client/src/components/listItems/userCourseListItem.scss index af8d6ee2..88269495 100644 --- a/devU-client/src/components/listItems/userCourseListItem.scss +++ b/devU-client/src/components/listItems/userCourseListItem.scss @@ -6,14 +6,63 @@ margin-bottom: 0.4rem; } +.tag { + min-height: 8px; + background-color: $primary; + margin-right: 10px; +} + .subText { font-size: 1rem; - display: flex; + display: grid; justify-content: space-between; - gap: 2rem; + gap: 1.5rem; width: 100%; } +.tag { + font-size: 0.8rem; + padding: 0.2rem 0.5rem; + border-radius: 0.5rem; + color: white; + font-weight: 700; +} + +.assignment { + font-size: 1.125rem; + text-decoration: none; + margin-bottom: 1rem; +} + +.meta { + display: flex; + text-decoration: none; + gap: 1rem; +} + +.container { + text-decoration: none; + + display: flex; + flex-direction: row; + + background: $list-item-background; + border-radius: 0.6rem; + + padding: 1.5rem; + + margin-bottom: 1rem; + + transition: background-color 0.2s linear; + + color: $text-color; + + &:hover, + &:focus { + background: $list-item-background-hover; + } +} + @media (max-width: $medium) { .subText { display: block; diff --git a/devU-client/src/components/listItems/userCourseListItem.tsx b/devU-client/src/components/listItems/userCourseListItem.tsx index e8731d11..1186ae38 100644 --- a/devU-client/src/components/listItems/userCourseListItem.tsx +++ b/devU-client/src/components/listItems/userCourseListItem.tsx @@ -1,16 +1,18 @@ import React from 'react' -import { UserCourse, Course } from 'devu-shared-modules' +import {Assignment, Course} from 'devu-shared-modules' import ListItemWrapper from 'components/shared/layouts/listItemWrapper' -import { prettyPrintDate } from 'utils/date.utils' +import {prettyPrintDate} from 'utils/date.utils' import styles from './userCourseListItem.scss' +import SimpleAssignmentListItem from "./simpleAssignmentListItem"; + type Props = { - userCourse: UserCourse - course: Course + course: Course + assignments?: Assignment[] } const UserCourseListItem = ({ course }: Props) => ( diff --git a/devU-client/src/components/pages/userCoursesListPage.tsx b/devU-client/src/components/pages/userCoursesListPage.tsx index 3737421f..a3dc7891 100644 --- a/devU-client/src/components/pages/userCoursesListPage.tsx +++ b/devU-client/src/components/pages/userCoursesListPage.tsx @@ -1,13 +1,12 @@ -import React, { useState, useEffect } from 'react' -import { Link } from 'react-router-dom' +import React, {useEffect, useState} from 'react' +import {Link} from 'react-router-dom' -import { UserCourse, Course } from 'devu-shared-modules' -import { useAppSelector } from 'redux/hooks' +import {Course, UserCourse} from 'devu-shared-modules' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import PageWrapper from 'components/shared/layouts/pageWrapper' import UserCourseListItem from 'components/listItems/userCourseListItem' -import Dropdown, { Option } from 'components/shared/inputs/dropdown' +import Dropdown, {Option} from 'components/shared/inputs/dropdown' import ErrorPage from './errorPage' import RequestService from 'services/request.service' @@ -28,7 +27,7 @@ const filterOptions: Option[] = [ ] const UserCoursesListPage = () => { - const userId = useAppSelector((store) => store.user.id) + const defaultFilter = LocalStorageService.get(FILTER_LOCAL_STORAGE_KEY) || 'active' @@ -62,7 +61,7 @@ const UserCoursesListPage = () => { setUserCourses(userCourses) setCourses(courseMap) - } catch (error) { + } catch (error: any) { setError(error) } finally { setLoading(false) diff --git a/devU-client/src/components/shared/layouts/listItemWrapper.tsx b/devU-client/src/components/shared/layouts/listItemWrapper.tsx index 944ee809..d71a8d8f 100644 --- a/devU-client/src/components/shared/layouts/listItemWrapper.tsx +++ b/devU-client/src/components/shared/layouts/listItemWrapper.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Link } from 'react-router-dom' +import {Link} from 'react-router-dom' import styles from './listItemWrapper.scss' @@ -10,7 +10,8 @@ type Props = { children: React.ReactNode tag?: string className?: string - + tagStyle?: string + containerStyle?: string } @@ -20,9 +21,9 @@ const colorHash = (input: string) => { return hash.hex(input) } -const ListItemWrapper = ({ to, children, tag, className = '' }: Props) => ( - - {tag &&
} +const ListItemWrapper = ({to, children, tag, className = '', tagStyle, containerStyle}: Props) => ( + + {tag &&
}
{children}
) From e542a46ad896b7e98ca5314711d7ae9fb18b3fe2 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 14:51:27 -0400 Subject: [PATCH 049/400] update home page to see enrolled course and assignment and have actual functionality to go to the corresponding page and update submissionList page to follow new path pattern --- .../src/entities/course/course.validator.ts | 9 ++- .../listItems/submissionListItem.tsx | 50 ++++++------ .../listItems/userCourseListItem.tsx | 29 ++++--- .../src/components/pages/homePage.scss | 3 +- devU-client/src/components/pages/homePage.tsx | 77 +++++++------------ .../components/pages/submissionDetailPage.tsx | 19 ++--- .../pages/submissionFeedbackPage.tsx | 10 ++- 7 files changed, 93 insertions(+), 104 deletions(-) diff --git a/devU-api/src/entities/course/course.validator.ts b/devU-api/src/entities/course/course.validator.ts index a657e332..3daeb274 100644 --- a/devU-api/src/entities/course/course.validator.ts +++ b/devU-api/src/entities/course/course.validator.ts @@ -1,10 +1,11 @@ -import { check } from 'express-validator' +import {check} from 'express-validator' import validate from '../../middleware/validator/generic.validator' -import { isAfterParam, isBeforeParam } from '../../middleware/validator/date.validator' +import {isAfterParam, isBeforeParam} from '../../middleware/validator/date.validator' -const name = check('name').isString().trim().isLength({ max: 128 }) -const number = check('number').isString().trim().isLength({ max: 128 }) +const name = check('name').isString().trim().isLength({max: 128, min: 1}) + +const number = check('number').isString().trim().isLength({max: 128, min: 1}) const startDate = check('startDate').isString().trim().isISO8601().custom(isBeforeParam('endDate')).toDate() const endDate = check('endDate').isString().trim().isISO8601().custom(isAfterParam('startDate')).toDate() diff --git a/devU-client/src/components/listItems/submissionListItem.tsx b/devU-client/src/components/listItems/submissionListItem.tsx index 70cc4922..0922c681 100644 --- a/devU-client/src/components/listItems/submissionListItem.tsx +++ b/devU-client/src/components/listItems/submissionListItem.tsx @@ -1,40 +1,42 @@ import React from 'react' -import { Assignment, Course, Submission } from 'devu-shared-modules' +import {Assignment, Course, Submission} from 'devu-shared-modules' import ListItemWrapper from 'components/shared/layouts/listItemWrapper' -import { prettyPrintDate } from 'utils/date.utils' +import {prettyPrintDate} from 'utils/date.utils' import styles from './submissionListItem.scss' type Props = { - submission: Submission - assignment?: Assignment - course?: Course + submission: Submission + assignment?: Assignment + course?: Course } const SubmissionListItem = ({ submission, assignment, course }: Props) => ( - - {course && ( -
- {course.name} - {course.number} -
- )} - {assignment && ( -
- {assignment.name} -
- )} -
-
Submitted At: {prettyPrintDate(submission.createdAt || '')}
- {assignment &&
Due At: {prettyPrintDate(assignment.dueDate)}
} -
-
+ + {course && ( +
+ {course.name} - {course.number} +
+ )} + {assignment && ( +
+ {assignment.name} +
+ )} +
+
Submitted At: {prettyPrintDate(submission.createdAt || '')}
+ {assignment &&
Due At: {prettyPrintDate(assignment.dueDate)}
} +
+
) export default SubmissionListItem diff --git a/devU-client/src/components/listItems/userCourseListItem.tsx b/devU-client/src/components/listItems/userCourseListItem.tsx index 1186ae38..4de955ae 100644 --- a/devU-client/src/components/listItems/userCourseListItem.tsx +++ b/devU-client/src/components/listItems/userCourseListItem.tsx @@ -15,18 +15,25 @@ type Props = { assignments?: Assignment[] } -const UserCourseListItem = ({ course }: Props) => ( - -
{course.name}
-
-
{course.number}
-
Semester: {course.semester}
-
Start Date: {prettyPrintDate(course.startDate)}
-
End Date: {prettyPrintDate(course.endDate)}
- - {/* Add any other class information here */} +const UserCourseListItem = ({course, assignments}: Props) => ( + + +
{course.name}
+
+
{course.number}
+
Semester: {course.semester}
+
Start Date: {prettyPrintDate(course.startDate)}
+
End Date: {prettyPrintDate(course.endDate)}
+ + {assignments && assignments.length > 0 ? (assignments.map((assignment) => ( + + ))) : (
No Assignments Due Yet
)} +
-
+ + + + ) export default UserCourseListItem diff --git a/devU-client/src/components/pages/homePage.scss b/devU-client/src/components/pages/homePage.scss index 228541e1..90cb96f6 100644 --- a/devU-client/src/components/pages/homePage.scss +++ b/devU-client/src/components/pages/homePage.scss @@ -2,11 +2,12 @@ .coursesContainer { display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(auto-fill, 500px); gap: 20px; /* adjust this value to set the space between the cards */ } .header { + color: $text-color; display: flex; align-items: center; } diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index f48a01a3..9dabcd41 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -1,25 +1,17 @@ -import React, { useEffect, useState } from 'react' -import { useHistory } from 'react-router-dom' +import React, {useEffect, useState} from 'react' +import {useHistory} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import ErrorPage from './errorPage' import styles from './homePage.scss' +import UserCourseListItem from "../listItems/userCourseListItem"; - -import Card from '@mui/material/Card' -import CardContent from '@mui/material/CardContent' -import Typography from '@mui/material/Typography' -import { CardActionArea } from '@mui/material' import Button from '@mui/material/Button' -import List from '@mui/material/List' -import ListItem from '@mui/material/ListItem' -import ListItemButton from '@mui/material/ListItemButton' -import ListItemText from '@mui/material/ListItemText' -import { useAppSelector } from 'redux/hooks' +import {useAppSelector} from 'redux/hooks' import RequestService from 'services/request.service' -import { Course, UserCourse } from 'devu-shared-modules' +import {Assignment, Course, UserCourse} from 'devu-shared-modules' const HomePage = () => { @@ -29,19 +21,27 @@ const HomePage = () => { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [courses, setCourses] = useState(new Array()) + const [assignments, setAssignments] = useState(new Map>()) useEffect(() => { fetchData() - }, []) + }, []) const fetchData = async () => { try { const userCourses = await RequestService.get( `/api/user-courses/user/${userId}` ) - const coursePromises = userCourses.map(uc => ( - RequestService.get( `/api/courses/${uc.courseId}` ) - )) - setCourses(await Promise.all(coursePromises)) - + const coursePromises = userCourses.map(uc => { + const course = RequestService.get(`/api/courses/${uc.courseId}`) + const assignments = RequestService.get(`/api/assignments/course/${uc.courseId}`) + return Promise.all([course, assignments]) + + }) + const result = await Promise.all(coursePromises) + const courses = result.map(([course]) => course) + const assignmentsMap = new Map>() + result.forEach(([course, assignments]) => assignmentsMap.set(course, assignments)) + setCourses(courses) + setAssignments(assignmentsMap) } catch (error) { setError(error) } finally { @@ -49,6 +49,7 @@ const HomePage = () => { } } + if (loading) return if (error) return @@ -56,45 +57,19 @@ const HomePage = () => {
-

Courses

+

My Courses

- {courses.map((course, index) => ( - - - - - {course.name} - - - {course.number} -
- {course.semester} -
- {course.startDate} - {course.endDate} -
-
-
- - - - - - - - - - - - -
- ))} + {courses.map((course) => ( + + + ))}
diff --git a/devU-client/src/components/pages/submissionDetailPage.tsx b/devU-client/src/components/pages/submissionDetailPage.tsx index 9b2238e3..ab5606bf 100644 --- a/devU-client/src/components/pages/submissionDetailPage.tsx +++ b/devU-client/src/components/pages/submissionDetailPage.tsx @@ -1,23 +1,23 @@ -import React,{useState,useEffect} from 'react' +import React, {useEffect, useState} from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import ErrorPage from './errorPage' import RequestService from 'services/request.service' -import { SubmissionScore, SubmissionProblemScore, Submission, Assignment, AssignmentProblem } from 'devu-shared-modules' -import { Link, useParams } from 'react-router-dom' +import {Assignment, AssignmentProblem, Submission, SubmissionProblemScore, SubmissionScore} from 'devu-shared-modules' +import {Link, useParams} from 'react-router-dom' import Button from '../shared/inputs/button' import TextField from '../shared/inputs/textField' -import { useActionless } from 'redux/hooks' -import { SET_ALERT } from 'redux/types/active.types' +import {useActionless} from 'redux/hooks' +import {SET_ALERT} from 'redux/types/active.types' - -const SubmissionDetailPage = () => { +const SubmissionDetailPage = () => { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [setAlert] = useActionless(SET_ALERT) + const {courseId, assignmentId} = useParams<{ courseId: string, assignmentId: string }>() const { submissionId } = useParams<{submissionId: string}>() const [submissionScore, setSubmissionScore] = useState(null) @@ -119,8 +119,9 @@ const SubmissionDetailPage = () => { ))} {submissionScore?.score ?? "N/A"} - - View Feedback + + View + Feedback

Submission Content:

diff --git a/devU-client/src/components/pages/submissionFeedbackPage.tsx b/devU-client/src/components/pages/submissionFeedbackPage.tsx index 0e86309f..e9a07b7d 100644 --- a/devU-client/src/components/pages/submissionFeedbackPage.tsx +++ b/devU-client/src/components/pages/submissionFeedbackPage.tsx @@ -1,15 +1,16 @@ -import React, { useState, useEffect } from 'react' -import { Link, useParams } from 'react-router-dom' +import React, {useEffect, useState} from 'react' +import {Link, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import ErrorPage from './errorPage' -import { Assignment, AssignmentProblem, Submission, SubmissionProblemScore, SubmissionScore } from 'devu-shared-modules' +import {Assignment, AssignmentProblem, Submission, SubmissionProblemScore, SubmissionScore} from 'devu-shared-modules' import RequestService from 'services/request.service' const SubmissionFeedbackPage = () => { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) + const {courseId, assignmentId} = useParams<{ courseId: string, assignmentId: string }>() const { submissionId } = useParams<{submissionId: string}>() const [submissionScore, setSubmissionScore] = useState(null) @@ -61,7 +62,8 @@ const SubmissionFeedbackPage = () => {
{sps.feedback}
))} - View Submission Details + View Submission + Details
) } From f95ed41a94db208eb457271649eadc9e1e06bf37 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 14:52:50 -0400 Subject: [PATCH 050/400] fix api test bug for fileUpload and login.developer --- .../tests/login.developer.controller.test.ts | 7 +++++-- .../src/fileUpload/test/fileUpload.serializer.test.ts | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/devU-api/src/auth/login.developer/tests/login.developer.controller.test.ts b/devU-api/src/auth/login.developer/tests/login.developer.controller.test.ts index 59900e57..23a3cf69 100644 --- a/devU-api/src/auth/login.developer/tests/login.developer.controller.test.ts +++ b/devU-api/src/auth/login.developer/tests/login.developer.controller.test.ts @@ -6,7 +6,7 @@ import UserService from '../../../entities/user/user.service' import AuthService from '../../../auth/auth.service' import Testing from '../../../utils/testing.utils' -import { refreshCookieOptions } from '../../../utils/cookie.utils' +import {refreshCookieOptions} from '../../../utils/cookie.utils' // Testing Globals let req: any @@ -42,7 +42,10 @@ describe('LoginDeveloperController', () => { test('Returns refreshToken as cookie', () => expect(res.cookie).toBeCalledWith('refreshToken', refreshToken, refreshCookieOptions)) test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) - test('Generic response returned ', () => expect(res.json).toBeCalledWith({ message: 'Login successful' })) + test('Generic response returned ', () => expect(res.json).toBeCalledWith({ + message: 'Login successful', + userId: 1 + })) }) describe('400 - Bad Request', () => { diff --git a/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts b/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts index dd26f46f..4d067589 100644 --- a/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts +++ b/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts @@ -7,14 +7,14 @@ let fakeFileUpload: FileUpload describe('fileUpload.serializer', () => { beforeEach(() => { let fieldName: string = "submissions" - const originalNames: string[] = ["test1.txt", "test2.txt", "test3.txt"] - const fileNames: string[] = ["modified1.txt", "modified2.txt", "modified3.txt"] - const etags: string[] = ["etag1", "etag2", "etag3"] + const originalNames: string = ["test1.txt", "test2.txt", "test3.txt"].join(",") + const fileNames: string = ["modified1.txt", "modified2.txt", "modified3.txt"].join(",") + const etags: string = ["etag1", "etag2", "etag3"].join(",") fakeFileUpload = { fieldName: fieldName, originalName: originalNames, - fileName: fileNames, + filename: fileNames, etags: etags } }); @@ -24,7 +24,7 @@ describe('fileUpload.serializer', () => { expect(actualResult).toBeDefined() expect(actualResult.fieldName).toEqual(fakeFileUpload.fieldName) expect(actualResult.originalName).toEqual(fakeFileUpload.originalName) - expect(actualResult.fileName).toEqual(fakeFileUpload.fileName) + expect(actualResult.filename).toEqual(fakeFileUpload.filename) expect(actualResult.etags).toEqual(fakeFileUpload.etags) }) From dcbcdf287a4a535c730d9726e711ef1c969033da Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 15:30:58 -0400 Subject: [PATCH 051/400] update userCourse entity for 1. adding a checkEnroll function to check if the user is enrolled the course and does not drop yet 2. update PUT method to check if the user has already enrolled the course and will throw error when trying to enroll course twice 3. update DELETE method to have courseId and userId to find the exact course need to be deleted. --- .../userCourse/userCourse.controller.ts | 36 ++++++++++++++----- .../entities/userCourse/userCourse.router.ts | 4 ++- .../entities/userCourse/userCourse.service.ts | 31 +++++++++++----- 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/devU-api/src/entities/userCourse/userCourse.controller.ts b/devU-api/src/entities/userCourse/userCourse.controller.ts index ddb1cb78..67963cf8 100644 --- a/devU-api/src/entities/userCourse/userCourse.controller.ts +++ b/devU-api/src/entities/userCourse/userCourse.controller.ts @@ -1,9 +1,9 @@ -import { Request, Response, NextFunction } from 'express' +import {NextFunction, Request, Response} from 'express' import UserCourseService from './userCourse.service' -import { serialize } from './userCourse.serializer' +import {serialize} from './userCourse.serializer' -import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' +import {GenericResponse, NotFound, Updated} from '../../utils/apiResponse.utils' export async function getAll(req: Request, res: Response, next: NextFunction) { try { @@ -68,9 +68,10 @@ export async function post(req: Request, res: Response, next: NextFunction) { export async function put(req: Request, res: Response, next: NextFunction) { try { - req.body.id = parseInt(req.params.id) - const results = await UserCourseService.update(req.body) - + req.body.courseId = parseInt(req.params.id) + const currentUser = req.currentUser?.userId + if (!currentUser) return res.status(401).json({message: 'Unauthorized'}) + const results = await UserCourseService.update(req.body, currentUser) if (!results.affected) return res.status(404).json(NotFound) res.status(200).json(Updated) @@ -79,10 +80,29 @@ export async function put(req: Request, res: Response, next: NextFunction) { } } +export async function checkEnroll(req: Request, res: Response, next: NextFunction) { + try { + const courseId = parseInt(req.params.courseId) + const userId = req.currentUser?.userId + if (!userId) return res.status(401).json({message: 'Unauthorized'}) + + const userCourse = await UserCourseService.checking(userId, courseId) + if (!userCourse) return res.status(404).json(NotFound) + + res.status(200).json(serialize(userCourse)) + } catch (err) { + next(err) + } +} + + export async function _delete(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id) - const results = await UserCourseService._delete(id) + const currentUser = req.currentUser?.userId + if (!currentUser) return res.status(401).json({message: 'Unauthorized'}) + + const results = await UserCourseService._delete(id, currentUser) if (!results.affected) return res.status(404).json(NotFound) @@ -92,4 +112,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { get, getByCourse, getAll, detail, post, put, _delete } +export default {get, getByCourse, getAll, detail, post, put, _delete, checkEnroll} diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index 12bd74e7..22f14b87 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -3,7 +3,7 @@ import express from 'express' // Middleware import validator from './userCourse.validator' -import { asInt } from '../../middleware/validator/generic.validator' +import {asInt} from '../../middleware/validator/generic.validator' // Controller import UserCourseController from './userCourse.controller' @@ -84,6 +84,8 @@ Router.get('/course/:id', asInt(), UserCourseController.getByCourse) */ Router.get('/:id', asInt(), UserCourseController.detail) +Router.get('/user/courses/:courseId', asInt("courseId"), UserCourseController.checkEnroll) + /** * @swagger * /user-courses: diff --git a/devU-api/src/entities/userCourse/userCourse.service.ts b/devU-api/src/entities/userCourse/userCourse.service.ts index 1012fcc6..d213556b 100644 --- a/devU-api/src/entities/userCourse/userCourse.service.ts +++ b/devU-api/src/entities/userCourse/userCourse.service.ts @@ -1,25 +1,33 @@ -import { getRepository, IsNull } from 'typeorm' +import {getRepository, IsNull} from 'typeorm' -import { UserCourse as UserCourseType } from 'devu-shared-modules' +import {UserCourse as UserCourseType} from 'devu-shared-modules' import UserCourse from './userCourse.model' const connect = () => getRepository(UserCourse) export async function create(userCourse: UserCourseType) { + const userId = userCourse.userId + const hasEnrolled = await connect().findOne({userId, courseId: userCourse.courseId}) + if (hasEnrolled) throw new Error('User already enrolled in course') return await connect().save(userCourse) } -export async function update(userCourse: UserCourseType) { - const { id, level, dropped } = userCourse +export async function update(userCourse: UserCourseType, currentUser: number) { + const {courseId, level, dropped} = userCourse + const userCourseData = await connect().findOne({courseId, userId: currentUser}) + if (!userCourseData) throw new Error('User not enrolled in course') + userCourseData.level = level + userCourseData.dropped = dropped + //@ts-ignore + return await connect().update(userCourse.id, userCourseData) - if (!id) throw new Error('Missing Id') - - return await connect().update(id, { level, dropped }) } -export async function _delete(id: number) { - return await connect().softDelete({ id, deletedAt: IsNull() }) +export async function _delete(courseId: number, userId: number) { + const userCourse = await connect().findOne({courseId, userId}) + if (!userCourse) throw new Error('User Not Found in Course') + return await connect().softDelete({courseId, userId, deletedAt: IsNull()}) } export async function retrieve(id: number) { @@ -37,6 +45,10 @@ export async function listByCourse(courseId: number) { return await connect().find({ courseId, deletedAt: IsNull() }) } +export async function checking(userId: number, courseId: number) { + return await connect().findOne({userId, courseId, dropped: false, deletedAt: IsNull()}) +} + export default { create, retrieve, @@ -45,4 +57,5 @@ export default { list, listAll, listByCourse, + checking } From 051819ca3a5b8d36fd4c8b5ef2da1463d64067bc Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 15:36:52 -0400 Subject: [PATCH 052/400] update submission entity for 1. adding a listByAssignment to get list of submission by assignments instead of all the submission for the user. --- .../submission/submission.controller.ts | 25 +++++++++++++++---- .../entities/submission/submission.router.ts | 4 ++- .../entities/submission/submission.service.ts | 15 ++++++++--- .../tests/userCourse.controller.test.ts | 9 ++++--- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/devU-api/src/entities/submission/submission.controller.ts b/devU-api/src/entities/submission/submission.controller.ts index 7a073b22..391a40d7 100644 --- a/devU-api/src/entities/submission/submission.controller.ts +++ b/devU-api/src/entities/submission/submission.controller.ts @@ -1,10 +1,10 @@ -import { Request, Response, NextFunction } from 'express' +import {NextFunction, Request, Response} from 'express' import SubmissionService from '../submission/submission.service' -import { GenericResponse, NotFound } from '../../utils/apiResponse.utils' +import {GenericResponse, NotFound} from '../../utils/apiResponse.utils' -import { serialize } from './submission.serializer' +import {serialize} from './submission.serializer' export async function get(req: Request, res: Response, next: NextFunction) { try { @@ -52,7 +52,7 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err:any) { - next(err) + res.status(400).json(new GenericResponse(err.message)) } } @@ -69,4 +69,19 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { get, detail, post, _delete } + +export async function getByAssignment(req: Request, res: Response, next: NextFunction) { + try { + const assignmentId = parseInt(req.params.assignmentId) + const userId = req.currentUser?.userId + if (!userId) return res.status(400).json(new GenericResponse('Request requires auth')) + const submissions = await SubmissionService.listByAssignment(assignmentId, userId) + const response = submissions.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} + +export default {get, detail, post, _delete, getByAssignment} diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index 8a899f4e..65cd976d 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -4,7 +4,7 @@ import Multer from 'multer' // Middleware import validator from '../submission/submission.validator' -import { asInt } from '../../middleware/validator/generic.validator' +import {asInt} from '../../middleware/validator/generic.validator' // Controller import SubmissionController from '../submission/submission.controller' @@ -37,6 +37,8 @@ const upload = Multer() */ Router.get('/', SubmissionController.get) +Router.get('/assignments/:assignmentId', asInt('assignmentId'), SubmissionController.getByAssignment) + /** * @swagger * /submissions/{id}: diff --git a/devU-api/src/entities/submission/submission.service.ts b/devU-api/src/entities/submission/submission.service.ts index 48ccbc71..35686daf 100644 --- a/devU-api/src/entities/submission/submission.service.ts +++ b/devU-api/src/entities/submission/submission.service.ts @@ -1,12 +1,12 @@ -import { getRepository, IsNull } from 'typeorm' +import {getRepository, IsNull} from 'typeorm' import SubmissionModel from '../submission/submission.model' import FileModel from '../../fileUpload/fileUpload.model' import CourseModel from '../course/course.model' -import { Submission, FileUpload } from 'devu-shared-modules' -import { uploadFile } from '../../fileStorage' -import { groupBy } from '../../database' +import {FileUpload, Submission} from 'devu-shared-modules' +import {uploadFile} from '../../fileStorage' +import {groupBy} from '../../database' const submissionConn = () => getRepository(SubmissionModel) const fileConn = () => getRepository(FileModel) @@ -59,9 +59,16 @@ export async function list(query: any, id: number) { return await groupBy(submissionConn(), OrderByMappings, query, { index: 'submittedBy', value: id }) } + +export async function listByAssignment(assignmentId: number, id: number) { + return await submissionConn().find({where: {assignmentId, submittedBy: id, deletedAt: IsNull()}}) +} + + export default { create, retrieve, _delete, list, + listByAssignment, } diff --git a/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts b/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts index 1ce39d6f..2570baac 100644 --- a/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts +++ b/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts @@ -1,6 +1,6 @@ -import { UpdateResult } from 'typeorm' +import {UpdateResult} from 'typeorm' -import { UserCourse } from 'devu-shared-modules' +import {UserCourse} from 'devu-shared-modules' import controller from '../userCourse.controller' @@ -8,10 +8,10 @@ import UserCourseModel from '../userCourse.model' import UserCourseService from '../userCourse.service' -import { serialize } from '../userCourse.serializer' +import {serialize} from '../userCourse.serializer' import Testing from '../../../utils/testing.utils' -import { GenericResponse, NotFound, Updated } from '../../../utils/apiResponse.utils' +import {GenericResponse, NotFound, Updated} from '../../../utils/apiResponse.utils' // Testing Globals let req: any @@ -51,6 +51,7 @@ describe('UserCourseController', () => { describe('200 - Ok', () => { beforeEach(async () => { UserCourseService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedUserCourses)) + req.params.id = expectedUserId await controller.get(req, res, next) // what we're testing }) From bb7f8fa90081ef52c9e7aa0a70b13129afec30ca Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 15:38:30 -0400 Subject: [PATCH 053/400] update populate-db to let joinCourse be a separate function to handle. --- devU-api/scripts/populate-db.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/devU-api/scripts/populate-db.ts b/devU-api/scripts/populate-db.ts index c52f69d8..3c44ed78 100644 --- a/devU-api/scripts/populate-db.ts +++ b/devU-api/scripts/populate-db.ts @@ -66,6 +66,17 @@ async function CreateCourse(name:string, number:string, semester:string) { return await SendPOST('/courses', JSON.stringify(courseData), 'admin'); } +async function joinCourse(courseId: number, userId: number, level: string) { + const userCourseData = { + userId: userId, + courseId: courseId, + level: level, + dropped: false + }; + console.log(`Joining course: ${courseId} for user: ${userId}`) + return await SendPOST('/user-courses', JSON.stringify(userCourseData), 'admin'); + +} async function createAssignment(courseId:number, name:string, categoryName:string) { const time = new Date().getTime(); @@ -176,10 +187,10 @@ async function runCourseAndSubmission() { const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id //Create enroll students - await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId1}, level:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${billy}, courseId:${courseId2}, level:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId1}, level:student, dropped:false}`, 'admin') - await SendPOST('/user-courses', `{userId:${bob}, courseId:${courseId2}, level:student, dropped:false}`, 'admin') + await joinCourse(courseId1, billy, 'student') + await joinCourse(courseId1, bob, 'student') + await joinCourse(courseId2, billy, 'student') + await joinCourse(courseId2, bob, 'student') //Create assignments const assignmentId1 = (await createAssignment(courseId1, 'Course1 Assignment 1', 'Quiz')).id From 524309f02941e6ffbfd0c12dcdb6a7a46649d733 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 18:08:59 -0400 Subject: [PATCH 054/400] update fileUpload tests to run tests without minio running --- devU-api/src/fileUpload/fileUpload.serializer.ts | 1 + devU-api/src/fileUpload/test/fileUpload.controller.test.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/devU-api/src/fileUpload/fileUpload.serializer.ts b/devU-api/src/fileUpload/fileUpload.serializer.ts index 0b66634e..ca1fc2e8 100644 --- a/devU-api/src/fileUpload/fileUpload.serializer.ts +++ b/devU-api/src/fileUpload/fileUpload.serializer.ts @@ -3,6 +3,7 @@ import {FileUpload} from "../../devu-shared-modules" export function serialize(file: FileUpload): FileUpload { return { + id: file.id, fieldName: file.fieldName, originalName: file.originalName, filename: file.filename, diff --git a/devU-api/src/fileUpload/test/fileUpload.controller.test.ts b/devU-api/src/fileUpload/test/fileUpload.controller.test.ts index 7410e0fe..971ff009 100644 --- a/devU-api/src/fileUpload/test/fileUpload.controller.test.ts +++ b/devU-api/src/fileUpload/test/fileUpload.controller.test.ts @@ -1,7 +1,7 @@ -import {FileUpload} from '../../../devu-shared-modules' import controller from '../fileUpload.controller' import FileUploadService from '../fileUpload.service' import Testing from '../../utils/testing.utils' +import FileModel from '../fileUpload.model' // Testing Globals let req: any @@ -9,7 +9,7 @@ let res: any let next: any let mockFile: any -let mockedFileUpload: FileUpload +let mockedFileUpload: FileModel let expectedError: Error @@ -30,6 +30,7 @@ describe('FileUploadController', () => { } ] }; + mockedFileUpload = Testing.generateTypeOrm(FileModel) expectedError = new Error('Expected Error') }) describe('GET - /file-upload/:bucketName', () => { @@ -55,6 +56,7 @@ describe('FileUploadController', () => { describe('201 - Created', () => { beforeEach(async () => { req.files = mockFile + FileUploadService.create = jest.fn().mockImplementation(() => Promise.resolve(mockedFileUpload)) await controller.post(req, res, next) }) test('Status code is 201', () => { From 1af9bf4dab40e30894965466d1431d8b06568745 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Tue, 23 Apr 2024 20:17:00 -0400 Subject: [PATCH 055/400] Added dynamic paths for certain routes --- devU-client/src/components/misc/navbar.tsx | 37 ++++++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/devU-client/src/components/misc/navbar.tsx b/devU-client/src/components/misc/navbar.tsx index 08c3bb9e..1e4d5109 100644 --- a/devU-client/src/components/misc/navbar.tsx +++ b/devU-client/src/components/misc/navbar.tsx @@ -50,24 +50,47 @@ const AssignmentBreadcrumb = ({ match }: any) => { return (<>{assignmentName}) } +const DynamicBreadcrumb = ({ match }: any) => { + const pathSegment = match.url.substr(match.url.lastIndexOf('/') + 1); + const breadcrumbName = pathSegment.charAt(0).toUpperCase() + pathSegment.slice(1); + + return <>{breadcrumbName}; +} + const routes = [ { path: '/', breadcrumb: 'Home' }, + { path: '/users/:userId', breadcrumb: UserBreadcrumb}, + { path: '/courses/:courseId', breadcrumb: CourseBreadcrumb}, - { path: '/courses/:courseId/gradebook', breadcrumb: 'Gradebook'}, + { path: '/courses/:courseId/:path', breadcrumb: DynamicBreadcrumb}, + { path: '/courses/:courseId/assignments/:assignmentId', breadcrumb: AssignmentBreadcrumb}, + { path: '/courses/:courseId/assignments/:assignmentId/:path', breadcrumb: DynamicBreadcrumb}, + + { path: '/courses/:courseId/assignments/:assignmentId/submissions/:submissionId', breadcrumb: 'Submission'}, + { path: '/courses/:courseId/assignments/:assignmentId/submissions/:submissionId/feedback', breadcrumb: 'Feedback'}, ] const Navbar = ({breadcrumbs}: any) => { + const excludedPaths = [ + 'assignments', + 'submissions', + ] + return (
- {breadcrumbs.map(({breadcrumb, match}: any, index: number) => ( - - {breadcrumb} - {index < (breadcrumbs.length - 1) ? ' > ' : ''} - - ))} + {breadcrumbs.map(({breadcrumb, match}: any, index: number) => { + if (excludedPaths.includes(match.params.path)) return <> + + return ( + + {breadcrumb} + {index < (breadcrumbs.length - 1) ? ' > ' : ''} + + ) + })}
) From c66e4b5d5f81c18998a97046351de872a9e24075 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 20:45:48 -0400 Subject: [PATCH 056/400] update scss files for 1. adding dark mode color for sub listItemWrapper section 2. changing light mode color for the line 3. removed some unused styles --- devU-client/src/assets/global.scss | 7 +++ devU-client/src/assets/variables.scss | 4 ++ .../listItems/simpleAssignmentListItem.scss | 25 ++------- .../components/pages/courseDetailPage.scss | 2 + .../src/components/pages/homePage.scss | 4 +- .../components/pages/userCoursesListPage.scss | 54 ++++++------------- .../pages/userSubmissionsListPage.scss | 14 +++++ 7 files changed, 48 insertions(+), 62 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 8274187e..c2d7b4f4 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -38,6 +38,9 @@ --list-item-background-hover: #e9e9e9; --list-item-subtext: #4b4b4b; + --list-simple-item-background: #9f9f9f4f; + --list-simple-item-background-hover: #a0a0a0; + --list-simple-item-subtext: #4b4b4b; // Non theme colors --grey-lightest: #dfe6e9; --grey-lighter: #b2bec3; @@ -77,6 +80,10 @@ --list-item-background-hover: #303030; --list-item-subtext: #e0e0e0; + --list-simple-item-background: #464646; + --list-simple-item-background-hover: #626262; + --list-simple-item-subtext: #4b4b4b; + background-color: var(--background); color: var(--text-color); } diff --git a/devU-client/src/assets/variables.scss b/devU-client/src/assets/variables.scss index 8ef49958..4f2afef6 100644 --- a/devU-client/src/assets/variables.scss +++ b/devU-client/src/assets/variables.scss @@ -21,6 +21,10 @@ $list-item-background: var(--list-item-background); $list-item-background-hover: var(--list-item-background-hover); $list-item-subtext: var(--list-item-subtext); +$list-simple-item-background: var(--list-simple-item-background); +$list-simple-item-background-hover: var(--list-simple-item-background-hover); +$list-simple-item-subtext: var(--list-item-subtext); + $focus: var(--focus); // These variables WILL NOT update with dark vs light theme diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.scss b/devU-client/src/components/listItems/simpleAssignmentListItem.scss index 108bdc37..8401fa4d 100644 --- a/devU-client/src/components/listItems/simpleAssignmentListItem.scss +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.scss @@ -1,25 +1,6 @@ @import 'variables'; -.something1 { - text-decoration: none; - - display: flex; - flex-direction: row; - - - border-radius: 0.6rem; - - justify-content: space-between; - - transition: background-color 0.2s linear; - - color: $text-color; - - gap: 1rem; - -} - -.something2 { +.title { padding: 10px; display: flex; flex-direction: row; @@ -60,7 +41,7 @@ display: flex; flex-direction: column; - background: $list-item-background; + background: $list-simple-item-background; border-radius: 0.6rem; padding: 0.5rem; @@ -71,6 +52,6 @@ &:hover, &:focus { - background: $list-item-background-hover; + background: $list-simple-item-background-hover; } } \ No newline at end of file diff --git a/devU-client/src/components/pages/courseDetailPage.scss b/devU-client/src/components/pages/courseDetailPage.scss index 643ffe12..bc29f9ca 100644 --- a/devU-client/src/components/pages/courseDetailPage.scss +++ b/devU-client/src/components/pages/courseDetailPage.scss @@ -12,12 +12,14 @@ } .smallLine { + background-color: $text-color; width: 50px; /* adjust this value to set the length of the small line */ border-top: 3px solid white; /* adjust this value to set the color and thickness of the line */ margin-right: 10px; /* adjust this value to set the space between the line and the text */ } .largeLine { + color: $text-color; flex-grow: 1; border-top: 3px solid white; /* adjust this value to set the color and thickness of the line */ margin-left: 10px; /* adjust this value to set the space between the line and the text */ diff --git a/devU-client/src/components/pages/homePage.scss b/devU-client/src/components/pages/homePage.scss index 90cb96f6..0ed3fd46 100644 --- a/devU-client/src/components/pages/homePage.scss +++ b/devU-client/src/components/pages/homePage.scss @@ -14,13 +14,13 @@ .smallLine { width: 50px; /* adjust this value to set the length of the small line */ - border-top: 3px solid white; /* adjust this value to set the color and thickness of the line */ + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ margin-right: 10px; /* adjust this value to set the space between the line and the text */ } .largeLine { flex-grow: 1; - border-top: 3px solid white; /* adjust this value to set the color and thickness of the line */ + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ margin-left: 10px; /* adjust this value to set the space between the line and the text */ margin-right: 10px; /* add this line to create some space between the line and the button */ } \ No newline at end of file diff --git a/devU-client/src/components/pages/userCoursesListPage.scss b/devU-client/src/components/pages/userCoursesListPage.scss index 1f115214..47c94174 100644 --- a/devU-client/src/components/pages/userCoursesListPage.scss +++ b/devU-client/src/components/pages/userCoursesListPage.scss @@ -1,48 +1,26 @@ @import 'variables'; - -.addCourseBtn { - justify-content: center; - background-color: $primary; - color: $text-color; - border: 0px; - padding: 10px 40px; - border-radius: 50px; - font-size: 14px; - font-weight: 700; - text-decoration: none; - cursor: pointer; -} - -.courseName { - color: $text-color; - text-decoration: none; - font-size: 14px; -} - -.filters { - display: flex; - justify-content: flex-end; - - margin-bottom: 1rem; -} - -.dropdown { - width: 300px; +.coursesContainer { + display: grid; + grid-template-columns: repeat(auto-fill, 500px); + gap: 20px; /* adjust this value to set the space between the cards */ } .header { color: $text-color; display: flex; - justify-content: space-between; + align-items: center; } -@media (max-width: 700px) { - .dropdown { - width: 100%; - } - - .header { - display: block; - } +.smallLine { + width: 50px; /* adjust this value to set the length of the small line */ + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ + margin-right: 10px; /* adjust this value to set the space between the line and the text */ } + +.largeLine { + flex-grow: 1; + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ + margin-left: 10px; /* adjust this value to set the space between the line and the text */ + margin-right: 10px; /* add this line to create some space between the line and the button */ +} \ No newline at end of file diff --git a/devU-client/src/components/pages/userSubmissionsListPage.scss b/devU-client/src/components/pages/userSubmissionsListPage.scss index 1f98a174..2e5a0758 100644 --- a/devU-client/src/components/pages/userSubmissionsListPage.scss +++ b/devU-client/src/components/pages/userSubmissionsListPage.scss @@ -17,6 +17,7 @@ color: $text-color; display: flex; justify-content: space-between; + align-items: center; } @media (max-width: 700px) { @@ -32,3 +33,16 @@ flex-direction: column; } } + +.smallLine { + width: 50px; /* adjust this value to set the length of the small line */ + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ + margin-right: 10px; /* adjust this value to set the space between the line and the text */ +} + +.largeLine { + flex-grow: 1; + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ + margin-left: 10px; /* adjust this value to set the space between the line and the text */ + margin-right: 10px; /* add this line to create some space between the line and the button */ +} \ No newline at end of file From 489ab5a4700d556c6ce99c1cd8529a0953470fe6 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 20:50:07 -0400 Subject: [PATCH 057/400] update routes avoid confusion for: 1. change All courses page file name into coursesListPage instead of userCoursesListPage 2. remove unused courseUsersListPage --- .../src/components/authenticatedRouter.tsx | 41 +++---- .../components/listItems/courseListItem.scss | 101 ++++++++++++++++ .../components/listItems/courseListItem.tsx | 57 +++++++++ .../components/pages/courseUsersListPage.tsx | 7 -- .../src/components/pages/coursesListPage.scss | 63 ++++++++++ .../src/components/pages/coursesListPage.tsx | 109 +++++++++++++++++- 6 files changed, 345 insertions(+), 33 deletions(-) create mode 100644 devU-client/src/components/listItems/courseListItem.scss create mode 100644 devU-client/src/components/listItems/courseListItem.tsx delete mode 100644 devU-client/src/components/pages/courseUsersListPage.tsx create mode 100644 devU-client/src/components/pages/coursesListPage.scss diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index 0c20108a..a56acfad 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -2,23 +2,16 @@ import React from 'react' import {Route, Switch} from 'react-router-dom' import AssignmentDetailPage from 'components/pages/assignmentDetailPage' - import AssignmentCreatePage from 'components/pages/assignmentFormPage' import AssignmentUpdatePage from 'components/pages/assignmentUpdatePage' - import CourseAssignmentsListPage from 'components/pages/courseAssignmentsListPage' import CourseDetailPage from 'components/pages/courseDetailPage' - import EditCourseFormPage from 'components/pages/coursesFormPage' - -import CoursesListPage from 'components/pages/coursesListPage' import CourseUpdatePage from 'components/pages/courseUpdatePage' -import CourseUsersListPage from 'components/pages/courseUsersListPage' import HomePage from 'components/pages/homePage' import NotFoundPage from 'components/pages/notFoundPage' import SubmissionDetailPage from 'components/pages/submissionDetailPage' import UserDetailPage from 'components/pages/userDetailPage' -import UserCoursesListPage from 'components/pages/userCoursesListPage' import UserSubmissionsListPage from 'components/pages/userSubmissionsListPage' import NonContainerAutoGraderForm from './pages/nonContainerAutoGraderForm' import GradebookStudentPage from './pages/gradebookStudentPage' @@ -26,35 +19,37 @@ import GradebookInstructorPage from './pages/gradebookInstructorPage' import SubmissionFeedbackPage from './pages/submissionFeedbackPage' import ContainerAutoGraderForm from './pages/containerAutoGraderForm' import CoursePreviewPage from './pages/coursePreviewPage' +import CoursesListPage from "./pages/coursesListPage"; +import userCoursesListPage from "./pages/userCoursesListPage"; const AuthenticatedRouter = () => ( - - + + - {/* Just reuse the homepage here, for now this is fine. we might want to change this in the future though which is why they exist as separate routes */} - - + {/* Just reuse the homepage here, for now this is fine. we might want to change this in the future though which is why they exist as separate routes */} + {/**/} + - - - - - - - - + + + + {/**/} + + + + - - + + - + ) diff --git a/devU-client/src/components/listItems/courseListItem.scss b/devU-client/src/components/listItems/courseListItem.scss new file mode 100644 index 00000000..71e151ba --- /dev/null +++ b/devU-client/src/components/listItems/courseListItem.scss @@ -0,0 +1,101 @@ +@import 'variables'; + +.name { + font-size: 1.2rem; + font-weight: 700; + margin-bottom: 0.4rem; +} + +.tag { + min-height: 8px; + background-color: $primary; + margin-right: 10px; +} + + +.container { + text-decoration: none; + + display: flex; + flex-direction: row; + + background: $list-simple-item-background; + border-radius: 0.6rem; + + padding: 1.5rem; + + margin-bottom: 1rem; + + transition: background-color 0.2s linear; + + color: $text-color; + + justify-content: space-between; + + &:hover, + &:focus { + background: $list-simple-item-background-hover; + } +} + +.courseContainer { + cursor: pointer; + text-decoration: none; + + background: $list-item-background; + border-radius: 0.6rem; + + padding: 1rem; + + margin-bottom: 1rem; + + transition: background-color 0.2s linear; + + color: $text-color; + + + &:hover, + &:focus { + background: $list-item-background-hover; + } +} + + +@media (max-width: $medium) { + .subText { + display: block; + } +} + +.triangle { + display: inline-block; + width: 0; + height: 0; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 8px solid $text-color; + transition: transform 0.3s ease; +} + + +.rotate { + transform: rotate(90deg); +} + +.courseInfo { + display: flex; + +} + +.infoContainer { + display: flex; + flex-direction: column; + align-items: center; + margin: 0 10px; + +} + +.info { + font-weight: bold; + padding: 0.5rem; +} \ No newline at end of file diff --git a/devU-client/src/components/listItems/courseListItem.tsx b/devU-client/src/components/listItems/courseListItem.tsx new file mode 100644 index 00000000..2dfa326e --- /dev/null +++ b/devU-client/src/components/listItems/courseListItem.tsx @@ -0,0 +1,57 @@ +import React, {useState} from 'react' + +import {Course} from 'devu-shared-modules' + +import {prettyPrintDate} from 'utils/date.utils' +import {prettyPrintSemester} from "../../utils/semester.utils"; + +import styles from './courseListItem.scss' +import {Link} from "react-router-dom"; +import ColorHash from "color-hash"; + +const colorHash = (input: string) => { + const hash = new ColorHash({hue: {min: 90, max: 270}}) + + return hash.hex(input) +} +type Props = { + course: Course +} + +const CourseListItem = ({course}: Props) => { + const [isOpen, setIsOpen] = useState(false) + + const toggleOpen = () => { + + setIsOpen(!isOpen) + } + + return ( +
+
+
+ +  {course.name} +
+ {isOpen && + + {infoSection("Course Number", course.number)} + {infoSection("Semester", prettyPrintSemester(course.semester))} + {infoSection("Start/End Date", prettyPrintDate(course.startDate), prettyPrintDate(course.endDate))} + + } +
+ ) +} + +const infoSection = (display: string, firstValue: string, secondValue?: string) => ( +
+
+ {display}: + {firstValue}{secondValue && `-${secondValue}`} +
+
+) + + +export default CourseListItem diff --git a/devU-client/src/components/pages/courseUsersListPage.tsx b/devU-client/src/components/pages/courseUsersListPage.tsx deleted file mode 100644 index e2eb8160..00000000 --- a/devU-client/src/components/pages/courseUsersListPage.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react' - -import PageWrapper from 'components/shared/layouts/pageWrapper' - -const CourseUserListPage = ({}) => Course User List - -export default CourseUserListPage diff --git a/devU-client/src/components/pages/coursesListPage.scss b/devU-client/src/components/pages/coursesListPage.scss new file mode 100644 index 00000000..e279b8ac --- /dev/null +++ b/devU-client/src/components/pages/coursesListPage.scss @@ -0,0 +1,63 @@ +@import 'variables'; + + +.addCourseBtn { + + background-color: $primary; + color: $text-color; + border: 0px; + padding: 10px 40px; + border-radius: 50px; + font-size: 14px; + font-weight: 700; + text-decoration: none; + cursor: pointer; +} + +.courseName { + color: $text-color; + text-decoration: none; + font-size: 14px; +} + +.filters { + display: flex; + justify-content: flex-end; + + margin-bottom: 1rem; +} + +.dropdown { + width: 300px; +} + +.header { + color: $text-color; + display: flex; + justify-content: space-between; + align-items: center; + cursor: default; +} + +@media (max-width: 700px) { + .dropdown { + width: 100%; + } + + .header { + display: block; + } +} + +.smallLine { + width: 50px; /* adjust this value to set the length of the small line */ + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ + margin-right: 10px; /* adjust this value to set the space between the line and the text */ +} + +.largeLine { + flex-grow: 1; + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ + margin-left: 10px; /* adjust this value to set the space between the line and the text */ + margin-right: 10px; /* add this line to create some space between the line and the button */ +} \ No newline at end of file diff --git a/devU-client/src/components/pages/coursesListPage.tsx b/devU-client/src/components/pages/coursesListPage.tsx index 563be28f..6930987c 100644 --- a/devU-client/src/components/pages/coursesListPage.tsx +++ b/devU-client/src/components/pages/coursesListPage.tsx @@ -1,7 +1,110 @@ -import React from 'react' +import React, {useEffect, useState} from 'react' +import {Link} from 'react-router-dom' +import {Course, UserCourse} from 'devu-shared-modules' + +import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import PageWrapper from 'components/shared/layouts/pageWrapper' +import Dropdown, {Option} from 'components/shared/inputs/dropdown' +import ErrorPage from './errorPage' + +import RequestService from 'services/request.service' +import LocalStorageService from 'services/localStorage.service' + +import styles from './coursesListPage.scss' +import CourseListItem from "../listItems/courseListItem"; +//import Button from 'components/shared/inputs/button' + +const FILTER_LOCAL_STORAGE_KEY = 'courses_filter' + +type Filter = 'all' | 'active' | 'inactive' | 'dropped' + +const filterOptions: Option[] = [ + {label: 'All', value: 'all'}, + {label: 'Active', value: 'active'}, + {label: 'Inactive', value: 'inactive'}, + {label: 'Dropped', value: 'dropped'}, +] + +const UserCoursesListPage = () => { + + const defaultFilter = LocalStorageService.get(FILTER_LOCAL_STORAGE_KEY) || 'active' + + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [userCourses, setUserCourses] = useState(new Array()) + const [filter, setFilter] = useState(defaultFilter) + + //Temporary place to store state for all courses + const [allCourses, setAllCourses] = useState(new Array()) + + useEffect(() => { + fetchData() + }, []) + + const fetchData = async () => { + try { + // const userCourses = await RequestService.get(`/api/user-courses?filterBy=${filter}`) + const courseRequests = userCourses.map((u) => RequestService.get(`/api/courses/${u.courseId}`)) + const courses = await Promise.all(courseRequests) + + // Mapify course ids so we can look them up more easilly via their id + const courseMap: Record = {} + for (const course of courses) courseMap[course.id || ''] = course + + // Temporary place to grab and display all courses + const allCourses = await RequestService.get('/api/courses') + setAllCourses(allCourses) + + setUserCourses(userCourses) + } catch (error: any) { + setError(error) + } finally { + setLoading(false) + } + } + + const handleFilterChange = (updatedFilter: Filter) => { + setFilter(updatedFilter) + fetchData() + + LocalStorageService.set(FILTER_LOCAL_STORAGE_KEY, updatedFilter) + } + + if (loading) return + if (error) return + + const defaultOption = filterOptions.find((o) => o.value === filter) + + + return ( + +
+
+

All Courses

+
+ + + Add Courses + + +
+ +
+
+ {allCourses.map(course => ( + + ))} +
+ ) + -const CoursesListPage = ({}) => Courses List +} -export default CoursesListPage +export default UserCoursesListPage From b21bc218ebb909631e2824c3d920c83b4ee5fe6b Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 20:55:10 -0400 Subject: [PATCH 058/400] update front end: 1. update assignments to My courses to have actual user Courses page. 2. remove previous course section for homepage to distinguish with my course page 3. add semester.utils for better printing semester to Spring 2024 instead of s2024 4. remove add course button for home page and My course page. --- .../listItems/simpleAssignmentListItem.tsx | 3 +- .../src/components/misc/globalToolbar.tsx | 4 +- devU-client/src/components/pages/homePage.tsx | 14 -- .../components/pages/userCoursesListPage.tsx | 127 ++++++------------ .../pages/userSubmissionsListPage.tsx | 37 ++--- devU-client/src/utils/semester.utils.ts | 20 +++ 6 files changed, 86 insertions(+), 119 deletions(-) create mode 100644 devU-client/src/utils/semester.utils.ts diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.tsx b/devU-client/src/components/listItems/simpleAssignmentListItem.tsx index 9ec2f1c8..51c981bc 100644 --- a/devU-client/src/components/listItems/simpleAssignmentListItem.tsx +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.tsx @@ -3,7 +3,6 @@ import ListItemWrapper from 'components/shared/layouts/listItemWrapper' import {prettyPrintDate} from 'utils/date.utils' import styles from './simpleAssignmentListItem.scss' import {Assignment} from 'devu-shared-modules' -// import Card from '@mui/material/Card' type Props = { @@ -12,7 +11,7 @@ type Props = { const SimpleAssignmentListItem = ({assignment}: Props) => ( + className={styles.title} tagStyle={styles.tag} containerStyle={styles.container}>
{assignment.name}
{assignment.categoryName}
Due At: {prettyPrintDate(assignment.dueDate)}
diff --git a/devU-client/src/components/misc/globalToolbar.tsx b/devU-client/src/components/misc/globalToolbar.tsx index d0e394e9..5a0d5d63 100644 --- a/devU-client/src/components/misc/globalToolbar.tsx +++ b/devU-client/src/components/misc/globalToolbar.tsx @@ -27,8 +27,8 @@ const GlobalToolbar = () => { Courses - - Assignments + + My Courses Submissions diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index 9dabcd41..b4068056 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -1,5 +1,4 @@ import React, {useEffect, useState} from 'react' -import {useHistory} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' @@ -7,7 +6,6 @@ import ErrorPage from './errorPage' import styles from './homePage.scss' import UserCourseListItem from "../listItems/userCourseListItem"; -import Button from '@mui/material/Button' import {useAppSelector} from 'redux/hooks' import RequestService from 'services/request.service' @@ -15,7 +13,6 @@ import {Assignment, Course, UserCourse} from 'devu-shared-modules' const HomePage = () => { - const history = useHistory() const userId = useAppSelector((store) => store.user.id) const [loading, setLoading] = useState(true) @@ -59,10 +56,6 @@ const HomePage = () => {

My Courses

- -
@@ -71,13 +64,6 @@ const HomePage = () => { ))}
- -
-
-

Previous Courses

-
-
-
) } diff --git a/devU-client/src/components/pages/userCoursesListPage.tsx b/devU-client/src/components/pages/userCoursesListPage.tsx index a3dc7891..f464a914 100644 --- a/devU-client/src/components/pages/userCoursesListPage.tsx +++ b/devU-client/src/components/pages/userCoursesListPage.tsx @@ -1,44 +1,24 @@ import React, {useEffect, useState} from 'react' -import {Link} from 'react-router-dom' -import {Course, UserCourse} from 'devu-shared-modules' - -import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import PageWrapper from 'components/shared/layouts/pageWrapper' -import UserCourseListItem from 'components/listItems/userCourseListItem' -import Dropdown, {Option} from 'components/shared/inputs/dropdown' +import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import ErrorPage from './errorPage' - -import RequestService from 'services/request.service' -import LocalStorageService from 'services/localStorage.service' - import styles from './userCoursesListPage.scss' -//import Button from 'components/shared/inputs/button' - -const FILTER_LOCAL_STORAGE_KEY = 'courses_filter' - -type Filter = 'all' | 'active' | 'inactive' | 'dropped' +import UserCourseListItem from "../listItems/userCourseListItem"; -const filterOptions: Option[] = [ - { label: 'All', value: 'all' }, - { label: 'Active', value: 'active' }, - { label: 'Inactive', value: 'inactive' }, - { label: 'Dropped', value: 'dropped' }, -] -const UserCoursesListPage = () => { +import {useAppSelector} from 'redux/hooks' +import RequestService from 'services/request.service' +import {Assignment, Course, UserCourse} from 'devu-shared-modules' - const defaultFilter = LocalStorageService.get(FILTER_LOCAL_STORAGE_KEY) || 'active' +const HomePage = () => { + const userId = useAppSelector((store) => store.user.id) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) - const [userCourses, setUserCourses] = useState(new Array()) - const [courses, setCourses] = useState>({}) - const [filter, setFilter] = useState(defaultFilter) - - //Temporary place to store state for all courses - const [allCourses, setAllCourses] = useState(new Array()) + const [courses, setCourses] = useState(new Array()) + const [assignments, setAssignments] = useState(new Map>()) useEffect(() => { fetchData() @@ -46,76 +26,55 @@ const UserCoursesListPage = () => { const fetchData = async () => { try { - // The filter isn't implemented by the API yet - // const userCourses = await RequestService.get(`/api/user-courses?filterBy=${filter}`) - const courseRequests = userCourses.map((u) => RequestService.get(`/api/courses/${u.courseId}`)) - const courses = await Promise.all(courseRequests) - - // Mapify course ids so we can look them up more easilly via their id - const courseMap: Record = {} - for (const course of courses) courseMap[course.id || ''] = course - - // Temporary place to grab and display all courses - const allCourses = await RequestService.get('/api/courses') - setAllCourses(allCourses) - - setUserCourses(userCourses) - setCourses(courseMap) - } catch (error: any) { + const userCourses = await RequestService.get(`/api/user-courses/user/${userId}`) + const coursePromises = userCourses.map(uc => { + const course = RequestService.get(`/api/courses/${uc.courseId}`) + const assignments = RequestService.get(`/api/assignments/course/${uc.courseId}`) + return Promise.all([course, assignments]) + + }) + const result = await Promise.all(coursePromises) + const courses = result.map(([course]) => course) + const assignmentsMap = new Map>() + result.forEach(([course, assignments]) => assignmentsMap.set(course, assignments)) + setCourses(courses) + setAssignments(assignmentsMap) + } catch (error) { setError(error) } finally { setLoading(false) } } - const handleFilterChange = (updatedFilter: Filter) => { - setFilter(updatedFilter) - fetchData() - - LocalStorageService.set(FILTER_LOCAL_STORAGE_KEY, updatedFilter) - } if (loading) return if (error) return - const defaultOption = filterOptions.find((o) => o.value === filter) + return ( + +
+
+

My Courses

+
- return ( - -
-

All Courses

- -
- - Add Courses -
-
- +
+ {courses.map((course) => ( + + + ))} +
+ +
+
+

Previous Courses

+
-
- {userCourses.map((userCourse) => ( - - ))} - {allCourses.map(course => ( -
- {course.name} -
- ))} - + + ) } -export default UserCoursesListPage +export default HomePage diff --git a/devU-client/src/components/pages/userSubmissionsListPage.tsx b/devU-client/src/components/pages/userSubmissionsListPage.tsx index 1e930e1f..6801aa8c 100644 --- a/devU-client/src/components/pages/userSubmissionsListPage.tsx +++ b/devU-client/src/components/pages/userSubmissionsListPage.tsx @@ -1,17 +1,17 @@ -import React, { useState, useEffect } from 'react' -import { Course, Assignment, Submission } from 'devu-shared-modules' +import React, {useEffect, useState} from 'react' +import {Assignment, Course, Submission} from 'devu-shared-modules' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import PageWrapper from 'components/shared/layouts/pageWrapper' import SubmissionListItem from 'components/listItems/submissionListItem' -import Dropdown, { Option } from 'components/shared/inputs/dropdown' +import Dropdown, {Option} from 'components/shared/inputs/dropdown' import ErrorPage from './errorPage' import RequestService from 'services/request.service' import LocalStorageService from 'services/localStorage.service' import styles from './userSubmissionsListPage.scss' -import { useAppSelector } from 'redux/hooks' +import {useAppSelector} from 'redux/hooks' const ORDER_BY_STORAGE_KEY = 'submissions_order_by' const GROUP_BY_STORAGE_KEY = 'submissions_group_by' @@ -106,28 +106,31 @@ const UserCoursesListPage = () => { return (
+
+

My Submissions

+
{submissions.map((submission) => ( - diff --git a/devU-client/src/utils/semester.utils.ts b/devU-client/src/utils/semester.utils.ts new file mode 100644 index 00000000..e4b7f1a0 --- /dev/null +++ b/devU-client/src/utils/semester.utils.ts @@ -0,0 +1,20 @@ +export function prettyPrintSemester(semester: string) { + let season = semester.substring(0, 1) + let year = semester.substring(1) + let seasonString = "" + switch (season) { + case "f": + seasonString = "Fall" + break + case "s": + seasonString = "Spring" + break + case "w": + seasonString = "Winter" + break + case "u": + seasonString = "Summer" + break + } + return seasonString + " " + year +} From abc98428a1566b62bddc410b72432b29085362fd Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 21:28:15 -0400 Subject: [PATCH 059/400] fix error for redirecting to the preview page instead of course page. --- devU-client/src/components/listItems/courseListItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-client/src/components/listItems/courseListItem.tsx b/devU-client/src/components/listItems/courseListItem.tsx index 2dfa326e..9040a0c9 100644 --- a/devU-client/src/components/listItems/courseListItem.tsx +++ b/devU-client/src/components/listItems/courseListItem.tsx @@ -34,7 +34,7 @@ const CourseListItem = ({course}: Props) => {  {course.name}
{isOpen && - + {infoSection("Course Number", course.number)} {infoSection("Semester", prettyPrintSemester(course.semester))} {infoSection("Start/End Date", prettyPrintDate(course.startDate), prettyPrintDate(course.endDate))} From a3773e5ec98e414197b174fc9831883e78b2be45 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 21:44:13 -0400 Subject: [PATCH 060/400] fix containerAutoGrader.validator bug for passing empty gradeFile and fix containerAutoGrader form has Non-Container Auto Grader Name --- .../containerAutoGrader.validator.ts | 11 ++++++----- .../src/components/pages/containerAutoGraderForm.tsx | 10 +++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts index 638dcdde..11740fb3 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts @@ -1,19 +1,20 @@ -import { check } from 'express-validator' +import {check} from 'express-validator' import validate from '../../middleware/validator/generic.validator' - const assignmentId = check('assignmentId').isNumeric() -const graderFile = check('graderFile').optional({ nullable: true }).custom(({ req }) => { - const file = req.files['grader'] +const graderFile = check('graderFile').custom(({req}) => { + const file = req?.files['grader'] if (file !== null) { if (file.size <= 0) { throw new Error('File is empty') } + } else { + throw new Error('does not have grader file') } -}) +}).withMessage('Grader file is required') const makefileFile = check('makefileFile').optional({ nullable: true }).custom(({ req }) => { const file = req.files['makefile'] diff --git a/devU-client/src/components/pages/containerAutoGraderForm.tsx b/devU-client/src/components/pages/containerAutoGraderForm.tsx index 13a0b83a..c30b2baa 100644 --- a/devU-client/src/components/pages/containerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/containerAutoGraderForm.tsx @@ -68,17 +68,17 @@ const ContainerAutoGraderForm = () => { return( -

Non Container Auto Grader Form

+

Container Auto Grader Form

-

Add a Non-Container Auto Grader

+

Add a Container Auto Grader

- - - +

From 1dcdbdff7b040fa856ba6e2af09602bc5748820e Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 21:47:03 -0400 Subject: [PATCH 061/400] fix id name issue, should be maxSubmissions instead of maxSubmission --- devU-client/src/components/pages/assignmentFormPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devU-client/src/components/pages/assignmentFormPage.tsx b/devU-client/src/components/pages/assignmentFormPage.tsx index 1a8be840..a949a4e8 100644 --- a/devU-client/src/components/pages/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/assignmentFormPage.tsx @@ -91,8 +91,8 @@ const AssignmentCreatePage = () => { className={invalidFields.get('description')}/> - + From a1457635fc2a9725c5d1af73f56e7befa265e86f Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 22:01:02 -0400 Subject: [PATCH 062/400] pull from the latest develop branch --- devU-client/src/components/listItems/userCourseListItem.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devU-client/src/components/listItems/userCourseListItem.tsx b/devU-client/src/components/listItems/userCourseListItem.tsx index 4de955ae..71eb5f88 100644 --- a/devU-client/src/components/listItems/userCourseListItem.tsx +++ b/devU-client/src/components/listItems/userCourseListItem.tsx @@ -9,6 +9,7 @@ import {prettyPrintDate} from 'utils/date.utils' import styles from './userCourseListItem.scss' import SimpleAssignmentListItem from "./simpleAssignmentListItem"; +import {prettyPrintSemester} from "../../utils/semester.utils"; type Props = { course: Course @@ -21,7 +22,7 @@ const UserCourseListItem = ({course, assignments}: Props) => (
{course.name}
{course.number}
-
Semester: {course.semester}
+
Semester: {prettyPrintSemester(course.semester)}
Start Date: {prettyPrintDate(course.startDate)}
End Date: {prettyPrintDate(course.endDate)}
From 7282e27e36b56e1d183ab0b412fb9e2b4fc5b73a Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Tue, 23 Apr 2024 22:45:10 -0400 Subject: [PATCH 063/400] Added Required Fields Added an asterisk next to required fields. (Doesn't do much though since form will not stop users from submitting empty forms) --- .../components/pages/assignmentFormPage.tsx | 17 ++++++++------- .../components/pages/assignmentUpdatePage.tsx | 21 ++++++++++--------- .../pages/containerAutoGraderForm.tsx | 13 ++++++------ .../src/components/pages/courseUpdatePage.tsx | 11 +++++----- .../src/components/pages/coursesFormPage.tsx | 11 +++++----- .../pages/nonContainerAutoGraderForm.tsx | 7 ++++--- 6 files changed, 43 insertions(+), 37 deletions(-) diff --git a/devU-client/src/components/pages/assignmentFormPage.tsx b/devU-client/src/components/pages/assignmentFormPage.tsx index fbc336e7..98aeb685 100644 --- a/devU-client/src/components/pages/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/assignmentFormPage.tsx @@ -76,23 +76,24 @@ const AssignmentCreatePage = () => { return(

Assignment Form

- +

Required Field *

+ - +

- +

- +
- - - - + + + + diff --git a/devU-client/src/components/pages/assignmentUpdatePage.tsx b/devU-client/src/components/pages/assignmentUpdatePage.tsx index 2f90f987..e07549d5 100644 --- a/devU-client/src/components/pages/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/assignmentUpdatePage.tsx @@ -125,36 +125,37 @@ const AssignmentUpdatePage = () => { return (

Assignment Detail Update

+

Required Field *

{toggleForm && (


- - + +
)}



- + - +

- +

- +
- - - - + + + + diff --git a/devU-client/src/components/pages/containerAutoGraderForm.tsx b/devU-client/src/components/pages/containerAutoGraderForm.tsx index 24c7a723..6a9fc296 100644 --- a/devU-client/src/components/pages/containerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/containerAutoGraderForm.tsx @@ -60,14 +60,15 @@ const ContainerAutoGraderForm = () => { return( -

Non Container Auto Grader Form

+

Container Auto Grader Form

-

Add a Non-Container Auto Grader

- - - +

Container Auto Grader

+

Required Field *

+ + +
- +




diff --git a/devU-client/src/components/pages/courseUpdatePage.tsx b/devU-client/src/components/pages/courseUpdatePage.tsx index 65b63777..1c8f59c8 100644 --- a/devU-client/src/components/pages/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/courseUpdatePage.tsx @@ -67,15 +67,16 @@ const CourseUpdatePage = ({}) => { return (

Course Detail Update

- - - +

Required Field *

+ + + - +

- +

diff --git a/devU-client/src/components/pages/coursesFormPage.tsx b/devU-client/src/components/pages/coursesFormPage.tsx index b26ea03e..67caa0cd 100644 --- a/devU-client/src/components/pages/coursesFormPage.tsx +++ b/devU-client/src/components/pages/coursesFormPage.tsx @@ -60,15 +60,16 @@ const EditCourseFormPage = () => { return (

Course Form

- - - +

Required Fields *

+ + + - +

- +

diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx index 6e72bb0a..9d56b5a9 100644 --- a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx @@ -74,9 +74,10 @@ const NonContainerAutoGraderForm = () => {

Non Container Auto Grader Form

Add a Non-Container Auto Grader

- - - +

Required Fields *

+ + +



From b188785a9646ccea59c70aa8ca531916fe0ad2a3 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Tue, 23 Apr 2024 23:44:24 -0400 Subject: [PATCH 064/400] update to handle merge conflict --- devU-client/src/components/authenticatedRouter.tsx | 2 +- devU-client/src/components/pages/submissionDetailPage.tsx | 1 - devU-client/src/components/pages/submissionFeedbackPage.tsx | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index a56acfad..f12c9fe3 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -40,7 +40,7 @@ const AuthenticatedRouter = () => ( {/**/} - + { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [setAlert] = useActionless(SET_ALERT) - const {courseId, assignmentId} = useParams<{ courseId: string, assignmentId: string }>() const { submissionId, assignmentId, courseId } = useParams<{submissionId: string, assignmentId: string, courseId: string}>() const [submissionScore, setSubmissionScore] = useState(null) diff --git a/devU-client/src/components/pages/submissionFeedbackPage.tsx b/devU-client/src/components/pages/submissionFeedbackPage.tsx index 5ebdba90..a2bb5a34 100644 --- a/devU-client/src/components/pages/submissionFeedbackPage.tsx +++ b/devU-client/src/components/pages/submissionFeedbackPage.tsx @@ -10,7 +10,6 @@ import RequestService from 'services/request.service' const SubmissionFeedbackPage = () => { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) - const {courseId, assignmentId} = useParams<{ courseId: string, assignmentId: string }>() const { submissionId, assignmentId, courseId } = useParams<{submissionId: string, assignmentId: string, courseId: string}>() const [submissionScore, setSubmissionScore] = useState(null) From 118d2c76da053ca7d8f008304193571fd29f3f3b Mon Sep 17 00:00:00 2001 From: SantarinX Date: Wed, 24 Apr 2024 00:40:58 -0400 Subject: [PATCH 065/400] update forms to auto go back after creation successfully --- .../components/pages/assignmentFormPage.tsx | 31 +++--- .../components/pages/assignmentUpdatePage.tsx | 104 +++++++++--------- .../pages/containerAutoGraderForm.tsx | 7 +- .../src/components/pages/courseDetailPage.tsx | 2 - .../components/pages/coursePreviewPage.tsx | 4 +- .../src/components/pages/courseUpdatePage.tsx | 11 +- .../src/components/pages/coursesFormPage.tsx | 10 +- .../pages/nonContainerAutoGraderForm.tsx | 15 ++- 8 files changed, 105 insertions(+), 79 deletions(-) diff --git a/devU-client/src/components/pages/assignmentFormPage.tsx b/devU-client/src/components/pages/assignmentFormPage.tsx index 71735386..745263e1 100644 --- a/devU-client/src/components/pages/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/assignmentFormPage.tsx @@ -13,11 +13,12 @@ import {SET_ALERT} from 'redux/types/active.types' import styles from '../shared/inputs/textField.scss' import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; -import {useParams} from 'react-router-dom' +import {useHistory, useParams} from 'react-router-dom' const AssignmentCreatePage = () => { const [setAlert] = useActionless(SET_ALERT) const {courseId} = useParams<{ courseId: string }>() + const history = useHistory() const [formData, setFormData] = useState({ courseId: courseId, @@ -78,7 +79,10 @@ const AssignmentCreatePage = () => { setInvalidFields(newFields) setAlert({ autoDelete: false, type: 'error', message }) }) - .finally(() => setLoading(false)) + .finally(() => { + setLoading(false) + history.goBack() + }) } @@ -86,29 +90,30 @@ const AssignmentCreatePage = () => {

Assignment Form

Required Field *

- +
- +

- +

- + - - + + - - + + +
+
diff --git a/devU-client/src/components/pages/assignmentUpdatePage.tsx b/devU-client/src/components/pages/assignmentUpdatePage.tsx index 96512dc1..f6637033 100644 --- a/devU-client/src/components/pages/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/assignmentUpdatePage.tsx @@ -2,7 +2,7 @@ import React, {useState} from 'react' import {ExpressValidationError} from 'devu-shared-modules' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' -import {useParams} from 'react-router-dom' +import {useHistory, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -88,8 +88,7 @@ const AssignmentUpdatePage = () => { const [dueDate, setDueDate] = useState(new Date()) const [loading, setLoading] = useState(false) const [invalidFields, setInvalidFields] = useState(new Map()) - - const { assignmentId } = useParams() as UrlParams + const history = useHistory() const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id @@ -133,56 +132,61 @@ const AssignmentUpdatePage = () => { setInvalidFields(newFields) setAlert({ autoDelete: false, type: 'error', message }) }) - .finally(() => setLoading(false)) + .finally(() => { + setLoading(false) + history.goBack() + }) } return ( - -

Assignment Detail Update

-

Required Field *

- - - - {toggleForm && ( -
-

- - - -
- )} - -



- - - -
- -
- -
- -
- -
- - - - - - - - -
+ +

Assignment Detail Update

+

Required Field *

+ + + + {toggleForm && ( +
+

+ + + +
+ )} + +



+ + + +
+ +
+ +
+ +
+ +
+ + + + + + + +
+ + +
) } diff --git a/devU-client/src/components/pages/containerAutoGraderForm.tsx b/devU-client/src/components/pages/containerAutoGraderForm.tsx index 93a501f5..08f95f73 100644 --- a/devU-client/src/components/pages/containerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/containerAutoGraderForm.tsx @@ -9,11 +9,12 @@ import RequestService from 'services/request.service' import {ExpressValidationError} from "../../../devu-shared-modules"; import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; import textStyles from "../shared/inputs/textField.scss"; -import { useParams } from 'react-router-dom' +import {useHistory, useParams} from 'react-router-dom' const ContainerAutoGraderForm = () => { const [setAlert] = useActionless(SET_ALERT) - + const {assignmentId} = useParams<{ assignmentId: string }>() + const history = useHistory() const [graderFile, setGraderFile] = useState() const [makefile, setMakefile] = useState() const [formData,setFormData] = useState({ @@ -57,6 +58,8 @@ const ContainerAutoGraderForm = () => { setInvalidFields(newFields) setAlert({autoDelete: false, type: 'error', message: message}) + }).finally(() => { + history.goBack() }) diff --git a/devU-client/src/components/pages/courseDetailPage.tsx b/devU-client/src/components/pages/courseDetailPage.tsx index cc13c120..784e801f 100644 --- a/devU-client/src/components/pages/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courseDetailPage.tsx @@ -55,9 +55,7 @@ const CourseDetailPage = () => { RequestService.delete(`/api/user-courses/${courseId}`).then(() => { setAlert({autoDelete: true, type: 'success', message: 'Course Dropped'}) - setTimeout(() => { history.push('/courses') - }, 3000) }).catch((error: Error) => { const message = error.message diff --git a/devU-client/src/components/pages/coursePreviewPage.tsx b/devU-client/src/components/pages/coursePreviewPage.tsx index 8767f00a..5ef5f272 100644 --- a/devU-client/src/components/pages/coursePreviewPage.tsx +++ b/devU-client/src/components/pages/coursePreviewPage.tsx @@ -22,6 +22,7 @@ const CoursePreviewPage = () => { const [course, setCourse] = useState() const [userCourses, setUserCourses] = useState() const userId = useAppSelector((store) => store.user.id) + const history = useHistory() const handleCheckEnroll = async () => { @@ -57,7 +58,7 @@ const CoursePreviewPage = () => { if (loading) return if (enrolled) { - useHistory().push(`/courses/${courseId}`) + history.push(`/courses/${courseId}`) } const handleJoinCourse = () => { @@ -74,6 +75,7 @@ const CoursePreviewPage = () => { setAlert({autoDelete: false, type: 'error', message}) }).finally(() => { setAlert({autoDelete: true, type: 'success', message: 'Course Joined'}) + history.goBack() }) } diff --git a/devU-client/src/components/pages/courseUpdatePage.tsx b/devU-client/src/components/pages/courseUpdatePage.tsx index c8c43d3e..02c458f9 100644 --- a/devU-client/src/components/pages/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/courseUpdatePage.tsx @@ -1,5 +1,5 @@ - import React, {useState} from 'react' -import {useParams} from 'react-router-dom' +import React, {useState} from 'react' +import {useHistory, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -23,7 +23,7 @@ type UrlParams = { const CourseUpdatePage = ({}) => { const [setAlert] = useActionless(SET_ALERT) - + const history = useHistory() const [formData,setFormData] = useState({ name: '', number: '', @@ -67,7 +67,10 @@ const CourseUpdatePage = ({}) => { setAlert({ autoDelete: false, type: 'error', message }) }) - .finally(() => setLoading(false)) + .finally(() => { + setLoading(false) + history.goBack() + }) } diff --git a/devU-client/src/components/pages/coursesFormPage.tsx b/devU-client/src/components/pages/coursesFormPage.tsx index d48cbcea..e21607ee 100644 --- a/devU-client/src/components/pages/coursesFormPage.tsx +++ b/devU-client/src/components/pages/coursesFormPage.tsx @@ -1,7 +1,7 @@ import React, {useState} from 'react' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' - +import {useHistory} from 'react-router-dom' import {ExpressValidationError} from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -15,8 +15,10 @@ import {SET_ALERT} from 'redux/types/active.types' import styles from '../shared/inputs/textField.scss' import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; + const EditCourseFormPage = () => { const [setAlert] = useActionless(SET_ALERT) + const history = useHistory(); const [formData,setFormData] = useState({ name: '', @@ -53,6 +55,7 @@ const EditCourseFormPage = () => { RequestService.post('/api/courses/', finalFormData) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Course Added' }) + }) .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message @@ -62,7 +65,10 @@ const EditCourseFormPage = () => { setAlert({ autoDelete: false, type: 'error', message }) }) - .finally(() => setLoading(false)) + .finally(() => { + history.goBack() + setLoading(false) + }) } return ( diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx index 2b6c15ce..96805666 100644 --- a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx @@ -9,11 +9,13 @@ import RequestService from 'services/request.service' import textStyles from '../shared/inputs/textField.scss' import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; import {ExpressValidationError} from "../../../devu-shared-modules"; - +import {useHistory, useParams} from 'react-router-dom' const NonContainerAutoGraderForm = () => { const [setAlert] = useActionless(SET_ALERT) const [invalidFields, setInvalidFields] = useState(new Map()) + const {assignmentId} = useParams<{ assignmentId: string }>() + const history = useHistory() const [formData,setFormData] = useState({ assignmentId: assignmentId, @@ -65,7 +67,10 @@ const NonContainerAutoGraderForm = () => { setInvalidFields(newFields) setAlert({autoDelete: false, type: 'error', message: message}) - }) + }).finally(() => { + history.goBack() + + }) setFormData({ @@ -83,11 +88,11 @@ const NonContainerAutoGraderForm = () => {

Add a Non-Container Auto Grader

Required Fields *

- - - From 459465efae293fb51f81ef1f8d30d12ca1971c3f Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Wed, 24 Apr 2024 13:27:53 -0400 Subject: [PATCH 066/400] Placeholder tests.. --- devU-api/src/entities/grader/grader.controller.ts | 3 +-- devU-api/src/entities/role/tests/role.controller.test.ts | 4 +++- devU-api/src/entities/role/tests/role.serializer.test.ts | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/devU-api/src/entities/grader/grader.controller.ts b/devU-api/src/entities/grader/grader.controller.ts index 8cfa8432..e6f4ceda 100644 --- a/devU-api/src/entities/grader/grader.controller.ts +++ b/devU-api/src/entities/grader/grader.controller.ts @@ -4,14 +4,13 @@ import GraderService from './grader.service' import { GenericResponse, NotFound } from '../../utils/apiResponse.utils' -import { serialize } from '../grader/grader.serializer' +import { serialize } from './grader.serializer' export async function grade(req: Request, res: Response, next: NextFunction) { try { const submissionId = parseInt(req.params.id) const grade = await GraderService.grade(submissionId) if (!grade || grade.length === 0) return res.status(404).json(NotFound) - const response = serialize(grade) res.status(200).json(response) diff --git a/devU-api/src/entities/role/tests/role.controller.test.ts b/devU-api/src/entities/role/tests/role.controller.test.ts index 2e09394a..5b07055a 100644 --- a/devU-api/src/entities/role/tests/role.controller.test.ts +++ b/devU-api/src/entities/role/tests/role.controller.test.ts @@ -1,3 +1,5 @@ // TODO: Role testing. Lots of it! -describe('this is where tests will go', () => {}) +describe('this is where tests will go', () => { + test('Placeholder', () => expect(true).toEqual(true)) +}) diff --git a/devU-api/src/entities/role/tests/role.serializer.test.ts b/devU-api/src/entities/role/tests/role.serializer.test.ts index 2e09394a..5b07055a 100644 --- a/devU-api/src/entities/role/tests/role.serializer.test.ts +++ b/devU-api/src/entities/role/tests/role.serializer.test.ts @@ -1,3 +1,5 @@ // TODO: Role testing. Lots of it! -describe('this is where tests will go', () => {}) +describe('this is where tests will go', () => { + test('Placeholder', () => expect(true).toEqual(true)) +}) From 3ce10ef664809c1093cf60db6af2167f79292bc0 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Wed, 24 Apr 2024 17:22:03 -0400 Subject: [PATCH 067/400] Missing close brace --- .../containerAutoGrader/containerAutoGrader.service.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index c8a60890..207997c9 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -106,10 +106,11 @@ export async function list() { //The grader has not changed to the new function, so the fake function keep here for now to avoid error //But need to be deleted when the grader entity changed to the getGraderByAssignmentId export async function getGraderObjectByAssignmentId(assignmentId: number) { - if (!assignmentId) throw new Error('Missing AssignmentId') - return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) + if (!assignmentId) throw new Error('Missing AssignmentId') + return await connect().find({assignmentId: assignmentId, deletedAt: IsNull()}) +} - export async function listByAssignmentId(assignmentId: number) { +export async function listByAssignmentId(assignmentId: number) { if (!assignmentId) throw new Error('Missing AssignmentId') return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) } From c31603de98953553d8e33749842af2d8c830f5f9 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Wed, 24 Apr 2024 17:27:11 -0400 Subject: [PATCH 068/400] update the filter for the course list to use the new patern --- .../components/listItems/courseListItem.scss | 3 ++- .../components/listItems/courseListItem.tsx | 18 ++++++++----- .../components/pages/courseDetailPage.scss | 7 +++++ .../src/components/pages/courseDetailPage.tsx | 9 ++++--- .../src/components/pages/coursesListPage.tsx | 26 +++++-------------- 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/devU-client/src/components/listItems/courseListItem.scss b/devU-client/src/components/listItems/courseListItem.scss index 71e151ba..9e92481c 100644 --- a/devU-client/src/components/listItems/courseListItem.scss +++ b/devU-client/src/components/listItems/courseListItem.scss @@ -7,9 +7,10 @@ } .tag { - min-height: 8px; + min-height: 3px; background-color: $primary; margin-right: 10px; + border-radius: 0.6rem; } diff --git a/devU-client/src/components/listItems/courseListItem.tsx b/devU-client/src/components/listItems/courseListItem.tsx index 9040a0c9..26054180 100644 --- a/devU-client/src/components/listItems/courseListItem.tsx +++ b/devU-client/src/components/listItems/courseListItem.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react' +import React, {useEffect, useState} from 'react' import {Course} from 'devu-shared-modules' @@ -16,24 +16,28 @@ const colorHash = (input: string) => { } type Props = { course: Course + isOpen: boolean } -const CourseListItem = ({course}: Props) => { - const [isOpen, setIsOpen] = useState(false) +const CourseListItem = ({course, isOpen}: Props) => { + const [isOpened, setIsOpen] = useState(isOpen) const toggleOpen = () => { - - setIsOpen(!isOpen) + setIsOpen(!isOpened) } + useEffect(() => { + setIsOpen(isOpen); + }, [isOpen]); + return (
- +  {course.name}
- {isOpen && + {isOpened && {infoSection("Course Number", course.number)} {infoSection("Semester", prettyPrintSemester(course.semester))} diff --git a/devU-client/src/components/pages/courseDetailPage.scss b/devU-client/src/components/pages/courseDetailPage.scss index bc29f9ca..fd0ac348 100644 --- a/devU-client/src/components/pages/courseDetailPage.scss +++ b/devU-client/src/components/pages/courseDetailPage.scss @@ -4,6 +4,7 @@ display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; /* adjust this value to set the space between the cards */ + } .header { @@ -28,4 +29,10 @@ .buttons { margin : 0 10px; +} + +.color{ + background-color: $list-item-background; + color: $text-color; + max-width : 345px; } \ No newline at end of file diff --git a/devU-client/src/components/pages/courseDetailPage.tsx b/devU-client/src/components/pages/courseDetailPage.tsx index 784e801f..7227128b 100644 --- a/devU-client/src/components/pages/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courseDetailPage.tsx @@ -27,7 +27,6 @@ const CourseDetailPage = () => { const [courseInfo, setCourseInfo] = useState(null) const [categoryMap, setCategoryMap] = useState>({}) const [setAlert] = useActionless(SET_ALERT) - const fetchCourseInfo = async () => { RequestService.get(`/api/courses/${courseId}`) .then((course) => { @@ -91,11 +90,12 @@ const CourseDetailPage = () => {
-
+
{Object.keys(categoryMap).map((category, index) => ( - +
+ - + {category} @@ -109,6 +109,7 @@ const CourseDetailPage = () => { ))} +
))}
diff --git a/devU-client/src/components/pages/coursesListPage.tsx b/devU-client/src/components/pages/coursesListPage.tsx index 6930987c..9caf8637 100644 --- a/devU-client/src/components/pages/coursesListPage.tsx +++ b/devU-client/src/components/pages/coursesListPage.tsx @@ -1,39 +1,30 @@ import React, {useEffect, useState} from 'react' import {Link} from 'react-router-dom' - import {Course, UserCourse} from 'devu-shared-modules' - import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import PageWrapper from 'components/shared/layouts/pageWrapper' import Dropdown, {Option} from 'components/shared/inputs/dropdown' import ErrorPage from './errorPage' - import RequestService from 'services/request.service' -import LocalStorageService from 'services/localStorage.service' - import styles from './coursesListPage.scss' import CourseListItem from "../listItems/courseListItem"; //import Button from 'components/shared/inputs/button' -const FILTER_LOCAL_STORAGE_KEY = 'courses_filter' - -type Filter = 'all' | 'active' | 'inactive' | 'dropped' +type Filter = true | false const filterOptions: Option[] = [ - {label: 'All', value: 'all'}, - {label: 'Active', value: 'active'}, - {label: 'Inactive', value: 'inactive'}, - {label: 'Dropped', value: 'dropped'}, + {label: 'Expand All', value: true}, + {label: 'Collapse All', value: false}, ] const UserCoursesListPage = () => { - const defaultFilter = LocalStorageService.get(FILTER_LOCAL_STORAGE_KEY) || 'active' const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [userCourses, setUserCourses] = useState(new Array()) - const [filter, setFilter] = useState(defaultFilter) + const [filter, setFilter] = useState(false ) + //Temporary place to store state for all courses const [allCourses, setAllCourses] = useState(new Array()) @@ -66,9 +57,6 @@ const UserCoursesListPage = () => { const handleFilterChange = (updatedFilter: Filter) => { setFilter(updatedFilter) - fetchData() - - LocalStorageService.set(FILTER_LOCAL_STORAGE_KEY, updatedFilter) } if (loading) return @@ -90,7 +78,7 @@ const UserCoursesListPage = () => {
{
{allCourses.map(course => ( - + ))} ) From 0ed9801830e23e431aaf4cace0244a8e4f7d4a91 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Wed, 24 Apr 2024 17:45:17 -0400 Subject: [PATCH 069/400] Merge branch 'develop' of github.com:makeopensource/devU into front-end-passover --- .../src/components/misc/globalToolbar.tsx | 2 + .../pages/courseAssignmentsListPage.tsx | 94 ++++++++++--------- devU-client/src/components/pages/homePage.tsx | 6 ++ .../components/pages/userCoursesListPage.tsx | 9 ++ .../src/components/utils/roleToggle.tsx | 28 ++++++ devU-client/src/redux/initialState/index.ts | 1 + devU-client/src/redux/reducers/index.ts | 2 + devU-client/src/redux/role.redux.ts | 45 +++++++++ devU-client/src/redux/store.ts | 4 +- 9 files changed, 143 insertions(+), 48 deletions(-) create mode 100644 devU-client/src/components/utils/roleToggle.tsx create mode 100644 devU-client/src/redux/role.redux.ts diff --git a/devU-client/src/components/misc/globalToolbar.tsx b/devU-client/src/components/misc/globalToolbar.tsx index 5a0d5d63..a7fd397b 100644 --- a/devU-client/src/components/misc/globalToolbar.tsx +++ b/devU-client/src/components/misc/globalToolbar.tsx @@ -7,6 +7,7 @@ import UserOptionsDropdown from 'components/utils/userOptionsDropdown' import styles from './globalToolbar.scss' +import RoleToggle from '../utils/roleToggle' const GlobalToolbar = () => { @@ -23,6 +24,7 @@ const GlobalToolbar = () => { {/* Turns into a sidebar via css on mobile */}
+ Courses diff --git a/devU-client/src/components/pages/courseAssignmentsListPage.tsx b/devU-client/src/components/pages/courseAssignmentsListPage.tsx index f5288b94..dfd806ea 100644 --- a/devU-client/src/components/pages/courseAssignmentsListPage.tsx +++ b/devU-client/src/components/pages/courseAssignmentsListPage.tsx @@ -1,62 +1,66 @@ -import React,{useEffect,useState} from 'react' +import React, { useEffect, useState } from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' -import {Assignment} from 'devu-shared-modules' +import { Assignment } from 'devu-shared-modules' import RequestService from 'services/request.service' import ErrorPage from './errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import { Link, useParams } from 'react-router-dom' import styles from './courseAssignmentsListPage.scss' +import { useAppSelector } from '../../redux/hooks' const CourseAssignmentsListPage = () => { - const { courseId } = useParams<{courseId: string}>() - const [error, setError] = useState(null) - const [loading, setLoading] = useState(true) - const [assignments, setAssignments] = useState(new Array()) - - useEffect(() => { - fetchData() - }, []) - - const fetchData = async () => { - try { - const assignments = await RequestService.get(`/api/assignments/course/${courseId}`) - setAssignments(assignments) - }catch(error){ - setError(error) - }finally{ - setLoading(false) - } + const role = useAppSelector((store) => store.roleMode) + const { courseId } = useParams<{ courseId: string }>() + const [error, setError] = useState(null) + const [loading, setLoading] = useState(true) + const [assignments, setAssignments] = useState(new Array()) + + useEffect(() => { + fetchData() + }, []) + + const fetchData = async () => { + try { + const assignments = await RequestService.get(`/api/assignments/course/${courseId}`) + setAssignments(assignments) + } catch (error) { + setError(error) + } finally { + setLoading(false) } + } + + if (loading) return + if (error) return + + return ( + +
+

Course Assignments List Page

+ +
+ {( role.isInstructor() && + Add Assignments + )} +
+
+ - if (loading) return - if (error) return - - return( - -
-

Course Assignments List Page

- -
- Add Assignments -
-
- - - {assignments.map(assignment => ( -
- - {assignment.name} -
- ))} -
- ) + {assignments.map(assignment => ( +
+ + {assignment.name} +
+ ))} +
+ ) } export default CourseAssignmentsListPage diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index b4068056..7921ba23 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -14,6 +14,7 @@ import {Assignment, Course, UserCourse} from 'devu-shared-modules' const HomePage = () => { const userId = useAppSelector((store) => store.user.id) + const role = useAppSelector((store) => store.roleMode) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) @@ -56,6 +57,11 @@ const HomePage = () => {

My Courses

+ + {role.isInstructor() && + }
diff --git a/devU-client/src/components/pages/userCoursesListPage.tsx b/devU-client/src/components/pages/userCoursesListPage.tsx index f464a914..09a31ce0 100644 --- a/devU-client/src/components/pages/userCoursesListPage.tsx +++ b/devU-client/src/components/pages/userCoursesListPage.tsx @@ -14,6 +14,9 @@ import {Assignment, Course, UserCourse} from 'devu-shared-modules' const HomePage = () => { const userId = useAppSelector((store) => store.user.id) + const role = useAppSelector((store) => store.roleMode) + + const defaultFilter = LocalStorageService.get(FILTER_LOCAL_STORAGE_KEY) || 'active' const [loading, setLoading] = useState(true) const [error, setError] = useState(null) @@ -58,6 +61,12 @@ const HomePage = () => {
+
+ {role.isInstructor() && ( + + Add Courses + + )}
diff --git a/devU-client/src/components/utils/roleToggle.tsx b/devU-client/src/components/utils/roleToggle.tsx new file mode 100644 index 00000000..8e7b2e95 --- /dev/null +++ b/devU-client/src/components/utils/roleToggle.tsx @@ -0,0 +1,28 @@ +import Switch from '@mui/material/Switch' +import FormControlLabel from '@mui/material/FormControlLabel' +import { updateUserRole } from '../../redux/role.redux' +import { useDispatch, useSelector } from 'react-redux' +import { RootState } from '../../redux/store' +import React from 'react' + +const RoleToggle = () => { + const dispatch = useDispatch(); + const userRole = useSelector((state: RootState) => state.roleMode.userRole); + + const handleToggle = () => { + const newRole = userRole === 'Student' ? 'Instructor' : 'Student'; + dispatch(updateUserRole(newRole)); + }; + + return ( + } + /> + ) +} + +export default RoleToggle diff --git a/devU-client/src/redux/initialState/index.ts b/devU-client/src/redux/initialState/index.ts index 0903d5db..974e81d7 100644 --- a/devU-client/src/redux/initialState/index.ts +++ b/devU-client/src/redux/initialState/index.ts @@ -4,4 +4,5 @@ import user from './user.initialState' export default { active, user, + } diff --git a/devU-client/src/redux/reducers/index.ts b/devU-client/src/redux/reducers/index.ts index 8bb7b35f..dfb90f5c 100644 --- a/devU-client/src/redux/reducers/index.ts +++ b/devU-client/src/redux/reducers/index.ts @@ -2,10 +2,12 @@ import { combineReducers } from 'redux' import active from './active.reducer' import user from './user.reducer' +import roleMode from '../role.redux' const reducers = combineReducers({ active, user, + roleMode, }) export default reducers diff --git a/devU-client/src/redux/role.redux.ts b/devU-client/src/redux/role.redux.ts new file mode 100644 index 00000000..a1559d2c --- /dev/null +++ b/devU-client/src/redux/role.redux.ts @@ -0,0 +1,45 @@ +// Define types for actions +type UpdateUserRoleAction = { + type: 'UPDATE_USER_ROLE'; + payload: string; +}; + +// Action Types +type UserActionTypes = UpdateUserRoleAction; + +// Action Creators +export const updateUserRole = (newRole: string): UpdateUserRoleAction => ({ + type: 'UPDATE_USER_ROLE', + payload: newRole, +}) + +// Define interface for the initial state +interface UserState { + userRole: string; + isInstructor: () => boolean +} + +// Load initial state from localStorage if available +const initialState: UserState = { + userRole: localStorage.getItem('userRole') || 'Student', // Default role is 'student' + isInstructor: function() { + return this.userRole === 'Instructor'; + }, +} + +// Reducer +const reducer = (state: UserState = initialState, action: UserActionTypes): UserState => { + switch (action.type) { + case 'UPDATE_USER_ROLE': + // Save to localStorage when role is updated + localStorage.setItem('userRole', action.payload) + return { + ...state, + userRole: action.payload, + } + default: + return state + } +} + +export default reducer diff --git a/devU-client/src/redux/store.ts b/devU-client/src/redux/store.ts index ca08d184..97b5cdb6 100644 --- a/devU-client/src/redux/store.ts +++ b/devU-client/src/redux/store.ts @@ -7,9 +7,7 @@ import rootReducer from './reducers/index' export const composeEnhancers = window && (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ function configureStore() { - const store = createStore(rootReducer, initialState, composeWithDevTools()) - - return store + return createStore(rootReducer, initialState, composeWithDevTools()) } const store = configureStore() From 9511004cedced40db30fc2c1bb38b83e92f0b62a Mon Sep 17 00:00:00 2001 From: SantarinX Date: Wed, 24 Apr 2024 17:45:32 -0400 Subject: [PATCH 070/400] fix merge errors --- devU-client/src/components/pages/homePage.tsx | 4 +++- devU-client/src/components/pages/userCoursesListPage.tsx | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index 7921ba23..66fb8307 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -5,16 +5,18 @@ import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import ErrorPage from './errorPage' import styles from './homePage.scss' import UserCourseListItem from "../listItems/userCourseListItem"; +import Button from '@mui/material/Button' import {useAppSelector} from 'redux/hooks' import RequestService from 'services/request.service' import {Assignment, Course, UserCourse} from 'devu-shared-modules' - +import {useHistory} from 'react-router-dom' const HomePage = () => { const userId = useAppSelector((store) => store.user.id) const role = useAppSelector((store) => store.roleMode) + const history = useHistory() const [loading, setLoading] = useState(true) const [error, setError] = useState(null) diff --git a/devU-client/src/components/pages/userCoursesListPage.tsx b/devU-client/src/components/pages/userCoursesListPage.tsx index 09a31ce0..a15543a6 100644 --- a/devU-client/src/components/pages/userCoursesListPage.tsx +++ b/devU-client/src/components/pages/userCoursesListPage.tsx @@ -5,7 +5,7 @@ import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import ErrorPage from './errorPage' import styles from './userCoursesListPage.scss' import UserCourseListItem from "../listItems/userCourseListItem"; - +import {Link} from 'react-router-dom' import {useAppSelector} from 'redux/hooks' import RequestService from 'services/request.service' @@ -16,7 +16,6 @@ const HomePage = () => { const userId = useAppSelector((store) => store.user.id) const role = useAppSelector((store) => store.roleMode) - const defaultFilter = LocalStorageService.get(FILTER_LOCAL_STORAGE_KEY) || 'active' const [loading, setLoading] = useState(true) const [error, setError] = useState(null) @@ -67,6 +66,7 @@ const HomePage = () => { Add Courses )} +
From 37a4b4f1048b36d3020e9f2fcac948e70c89a20d Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Wed, 24 Apr 2024 17:53:51 -0400 Subject: [PATCH 071/400] Formatter and cleaning up bad merging --- devU-api/scripts/populate-db.ts | 27 +++-- .../tests/login.developer.controller.test.ts | 11 +- .../containerAutoGrader.router.ts | 2 +- .../containerAutoGrader.service.ts | 16 +-- .../containerAutoGrader.validator.ts | 18 +-- .../src/entities/course/course.validator.ts | 8 +- .../role/tests/role.controller.test.ts | 2 +- .../role/tests/role.serializer.test.ts | 2 +- .../submission/submission.controller.ts | 10 +- .../entities/submission/submission.router.ts | 3 +- .../entities/submission/submission.service.ts | 12 +- .../tests/userCourse.controller.test.ts | 8 +- .../userCourse/userCourse.controller.ts | 15 ++- .../entities/userCourse/userCourse.router.ts | 2 +- .../entities/userCourse/userCourse.service.ts | 32 +++--- .../src/fileUpload/fileUpload.serializer.ts | 14 +-- .../test/fileUpload.controller.test.ts | 103 +++++++++--------- .../test/fileUpload.serializer.test.ts | 46 ++++---- 18 files changed, 161 insertions(+), 170 deletions(-) diff --git a/devU-api/scripts/populate-db.ts b/devU-api/scripts/populate-db.ts index 99b9ddb8..3fe380e4 100644 --- a/devU-api/scripts/populate-db.ts +++ b/devU-api/scripts/populate-db.ts @@ -68,15 +68,14 @@ async function CreateCourse(name: string, number: string, semester: string) { } async function joinCourse(courseId: number, userId: number, level: string) { - const userCourseData = { - userId: userId, - courseId: courseId, - level: level, - dropped: false - }; - console.log(`Joining course: ${courseId} for user: ${userId}`) - return await SendPOST('/user-courses', JSON.stringify(userCourseData), 'admin'); - + const userCourseData = { + userId: userId, + courseId: courseId, + level: level, + dropped: false, + } + console.log(`Joining course: ${courseId} for user: ${userId}`) + return await SendPOST('/user-courses', JSON.stringify(userCourseData), 'admin') } async function createAssignment(courseId: number, name: string, categoryName: string) { @@ -233,11 +232,11 @@ async function runCourseAndSubmission() { // JSON.stringify({ userId: bob, courseId: courseId2, role: 'student', dropped: false }), // 'admin' // ) - //Create enroll students - await joinCourse(courseId1, billy, 'student') - await joinCourse(courseId1, bob, 'student') - await joinCourse(courseId2, billy, 'student') - await joinCourse(courseId2, bob, 'student') + //Create enroll students + await joinCourse(courseId1, billy, 'student') + await joinCourse(courseId1, bob, 'student') + await joinCourse(courseId2, billy, 'student') + await joinCourse(courseId2, bob, 'student') //Create assignments const assignment1 = await createAssignment(courseId1, 'Course1 Assignment 1', 'Quiz') diff --git a/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts b/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts index 7deac960..ed3a5d8e 100644 --- a/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts +++ b/devU-api/src/authentication/login.developer/tests/login.developer.controller.test.ts @@ -6,7 +6,7 @@ import UserService from '../../../entities/user/user.service' import AuthService from '../../authentication.service' import Testing from '../../../utils/testing.utils' -import {refreshCookieOptions} from '../../../utils/cookie.utils' +import { refreshCookieOptions } from '../../../utils/cookie.utils' // Testing Globals let req: any @@ -42,10 +42,11 @@ describe('LoginDeveloperController', () => { test('Returns refreshToken as cookie', () => expect(res.cookie).toBeCalledWith('refreshToken', refreshToken, refreshCookieOptions)) test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) - test('Generic response returned ', () => expect(res.json).toBeCalledWith({ - message: 'Login successful', - userId: 1 - })) + test('Generic response returned ', () => + expect(res.json).toBeCalledWith({ + message: 'Login successful', + userId: 1, + })) }) describe('400 - Bad Request', () => { diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts index a0e4c1b5..7e0cf42b 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts @@ -59,7 +59,7 @@ Router.get('/:id', isAuthorized('assignmentViewAll'), asInt(), ContainerAutoGrad * schema: * type: integer */ -Router.get('/assignment/:id', asInt(), ContainerAutoGraderController.getObjectByAssignment); +Router.get('/assignment/:id', asInt(), ContainerAutoGraderController.getObjectByAssignment) /** * @swagger diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index 207997c9..cf200a89 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -107,7 +107,7 @@ export async function list() { //But need to be deleted when the grader entity changed to the getGraderByAssignmentId export async function getGraderObjectByAssignmentId(assignmentId: number) { if (!assignmentId) throw new Error('Missing AssignmentId') - return await connect().find({assignmentId: assignmentId, deletedAt: IsNull()}) + return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) } export async function listByAssignmentId(assignmentId: number) { @@ -133,11 +133,11 @@ export async function getGraderByAssignmentId(assignmentId: number) { } export default { - create, - retrieve, - update, - _delete, - list, - getGraderObjectByAssignmentId, - listByAssignmentId, + create, + retrieve, + update, + _delete, + list, + getGraderObjectByAssignmentId, + listByAssignmentId, } diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts index 76afbd1d..1f264307 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts @@ -1,22 +1,22 @@ -import {check} from 'express-validator' +import { check } from 'express-validator' import validate from '../../middleware/validator/generic.validator' - const assignmentId = check('assignmentId').isNumeric() const graderFile = check('graderFile') - .optional({ nullable: true }) - .custom(({req}) => { + .optional({ nullable: true }) + .custom(({ req }) => { const file = req?.files['grader'] if (file !== null) { - if (file.size <= 0) { - throw new Error('File is empty') - } + if (file.size <= 0) { + throw new Error('File is empty') + } } else { - throw new Error('does not have grader file') + throw new Error('does not have grader file') } -}).withMessage('Grader file is required') + }) + .withMessage('Grader file is required') const makefileFile = check('makefileFile') .optional({ nullable: true }) diff --git a/devU-api/src/entities/course/course.validator.ts b/devU-api/src/entities/course/course.validator.ts index 3daeb274..8f2f5246 100644 --- a/devU-api/src/entities/course/course.validator.ts +++ b/devU-api/src/entities/course/course.validator.ts @@ -1,11 +1,11 @@ -import {check} from 'express-validator' +import { check } from 'express-validator' import validate from '../../middleware/validator/generic.validator' -import {isAfterParam, isBeforeParam} from '../../middleware/validator/date.validator' +import { isAfterParam, isBeforeParam } from '../../middleware/validator/date.validator' -const name = check('name').isString().trim().isLength({max: 128, min: 1}) +const name = check('name').isString().trim().isLength({ max: 128, min: 1 }) -const number = check('number').isString().trim().isLength({max: 128, min: 1}) +const number = check('number').isString().trim().isLength({ max: 128, min: 1 }) const startDate = check('startDate').isString().trim().isISO8601().custom(isBeforeParam('endDate')).toDate() const endDate = check('endDate').isString().trim().isISO8601().custom(isAfterParam('startDate')).toDate() diff --git a/devU-api/src/entities/role/tests/role.controller.test.ts b/devU-api/src/entities/role/tests/role.controller.test.ts index 5b07055a..fce1b689 100644 --- a/devU-api/src/entities/role/tests/role.controller.test.ts +++ b/devU-api/src/entities/role/tests/role.controller.test.ts @@ -1,5 +1,5 @@ // TODO: Role testing. Lots of it! describe('this is where tests will go', () => { - test('Placeholder', () => expect(true).toEqual(true)) + test('Placeholder', () => expect(true).toEqual(true)) }) diff --git a/devU-api/src/entities/role/tests/role.serializer.test.ts b/devU-api/src/entities/role/tests/role.serializer.test.ts index 5b07055a..fce1b689 100644 --- a/devU-api/src/entities/role/tests/role.serializer.test.ts +++ b/devU-api/src/entities/role/tests/role.serializer.test.ts @@ -1,5 +1,5 @@ // TODO: Role testing. Lots of it! describe('this is where tests will go', () => { - test('Placeholder', () => expect(true).toEqual(true)) + test('Placeholder', () => expect(true).toEqual(true)) }) diff --git a/devU-api/src/entities/submission/submission.controller.ts b/devU-api/src/entities/submission/submission.controller.ts index 15e9ab4c..49e14897 100644 --- a/devU-api/src/entities/submission/submission.controller.ts +++ b/devU-api/src/entities/submission/submission.controller.ts @@ -1,10 +1,10 @@ -import {NextFunction, Request, Response} from 'express' +import { NextFunction, Request, Response } from 'express' import SubmissionService from '../submission/submission.service' -import {GenericResponse, NotFound} from '../../utils/apiResponse.utils' +import { GenericResponse, NotFound } from '../../utils/apiResponse.utils' -import {serialize} from './submission.serializer' +import { serialize } from './submission.serializer' export async function get(req: Request, res: Response, next: NextFunction) { try { @@ -51,7 +51,7 @@ export async function post(req: Request, res: Response, next: NextFunction) { const response = serialize(submission) res.status(201).json(response) - } catch (err:any) { + } catch (err: any) { res.status(400).json(new GenericResponse(err.message)) } } @@ -83,7 +83,6 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } - export async function getByAssignment(req: Request, res: Response, next: NextFunction) { try { const assignmentId = parseInt(req.params.assignmentId) @@ -98,5 +97,4 @@ export async function getByAssignment(req: Request, res: Response, next: NextFun } } -export default {get, detail, post, _delete, getByAssignment} export default { get, detail, post, revoke, getByAssignment, unrevoke, _delete } diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index 5b7c7fb4..2c7d26cf 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -4,9 +4,8 @@ import Multer from 'multer' // Middleware import validator from '../submission/submission.validator' -import { asInt } from '../../middleware/validator/generic.validator' import { isAuthorized } from '../../authorization/authorization.middleware' -import {asInt} from '../../middleware/validator/generic.validator' +import { asInt } from '../../middleware/validator/generic.validator' // Controller import SubmissionController from '../submission/submission.controller' diff --git a/devU-api/src/entities/submission/submission.service.ts b/devU-api/src/entities/submission/submission.service.ts index f73f5ccc..77ea5f74 100644 --- a/devU-api/src/entities/submission/submission.service.ts +++ b/devU-api/src/entities/submission/submission.service.ts @@ -1,12 +1,12 @@ -import {getRepository, IsNull} from 'typeorm' +import { getRepository, IsNull } from 'typeorm' import SubmissionModel from '../submission/submission.model' import FileModel from '../../fileUpload/fileUpload.model' import CourseModel from '../course/course.model' -import {FileUpload, Submission} from 'devu-shared-modules' -import {uploadFile} from '../../fileStorage' -import {groupBy} from '../../database' +import { FileUpload, Submission } from 'devu-shared-modules' +import { uploadFile } from '../../fileStorage' +import { groupBy } from '../../database' const submissionConn = () => getRepository(SubmissionModel) const fileConn = () => getRepository(FileModel) @@ -59,12 +59,10 @@ export async function list(query: any, id: number) { return await groupBy(submissionConn(), OrderByMappings, query, { index: 'submittedBy', value: id }) } - export async function listByAssignment(assignmentId: number, id: number) { - return await submissionConn().find({where: {assignmentId, submittedBy: id, deletedAt: IsNull()}}) + return await submissionConn().find({ where: { assignmentId, submittedBy: id, deletedAt: IsNull() } }) } - export default { create, retrieve, diff --git a/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts b/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts index dc7fb609..c3ec2b10 100644 --- a/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts +++ b/devU-api/src/entities/userCourse/tests/userCourse.controller.test.ts @@ -1,6 +1,6 @@ -import {UpdateResult} from 'typeorm' +import { UpdateResult } from 'typeorm' -import {UserCourse} from 'devu-shared-modules' +import { UserCourse } from 'devu-shared-modules' import controller from '../userCourse.controller' @@ -8,10 +8,10 @@ import UserCourseModel from '../userCourse.model' import UserCourseService from '../userCourse.service' -import {serialize} from '../userCourse.serializer' +import { serialize } from '../userCourse.serializer' import Testing from '../../../utils/testing.utils' -import {GenericResponse, NotFound, Updated} from '../../../utils/apiResponse.utils' +import { GenericResponse, NotFound, Updated } from '../../../utils/apiResponse.utils' // Testing Globals let req: any diff --git a/devU-api/src/entities/userCourse/userCourse.controller.ts b/devU-api/src/entities/userCourse/userCourse.controller.ts index 8c79cf88..d28bf8ce 100644 --- a/devU-api/src/entities/userCourse/userCourse.controller.ts +++ b/devU-api/src/entities/userCourse/userCourse.controller.ts @@ -1,9 +1,9 @@ -import {NextFunction, Request, Response} from 'express' +import { NextFunction, Request, Response } from 'express' import UserCourseService from './userCourse.service' -import {serialize} from './userCourse.serializer' +import { serialize } from './userCourse.serializer' -import {GenericResponse, NotFound, Updated} from '../../utils/apiResponse.utils' +import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' export async function getAll(req: Request, res: Response, next: NextFunction) { try { @@ -85,7 +85,7 @@ export async function put(req: Request, res: Response, next: NextFunction) { try { req.body.courseId = parseInt(req.params.id) const currentUser = req.currentUser?.userId - if (!currentUser) return res.status(401).json({message: 'Unauthorized'}) + if (!currentUser) return res.status(401).json({ message: 'Unauthorized' }) const results = await UserCourseService.update(req.body, currentUser) if (!results.affected) return res.status(404).json(NotFound) @@ -99,7 +99,7 @@ export async function checkEnroll(req: Request, res: Response, next: NextFunctio try { const courseId = parseInt(req.params.courseId) const userId = req.currentUser?.userId - if (!userId) return res.status(401).json({message: 'Unauthorized'}) + if (!userId) return res.status(401).json({ message: 'Unauthorized' }) const userCourse = await UserCourseService.checking(userId, courseId) if (!userCourse) return res.status(404).json(NotFound) @@ -110,12 +110,11 @@ export async function checkEnroll(req: Request, res: Response, next: NextFunctio } } - export async function _delete(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id) const currentUser = req.currentUser?.userId - if (!currentUser) return res.status(401).json({message: 'Unauthorized'}) + if (!currentUser) return res.status(401).json({ message: 'Unauthorized' }) const results = await UserCourseService._delete(id, currentUser) @@ -127,4 +126,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default {get, getByCourse, getAll, detail, detailByUser, post, put, _delete, checkEnroll} +export default { get, getByCourse, getAll, detail, detailByUser, post, put, _delete, checkEnroll } diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index 5fc8090a..d7f468fa 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -78,7 +78,7 @@ Router.get( UserCourseController.detailByUser ) -Router.get('/user/courses/:courseId', asInt("courseId"), UserCourseController.checkEnroll) +Router.get('/user/courses/:courseId', asInt('courseId'), UserCourseController.checkEnroll) /** * @swagger diff --git a/devU-api/src/entities/userCourse/userCourse.service.ts b/devU-api/src/entities/userCourse/userCourse.service.ts index 7c41686a..5c636a64 100644 --- a/devU-api/src/entities/userCourse/userCourse.service.ts +++ b/devU-api/src/entities/userCourse/userCourse.service.ts @@ -7,27 +7,27 @@ import UserCourse from './userCourse.model' const connect = () => getRepository(UserCourse) export async function create(userCourse: UserCourseType) { - const userId = userCourse.userId - const hasEnrolled = await connect().findOne({userId, courseId: userCourse.courseId}) - if (hasEnrolled) throw new Error('User already enrolled in course') + const userId = userCourse.userId + const hasEnrolled = await connect().findOne({ userId, courseId: userCourse.courseId }) + if (hasEnrolled) throw new Error('User already enrolled in course') return await connect().save(userCourse) } - export async function update(userCourse: UserCourseType, currentUser: number) { - const {courseId, level, dropped} = userCourse - if (!courseId) throw new Error('Missing Id') - const userCourseData = await connect().findOne({courseId, userId: currentUser}) - if (!userCourseData) throw new Error('User not enrolled in course') - userCourseData.level = level - userCourseData.dropped = dropped - return await connect().update(userCourse.id, userCourseData) + const { courseId, role, dropped } = userCourse + if (!courseId) throw new Error('Missing course Id') + const userCourseData = await connect().findOne({ courseId, userId: currentUser }) + if (!userCourseData) throw new Error('User not enrolled in course') + userCourseData.role = role + userCourseData.dropped = dropped + if (!userCourse.id) throw new Error('Missing Id') + return await connect().update(userCourse.id, userCourseData) } export async function _delete(courseId: number, userId: number) { - const userCourse = await connect().findOne({courseId, userId}) - if (!userCourse) throw new Error('User Not Found in Course') - return await connect().softDelete({courseId, userId, deletedAt: IsNull()}) + const userCourse = await connect().findOne({ courseId, userId }) + if (!userCourse) throw new Error('User Not Found in Course') + return await connect().softDelete({ courseId, userId, deletedAt: IsNull() }) } export async function retrieve(id: number) { @@ -52,7 +52,7 @@ export async function listByCourse(courseId: number) { } export async function checking(userId: number, courseId: number) { - return await connect().findOne({userId, courseId, dropped: false, deletedAt: IsNull()}) + return await connect().findOne({ userId, courseId, dropped: false, deletedAt: IsNull() }) } export default { @@ -64,5 +64,5 @@ export default { list, listAll, listByCourse, - checking + checking, } diff --git a/devU-api/src/fileUpload/fileUpload.serializer.ts b/devU-api/src/fileUpload/fileUpload.serializer.ts index 28ef7e66..39bac644 100644 --- a/devU-api/src/fileUpload/fileUpload.serializer.ts +++ b/devU-api/src/fileUpload/fileUpload.serializer.ts @@ -1,11 +1,11 @@ import { FileUpload } from '../../devu-shared-modules' export function serialize(file: FileUpload): FileUpload { - return { - id: file.id, - fieldName: file.fieldName, - originalName: file.originalName, - filename: file.filename, - etags: file.etags - } + return { + id: file.id, + fieldName: file.fieldName, + originalName: file.originalName, + filename: file.filename, + etags: file.etags, + } } diff --git a/devU-api/src/fileUpload/test/fileUpload.controller.test.ts b/devU-api/src/fileUpload/test/fileUpload.controller.test.ts index c924c5fc..ca93cc41 100644 --- a/devU-api/src/fileUpload/test/fileUpload.controller.test.ts +++ b/devU-api/src/fileUpload/test/fileUpload.controller.test.ts @@ -13,64 +13,63 @@ let mockFile: any let mockedFileUpload: FileModel let expectedError: Error - describe('FileUploadController', () => { - beforeEach(() => { - req = Testing.fakeRequest() - res = Testing.fakeResponse() - next = Testing.fakeNext() - mockFile = { - studentSubmission: [ - { - fieldname: 'studentSubmission', - originalname: 'test.txt', - encoding: '7bit', - mimetype: 'text/plain', - buffer: Buffer.from('Hello, world!', 'utf-8'), - size: 13, - } - ] - }; - mockedFileUpload = Testing.generateTypeOrm(FileModel) - expectedError = new Error('Expected Error') - }) - describe('GET - /file-upload/:bucketName', () => { - describe('200 - Ok', () => { - beforeEach(async () => { - FileUploadService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedFileUpload)) - req.files = mockFile - await controller.get(req, res, next) - }) + beforeEach(() => { + req = Testing.fakeRequest() + res = Testing.fakeResponse() + next = Testing.fakeNext() + mockFile = { + studentSubmission: [ + { + fieldname: 'studentSubmission', + originalname: 'test.txt', + encoding: '7bit', + mimetype: 'text/plain', + buffer: Buffer.from('Hello, world!', 'utf-8'), + size: 13, + }, + ], + } + mockedFileUpload = Testing.generateTypeOrm(FileModel) + expectedError = new Error('Expected Error') + }) + describe('GET - /file-upload/:bucketName', () => { + describe('200 - Ok', () => { + beforeEach(async () => { + FileUploadService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedFileUpload)) + req.files = mockFile + await controller.get(req, res, next) + }) test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) }) - describe('400 - Bad request', () => { - test('Next called with expected error', async () => { - FileUploadService.list = jest.fn().mockImplementation(() => Promise.reject(expectedError)) - await controller.get(req, res, next) // what we're testing - expect(next).toBeCalledWith(expectedError) - }) - }) + describe('400 - Bad request', () => { + test('Next called with expected error', async () => { + FileUploadService.list = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + await controller.get(req, res, next) // what we're testing + expect(next).toBeCalledWith(expectedError) + }) + }) + }) + describe('POST - /file-upload', () => { + describe('201 - Created', () => { + beforeEach(async () => { + req.files = mockFile + FileUploadService.create = jest.fn().mockImplementation(() => Promise.resolve(mockedFileUpload)) + await controller.post(req, res, next) + }) + test('Status code is 201', () => { + expect(res.status).toBeCalledWith(201) + }) + }) + describe('400 - Bad request', () => { + beforeEach(async () => { + req.files = undefined + await controller.post(req, res, next) + }) + test('Status code is 400', () => expect(res.status).toBeCalledWith(400)) }) - describe('POST - /file-upload', () => { - describe('201 - Created', () => { - beforeEach(async () => { - req.files = mockFile - FileUploadService.create = jest.fn().mockImplementation(() => Promise.resolve(mockedFileUpload)) - await controller.post(req, res, next) - }) - test('Status code is 201', () => { - expect(res.status).toBeCalledWith(201) - }) - }) - describe('400 - Bad request', () => { - beforeEach(async () => { - req.files = undefined - await controller.post(req, res, next) - }) - test('Status code is 400', () => expect(res.status).toBeCalledWith(400)) - }) describe('403 - Wrong file type', () => { beforeEach(async () => { diff --git a/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts b/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts index 9e7a57d1..a4a3b176 100644 --- a/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts +++ b/devU-api/src/fileUpload/test/fileUpload.serializer.test.ts @@ -4,29 +4,27 @@ import { FileUpload } from '../../../devu-shared-modules' let fakeFileUpload: FileUpload describe('fileUpload.serializer', () => { - beforeEach(() => { - let fieldName: string = "submissions" - const originalNames: string = ["test1.txt", "test2.txt", "test3.txt"].join(",") - const fileNames: string = ["modified1.txt", "modified2.txt", "modified3.txt"].join(",") - const etags: string = ["etag1", "etag2", "etag3"].join(",") - - fakeFileUpload = { - fieldName: fieldName, - originalName: originalNames, - filename: fileNames, - etags: etags - } - }); - describe('Serializing fileUpload', () => { - test('fileUpload values exist in the response', () => { - const actualResult = serialize(fakeFileUpload) - expect(actualResult).toBeDefined() - expect(actualResult.fieldName).toEqual(fakeFileUpload.fieldName) - expect(actualResult.originalName).toEqual(fakeFileUpload.originalName) - expect(actualResult.filename).toEqual(fakeFileUpload.filename) - expect(actualResult.etags).toEqual(fakeFileUpload.etags) - }) + beforeEach(() => { + let fieldName: string = 'submissions' + const originalNames: string = ['test1.txt', 'test2.txt', 'test3.txt'].join(',') + const fileNames: string = ['modified1.txt', 'modified2.txt', 'modified3.txt'].join(',') + const etags: string = ['etag1', 'etag2', 'etag3'].join(',') + fakeFileUpload = { + fieldName: fieldName, + originalName: originalNames, + filename: fileNames, + etags: etags, + } + }) + describe('Serializing fileUpload', () => { + test('fileUpload values exist in the response', () => { + const actualResult = serialize(fakeFileUpload) + expect(actualResult).toBeDefined() + expect(actualResult.fieldName).toEqual(fakeFileUpload.fieldName) + expect(actualResult.originalName).toEqual(fakeFileUpload.originalName) + expect(actualResult.filename).toEqual(fakeFileUpload.filename) + expect(actualResult.etags).toEqual(fakeFileUpload.etags) }) - -}); \ No newline at end of file + }) +}) From ea1ac2f21eba279b5cc015af4f181255134f44f8 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Wed, 24 Apr 2024 18:03:58 -0400 Subject: [PATCH 072/400] comment out errors.. --- devU-api/src/tango/tests/tango.endpoint.test.ts | 2 +- devU-client/src/components/pages/homePage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/devU-api/src/tango/tests/tango.endpoint.test.ts b/devU-api/src/tango/tests/tango.endpoint.test.ts index e62a420c..ae8ebea7 100644 --- a/devU-api/src/tango/tests/tango.endpoint.test.ts +++ b/devU-api/src/tango/tests/tango.endpoint.test.ts @@ -2,7 +2,7 @@ import { getInfo, preallocateInstances } from '../tango.service' async function main() { const res = await getInfo() - const pre = preallocateInstances() + // const pre = preallocateInstances() console.log(res) } diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index c0e61ad5..8b4f17ac 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -14,7 +14,7 @@ import {Assignment, Course, UserCourse} from 'devu-shared-modules' const HomePage = () => { const userId = useAppSelector((store) => store.user.id) - const role = useAppSelector((store) => store.roleMode) + // const role = useAppSelector((store) => store.roleMode) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) From fccaf811382ea7cdcf2fcc2eceeb7d916ef67ce9 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Wed, 24 Apr 2024 18:11:01 -0400 Subject: [PATCH 073/400] Superficial change so I can create a PR --- devU-api/src/entities/submission/submission.router.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index 2c7d26cf..85798c19 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -97,6 +97,7 @@ Router.post('/', isAuthorized('enrolled'), upload.single('files'), validator, Su * required: true * schema: * type: integer + * */ Router.put('/:id/revoke', isAuthorized('submissionChangeState'), asInt(), SubmissionController.revoke) From 48f5f4bef2de3486337e94e434f2b775a449f926 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Thu, 25 Apr 2024 15:27:41 -0400 Subject: [PATCH 074/400] WIP for debugging --- .../containerAutoGrader.validator.ts | 2 +- .../src/entities/grader/grader.controller.ts | 14 +++++++++++++- devU-api/src/entities/grader/grader.router.ts | 2 ++ .../src/entities/grader/grader.service.ts | 19 ++++++++++++++----- devU-api/src/tango/tango.service.ts | 8 +++----- docker-compose.yml | 3 ++- 6 files changed, 35 insertions(+), 13 deletions(-) diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts index 11740fb3..48f381f7 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts @@ -5,7 +5,7 @@ import validate from '../../middleware/validator/generic.validator' const assignmentId = check('assignmentId').isNumeric() -const graderFile = check('graderFile').custom(({req}) => { +const graderFile = check('graderFile').optional({ nullable: true }).custom(({req}) => { const file = req?.files['grader'] if (file !== null) { if (file.size <= 0) { diff --git a/devU-api/src/entities/grader/grader.controller.ts b/devU-api/src/entities/grader/grader.controller.ts index ab2a622c..1d08e5b7 100644 --- a/devU-api/src/entities/grader/grader.controller.ts +++ b/devU-api/src/entities/grader/grader.controller.ts @@ -20,4 +20,16 @@ export async function grade(req: Request, res: Response, next: NextFunction) { } } -export default { grade } +export async function tangoCallback(req: Request, res: Response, next: NextFunction) { + console.log("haiiiii hiiiii :3") + try { + const submissionId = parseInt(req.params.id) + const response = await GraderService.tangoCallback(submissionId) + + res.status(200).json(response) + } catch (err) { + res.status(400).json(new GenericResponse(err.message)) + } +} + +export default { grade, tangoCallback } diff --git a/devU-api/src/entities/grader/grader.router.ts b/devU-api/src/entities/grader/grader.router.ts index 1ed309a8..2ae38d63 100644 --- a/devU-api/src/entities/grader/grader.router.ts +++ b/devU-api/src/entities/grader/grader.router.ts @@ -32,4 +32,6 @@ const Router = express.Router() */ Router.post('/:id', asInt(), GraderController.grade) +Router.post('/callback/:id', asInt(), GraderController.tangoCallback) + export default Router \ No newline at end of file diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index 1d3848bb..75fbc9a2 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -6,7 +6,7 @@ import containerAutograderService from '../containerAutoGrader/containerAutoGrad import assignmentProblemService from '../assignmentProblem/assignmentProblem.service' import assignmentScoreService from '../assignmentScore/assignmentScore.service' import courseService from '../course/course.service' -import { addJob, openDirectory, uploadFile } from '../../tango/tango.service' +import { addJob, createCourse, uploadFile } from '../../tango/tango.service' import { SubmissionScore, SubmissionProblemScore, ContainerAutoGrader, AssignmentScore } from 'devu-shared-modules' import { checkAnswer } from '../nonContainerAutoGrader/nonContainerAutoGrader.grader' @@ -15,6 +15,7 @@ import { serialize as serializeAssignmentScore } from '../assignmentScore/assign import { downloadFile, initializeMinio } from '../../fileStorage' import crypto from 'crypto' +import environment from '../../environment' export async function grade(submissionId: number) { const submission = await submissionService.retrieve(submissionId) @@ -66,7 +67,7 @@ export async function grade(submissionId: number) { var response = null const labName = bucketName + '-' + submission.assignmentId const optionFiles = [] - const openResponse = await openDirectory(labName) + const openResponse = await createCourse(labName) if (openResponse) { if (!(openResponse.files["Graderfile"]) || openResponse.files["Graderfile"] !== crypto.createHash('md5').update(graderData).digest('hex')) { await uploadFile(labName, graderData, "Graderfile") @@ -80,15 +81,17 @@ export async function grade(submissionId: number) { optionFiles.push({localFile: filepath, destFile: filepath}) } } + console.log(environment.apiUrl) + console.log(labName) const jobOptions = { image: autogradingImage, - files: [{localFile: "Graderfile", destFile: "Graderfile"}, + files: [{localFile: "Graderfile", destFile: "autograde.tar"}, {localFile: "Makefile", destFile: "Makefile"},] .concat(optionFiles), jobName: labName, output_file: labName, timeout: timeout, - callback_url: "" + callback_url: `${environment.apiUrl}/grade/callback/${submissionId}` } response = await addJob(labName, jobOptions) } @@ -140,4 +143,10 @@ export async function mockContainerCheckAnswer(file: string, containerAutoGrader return gradeResults } -export default { grade } \ No newline at end of file +export async function tangoCallback(submissionId: number) { + console.log("!!!!!!!!!!!!!!YAYYYY!!!!!!!!!!!!!!\n!!!!!!!!!!!!!!YAYYYY!!!!!!!!!!!!!!\n!!!!!!!!!!!!!!YAYYYY!!!!!!!!!!!!!!\n") + console.log(`${submissionId} did it yayyy`) + return {id: submissionId} +} + +export default { grade, tangoCallback } \ No newline at end of file diff --git a/devU-api/src/tango/tango.service.ts b/devU-api/src/tango/tango.service.ts index cf720a53..33dfe851 100644 --- a/devU-api/src/tango/tango.service.ts +++ b/devU-api/src/tango/tango.service.ts @@ -1,7 +1,7 @@ import './tango.types' import fetch from "node-fetch"; -const tangoHost = `http://127.0.0.1:3000` +const tangoHost = `http://tango:3000` const tangoKey = process.env.TANGO_KEY ?? 'test' // for more info https://docs.autolabproject.com/tango-rest/ @@ -22,11 +22,9 @@ export async function createCourse(course: string): Promise * @param fileName - The file name, used to identify the file when uploaded * @param file - The file to be uploaded. */ -export async function uploadFile(course: string, file: File, fileName: string): Promise { +export async function uploadFile(course: string, file: Buffer, fileName: string): Promise { const url = `${tangoHost}/upload/${tangoKey}/${course}/` - const formData = new FormData() - formData.append('file', file) - const response = await fetch(url, { method: 'POST', body: formData, headers: { 'filename': fileName } }) + const response = await fetch(url, { method: 'POST', body: file, headers: { 'filename': fileName } }) return response.ok ? await response.json() as UploadResponse : null } diff --git a/docker-compose.yml b/docker-compose.yml index f1816242..833d2733 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -91,7 +91,7 @@ services: environment: - DOCKER_REDIS_HOSTNAME=redis - RESTFUL_KEY=devutangokey - - DOCKER_TANGO_HOST_VOLUME_PATH= #TODO add the absolute path to tango_files here before running + - DOCKER_TANGO_HOST_VOLUME_PATH= C:\Users\Keifer\Desktop\Programming\devU\tango_files #TODO add the absolute path to tango_files here before running depends_on: - redis @@ -101,6 +101,7 @@ services: - ./logs/tango/:/var/log/tango/ - ./logs/tangonginx:/var/log/nginx - ./tango_files/volumes:/opt/TangoService/Tango/volumes + # certbot: # container_name: certbot From 0ca1928c4d1a82f329f61eaf60de3ad7c69f81c6 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Thu, 25 Apr 2024 20:05:36 -0400 Subject: [PATCH 075/400] Callback http request is now successfully sent and recieved --- devU-api/src/entities/grader/grader.router.ts | 5 +++-- devU-api/src/entities/grader/grader.service.ts | 2 +- devU-api/src/router/index.ts | 2 +- docker-compose.yml | 5 +++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/devU-api/src/entities/grader/grader.router.ts b/devU-api/src/entities/grader/grader.router.ts index 2ae38d63..0df60a4c 100644 --- a/devU-api/src/entities/grader/grader.router.ts +++ b/devU-api/src/entities/grader/grader.router.ts @@ -4,6 +4,7 @@ import express from 'express' // Middleware //import validator from './grader.validator' import { asInt } from '../../middleware/validator/generic.validator' +import { isAuthorized } from '../../auth/auth.middleware' // Controller import GraderController from './grader.controller' @@ -30,8 +31,8 @@ const Router = express.Router() * schema: * type: integer */ -Router.post('/:id', asInt(), GraderController.grade) +Router.post('/:id', asInt(), isAuthorized, GraderController.grade) -Router.post('/callback/:id', asInt(), GraderController.tangoCallback) +Router.post('/callback/:id', asInt(), GraderController.tangoCallback) //Unauthorized route so tango can make callback without needing token export default Router \ No newline at end of file diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index 75fbc9a2..692f30e3 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -91,7 +91,7 @@ export async function grade(submissionId: number) { jobName: labName, output_file: labName, timeout: timeout, - callback_url: `${environment.apiUrl}/grade/callback/${submissionId}` + callback_url: `http://api:3001/grade/callback/${submissionId}` } response = await addJob(labName, jobOptions) } diff --git a/devU-api/src/router/index.ts b/devU-api/src/router/index.ts index 76e85ced..f352eba2 100644 --- a/devU-api/src/router/index.ts +++ b/devU-api/src/router/index.ts @@ -42,7 +42,7 @@ Router.use('/container-auto-graders', isAuthorized, containerAutoGrader) Router.use('/submission-problem-scores', isAuthorized, submissionProblemScore) Router.use('/file-upload', isAuthorized, fileUpload) Router.use('/deadline-extensions', isAuthorized, deadlineExtensions) -Router.use('/grade', isAuthorized, grader) +Router.use('/grade', grader) //Only the /grade/callback/:id route isn't authorized Router.use('/categories', isAuthorized, categories) Router.use('/assignment-scores', isAuthorized, assignmentScore) diff --git a/docker-compose.yml b/docker-compose.yml index 833d2733..5070e2ef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: - ./devU-api/config:/config api: # Runs the API + container_name: api build: context: . dockerfile: api.Dockerfile @@ -91,7 +92,7 @@ services: environment: - DOCKER_REDIS_HOSTNAME=redis - RESTFUL_KEY=devutangokey - - DOCKER_TANGO_HOST_VOLUME_PATH= C:\Users\Keifer\Desktop\Programming\devU\tango_files #TODO add the absolute path to tango_files here before running + - DOCKER_TANGO_HOST_VOLUME_PATH=C:\Users\Keifer\Desktop\Programming\devU\tango_files #TODO add the absolute path to tango_files here before running depends_on: - redis @@ -100,7 +101,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ./logs/tango/:/var/log/tango/ - ./logs/tangonginx:/var/log/nginx - - ./tango_files/volumes:/opt/TangoService/Tango/volumes + - ./tango_files:/opt/TangoService/Tango/volumes # certbot: From 51c1ebd442d6112bfb862737bfec11f44bfdbc57 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Thu, 25 Apr 2024 22:48:14 -0400 Subject: [PATCH 076/400] Update user-course path in populate db script --- devU-api/scripts/populate-db.ts | 30 +++---------------- devU-client/src/components/pages/homePage.tsx | 2 +- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/devU-api/scripts/populate-db.ts b/devU-api/scripts/populate-db.ts index 3fe380e4..4abe7de4 100644 --- a/devU-api/scripts/populate-db.ts +++ b/devU-api/scripts/populate-db.ts @@ -67,15 +67,15 @@ async function CreateCourse(name: string, number: string, semester: string) { return await SendPOST('/courses/instructor', JSON.stringify(courseData), 'admin') } -async function joinCourse(courseId: number, userId: number, level: string) { +async function joinCourse(courseId: number, userId: number, role: string) { const userCourseData = { userId: userId, courseId: courseId, - level: level, + role: role, dropped: false, } console.log(`Joining course: ${courseId} for user: ${userId}`) - return await SendPOST('/user-courses', JSON.stringify(userCourseData), 'admin') + return await SendPOST(`/course/${courseId}/user-courses`, JSON.stringify(userCourseData), 'admin') } async function createAssignment(courseId: number, name: string, categoryName: string) { @@ -210,29 +210,7 @@ async function runCourseAndSubmission() { const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024')).id const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id - //Create enroll students - // const response = await SendPOST( - // `/course/${courseId1}/user-courses`, - // JSON.stringify({ userId: billy, courseId: courseId1, role: 'student', dropped: false }), - // 'admin' - // ) - // console.log(response) - // await SendPOST( - // `/course/${courseId2}/user-courses`, - // JSON.stringify({ userId: billy, courseId: courseId2, role: 'student', dropped: false }), - // 'admin' - // ) - // await SendPOST( - // `/course/${courseId1}/user-courses`, - // JSON.stringify({ userId: bob, courseId: courseId1, role: 'student', dropped: false }), - // 'admin' - // ) - // await SendPOST( - // `/course/${courseId2}/user-courses`, - // JSON.stringify({ userId: bob, courseId: courseId2, role: 'student', dropped: false }), - // 'admin' - // ) - //Create enroll students + //Enroll students await joinCourse(courseId1, billy, 'student') await joinCourse(courseId1, bob, 'student') await joinCourse(courseId2, billy, 'student') diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index 66fb8307..9b697202 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -76,4 +76,4 @@ const HomePage = () => { ) } -export default HomePage +export default HomePage \ No newline at end of file From b7c7427c88437a6bef93e50e0cc81fed577f4dc1 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Fri, 26 Apr 2024 00:41:31 -0400 Subject: [PATCH 077/400] bug fixes in tests --- .../assignment/tests/assignment.controller.test.ts | 4 ++-- .../grader/tests/grader.controller.test.ts | 6 +----- .../fileUpload/test/fileUpload.controller.test.ts | 2 +- devU-api/src/tango/tests/tango.endpoint._test.ts | 14 ++++++++++++++ devU-api/src/tango/tests/tango.endpoint.test.ts | 11 ----------- devU-api/src/utils/testing.utils.ts | 1 + 6 files changed, 19 insertions(+), 19 deletions(-) create mode 100644 devU-api/src/tango/tests/tango.endpoint._test.ts delete mode 100644 devU-api/src/tango/tests/tango.endpoint.test.ts diff --git a/devU-api/src/entities/assignment/tests/assignment.controller.test.ts b/devU-api/src/entities/assignment/tests/assignment.controller.test.ts index b9a2c908..4b6105cb 100644 --- a/devU-api/src/entities/assignment/tests/assignment.controller.test.ts +++ b/devU-api/src/entities/assignment/tests/assignment.controller.test.ts @@ -53,7 +53,7 @@ describe('AssignmentController', () => { describe('GET - /assignments', () => { describe('200 - Ok', () => { beforeEach(async () => { - AssignmentService.list = jest.fn().mockImplementation(() => Promise.resolve(mockedAssignments)) + AssignmentService.listByCourse = jest.fn().mockImplementation(() => Promise.resolve(mockedAssignments)) await controller.getByCourse(req, res, next) // what we're testing }) @@ -63,7 +63,7 @@ describe('AssignmentController', () => { describe('400 - Bad request', () => { test('Next called with expected error', async () => { - AssignmentService.list = jest.fn().mockImplementation(() => Promise.reject(expectedError)) + AssignmentService.listByCourse = jest.fn().mockImplementation(() => Promise.reject(expectedError)) try { await controller.getByCourse(req, res, next) diff --git a/devU-api/src/entities/grader/tests/grader.controller.test.ts b/devU-api/src/entities/grader/tests/grader.controller.test.ts index 27ff4b0c..876b7f3c 100644 --- a/devU-api/src/entities/grader/tests/grader.controller.test.ts +++ b/devU-api/src/entities/grader/tests/grader.controller.test.ts @@ -1,5 +1,4 @@ import { GraderInfo, SubmissionScore, SubmissionProblemScore } from 'devu-shared-modules' -import SubmissionModel from '../../submission/submission.model' import controller from '../grader.controller' @@ -18,7 +17,6 @@ let req: any let res: any let next: any -let mockedSubmission: SubmissionModel let expectedReturn = new Array() let expectedResult: GraderInfo let expectedError: Error @@ -33,8 +31,6 @@ describe('GraderController', () => { res = Testing.fakeResponse() next = Testing.fakeNext() - mockedSubmission = Testing.generateTypeOrm(SubmissionModel) - expectedReturn.push(expectedProblemScore1) expectedReturn.push(expectedProblemScore2) expectedReturn.push(expectedScore) @@ -45,7 +41,7 @@ describe('GraderController', () => { describe('POST - /grade/:id', () => { describe('200 - Ok', () => { beforeEach(async () => { - GraderService.grade = jest.fn().mockImplementation(() => Promise.resolve(mockedSubmission)) + GraderService.grade = jest.fn().mockImplementation(() => Promise.resolve(expectedReturn)) await controller.grade(req, res, next) }) test('Status code is 200', () => expect(res.status).toBeCalledWith(200)) diff --git a/devU-api/src/fileUpload/test/fileUpload.controller.test.ts b/devU-api/src/fileUpload/test/fileUpload.controller.test.ts index ca93cc41..8fa23395 100644 --- a/devU-api/src/fileUpload/test/fileUpload.controller.test.ts +++ b/devU-api/src/fileUpload/test/fileUpload.controller.test.ts @@ -1,4 +1,4 @@ -import { FileUpload } from '../../../devu-shared-modules' +// import { FileUpload } from '../../../devu-shared-modules' import controller from '../fileUpload.controller' import FileUploadService from '../fileUpload.service' import Testing from '../../utils/testing.utils' diff --git a/devU-api/src/tango/tests/tango.endpoint._test.ts b/devU-api/src/tango/tests/tango.endpoint._test.ts new file mode 100644 index 00000000..dae723b7 --- /dev/null +++ b/devU-api/src/tango/tests/tango.endpoint._test.ts @@ -0,0 +1,14 @@ +// TODO: What even is this file? It doesn't follow any of our testing structure + +// import { getInfo, preallocateInstances } from '../tango.service' +// import { getInfo } from '../tango.service' +// +// async function main() { +// const res = await getInfo() +// // const pre = preallocateInstances() +// console.log(res) +// } + +// main().then(value => { +// console.log('main complete') +// }) diff --git a/devU-api/src/tango/tests/tango.endpoint.test.ts b/devU-api/src/tango/tests/tango.endpoint.test.ts deleted file mode 100644 index ae8ebea7..00000000 --- a/devU-api/src/tango/tests/tango.endpoint.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { getInfo, preallocateInstances } from '../tango.service' - -async function main() { - const res = await getInfo() - // const pre = preallocateInstances() - console.log(res) -} - -main().then(value => { - console.log('main complete') -}) diff --git a/devU-api/src/utils/testing.utils.ts b/devU-api/src/utils/testing.utils.ts index aaa9bdec..21e0f605 100644 --- a/devU-api/src/utils/testing.utils.ts +++ b/devU-api/src/utils/testing.utils.ts @@ -9,6 +9,7 @@ export function fakeRequest(overrides?: Partial): Request { req.body = {} req.headers = {} req.cookies = {} + req.query = {} //@ts-ignore - don't feel like defining every single socket property req.socket = { From 0d10dd9e0e6b5108b4e823d74365a1be0001184c Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Fri, 26 Apr 2024 01:27:29 -0400 Subject: [PATCH 078/400] Finish end point to pull all courses of a given user --- devU-api/src/entities/course/course.service.ts | 13 +++++++++++-- .../src/entities/userCourse/userCourse.service.ts | 12 ++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/devU-api/src/entities/course/course.service.ts b/devU-api/src/entities/course/course.service.ts index 2d1dbbc9..b1abe134 100644 --- a/devU-api/src/entities/course/course.service.ts +++ b/devU-api/src/entities/course/course.service.ts @@ -4,6 +4,7 @@ import CourseModel from './course.model' import { Course } from 'devu-shared-modules' import { initializeMinio } from '../../fileStorage' +import UserCourseService from "../userCourse/userCourse.service"; const connect = () => getRepository(CourseModel) @@ -32,8 +33,16 @@ export async function list() { return await connect().find({ deletedAt: IsNull() }) } export async function listByUser(userId: number) { - // TODO: lookup using UserCourses - return await connect().find({ deletedAt: IsNull() }) + const userCourses = await UserCourseService.listByUser(userId) + const courses: CourseModel[] = [] + // TODO: There is a more efficient way to do this than a query in a loop.. I'm too rusty on SQL to think of it rn + for(const userCourse of userCourses) { + const course = await connect().findOne({id: userCourse.courseId, deletedAt: IsNull()}) + if(course){ + courses.push() + } + } + return courses } export default { diff --git a/devU-api/src/entities/userCourse/userCourse.service.ts b/devU-api/src/entities/userCourse/userCourse.service.ts index 5c636a64..d8660741 100644 --- a/devU-api/src/entities/userCourse/userCourse.service.ts +++ b/devU-api/src/entities/userCourse/userCourse.service.ts @@ -51,10 +51,17 @@ export async function listByCourse(courseId: number) { return await connect().find({ courseId, deletedAt: IsNull() }) } -export async function checking(userId: number, courseId: number) { +export async function listByUser(userId: number) { + return await connect().find({ userId, dropped: false, deletedAt: IsNull() }) +} + +export async function checkIfEnrolled(userId: number, courseId: number) { return await connect().findOne({ userId, courseId, dropped: false, deletedAt: IsNull() }) } + + + export default { create, retrieve, @@ -64,5 +71,6 @@ export default { list, listAll, listByCourse, - checking, + listByUser, + checking: checkIfEnrolled, } From 64d48855141444f51124e7e8bc79428a7a26a8f1 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Fri, 26 Apr 2024 03:02:41 -0400 Subject: [PATCH 079/400] Updated Assignment Detail Page Updated the assignment detail page. Changed the styling and updated the api calls to match the newest versions. However, the requests seem to give back a 404 error so there may be some error in the backend?... or maybe I missed something. --- .../pages/assignmentDetailPage.scss | 70 ++++-- .../components/pages/assignmentDetailPage.tsx | 201 +++++++++--------- 2 files changed, 153 insertions(+), 118 deletions(-) diff --git a/devU-client/src/components/pages/assignmentDetailPage.scss b/devU-client/src/components/pages/assignmentDetailPage.scss index 8cff76c0..07f0deaf 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.scss +++ b/devU-client/src/components/pages/assignmentDetailPage.scss @@ -1,20 +1,58 @@ @import 'variables'; -.button { - justify-content: center; - background-color: $primary; - color: $text-color; - border: 0px; - padding: 10px 40px; - border-radius: 50px; - font-size: 14px; - font-weight: 700; - text-decoration: none; - cursor: pointer; -} - .header { - color: $text-color; display: flex; - justify-content: space-between; -} \ No newline at end of file + align-items: center; +} + +.smallLine { + width: 50px; /* adjust this value to set the length of the small line */ + border-top: 3px solid white; /* adjust this value to set the color and thickness of the line */ + margin-right: 10px; /* adjust this value to set the space between the line and the text */ +} + +.largeLine { + flex-grow: 1; + border-top: 3px solid white; /* adjust this value to set the color and thickness of the line */ + margin-left: 10px; /* adjust this value to set the space between the line and the text */ + margin-right: 10px; /* add this line to create some space between the line and the button */ +} + +.buttons { + margin : 0 10px; +} + +.card { + width: 60%; + height: 50%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding-top: 20px; +} + +.accordion { + width: 90%; + display: flex; + flex-direction: column; + margin-top: 10px; + margin-left: 10px; + margin-right: 10px; +} + +.accordionDetails { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.textField { + align-items: center; + justify-content: center; +} + +.submissionCard { + margin-bottom : 15px; +} diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index dafc438a..73bc3e64 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -1,26 +1,31 @@ -import React, {useEffect, useState} from 'react' -import {Link, useParams} from 'react-router-dom' +import React,{ useState, useEffect } from 'react' +import {/*Link,*/ useHistory} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' -import { - Assignment, - AssignmentProblem, - ContainerAutoGrader, - Submission, - SubmissionProblemScore, - SubmissionScore -} from 'devu-shared-modules' +import { AssignmentProblem, Submission, /*SubmissionScore, SubmissionProblemScore,*/ Assignment, ContainerAutoGrader } from 'devu-shared-modules' import RequestService from 'services/request.service' import ErrorPage from './errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import TextField from 'components/shared/inputs/textField' -import Button from 'components/shared/inputs/button' -import {useActionless, useAppSelector} from 'redux/hooks' -import {SET_ALERT} from 'redux/types/active.types' +import { useAppSelector,useActionless } from 'redux/hooks' +import { SET_ALERT } from 'redux/types/active.types' +import { useParams } from 'react-router-dom' + +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' +import {CardActionArea} from '@mui/material' +import Stack from '@mui/material/Stack' +import Button from '@mui/material/Button' +import {Accordion} from '@mui/material' +import {AccordionSummary} from '@mui/material' +import {AccordionDetails} from '@mui/material' +import {Typography} from '@mui/material' +import {TextField} from '@mui/material' +import Grid from '@mui/material/Unstable_Grid2' import styles from './assignmentDetailPage.scss' const AssignmentDetailPage = () => { const [setAlert] = useActionless(SET_ALERT) + const history = useHistory() const { assignmentId, courseId } = useParams<{assignmentId: string, courseId: string}>() const userId = useAppSelector((store) => store.user.id) @@ -30,8 +35,8 @@ const AssignmentDetailPage = () => { const [file, setFile] = useState() const [assignmentProblems, setAssignmentProblems] = useState(new Array()) const [submissions, setSubmissions] = useState(new Array()) - const [submissionScores, setSubmissionScores] = useState(new Array()) - const [submissionProblemScores, setSubmissionProblemScores] = useState(new Array()) + // const [submissionScores, setSubmissionScores] = useState(new Array()) + // const [submissionProblemScores, setSubmissionProblemScores] = useState(new Array()) const [assignment, setAssignment] = useState() const [containerAutograder, setContainerAutograder] = useState() @@ -42,27 +47,27 @@ const AssignmentDetailPage = () => { const fetchData = async () => { try { - const assignment = await RequestService.get(`/api/assignments/${assignmentId}`) + const assignment = await RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`) setAssignment(assignment) - const assignmentProblemsReq = await RequestService.get(`/api/assignment-problems/${assignmentId}`) + const assignmentProblemsReq = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems/`) setAssignmentProblems(assignmentProblemsReq) - const submissionsReq = await RequestService.get(`/api/submissions/assignments/${assignmentId}`) + const submissionsReq = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submissions/`) submissionsReq.sort((a, b) => (Date.parse(b.createdAt ?? '') - Date.parse(a.createdAt ?? ''))) setSubmissions(submissionsReq) - const submissionScoresPromises = submissionsReq.map(s => { - return RequestService.get(`/api/submission-scores?submission=${s.id}`) - }) - const submissionScoresReq = (await Promise.all(submissionScoresPromises)).reduce((a, b) => a.concat(b), []) - setSubmissionScores(submissionScoresReq) + // const submissionScoresPromises = submissionsReq.map(s => { + // return RequestService.get(`/api/submission-scores?submission=${s.id}`) + // }) + // const submissionScoresReq = (await Promise.all(submissionScoresPromises)).reduce((a, b) => a.concat(b), []) + // setSubmissionScores(submissionScoresReq) - const submissionProblemScoresPromises = submissionsReq.map(s => { - return RequestService.get(`/api/submission-problem-scores/${s.id}`) - }) - const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) - setSubmissionProblemScores(submissionProblemScoresReq) + // const submissionProblemScoresPromises = submissionsReq.map(s => { + // return RequestService.get(`/api/submission-problem-scores/${s.id}`) + // }) + // const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) + // setSubmissionProblemScores(submissionProblemScoresReq) const containerAutograder = (await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`)).pop() ?? null setContainerAutograder(containerAutograder) @@ -77,9 +82,9 @@ const AssignmentDetailPage = () => { if (loading) return if (error) return - const handleChange = (value : string, e : React.ChangeEvent) => { + const handleChange = (e : React.ChangeEvent) => { const key = e.target.id - setFormData(prevState => ({...prevState,[key] : value})) + setFormData(prevState => ({...prevState,[key] : e.target.value})) } const handleFileChange = (e : React.ChangeEvent) => { setFile(e.target.files?.item(0)) @@ -130,83 +135,75 @@ const AssignmentDetailPage = () => { return( -

{assignment?.name}

- - Update Assignment -


- Add - Non-Container Auto-Graders -


- Add - Container Auto-Grader - - {/**Assignment Problems & Submission */} - {assignmentProblems.map(assignmentProblem => ( -
-

{assignmentProblem.problemName}

- -
- ))} +
+
+

{assignment?.name}

+
+ + + + {/* + */} + +
+ + + + + {assignmentProblems && assignmentProblems.length > 0 ? ( + assignmentProblems.map((assignmentProblem, index) => ( + + + {`Assignment Problem ${index + 1}`} + + + {assignmentProblem.problemName} + + + + )) + ) : ( + + No Problems Exist + + )} + {containerAutograder && ()} - -
+ + {assignmentProblems? ( + + ) : null} +
+
+ + +
+
+

{`Submissions`}

+
+
{/**Submissions List */} - {submissions.map((s, index) => ( - ss.submissionId === s.id)} - submissionProblemScores={submissionProblemScores.filter(sps => sps.submissionId === s.id)} - assignmentProblems={assignmentProblems} - courseId={courseId} - assignmentId={assignmentId} - /> +
+ {submissions.map((submission, index) => ( + + {history.push(`/courses/${courseId}/assignments/${assignmentId}/submissions/${submission.id}`)}}> + + {`Submission ${submissions.length - index}`} + {`Submitted at: ${submission.createdAt}`} + + + ))} +
) } -type SubmissionProps = { - index: number, - submission: Submission, - submissionScore?: SubmissionScore, - submissionProblemScores: SubmissionProblemScore[], - assignmentProblems: AssignmentProblem[], - courseId: string - assignmentId: string -} -const SubmissionComponent = ({ - index, - submission, - submissionScore, - submissionProblemScores, - assignmentProblems, - courseId, - assignmentId - }: SubmissionProps) => { - return ( -
-

Submission {index}:

- - {assignmentProblems.map(ap => ( - - ))} - - - {assignmentProblems.map(ap => ( - - ))} - - -
{ap.problemName} ({ap.maxScore})Total Score
{submissionProblemScores.find(sps => sps.assignmentProblemId === ap.id)?.score ?? "N/A"}{submissionScore?.score ?? "N/A"}
-
- Submission Details - Submission Feedback -


-
- ) -} export default AssignmentDetailPage From 6b883ca32a7078c3c5c63a28f222e1f6d9216900 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Fri, 26 Apr 2024 03:24:48 -0400 Subject: [PATCH 080/400] Updated tango pollJob, container grading callback mostly complete --- .../src/entities/grader/grader.controller.ts | 5 +- devU-api/src/entities/grader/grader.router.ts | 2 +- .../src/entities/grader/grader.service.ts | 61 ++++++++++++------- devU-api/src/tango/tango.service.ts | 10 +-- 4 files changed, 47 insertions(+), 31 deletions(-) diff --git a/devU-api/src/entities/grader/grader.controller.ts b/devU-api/src/entities/grader/grader.controller.ts index 1d08e5b7..830ca6dc 100644 --- a/devU-api/src/entities/grader/grader.controller.ts +++ b/devU-api/src/entities/grader/grader.controller.ts @@ -21,13 +21,14 @@ export async function grade(req: Request, res: Response, next: NextFunction) { } export async function tangoCallback(req: Request, res: Response, next: NextFunction) { - console.log("haiiiii hiiiii :3") try { - const submissionId = parseInt(req.params.id) + console.log('awawawa') + const submissionId = req.params.id const response = await GraderService.tangoCallback(submissionId) res.status(200).json(response) } catch (err) { + console.log("wah :((") res.status(400).json(new GenericResponse(err.message)) } } diff --git a/devU-api/src/entities/grader/grader.router.ts b/devU-api/src/entities/grader/grader.router.ts index 0df60a4c..b0fc26b9 100644 --- a/devU-api/src/entities/grader/grader.router.ts +++ b/devU-api/src/entities/grader/grader.router.ts @@ -33,6 +33,6 @@ const Router = express.Router() */ Router.post('/:id', asInt(), isAuthorized, GraderController.grade) -Router.post('/callback/:id', asInt(), GraderController.tangoCallback) //Unauthorized route so tango can make callback without needing token +Router.post('/callback/:id', GraderController.tangoCallback) //Unauthorized route so tango can make callback without needing token export default Router \ No newline at end of file diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index 692f30e3..622b5d6a 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -6,9 +6,9 @@ import containerAutograderService from '../containerAutoGrader/containerAutoGrad import assignmentProblemService from '../assignmentProblem/assignmentProblem.service' import assignmentScoreService from '../assignmentScore/assignmentScore.service' import courseService from '../course/course.service' -import { addJob, createCourse, uploadFile } from '../../tango/tango.service' +import { addJob, createCourse, uploadFile, pollJob } from '../../tango/tango.service' -import { SubmissionScore, SubmissionProblemScore, ContainerAutoGrader, AssignmentScore } from 'devu-shared-modules' +import { SubmissionScore, SubmissionProblemScore, AssignmentScore } from 'devu-shared-modules' import { checkAnswer } from '../nonContainerAutoGrader/nonContainerAutoGrader.grader' import { serialize as serializeNonContainer } from '../nonContainerAutoGrader/nonContainerAutoGrader.serializer' import { serialize as serializeAssignmentScore } from '../assignmentScore/assignmentScore.serializer' @@ -65,7 +65,7 @@ export async function grade(submissionId: number) { initializeMinio(bucketName) var response = null - const labName = bucketName + '-' + submission.assignmentId + const labName = `${bucketName}-${submission.assignmentId}` const optionFiles = [] const openResponse = await createCourse(labName) if (openResponse) { @@ -88,15 +88,15 @@ export async function grade(submissionId: number) { files: [{localFile: "Graderfile", destFile: "autograde.tar"}, {localFile: "Makefile", destFile: "Makefile"},] .concat(optionFiles), - jobName: labName, - output_file: labName, + jobName: `${labName}-${submissionId}`, + output_file: `${labName}-${submissionId}-output.txt`, timeout: timeout, - callback_url: `http://api:3001/grade/callback/${submissionId}` + callback_url: `http://api:3001/grade/callback/${labName}-${submissionId}-output.txt` } response = await addJob(labName, jobOptions) } } catch (e) { - console.log(e) + console.error(e) } //remember, immediate callback is made when job has been added to queue, not sure how we're handling the rest of it yet though lmao @@ -127,26 +127,41 @@ export async function grade(submissionId: number) { return response } -//Temporary mock function, delete when the container autograder grading function is written -export async function mockContainerCheckAnswer(file: string, containerAutoGrader: ContainerAutoGrader) { - let gradeResults = [] - //SubmissionProblemScore 1 - gradeResults.push({score: 5, feedback: "Grader #" + containerAutoGrader.id + " graded " + file + " problem 1 for 5/5 points"}) +export async function tangoCallback(outputFile: string) { + console.log('goot!') + //Output filename consists of 4 sections separated by hyphens. + and () only for visual clarity, not a part of the filename + //(course.number+course.semester+course.id)-(assignment.id)-(submission.id)-(output.txt) + const filenameSplit = outputFile.split('-') + const labName = `${filenameSplit[0]}-${filenameSplit[1]}` + const assignmentId = Number(filenameSplit[1]) + const submissionId = Number(filenameSplit[2]) - //SubmissionProblemScore 2 - gradeResults.push({score: 5, feedback: "Grader #" + containerAutoGrader.id + " graded " + file + " problem 2 for 5/5 points"}) + const response = await pollJob(labName, outputFile) + if (typeof response !== 'string') { + throw new Error('Autograder output file not found') + } + const splitResponse = (response as string).split(/\r\n|\r|\n/) + const scores = JSON.parse(splitResponse[splitResponse.length - 2]) - //SubmissionProblemScore 3 - gradeResults.push({score: 10, feedback: "Grader #" + containerAutoGrader.id + " graded " + file + " problem 3 for 10/10 points"}) - - return gradeResults -} + let score = 0 + const assignmentProblems = await assignmentProblemService.list(assignmentId) + for (const question in scores['scores']) { + const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) + if (assignmentProblem) { + const problemScoreObj: SubmissionProblemScore = { + submissionId: submissionId, + assignmentProblemId: assignmentProblem.id, + score: Number(scores[question]), + feedback: '' //Not sure what to do for individual problemscore feedback + } + submissionProblemScoreService.create(problemScoreObj) + score += Number(scores[question]) + } + + } -export async function tangoCallback(submissionId: number) { - console.log("!!!!!!!!!!!!!!YAYYYY!!!!!!!!!!!!!!\n!!!!!!!!!!!!!!YAYYYY!!!!!!!!!!!!!!\n!!!!!!!!!!!!!!YAYYYY!!!!!!!!!!!!!!\n") - console.log(`${submissionId} did it yayyy`) - return {id: submissionId} + return {output: response} } export default { grade, tangoCallback } \ No newline at end of file diff --git a/devU-api/src/tango/tango.service.ts b/devU-api/src/tango/tango.service.ts index 33dfe851..55f2203a 100644 --- a/devU-api/src/tango/tango.service.ts +++ b/devU-api/src/tango/tango.service.ts @@ -48,13 +48,13 @@ export async function addJob(course: string, job: AddJobRequest): Promise { +export async function pollJob(course: string, outputFile: string): Promise { //PollSuccessResponse const url = `${tangoHost}/poll/${tangoKey}/${course}/${outputFile}/` const response = await fetch(url, { method: 'GET' }) - const data = await response.json() - return response.ok ? - data as PollSuccessResponse - : data as PollFailureResponse + + return response.headers.get('Content-Type')?.includes('application/json') + ? (await response.json()) as PollFailureResponse + : (await response.text()) as PollSuccessResponse } /** From 4fb656de6290fd395d47041cf041762e4a34c3b9 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Fri, 26 Apr 2024 08:47:07 -0400 Subject: [PATCH 081/400] Home page link update; API to use Node 20 (Client breaks on 20) --- api.Dockerfile | 4 ++-- devU-client/src/components/pages/homePage.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api.Dockerfile b/api.Dockerfile index bf4aabff..4b252993 100644 --- a/api.Dockerfile +++ b/api.Dockerfile @@ -1,4 +1,4 @@ -FROM node:16 as module_builder +FROM node:20 as module_builder WORKDIR /tmp @@ -8,7 +8,7 @@ RUN npm install && \ npm run clean-directory && \ npm run build-docker -FROM node:16 +FROM node:20 WORKDIR /app diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index 9b697202..fe99de3e 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -29,10 +29,10 @@ const HomePage = () => { const fetchData = async () => { try { - const userCourses = await RequestService.get( `/api/user-courses/user/${userId}` ) - const coursePromises = userCourses.map(uc => { - const course = RequestService.get(`/api/courses/${uc.courseId}`) - const assignments = RequestService.get(`/api/assignments/course/${uc.courseId}`) + const enrolledCourses = await RequestService.get( `/api/courses/user/${userId}` ) + const coursePromises = enrolledCourses.map(_course => { + const course = RequestService.get(`/api/courses/${_course.id}`) // TODO: Optimize out this redundant call + const assignments = RequestService.get(`/api/course/${_course.id}/assignments/`) return Promise.all([course, assignments]) }) From d4ca40e4244e1667d8180a3db10a3de37001318d Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:14:36 -0400 Subject: [PATCH 082/400] Updated add course form link Updated add course form link --- devU-client/src/components/pages/homePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index fe99de3e..a67a9cc1 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -61,7 +61,7 @@ const HomePage = () => {
{role.isInstructor() && }
From 5f79a9635da7645e394caabcd799042d34c36997 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Fri, 26 Apr 2024 11:04:01 -0400 Subject: [PATCH 083/400] bug fixes.. --- .../src/entities/course/course.controller.ts | 2 +- .../src/entities/course/course.service.ts | 59 ++++++++++--------- .../entities/userCourse/userCourse.service.ts | 2 +- devU-client/src/components/pages/homePage.tsx | 3 +- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/devU-api/src/entities/course/course.controller.ts b/devU-api/src/entities/course/course.controller.ts index 99617cb4..157f9ce2 100644 --- a/devU-api/src/entities/course/course.controller.ts +++ b/devU-api/src/entities/course/course.controller.ts @@ -31,7 +31,7 @@ export async function getByUser(req: Request, res: Response, next: NextFunction) export async function detail(req: Request, res: Response, next: NextFunction) { try { - const id = parseInt(req.params.id) + const id = parseInt(req.params.courseId) const course = await CourseService.retrieve(id) if (!course) return res.status(404).json(NotFound) diff --git a/devU-api/src/entities/course/course.service.ts b/devU-api/src/entities/course/course.service.ts index b1abe134..ce15dfc1 100644 --- a/devU-api/src/entities/course/course.service.ts +++ b/devU-api/src/entities/course/course.service.ts @@ -1,55 +1,56 @@ -import { getRepository, IsNull } from 'typeorm' +import {getRepository, IsNull} from 'typeorm' import CourseModel from './course.model' -import { Course } from 'devu-shared-modules' -import { initializeMinio } from '../../fileStorage' +import {Course} from 'devu-shared-modules' +import {initializeMinio} from '../../fileStorage' import UserCourseService from "../userCourse/userCourse.service"; const connect = () => getRepository(CourseModel) export async function create(course: Course) { - const output = await connect().save(course) - const bucketName = (course.number + course.semester + course.id).replace(/ /g, '-').toLowerCase() - await initializeMinio(bucketName) - return output + const output = await connect().save(course) + const bucketName = (course.number + course.semester + course.id).replace(/ /g, '-').toLowerCase() + await initializeMinio(bucketName) + return output } export async function update(course: Course) { - const { id, name, semester, number, startDate, endDate } = course - if (!id) throw new Error('Missing Id') - return await connect().update(id, { name, semester, number, startDate, endDate }) + const {id, name, semester, number, startDate, endDate} = course + if (!id) throw new Error('Missing Id') + return await connect().update(id, {name, semester, number, startDate, endDate}) } export async function _delete(id: number) { - return await connect().softDelete({ id, deletedAt: IsNull() }) + return await connect().softDelete({id, deletedAt: IsNull()}) } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOne({id, deletedAt: IsNull()}) } export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().find({deletedAt: IsNull()}) } + export async function listByUser(userId: number) { - const userCourses = await UserCourseService.listByUser(userId) - const courses: CourseModel[] = [] - // TODO: There is a more efficient way to do this than a query in a loop.. I'm too rusty on SQL to think of it rn - for(const userCourse of userCourses) { - const course = await connect().findOne({id: userCourse.courseId, deletedAt: IsNull()}) - if(course){ - courses.push() - } - } - return courses + const userCourses = await UserCourseService.listByUser(userId) + let courses: CourseModel[] = [] + // TODO: There is a more efficient way to do this than a query in a loop.. I'm too rusty on SQL to think of it rn + for (const userCourse of userCourses) { + const course = await connect().findOne({id: userCourse.courseId, deletedAt: IsNull()}) + if (course) { + courses.push(course) + } + } + return courses } export default { - create, - retrieve, - update, - _delete, - list, - listByUser, + create, + retrieve, + update, + _delete, + list, + listByUser, } diff --git a/devU-api/src/entities/userCourse/userCourse.service.ts b/devU-api/src/entities/userCourse/userCourse.service.ts index d8660741..abe71825 100644 --- a/devU-api/src/entities/userCourse/userCourse.service.ts +++ b/devU-api/src/entities/userCourse/userCourse.service.ts @@ -52,7 +52,7 @@ export async function listByCourse(courseId: number) { } export async function listByUser(userId: number) { - return await connect().find({ userId, dropped: false, deletedAt: IsNull() }) + return await connect().find({ userId: userId, dropped: false, deletedAt: IsNull() }) } export async function checkIfEnrolled(userId: number, courseId: number) { diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index a67a9cc1..13d33fd8 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -32,9 +32,8 @@ const HomePage = () => { const enrolledCourses = await RequestService.get( `/api/courses/user/${userId}` ) const coursePromises = enrolledCourses.map(_course => { const course = RequestService.get(`/api/courses/${_course.id}`) // TODO: Optimize out this redundant call - const assignments = RequestService.get(`/api/course/${_course.id}/assignments/`) + const assignments = RequestService.get(`/api/course/${_course.id}/assignments/released`) return Promise.all([course, assignments]) - }) const result = await Promise.all(coursePromises) const courses = result.map(([course]) => course) From 980d5dc19fae8881e5cfdba99c6444c91e1a392d Mon Sep 17 00:00:00 2001 From: SantarinX Date: Fri, 26 Apr 2024 11:07:13 -0400 Subject: [PATCH 084/400] update bug fixs --- devU-client/src/components/misc/globalToolbar.tsx | 4 ++-- devU-client/src/components/pages/coursePreviewPage.tsx | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/devU-client/src/components/misc/globalToolbar.tsx b/devU-client/src/components/misc/globalToolbar.tsx index a7fd397b..59b57483 100644 --- a/devU-client/src/components/misc/globalToolbar.tsx +++ b/devU-client/src/components/misc/globalToolbar.tsx @@ -32,9 +32,9 @@ const GlobalToolbar = () => { My Courses - + {/* Submissions - + */}
diff --git a/devU-client/src/components/pages/coursePreviewPage.tsx b/devU-client/src/components/pages/coursePreviewPage.tsx index 5ef5f272..2824217b 100644 --- a/devU-client/src/components/pages/coursePreviewPage.tsx +++ b/devU-client/src/components/pages/coursePreviewPage.tsx @@ -69,10 +69,13 @@ const CoursePreviewPage = () => { dropped: false }; - RequestService.post('/api/user-courses', userCourseData) + RequestService.post(`/api/courses/${courseId}/user-courses`, userCourseData) .catch((error: Error) => { const message = error.message setAlert({autoDelete: false, type: 'error', message}) + }).catch((error: Error) => { + const message = error.message + setAlert({autoDelete: false, type: 'error', message}) }).finally(() => { setAlert({autoDelete: true, type: 'success', message: 'Course Joined'}) history.goBack() From 494cf41f6404900f26ea3b8c655f99857f888056 Mon Sep 17 00:00:00 2001 From: Keiferms3 Date: Fri, 26 Apr 2024 11:11:55 -0400 Subject: [PATCH 085/400] Small changes to grader service during meeting --- .../src/entities/grader/grader.service.ts | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index 622b5d6a..4266125f 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -141,12 +141,13 @@ export async function tangoCallback(outputFile: string) { if (typeof response !== 'string') { throw new Error('Autograder output file not found') } - const splitResponse = (response as string).split(/\r\n|\r|\n/) - const scores = JSON.parse(splitResponse[splitResponse.length - 2]) + const splitResponse = response.split(/\r\n|\r|\n/) + const scores = (JSON.parse(splitResponse[splitResponse.length - 2])).scores let score = 0 const assignmentProblems = await assignmentProblemService.list(assignmentId) - for (const question in scores['scores']) { + const submissionScore = await submissionScoreService.retrieve(submissionId) + for (const question in scores) { const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) if (assignmentProblem) { const problemScoreObj: SubmissionProblemScore = { @@ -158,7 +159,19 @@ export async function tangoCallback(outputFile: string) { submissionProblemScoreService.create(problemScoreObj) score += Number(scores[question]) } - + } + if (submissionScore) { + submissionScore.score += score + submissionScore.feedback += `\n${response}` + + submissionScoreService.update(submissionScore) + } else { + const scoreObj: SubmissionScore = { + submissionId: submissionId, + score: score, //Sum of all SubmissionProblemScore scores + feedback: response //Feedback from Tango + } + submissionScoreService.create(scoreObj) } return {output: response} From 0c82ab37641ebc0c3ae9d129d9212deeb05f1993 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:38:21 -0400 Subject: [PATCH 086/400] Updated Styling for most/all forms Updated Styling for most/all forms? --- .../components/pages/assignmentFormPage.scss | 41 +++++++ .../components/pages/assignmentFormPage.tsx | 81 ++++++++----- .../components/pages/assignmentUpdatePage.tsx | 109 +++++++++++------- .../pages/containerAutoGraderForm.scss | 13 +++ .../pages/containerAutoGraderForm.tsx | 39 +++++-- .../components/pages/courseUpdatePage.scss | 27 +++++ .../src/components/pages/courseUpdatePage.tsx | 53 ++++++--- .../src/components/pages/coursesFormPage.scss | 30 ++++- .../src/components/pages/coursesFormPage.tsx | 58 ++++++---- devU-client/src/components/pages/homePage.tsx | 2 +- .../pages/nonContainerAutoGraderForm.scss | 11 ++ .../pages/nonContainerAutoGraderForm.tsx | 31 +++-- .../components/pages/userCoursesListPage.tsx | 2 +- 13 files changed, 359 insertions(+), 138 deletions(-) create mode 100644 devU-client/src/components/pages/assignmentFormPage.scss create mode 100644 devU-client/src/components/pages/containerAutoGraderForm.scss create mode 100644 devU-client/src/components/pages/courseUpdatePage.scss diff --git a/devU-client/src/components/pages/assignmentFormPage.scss b/devU-client/src/components/pages/assignmentFormPage.scss new file mode 100644 index 00000000..57f00d12 --- /dev/null +++ b/devU-client/src/components/pages/assignmentFormPage.scss @@ -0,0 +1,41 @@ +@import 'variables'; + +.form { + display: absolute; + background-color: #4e4e4e; + border-radius: 10px; + padding: 20px; + width: 50%; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + } + + .datepickerContainer { + display: flex; + justify-content: center; + } + + .datepickerContainer > * { + margin: 0 10px; + } + +.header { + color: $text-color; + display: flex; + align-items: center; +} + +.smallLine { + width: 50px; /* adjust this value to set the length of the small line */ + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ + margin-right: 10px; /* adjust this value to set the space between the line and the text */ +} + +.largeLine { + flex-grow: 1; + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ + margin-left: 10px; /* adjust this value to set the space between the line and the text */ + margin-right: 10px; /* add this line to create some space between the line and the button */ +} \ No newline at end of file diff --git a/devU-client/src/components/pages/assignmentFormPage.tsx b/devU-client/src/components/pages/assignmentFormPage.tsx index 745263e1..8ef8e5fe 100644 --- a/devU-client/src/components/pages/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/assignmentFormPage.tsx @@ -7,7 +7,8 @@ import PageWrapper from 'components/shared/layouts/pageWrapper' import RequestService from 'services/request.service' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -import Button from 'components/shared/inputs/button' +// import Button from 'components/shared/inputs/button' +import Button from '@mui/material/Button' import {SET_ALERT} from 'redux/types/active.types' @@ -15,6 +16,8 @@ import styles from '../shared/inputs/textField.scss' import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; import {useHistory, useParams} from 'react-router-dom' +import formStyles from './assignmentFormPage.scss' + const AssignmentCreatePage = () => { const [setAlert] = useActionless(SET_ALERT) const {courseId} = useParams<{ courseId: string }>() @@ -29,7 +32,6 @@ const AssignmentCreatePage = () => { maxSubmissions: null, disableHandins: false, }) - const [loading, setLoading] = useState(false) const [endDate, setEndDate] = useState(new Date()) const [dueDate, setDueDate] = useState(new Date()) const [startDate, setStartDate] = useState(new Date()) @@ -67,7 +69,6 @@ const AssignmentCreatePage = () => { disableHandins: formData.disableHandins, } - setLoading(true) RequestService.post('/api/assignments/', finalFormData) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Assignment Added' }) @@ -80,7 +81,6 @@ const AssignmentCreatePage = () => { setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => { - setLoading(false) history.goBack() }) @@ -89,32 +89,53 @@ const AssignmentCreatePage = () => { return(

Assignment Form

-

Required Field *

- - -
- -
- -
- -
- -
- - - - - - - -
- - +
+

Required Field *

+ + + + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + + + + + + + + +
+ + +
+ +
+ +
+ +
+
) diff --git a/devU-client/src/components/pages/assignmentUpdatePage.tsx b/devU-client/src/components/pages/assignmentUpdatePage.tsx index f6637033..119c6367 100644 --- a/devU-client/src/components/pages/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/assignmentUpdatePage.tsx @@ -10,7 +10,9 @@ import RequestService from 'services/request.service' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -import Button from 'components/shared/inputs/button' +// import Button from 'components/shared/inputs/button' +import Button from '@mui/material/Button' +import formStyles from './assignmentFormPage.scss' import {SET_ALERT} from 'redux/types/active.types' import styles from 'components/shared/inputs/textField.scss' @@ -36,7 +38,6 @@ const AssignmentUpdatePage = () => { const toggleProblemForm = () => { setToggleForm(!toggleForm) } const handleSubmit = () => { - setLoading(true) const finalProblemFormData = { assignmentId: parseInt(problemFormData.assignmentId), problemName: problemFormData.problemName, @@ -55,7 +56,6 @@ const AssignmentUpdatePage = () => { setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => { - setLoading(false) setProblemFormData({ assignmentId: assignmentId, problemName: '', @@ -86,7 +86,6 @@ const AssignmentUpdatePage = () => { const [startDate, setStartDate] = useState(new Date()) const [endDate, setEndDate] = useState(new Date()) const [dueDate, setDueDate] = useState(new Date()) - const [loading, setLoading] = useState(false) const [invalidFields, setInvalidFields] = useState(new Map()) const history = useHistory() @@ -118,7 +117,6 @@ const AssignmentUpdatePage = () => { disableHandins: formData.disableHandins, } - setLoading(true) RequestService.put(`/api/assignments/${assignmentId}`, finalFormData) .then(() => { @@ -133,59 +131,88 @@ const AssignmentUpdatePage = () => { setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => { - setLoading(false) history.goBack() }) } return ( -

Assignment Detail Update

-

Required Field *

+
+
+

Assignment Detail Update

+
+ +
- {toggleForm && ( -
-

- +

Required Field *

+ + + - Max Score * + - + +
+ +
)}



- - - -
- -
- -
- -
- -
- - - - - - - -
- - +
+

Required Field *

+ + + + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + + + + + + + + +
+ + +
+ +
+ +
+ +
+
) } diff --git a/devU-client/src/components/pages/containerAutoGraderForm.scss b/devU-client/src/components/pages/containerAutoGraderForm.scss new file mode 100644 index 00000000..5837c701 --- /dev/null +++ b/devU-client/src/components/pages/containerAutoGraderForm.scss @@ -0,0 +1,13 @@ +@import 'variables'; + +.form { + display: absolute; + background-color: #4e4e4e; + border-radius: 10px; + padding: 20px; + width: 50%; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + } \ No newline at end of file diff --git a/devU-client/src/components/pages/containerAutoGraderForm.tsx b/devU-client/src/components/pages/containerAutoGraderForm.tsx index 08f95f73..6e84d31f 100644 --- a/devU-client/src/components/pages/containerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/containerAutoGraderForm.tsx @@ -2,7 +2,7 @@ import React, {useState} from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' import styles from './nonContainerAutoGraderForm.scss' import TextField from 'components/shared/inputs/textField' -import Button from 'components/shared/inputs/button' +// import Button from 'components/shared/inputs/button' import {useActionless} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' import RequestService from 'services/request.service' @@ -11,6 +11,8 @@ import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textFi import textStyles from "../shared/inputs/textField.scss"; import {useHistory, useParams} from 'react-router-dom' +import Button from '@mui/material/Button' + const ContainerAutoGraderForm = () => { const [setAlert] = useActionless(SET_ALERT) const {assignmentId} = useParams<{ assignmentId: string }>() @@ -73,20 +75,35 @@ const ContainerAutoGraderForm = () => { return(

Container Auto Grader Form

-
-

Container Auto Grader

+

Required Field *

- Autograding Image * + - Timeout * + - -
- -
-



- + +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +

Existing Problems

diff --git a/devU-client/src/components/pages/courseUpdatePage.scss b/devU-client/src/components/pages/courseUpdatePage.scss new file mode 100644 index 00000000..29aa4f46 --- /dev/null +++ b/devU-client/src/components/pages/courseUpdatePage.scss @@ -0,0 +1,27 @@ +@import 'variables'; + +.form { + display: absolute; + background-color: #4e4e4e; + border-radius: 10px; + padding: 20px; + width: 50%; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + } + + .datepickerContainer { + display: flex; + justify-content: center; + } + + .datepickerContainer > * { + margin: 0 10px; + } + + .submitBtn { + display: flex; + justify-content: center; + } \ No newline at end of file diff --git a/devU-client/src/components/pages/courseUpdatePage.tsx b/devU-client/src/components/pages/courseUpdatePage.tsx index 02c458f9..3635b89f 100644 --- a/devU-client/src/components/pages/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/courseUpdatePage.tsx @@ -11,11 +11,14 @@ import {ExpressValidationError} from 'devu-shared-modules' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -import Button from 'components/shared/inputs/button' +// import Button from 'components/shared/inputs/button' +import Button from '@mui/material/Button' import {SET_ALERT} from 'redux/types/active.types' import styles from '../shared/inputs/textField.scss' import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; +import formStyles from './courseUpdatePage.scss' + type UrlParams = { courseId: string } @@ -31,7 +34,6 @@ const CourseUpdatePage = ({}) => { }) const [startDate, setStartDate] = useState(new Date()) const [endDate, setEndDate] = useState(new Date()) - const [loading, setLoading] = useState(false) const [invalidFields, setInvalidFields] = useState(new Map()) const { courseId } = useParams() as UrlParams @@ -54,7 +56,6 @@ const CourseUpdatePage = ({}) => { endDate : endDate.toISOString(), } - setLoading(true) RequestService.put(`/api/courses/${courseId}`, finalFormData) .then(() => { @@ -68,7 +69,6 @@ const CourseUpdatePage = ({}) => { setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => { - setLoading(false) history.goBack() }) } @@ -77,19 +77,38 @@ const CourseUpdatePage = ({}) => { return (

Course Detail Update

- - - - -
- -
- -
- -
- +
+

Required Fields *

+ + + + + + + + +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
) } diff --git a/devU-client/src/components/pages/coursesFormPage.scss b/devU-client/src/components/pages/coursesFormPage.scss index 7c2126af..b03e4036 100644 --- a/devU-client/src/components/pages/coursesFormPage.scss +++ b/devU-client/src/components/pages/coursesFormPage.scss @@ -1,7 +1,27 @@ @import 'variables'; -.header { - color: $text-color; - display: flex; - justify-content: space-between; - } +.form { + display: absolute; + background-color: #4e4e4e; + border-radius: 10px; + padding: 20px; + width: 50%; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; +} + +.datepickerContainer { + display: flex; + justify-content: center; +} + +.datepickerContainer > * { + margin: 0 10px; +} + +.submitBtn { + display: flex; + justify-content: center; +} diff --git a/devU-client/src/components/pages/coursesFormPage.tsx b/devU-client/src/components/pages/coursesFormPage.tsx index e21607ee..ec20d542 100644 --- a/devU-client/src/components/pages/coursesFormPage.tsx +++ b/devU-client/src/components/pages/coursesFormPage.tsx @@ -10,11 +10,14 @@ import RequestService from 'services/request.service' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -import Button from 'components/shared/inputs/button' +// import Button from 'components/shared/inputs/button' import {SET_ALERT} from 'redux/types/active.types' import styles from '../shared/inputs/textField.scss' +import formStyles from './coursesFormPage.scss' import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; +import Button from '@mui/material/Button' + const EditCourseFormPage = () => { const [setAlert] = useActionless(SET_ALERT) @@ -27,7 +30,6 @@ const EditCourseFormPage = () => { }) const [startDate, setStartDate] = useState(new Date()) const [endDate, setEndDate] = useState(new Date()) - const [loading, setLoading] = useState(false) const [invalidFields, setInvalidFields] = useState(new Map()) const handleChange = (value: String, e : React.ChangeEvent) => { @@ -50,8 +52,6 @@ const EditCourseFormPage = () => { endDate : endDate.toISOString(), } - setLoading(true) - RequestService.post('/api/courses/', finalFormData) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Course Added' }) @@ -67,30 +67,44 @@ const EditCourseFormPage = () => { }) .finally(() => { history.goBack() - setLoading(false) }) } return (

Course Form

-

Required Fields *

- - - - -
- -
- -
- -
- - +
+

Required Fields *

+ + + + + + + + +
+
+ +
+ +
+
+ +
+ +
+
+
+ +
+ +
+ +
) diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index 13d33fd8..575c229a 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -29,7 +29,7 @@ const HomePage = () => { const fetchData = async () => { try { - const enrolledCourses = await RequestService.get( `/api/courses/user/${userId}` ) + const enrolledCourses = await RequestService.get( `/api/user-courses/user/${userId}` ) const coursePromises = enrolledCourses.map(_course => { const course = RequestService.get(`/api/courses/${_course.id}`) // TODO: Optimize out this redundant call const assignments = RequestService.get(`/api/course/${_course.id}/assignments/released`) diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.scss b/devU-client/src/components/pages/nonContainerAutoGraderForm.scss index 878d9373..5837c701 100644 --- a/devU-client/src/components/pages/nonContainerAutoGraderForm.scss +++ b/devU-client/src/components/pages/nonContainerAutoGraderForm.scss @@ -1,2 +1,13 @@ @import 'variables'; +.form { + display: absolute; + background-color: #4e4e4e; + border-radius: 10px; + padding: 20px; + width: 50%; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; + } \ No newline at end of file diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx index 96805666..f5893abc 100644 --- a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx @@ -2,7 +2,7 @@ import React, {useState} from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' import styles from './nonContainerAutoGraderForm.scss' import TextField from 'components/shared/inputs/textField' -import Button from 'components/shared/inputs/button' +// import Button from 'components/shared/inputs/button' import {useActionless} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' import RequestService from 'services/request.service' @@ -11,6 +11,8 @@ import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textFi import {ExpressValidationError} from "../../../devu-shared-modules"; import {useHistory, useParams} from 'react-router-dom' +import Button from '@mui/material/Button' + const NonContainerAutoGraderForm = () => { const [setAlert] = useActionless(SET_ALERT) const [invalidFields, setInvalidFields] = useState(new Map()) @@ -85,19 +87,28 @@ const NonContainerAutoGraderForm = () => { return(

Non Container Auto Grader Form

-
-

Add a Non-Container Auto Grader

+

Required Fields *

- Question * + - Answer * + - Score * + - - -



- + +
+ + +
+
+ +
+ +

Existing Problems

diff --git a/devU-client/src/components/pages/userCoursesListPage.tsx b/devU-client/src/components/pages/userCoursesListPage.tsx index a15543a6..170f0df3 100644 --- a/devU-client/src/components/pages/userCoursesListPage.tsx +++ b/devU-client/src/components/pages/userCoursesListPage.tsx @@ -62,7 +62,7 @@ const HomePage = () => {
{role.isInstructor() && ( - + Add Courses )} From 14c3a4edc0c42dc7aac072e112e54dcdf6721c42 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Fri, 26 Apr 2024 03:02:41 -0400 Subject: [PATCH 087/400] Updated Assignment Detail Page Updated the assignment detail page. Changed the styling and updated the api calls to match the newest versions. However, the requests seem to give back a 404 error so there may be some error in the backend?... or maybe I missed something. --- .../pages/assignmentDetailPage.scss | 70 ++++-- .../components/pages/assignmentDetailPage.tsx | 201 +++++++++--------- 2 files changed, 153 insertions(+), 118 deletions(-) diff --git a/devU-client/src/components/pages/assignmentDetailPage.scss b/devU-client/src/components/pages/assignmentDetailPage.scss index 8cff76c0..07f0deaf 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.scss +++ b/devU-client/src/components/pages/assignmentDetailPage.scss @@ -1,20 +1,58 @@ @import 'variables'; -.button { - justify-content: center; - background-color: $primary; - color: $text-color; - border: 0px; - padding: 10px 40px; - border-radius: 50px; - font-size: 14px; - font-weight: 700; - text-decoration: none; - cursor: pointer; -} - .header { - color: $text-color; display: flex; - justify-content: space-between; -} \ No newline at end of file + align-items: center; +} + +.smallLine { + width: 50px; /* adjust this value to set the length of the small line */ + border-top: 3px solid white; /* adjust this value to set the color and thickness of the line */ + margin-right: 10px; /* adjust this value to set the space between the line and the text */ +} + +.largeLine { + flex-grow: 1; + border-top: 3px solid white; /* adjust this value to set the color and thickness of the line */ + margin-left: 10px; /* adjust this value to set the space between the line and the text */ + margin-right: 10px; /* add this line to create some space between the line and the button */ +} + +.buttons { + margin : 0 10px; +} + +.card { + width: 60%; + height: 50%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding-top: 20px; +} + +.accordion { + width: 90%; + display: flex; + flex-direction: column; + margin-top: 10px; + margin-left: 10px; + margin-right: 10px; +} + +.accordionDetails { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.textField { + align-items: center; + justify-content: center; +} + +.submissionCard { + margin-bottom : 15px; +} diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index dafc438a..73bc3e64 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -1,26 +1,31 @@ -import React, {useEffect, useState} from 'react' -import {Link, useParams} from 'react-router-dom' +import React,{ useState, useEffect } from 'react' +import {/*Link,*/ useHistory} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' -import { - Assignment, - AssignmentProblem, - ContainerAutoGrader, - Submission, - SubmissionProblemScore, - SubmissionScore -} from 'devu-shared-modules' +import { AssignmentProblem, Submission, /*SubmissionScore, SubmissionProblemScore,*/ Assignment, ContainerAutoGrader } from 'devu-shared-modules' import RequestService from 'services/request.service' import ErrorPage from './errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import TextField from 'components/shared/inputs/textField' -import Button from 'components/shared/inputs/button' -import {useActionless, useAppSelector} from 'redux/hooks' -import {SET_ALERT} from 'redux/types/active.types' +import { useAppSelector,useActionless } from 'redux/hooks' +import { SET_ALERT } from 'redux/types/active.types' +import { useParams } from 'react-router-dom' + +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' +import {CardActionArea} from '@mui/material' +import Stack from '@mui/material/Stack' +import Button from '@mui/material/Button' +import {Accordion} from '@mui/material' +import {AccordionSummary} from '@mui/material' +import {AccordionDetails} from '@mui/material' +import {Typography} from '@mui/material' +import {TextField} from '@mui/material' +import Grid from '@mui/material/Unstable_Grid2' import styles from './assignmentDetailPage.scss' const AssignmentDetailPage = () => { const [setAlert] = useActionless(SET_ALERT) + const history = useHistory() const { assignmentId, courseId } = useParams<{assignmentId: string, courseId: string}>() const userId = useAppSelector((store) => store.user.id) @@ -30,8 +35,8 @@ const AssignmentDetailPage = () => { const [file, setFile] = useState() const [assignmentProblems, setAssignmentProblems] = useState(new Array()) const [submissions, setSubmissions] = useState(new Array()) - const [submissionScores, setSubmissionScores] = useState(new Array()) - const [submissionProblemScores, setSubmissionProblemScores] = useState(new Array()) + // const [submissionScores, setSubmissionScores] = useState(new Array()) + // const [submissionProblemScores, setSubmissionProblemScores] = useState(new Array()) const [assignment, setAssignment] = useState() const [containerAutograder, setContainerAutograder] = useState() @@ -42,27 +47,27 @@ const AssignmentDetailPage = () => { const fetchData = async () => { try { - const assignment = await RequestService.get(`/api/assignments/${assignmentId}`) + const assignment = await RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`) setAssignment(assignment) - const assignmentProblemsReq = await RequestService.get(`/api/assignment-problems/${assignmentId}`) + const assignmentProblemsReq = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems/`) setAssignmentProblems(assignmentProblemsReq) - const submissionsReq = await RequestService.get(`/api/submissions/assignments/${assignmentId}`) + const submissionsReq = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submissions/`) submissionsReq.sort((a, b) => (Date.parse(b.createdAt ?? '') - Date.parse(a.createdAt ?? ''))) setSubmissions(submissionsReq) - const submissionScoresPromises = submissionsReq.map(s => { - return RequestService.get(`/api/submission-scores?submission=${s.id}`) - }) - const submissionScoresReq = (await Promise.all(submissionScoresPromises)).reduce((a, b) => a.concat(b), []) - setSubmissionScores(submissionScoresReq) + // const submissionScoresPromises = submissionsReq.map(s => { + // return RequestService.get(`/api/submission-scores?submission=${s.id}`) + // }) + // const submissionScoresReq = (await Promise.all(submissionScoresPromises)).reduce((a, b) => a.concat(b), []) + // setSubmissionScores(submissionScoresReq) - const submissionProblemScoresPromises = submissionsReq.map(s => { - return RequestService.get(`/api/submission-problem-scores/${s.id}`) - }) - const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) - setSubmissionProblemScores(submissionProblemScoresReq) + // const submissionProblemScoresPromises = submissionsReq.map(s => { + // return RequestService.get(`/api/submission-problem-scores/${s.id}`) + // }) + // const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) + // setSubmissionProblemScores(submissionProblemScoresReq) const containerAutograder = (await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`)).pop() ?? null setContainerAutograder(containerAutograder) @@ -77,9 +82,9 @@ const AssignmentDetailPage = () => { if (loading) return if (error) return - const handleChange = (value : string, e : React.ChangeEvent) => { + const handleChange = (e : React.ChangeEvent) => { const key = e.target.id - setFormData(prevState => ({...prevState,[key] : value})) + setFormData(prevState => ({...prevState,[key] : e.target.value})) } const handleFileChange = (e : React.ChangeEvent) => { setFile(e.target.files?.item(0)) @@ -130,83 +135,75 @@ const AssignmentDetailPage = () => { return( -

{assignment?.name}

- - Update Assignment -


- Add - Non-Container Auto-Graders -


- Add - Container Auto-Grader - - {/**Assignment Problems & Submission */} - {assignmentProblems.map(assignmentProblem => ( -
-

{assignmentProblem.problemName}

- -
- ))} +
+
+

{assignment?.name}

+
+ + + + {/* + */} + +
+ + + + + {assignmentProblems && assignmentProblems.length > 0 ? ( + assignmentProblems.map((assignmentProblem, index) => ( + + + {`Assignment Problem ${index + 1}`} + + + {assignmentProblem.problemName} + + + + )) + ) : ( + + No Problems Exist + + )} + {containerAutograder && ()} - -
+ + {assignmentProblems? ( + + ) : null} +
+
+ + +
+
+

{`Submissions`}

+
+
{/**Submissions List */} - {submissions.map((s, index) => ( - ss.submissionId === s.id)} - submissionProblemScores={submissionProblemScores.filter(sps => sps.submissionId === s.id)} - assignmentProblems={assignmentProblems} - courseId={courseId} - assignmentId={assignmentId} - /> +
+ {submissions.map((submission, index) => ( + + {history.push(`/courses/${courseId}/assignments/${assignmentId}/submissions/${submission.id}`)}}> + + {`Submission ${submissions.length - index}`} + {`Submitted at: ${submission.createdAt}`} + + + ))} +
) } -type SubmissionProps = { - index: number, - submission: Submission, - submissionScore?: SubmissionScore, - submissionProblemScores: SubmissionProblemScore[], - assignmentProblems: AssignmentProblem[], - courseId: string - assignmentId: string -} -const SubmissionComponent = ({ - index, - submission, - submissionScore, - submissionProblemScores, - assignmentProblems, - courseId, - assignmentId - }: SubmissionProps) => { - return ( -
-

Submission {index}:

- - {assignmentProblems.map(ap => ( - - ))} - - - {assignmentProblems.map(ap => ( - - ))} - - -
{ap.problemName} ({ap.maxScore})Total Score
{submissionProblemScores.find(sps => sps.assignmentProblemId === ap.id)?.score ?? "N/A"}{submissionScore?.score ?? "N/A"}
-
- Submission Details - Submission Feedback -


-
- ) -} export default AssignmentDetailPage From 436512e945030713408eecb5336a2c87c742a117 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Fri, 26 Apr 2024 11:43:15 -0400 Subject: [PATCH 088/400] more bug fixes.. --- devU-api/src/authorization/authorization.middleware.ts | 2 +- devU-api/src/entities/assignment/assignment.controller.ts | 6 +++--- devU-api/src/entities/assignment/assignment.router.ts | 6 +++--- .../entities/assignmentProblem/assignmentProblem.router.ts | 2 +- devU-api/src/entities/course/course.router.ts | 3 ++- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/devU-api/src/authorization/authorization.middleware.ts b/devU-api/src/authorization/authorization.middleware.ts index 87fd9ac0..567554a7 100644 --- a/devU-api/src/authorization/authorization.middleware.ts +++ b/devU-api/src/authorization/authorization.middleware.ts @@ -75,7 +75,7 @@ export function isAuthorized(permission: string, permissionIfSelf?: string) { * but not checked to be a valid assignment id */ export async function isAuthorizedByAssignmentStatus(req: Request, res: Response, next: NextFunction) { - const assignmentId = parseInt(req.params['assignmentId']) + const assignmentId = parseInt(req.params.assignmentId) const isAssignmentReleased = await AssignmentService.isReleased(assignmentId) const permissionString = isAssignmentReleased ? 'assignmentViewReleased' : 'assignmentViewAll' await isAuthorized(permissionString)(req, res, next) diff --git a/devU-api/src/entities/assignment/assignment.controller.ts b/devU-api/src/entities/assignment/assignment.controller.ts index 7c540b14..319e4c95 100644 --- a/devU-api/src/entities/assignment/assignment.controller.ts +++ b/devU-api/src/entities/assignment/assignment.controller.ts @@ -8,7 +8,7 @@ import { serialize } from './assignment.serializer' export async function detail(req: Request, res: Response, next: NextFunction) { try { - const id = parseInt(req.params.id) + const id = parseInt(req.params.assignmentId) const courseId = parseInt(req.params.courseId) const assignment = await AssignmentService.retrieve(id, courseId) @@ -60,7 +60,7 @@ export async function post(req: Request, res: Response, next: NextFunction) { export async function put(req: Request, res: Response, next: NextFunction) { try { - req.body.id = parseInt(req.params.id) + req.body.id = parseInt(req.params.assignmentId) const results = await AssignmentService.update(req.body) if (!results.affected) return res.status(404).json(NotFound) @@ -73,7 +73,7 @@ export async function put(req: Request, res: Response, next: NextFunction) { export async function _delete(req: Request, res: Response, next: NextFunction) { try { - const id = parseInt(req.params.id) + const id = parseInt(req.params.assignmentId) const results = await AssignmentService._delete(id) if (!results.affected) return res.status(404).json(NotFound) diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index 437a2462..9150266d 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -75,7 +75,7 @@ Router.get('/', isAuthorized('assignmentViewAll'), AssignmentsController.getByCo * schema: * type: integer */ -Router.get('/:id', asInt(), isAuthorizedByAssignmentStatus, AssignmentsController.detail) +Router.get('/:assignmentId', asInt("assignmentId"), isAuthorizedByAssignmentStatus, AssignmentsController.detail) /** * @swagger @@ -130,7 +130,7 @@ Router.post('/', isAuthorized('assignmentEditAll'), validator, AssignmentsContro * schema: * $ref: '#/components/schemas/Assignment' */ -Router.put('/:id', isAuthorized('assignmentEditAll'), asInt(), validator, AssignmentsController.put) +Router.put('/:assignmentId', isAuthorized('assignmentEditAll'), asInt("assignmentId"), validator, AssignmentsController.put) /** * @swagger @@ -155,6 +155,6 @@ Router.put('/:id', isAuthorized('assignmentEditAll'), asInt(), validator, Assign * schema: * type: integer */ -Router.delete('/:id', isAuthorized('assignmentEditAll'), asInt(), AssignmentsController._delete) +Router.delete('/:assignmentId', isAuthorized('assignmentEditAll'), asInt("assignmentId"), AssignmentsController._delete) export default Router diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts index ca51b142..13b9bca4 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.router.ts @@ -29,7 +29,7 @@ const Router = express.Router({ mergeParams: true }) * schema: * type: integer */ -Router.get('/', isAuthorized('enrolled'), asInt(), AssignmentProblemController.get) +Router.get('/', isAuthorized('enrolled'), AssignmentProblemController.get) // Router.get('/', isAuthorizedByAssignmentStatus, asInt(), AssignmentProblemController.get) // TODO: assignment released status diff --git a/devU-api/src/entities/course/course.router.ts b/devU-api/src/entities/course/course.router.ts index 7e4198be..4a167d39 100644 --- a/devU-api/src/entities/course/course.router.ts +++ b/devU-api/src/entities/course/course.router.ts @@ -57,7 +57,8 @@ Router.get('/user/:userId', asInt('userId'), CourseController.getByUser) * schema: * type: integer */ -Router.get('/:courseId', isAuthorized('enrolled'), asInt('courseId'), CourseController.detail) +Router.get('/:courseId', /*isAuthorized('enrolled'),*/ asInt('courseId'), CourseController.detail) +// TODO: authorization. Can only view if enrolled /** * @swagger From ce0d3ada3cb081f7e4b3261ea479f575128e6d0f Mon Sep 17 00:00:00 2001 From: SantarinX Date: Fri, 26 Apr 2024 11:48:04 -0400 Subject: [PATCH 089/400] chaning homepage to the new path --- devU-client/src/components/pages/assignmentDetailPage.tsx | 2 +- devU-client/src/components/pages/coursePreviewPage.tsx | 6 +++--- devU-client/src/components/pages/homePage.tsx | 2 +- devU-client/src/components/pages/userCoursesListPage.tsx | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index dafc438a..5c352e35 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -42,7 +42,7 @@ const AssignmentDetailPage = () => { const fetchData = async () => { try { - const assignment = await RequestService.get(`/api/assignments/${assignmentId}`) + const assignment = await RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`) setAssignment(assignment) const assignmentProblemsReq = await RequestService.get(`/api/assignment-problems/${assignmentId}`) diff --git a/devU-client/src/components/pages/coursePreviewPage.tsx b/devU-client/src/components/pages/coursePreviewPage.tsx index 2824217b..9ffd531a 100644 --- a/devU-client/src/components/pages/coursePreviewPage.tsx +++ b/devU-client/src/components/pages/coursePreviewPage.tsx @@ -26,7 +26,7 @@ const CoursePreviewPage = () => { const handleCheckEnroll = async () => { - RequestService.get(`/api/user-courses/user/courses/${courseId}`) + RequestService.get(`/api/course/${courseId}/user-courses/user`) .then((response) => { setUserCourses(response); }) @@ -65,11 +65,11 @@ const CoursePreviewPage = () => { const userCourseData = { userId: userId, courseId: courseId, - level: 'student', + role: 'student', dropped: false }; - RequestService.post(`/api/courses/${courseId}/user-courses`, userCourseData) + RequestService.post(`/api/course/${courseId}/user-courses`, userCourseData) .catch((error: Error) => { const message = error.message setAlert({autoDelete: false, type: 'error', message}) diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index 575c229a..13d33fd8 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -29,7 +29,7 @@ const HomePage = () => { const fetchData = async () => { try { - const enrolledCourses = await RequestService.get( `/api/user-courses/user/${userId}` ) + const enrolledCourses = await RequestService.get( `/api/courses/user/${userId}` ) const coursePromises = enrolledCourses.map(_course => { const course = RequestService.get(`/api/courses/${_course.id}`) // TODO: Optimize out this redundant call const assignments = RequestService.get(`/api/course/${_course.id}/assignments/released`) diff --git a/devU-client/src/components/pages/userCoursesListPage.tsx b/devU-client/src/components/pages/userCoursesListPage.tsx index 170f0df3..a6bf4b4d 100644 --- a/devU-client/src/components/pages/userCoursesListPage.tsx +++ b/devU-client/src/components/pages/userCoursesListPage.tsx @@ -12,7 +12,7 @@ import RequestService from 'services/request.service' import {Assignment, Course, UserCourse} from 'devu-shared-modules' -const HomePage = () => { +const UserCoursesListPage = () => { const userId = useAppSelector((store) => store.user.id) const role = useAppSelector((store) => store.roleMode) @@ -28,7 +28,7 @@ const HomePage = () => { const fetchData = async () => { try { - const userCourses = await RequestService.get(`/api/user-courses/user/${userId}`) + const userCourses = await RequestService.get(`/api/courses/user/${userId}`) const coursePromises = userCourses.map(uc => { const course = RequestService.get(`/api/courses/${uc.courseId}`) const assignments = RequestService.get(`/api/assignments/course/${uc.courseId}`) @@ -86,4 +86,4 @@ const HomePage = () => { ) } -export default HomePage +export default UserCoursesListPage From ed5cc9cff6d611b6a6d0213fdfa11361945acb46 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Fri, 26 Apr 2024 11:58:02 -0400 Subject: [PATCH 090/400] remove authorization from submissions end point (Filtering by logged in user for now) --- .../submission/submission.controller.ts | 17 +++++++++++++- .../entities/submission/submission.router.ts | 23 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/devU-api/src/entities/submission/submission.controller.ts b/devU-api/src/entities/submission/submission.controller.ts index 49e14897..a12a3e1d 100644 --- a/devU-api/src/entities/submission/submission.controller.ts +++ b/devU-api/src/entities/submission/submission.controller.ts @@ -21,6 +21,21 @@ export async function get(req: Request, res: Response, next: NextFunction) { next(err) } } +export async function listByUser(req: Request, res: Response, next: NextFunction) { + try { + const userId = req.currentUser?.userId + const query = req.query + + if (!userId) return res.status(400).json(new GenericResponse('Request requires auth')) + + const submissions = await SubmissionService.list(query, userId) + const response = submissions.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} export async function detail(req: Request, res: Response, next: NextFunction) { try { @@ -97,4 +112,4 @@ export async function getByAssignment(req: Request, res: Response, next: NextFun } } -export default { get, detail, post, revoke, getByAssignment, unrevoke, _delete } +export default { get, listByUser, detail, post, revoke, getByAssignment, unrevoke, _delete } diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index 85798c19..18ff18f8 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -37,7 +37,9 @@ const upload = Multer() * type: integer * */ -Router.get('/', isAuthorized('submissionViewAll'), SubmissionController.get) +Router.get('/', /*isAuthorized('submissionViewAll'),*/ SubmissionController.get) +// TODO: Authorization + Router.get('/assignments/:assignmentId', asInt('assignmentId'), SubmissionController.getByAssignment) @@ -59,6 +61,25 @@ Router.get('/assignments/:assignmentId', asInt('assignmentId'), SubmissionContro * type: integer */ Router.get('/:id', isAuthorized('enrolled'), asInt(), SubmissionController.detail) + +/** + * @swagger + * /course/:courseId/assignment/:assignmentId/submissions/user/{userId}: + * get: + * summary: Retrieve submission by user + * tags: + * - Submissions + * responses: + * '200': + * description: OK + * parameters: + * - name: userId + * in: path + * required: true + * schema: + * type: integer + */ +Router.get('/user/:userId', isAuthorized('enrolled'), asInt("userId"), SubmissionController.listByUser) // TODO: submissionViewAll or enrolled/self /** From 92a53cd92800e68e605d34bbadbde77d219b3c62 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Fri, 26 Apr 2024 12:48:49 -0400 Subject: [PATCH 091/400] Container autograding functional --- .../containerAutoGrader.service.ts | 2 +- .../src/entities/grader/grader.service.ts | 147 ++++++++++-------- 2 files changed, 81 insertions(+), 68 deletions(-) diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index 716868ad..1a2eba31 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -93,7 +93,7 @@ export async function getGraderObjectByAssignmentId(assignmentId: number) { export async function getGraderByAssignmentId(assignmentId: number){ const containerAutoGraders = await connect().findOne({ assignmentId: assignmentId, deletedAt: IsNull() }) - if (!containerAutoGraders) throw new Error('No containerAutoGraders found') + if (!containerAutoGraders) return {graderData: null, makefileData: null, autogradingImage: null, timeout: null} const { graderFile, makefileFile, autogradingImage, timeout } = containerAutoGraders const graderData = await downloadFile('graders', graderFile) diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index 4266125f..b5320f3e 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -8,18 +8,20 @@ import assignmentScoreService from '../assignmentScore/assignmentScore.service' import courseService from '../course/course.service' import { addJob, createCourse, uploadFile, pollJob } from '../../tango/tango.service' -import { SubmissionScore, SubmissionProblemScore, AssignmentScore } from 'devu-shared-modules' +import { SubmissionScore, SubmissionProblemScore, AssignmentScore, Submission } from 'devu-shared-modules' import { checkAnswer } from '../nonContainerAutoGrader/nonContainerAutoGrader.grader' import { serialize as serializeNonContainer } from '../nonContainerAutoGrader/nonContainerAutoGrader.serializer' import { serialize as serializeAssignmentScore } from '../assignmentScore/assignmentScore.serializer' +import { serialize as serializeSubmissionScore} from '../submissionScore/submissionScore.serializer' +import { serialize as serializeSubmission } from '../submission/submission.serializer' import { downloadFile, initializeMinio } from '../../fileStorage' -import crypto from 'crypto' import environment from '../../environment' export async function grade(submissionId: number) { - const submission = await submissionService.retrieve(submissionId) - if (!submission) return null + const submissionModel = await submissionService.retrieve(submissionId) + if (!submissionModel) throw new Error('Submission not found.') + const submission = serializeSubmission(submissionModel) const assignmentId = submission.assignmentId @@ -35,6 +37,7 @@ export async function grade(submissionId: number) { let score = 0 let feedback = '' let allScores = [] //This is the return value, the serializer parses it into a GraderInfo object for the controller to return + let containerGrading = true //Is set to false if no container autograders are found for this assignment //Run Non-Container Autograders for (const question in form) { @@ -57,48 +60,48 @@ export async function grade(submissionId: number) { } //Run Container Autograders - try { - const {graderData, makefileData, autogradingImage, timeout} = await containerAutograderService.getGraderByAssignmentId(assignmentId) - const bucketName = await courseService.retrieve(submission.courseId).then((course) => { - return course ? (course.number + course.semester + course.id).toLowerCase() : 'submission' - }) - initializeMinio(bucketName) - - var response = null - const labName = `${bucketName}-${submission.assignmentId}` - const optionFiles = [] - const openResponse = await createCourse(labName) - if (openResponse) { - if (!(openResponse.files["Graderfile"]) || openResponse.files["Graderfile"] !== crypto.createHash('md5').update(graderData).digest('hex')) { + const {graderData, makefileData, autogradingImage, timeout} = await containerAutograderService.getGraderByAssignmentId(assignmentId) + if (!graderData || !makefileData || !autogradingImage || !timeout) { + containerGrading = false + } else { + try { + const bucketName = await courseService.retrieve(submission.courseId).then((course) => { + return course ? (course.number + course.semester + course.id).toLowerCase() : 'submission' + }) + initializeMinio(bucketName) + + var response = null + const labName = `${bucketName}-${submission.assignmentId}` + const optionFiles = [] + const openResponse = await createCourse(labName) + if (openResponse) { await uploadFile(labName, graderData, "Graderfile") - } - if (!(openResponse.files["Makefile"]) || openResponse.files["Makefile"] !== crypto.createHash('md5').update(makefileData).digest('hex')) { await uploadFile(labName, makefileData, "Makefile") - } - for (const filepath of filepaths){ - const buffer = await downloadFile(bucketName, filepath) - if (await uploadFile(labName, buffer, filepath)) { - optionFiles.push({localFile: filepath, destFile: filepath}) + + for (const filepath of filepaths){ + const buffer = await downloadFile(bucketName, filepath) + if (await uploadFile(labName, buffer, filepath)) { + optionFiles.push({localFile: filepath, destFile: filepath}) + } } + console.log(environment.apiUrl) + console.log(labName) + const jobOptions = { + image: autogradingImage, + files: [{localFile: "Graderfile", destFile: "autograde.tar"}, + {localFile: "Makefile", destFile: "Makefile"},] + .concat(optionFiles), + jobName: `${labName}-${submissionId}`, + output_file: `${labName}-${submissionId}-output.txt`, + timeout: timeout, + callback_url: `http://api:3001/grade/callback/${labName}-${submissionId}-output.txt` + } + response = await addJob(labName, jobOptions) } - console.log(environment.apiUrl) - console.log(labName) - const jobOptions = { - image: autogradingImage, - files: [{localFile: "Graderfile", destFile: "autograde.tar"}, - {localFile: "Makefile", destFile: "Makefile"},] - .concat(optionFiles), - jobName: `${labName}-${submissionId}`, - output_file: `${labName}-${submissionId}-output.txt`, - timeout: timeout, - callback_url: `http://api:3001/grade/callback/${labName}-${submissionId}-output.txt` - } - response = await addJob(labName, jobOptions) + } catch (e: any) { + throw new Error(e) } - } catch (e) { - console.error(e) } - //remember, immediate callback is made when job has been added to queue, not sure how we're handling the rest of it yet though lmao //Grading is finished. Create SubmissionScore and AssignmentScore and save to db. const scoreObj: SubmissionScore = { @@ -108,28 +111,14 @@ export async function grade(submissionId: number) { } allScores.push(await submissionScoreService.create(scoreObj)) - //PLACEHOLDER AssignmentScore logic. This should be customizable, but for now AssignmentScore will simply equal the latest SubmissionScore - const assignmentScoreModel = await assignmentScoreService.retrieveByUser(submission.assignmentId, submission.userId) - if (assignmentScoreModel) { //If assignmentScore already exists, update existing entity - const assignmentScore = serializeAssignmentScore(assignmentScoreModel) - assignmentScore.score = score - assignmentScoreService.update(assignmentScore) - - } else { //Otherwise make a new one - const assignmentScore: AssignmentScore = { - assignmentId: submission.assignmentId, - userId: submission.userId, - score: score, - } - await assignmentScoreService.create(assignmentScore) - } + //If containergrading is true, tangoCallback handles assignmentScore creation + if (containerGrading === false) updateAssignmentScore(submission, score) return response } export async function tangoCallback(outputFile: string) { - console.log('goot!') //Output filename consists of 4 sections separated by hyphens. + and () only for visual clarity, not a part of the filename //(course.number+course.semester+course.id)-(assignment.id)-(submission.id)-(output.txt) const filenameSplit = outputFile.split('-') @@ -138,15 +127,18 @@ export async function tangoCallback(outputFile: string) { const submissionId = Number(filenameSplit[2]) const response = await pollJob(labName, outputFile) - if (typeof response !== 'string') { - throw new Error('Autograder output file not found') - } + if (typeof response !== 'string') throw new Error('Autograder output file not found') + const splitResponse = response.split(/\r\n|\r|\n/) const scores = (JSON.parse(splitResponse[splitResponse.length - 2])).scores let score = 0 const assignmentProblems = await assignmentProblemService.list(assignmentId) - const submissionScore = await submissionScoreService.retrieve(submissionId) + const submissionScoreModel = await submissionScoreService.retrieve(submissionId) + const submissionModel = await submissionService.retrieve(submissionId) + if (!submissionModel) throw new Error("Submission not found.") + const submission = serializeSubmission(submissionModel) + for (const question in scores) { const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) if (assignmentProblem) { @@ -160,21 +152,42 @@ export async function tangoCallback(outputFile: string) { score += Number(scores[question]) } } - if (submissionScore) { - submissionScore.score += score + if (submissionScoreModel) { //If noncontainer grading has occured + var submissionScore = serializeSubmissionScore(submissionScoreModel) + submissionScore.score = (submissionScore.score ?? 0) + score + score = submissionScore.score submissionScore.feedback += `\n${response}` - submissionScoreService.update(submissionScore) - } else { - const scoreObj: SubmissionScore = { + await submissionScoreService.update(submissionScore) + } else { //If submission is exclusively container graded + var submissionScore: SubmissionScore = { submissionId: submissionId, score: score, //Sum of all SubmissionProblemScore scores feedback: response //Feedback from Tango } - submissionScoreService.create(scoreObj) + await submissionScoreService.create(submissionScore) } + await updateAssignmentScore(submission, score) + + return (submissionScore) +} + +//Currently just sets assignmentscore to the latest submission. Pulled this function out for easy future modification. +async function updateAssignmentScore(submission: Submission, score: number) { + const assignmentScoreModel = await assignmentScoreService.retrieveByUser(submission.assignmentId, submission.userId) + if (assignmentScoreModel) { //If assignmentScore already exists, update existing entity + const assignmentScore = serializeAssignmentScore(assignmentScoreModel) + assignmentScore.score = score + await assignmentScoreService.update(assignmentScore) - return {output: response} + } else { //Otherwise create a new one + const assignmentScore: AssignmentScore = { + assignmentId: submission.assignmentId, + userId: submission.userId, + score: score, + } + await assignmentScoreService.create(assignmentScore) + } } export default { grade, tangoCallback } \ No newline at end of file From 0d73a79be2aac769745f6b4e765f1bee803ec322 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Fri, 26 Apr 2024 13:49:58 -0400 Subject: [PATCH 092/400] update frontend paths and making changes to role button display options --- .../userCourse/userCourse.controller.ts | 8 +-- .../entities/userCourse/userCourse.router.ts | 12 ++--- .../src/components/authenticatedRouter.tsx | 45 +++++++++-------- .../components/listItems/courseListItem.tsx | 2 +- .../listItems/simpleAssignmentListItem.tsx | 2 +- .../listItems/userCourseListItem.tsx | 2 +- .../src/components/misc/globalToolbar.tsx | 6 +-- .../components/pages/assignmentDetailPage.tsx | 49 +++++++++---------- .../src/components/pages/courseDetailPage.tsx | 31 +++++++----- .../components/pages/coursePreviewPage.tsx | 4 +- .../src/components/pages/coursesListPage.tsx | 16 +++--- devU-client/src/components/pages/homePage.tsx | 9 ---- .../components/utils/userOptionsDropdown.tsx | 6 +-- 13 files changed, 94 insertions(+), 98 deletions(-) diff --git a/devU-api/src/entities/userCourse/userCourse.controller.ts b/devU-api/src/entities/userCourse/userCourse.controller.ts index d28bf8ce..09c7729b 100644 --- a/devU-api/src/entities/userCourse/userCourse.controller.ts +++ b/devU-api/src/entities/userCourse/userCourse.controller.ts @@ -1,9 +1,9 @@ -import { NextFunction, Request, Response } from 'express' +import {NextFunction, Request, Response} from 'express' import UserCourseService from './userCourse.service' -import { serialize } from './userCourse.serializer' +import {serialize} from './userCourse.serializer' -import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' +import {GenericResponse, NotFound, Updated} from '../../utils/apiResponse.utils' export async function getAll(req: Request, res: Response, next: NextFunction) { try { @@ -112,7 +112,7 @@ export async function checkEnroll(req: Request, res: Response, next: NextFunctio export async function _delete(req: Request, res: Response, next: NextFunction) { try { - const id = parseInt(req.params.id) + const id = parseInt(req.params.courseId) const currentUser = req.currentUser?.userId if (!currentUser) return res.status(401).json({ message: 'Unauthorized' }) diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index d7f468fa..96951dda 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -3,14 +3,14 @@ import express from 'express' // Middleware import validator from './userCourse.validator' -import { asInt } from '../../middleware/validator/generic.validator' -import { extractOwnerByPathParam, isAuthorized } from '../../authorization/authorization.middleware' +import {asInt} from '../../middleware/validator/generic.validator' +import {extractOwnerByPathParam, isAuthorized} from '../../authorization/authorization.middleware' // Controller import UserCourseController from './userCourse.controller' const Router = express.Router({ mergeParams: true }) - +Router.get('/users', UserCourseController.checkEnroll) /** * @swagger * /course/:courseId/user-courses/: @@ -70,6 +70,8 @@ Router.get('/:id', isAuthorized('courseViewAll'), asInt(), UserCourseController. * schema: * type: integer */ + + Router.get( '/user/:userId', extractOwnerByPathParam('userId'), @@ -78,8 +80,6 @@ Router.get( UserCourseController.detailByUser ) -Router.get('/user/courses/:courseId', asInt('courseId'), UserCourseController.checkEnroll) - /** * @swagger * /course/:courseId/user-courses: @@ -140,6 +140,6 @@ Router.put('/:id', isAuthorized('userCourseEditAll'), asInt(), validator, UserCo * schema: * type: integer */ -Router.delete('/:id', asInt(), UserCourseController._delete) +Router.delete('/', UserCourseController._delete) // TODO: eventually add authorization to this. For now, everyone can remove anyone export default Router diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index f12c9fe3..6c3f2bfb 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -12,7 +12,6 @@ import HomePage from 'components/pages/homePage' import NotFoundPage from 'components/pages/notFoundPage' import SubmissionDetailPage from 'components/pages/submissionDetailPage' import UserDetailPage from 'components/pages/userDetailPage' -import UserSubmissionsListPage from 'components/pages/userSubmissionsListPage' import NonContainerAutoGraderForm from './pages/nonContainerAutoGraderForm' import GradebookStudentPage from './pages/gradebookStudentPage' import GradebookInstructorPage from './pages/gradebookInstructorPage' @@ -20,35 +19,35 @@ import SubmissionFeedbackPage from './pages/submissionFeedbackPage' import ContainerAutoGraderForm from './pages/containerAutoGraderForm' import CoursePreviewPage from './pages/coursePreviewPage' import CoursesListPage from "./pages/coursesListPage"; -import userCoursesListPage from "./pages/userCoursesListPage"; const AuthenticatedRouter = () => ( + - - - {/* Just reuse the homepage here, for now this is fine. we might want to change this in the future though which is why they exist as separate routes */} - {/**/} - + - + + + + + + + + + + + + + + + - - - - - {/**/} - - - - - - - - - + ) diff --git a/devU-client/src/components/listItems/courseListItem.tsx b/devU-client/src/components/listItems/courseListItem.tsx index 26054180..b6f0af1f 100644 --- a/devU-client/src/components/listItems/courseListItem.tsx +++ b/devU-client/src/components/listItems/courseListItem.tsx @@ -38,7 +38,7 @@ const CourseListItem = ({course, isOpen}: Props) => {  {course.name}
{isOpened && - + {infoSection("Course Number", course.number)} {infoSection("Semester", prettyPrintSemester(course.semester))} {infoSection("Start/End Date", prettyPrintDate(course.startDate), prettyPrintDate(course.endDate))} diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.tsx b/devU-client/src/components/listItems/simpleAssignmentListItem.tsx index 51c981bc..dbdb8527 100644 --- a/devU-client/src/components/listItems/simpleAssignmentListItem.tsx +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.tsx @@ -10,7 +10,7 @@ type Props = { } const SimpleAssignmentListItem = ({assignment}: Props) => ( -
{assignment.name}
{assignment.categoryName}
diff --git a/devU-client/src/components/listItems/userCourseListItem.tsx b/devU-client/src/components/listItems/userCourseListItem.tsx index 71eb5f88..f375a47f 100644 --- a/devU-client/src/components/listItems/userCourseListItem.tsx +++ b/devU-client/src/components/listItems/userCourseListItem.tsx @@ -17,7 +17,7 @@ type Props = { } const UserCourseListItem = ({course, assignments}: Props) => ( - +
{course.name}
diff --git a/devU-client/src/components/misc/globalToolbar.tsx b/devU-client/src/components/misc/globalToolbar.tsx index 59b57483..61781b69 100644 --- a/devU-client/src/components/misc/globalToolbar.tsx +++ b/devU-client/src/components/misc/globalToolbar.tsx @@ -29,9 +29,9 @@ const GlobalToolbar = () => { Courses - - My Courses - + {/**/} + {/* My Courses*/} + {/**/} {/* Submissions */} diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index 73bc3e64..9e536791 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -1,24 +1,18 @@ -import React,{ useState, useEffect } from 'react' -import {/*Link,*/ useHistory} from 'react-router-dom' +import React, {useEffect, useState} from 'react' +import {useHistory, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' -import { AssignmentProblem, Submission, /*SubmissionScore, SubmissionProblemScore,*/ Assignment, ContainerAutoGrader } from 'devu-shared-modules' +import {Assignment, AssignmentProblem, Submission,} from 'devu-shared-modules' import RequestService from 'services/request.service' import ErrorPage from './errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import { useAppSelector,useActionless } from 'redux/hooks' -import { SET_ALERT } from 'redux/types/active.types' -import { useParams } from 'react-router-dom' +import {useActionless, useAppSelector} from 'redux/hooks' +import {SET_ALERT} from 'redux/types/active.types' import Card from '@mui/material/Card' import CardContent from '@mui/material/CardContent' -import {CardActionArea} from '@mui/material' +import {Accordion, AccordionDetails, AccordionSummary, CardActionArea, TextField, Typography} from '@mui/material' import Stack from '@mui/material/Stack' import Button from '@mui/material/Button' -import {Accordion} from '@mui/material' -import {AccordionSummary} from '@mui/material' -import {AccordionDetails} from '@mui/material' -import {Typography} from '@mui/material' -import {TextField} from '@mui/material' import Grid from '@mui/material/Unstable_Grid2' import styles from './assignmentDetailPage.scss' @@ -28,6 +22,7 @@ const AssignmentDetailPage = () => { const history = useHistory() const { assignmentId, courseId } = useParams<{assignmentId: string, courseId: string}>() const userId = useAppSelector((store) => store.user.id) + const role = useAppSelector((store) => store.roleMode) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) @@ -38,8 +33,9 @@ const AssignmentDetailPage = () => { // const [submissionScores, setSubmissionScores] = useState(new Array()) // const [submissionProblemScores, setSubmissionProblemScores] = useState(new Array()) const [assignment, setAssignment] = useState() - const [containerAutograder, setContainerAutograder] = useState() + // const [containerAutograder, setContainerAutograder] = useState() + const containerAutograder = false; useEffect(() => { fetchData() }, []); @@ -47,8 +43,8 @@ const AssignmentDetailPage = () => { const fetchData = async () => { try { - const assignment = await RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`) - setAssignment(assignment) + const assignments = await RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`) + setAssignment(assignments) const assignmentProblemsReq = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems/`) setAssignmentProblems(assignmentProblemsReq) @@ -69,8 +65,8 @@ const AssignmentDetailPage = () => { // const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) // setSubmissionProblemScores(submissionProblemScoresReq) - const containerAutograder = (await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`)).pop() ?? null - setContainerAutograder(containerAutograder) + // const containerAutograder = (await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`)).pop() ?? null + // setContainerAutograder(containerAutograder) } catch (error) { setError(error) @@ -139,17 +135,16 @@ const AssignmentDetailPage = () => {

{assignment?.name}

- + {role.isInstructor() && } + {role.isInstructor() && } - - {/* - */} + {role.isInstructor() && }
diff --git a/devU-client/src/components/pages/courseDetailPage.tsx b/devU-client/src/components/pages/courseDetailPage.tsx index 7227128b..d788531f 100644 --- a/devU-client/src/components/pages/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courseDetailPage.tsx @@ -18,7 +18,7 @@ import Stack from '@mui/material/Stack' import styles from './courseDetailPage.scss' import {SET_ALERT} from "../../redux/types/active.types"; -import {useActionless} from "../../redux/hooks"; +import {useActionless, useAppSelector} from "redux/hooks"; const CourseDetailPage = () => { const history = useHistory() @@ -27,12 +27,13 @@ const CourseDetailPage = () => { const [courseInfo, setCourseInfo] = useState(null) const [categoryMap, setCategoryMap] = useState>({}) const [setAlert] = useActionless(SET_ALERT) + const role = useAppSelector((store) => store.roleMode) const fetchCourseInfo = async () => { RequestService.get(`/api/courses/${courseId}`) .then((course) => { setCourseInfo(course) }) - RequestService.get(`/api/assignments/course/${courseId}`) + RequestService.get(`/api/course/${courseId}/assignments/released`) .then((assignments) => { console.log(assignments) let categoryMap : Record = {} @@ -52,7 +53,7 @@ const CourseDetailPage = () => { const handleDropCourse = () => { - RequestService.delete(`/api/user-courses/${courseId}`).then(() => { + RequestService.delete(`/api/course/${courseId}/user-courses`).then(() => { setAlert({autoDelete: true, type: 'success', message: 'Course Dropped'}) history.push('/courses') @@ -77,14 +78,20 @@ const CourseDetailPage = () => { - - + + {role.isInstructor() && + } + + {role.isInstructor() && + } + + @@ -102,7 +109,9 @@ const CourseDetailPage = () => { {categoryMap[category].map((assignment, index) => ( - {history.push(`/courses/${courseId}/assignments/${assignment.id}`)}}> + { + history.push(`/course/${courseId}/assignment/${assignment.id}`) + }}> diff --git a/devU-client/src/components/pages/coursePreviewPage.tsx b/devU-client/src/components/pages/coursePreviewPage.tsx index 9ffd531a..bef2cd3e 100644 --- a/devU-client/src/components/pages/coursePreviewPage.tsx +++ b/devU-client/src/components/pages/coursePreviewPage.tsx @@ -26,7 +26,7 @@ const CoursePreviewPage = () => { const handleCheckEnroll = async () => { - RequestService.get(`/api/course/${courseId}/user-courses/user`) + RequestService.get(`/api/course/${courseId}/user-courses/users`) .then((response) => { setUserCourses(response); }) @@ -58,7 +58,7 @@ const CoursePreviewPage = () => { if (loading) return if (enrolled) { - history.push(`/courses/${courseId}`) + history.push(`/course/${courseId}`) } const handleJoinCourse = () => { diff --git a/devU-client/src/components/pages/coursesListPage.tsx b/devU-client/src/components/pages/coursesListPage.tsx index 9caf8637..d67de2b5 100644 --- a/devU-client/src/components/pages/coursesListPage.tsx +++ b/devU-client/src/components/pages/coursesListPage.tsx @@ -1,5 +1,4 @@ import React, {useEffect, useState} from 'react' -import {Link} from 'react-router-dom' import {Course, UserCourse} from 'devu-shared-modules' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -8,7 +7,9 @@ import ErrorPage from './errorPage' import RequestService from 'services/request.service' import styles from './coursesListPage.scss' import CourseListItem from "../listItems/courseListItem"; -//import Button from 'components/shared/inputs/button' +import {useAppSelector} from "../../redux/hooks"; +import Button from "@mui/material/Button"; +import {useHistory} from "react-router-dom"; type Filter = true | false @@ -24,7 +25,8 @@ const UserCoursesListPage = () => { const [error, setError] = useState(null) const [userCourses, setUserCourses] = useState(new Array()) const [filter, setFilter] = useState(false ) - + const role = useAppSelector((store) => store.roleMode) + const history = useHistory() //Temporary place to store state for all courses const [allCourses, setAllCourses] = useState(new Array()) @@ -72,10 +74,10 @@ const UserCoursesListPage = () => {

All Courses

- - Add Courses - - + {role.isInstructor() && + }
{ const userId = useAppSelector((store) => store.user.id) - const role = useAppSelector((store) => store.roleMode) - const history = useHistory() const [loading, setLoading] = useState(true) const [error, setError] = useState(null) @@ -59,10 +54,6 @@ const HomePage = () => {

My Courses

- {role.isInstructor() && - }
diff --git a/devU-client/src/components/utils/userOptionsDropdown.tsx b/devU-client/src/components/utils/userOptionsDropdown.tsx index 723dffba..48eb6161 100644 --- a/devU-client/src/components/utils/userOptionsDropdown.tsx +++ b/devU-client/src/components/utils/userOptionsDropdown.tsx @@ -1,9 +1,9 @@ import React from 'react' -import { Link } from 'react-router-dom' +import {Link} from 'react-router-dom' import FaIcon from 'components/shared/icons/faIcon' -import { useAppSelector } from 'redux/hooks' +import {useAppSelector} from 'redux/hooks' import RequestService from 'services/request.service' @@ -32,7 +32,7 @@ const UserOptionsDropdown = () => { {/* Menu open closed controlled via CSS */}
- + Account
diff --git a/devU-client/src/components/misc/navbar.tsx b/devU-client/src/components/misc/navbar.tsx index 1e4d5109..54cf557a 100644 --- a/devU-client/src/components/misc/navbar.tsx +++ b/devU-client/src/components/misc/navbar.tsx @@ -1,9 +1,9 @@ -import React, { useEffect, useState } from 'react' +import React, {useEffect, useState} from 'react' import withBreadcrumbs from 'react-router-breadcrumbs-hoc' -import { Link } from 'react-router-dom' +import {Link} from 'react-router-dom' import styles from './navbar.scss' import RequestService from 'services/request.service' -import { Assignment, Course, User } from 'devu-shared-modules' +import {Assignment, Course, User} from 'devu-shared-modules' const UserBreadcrumb = ({ match }: any) => { const [userName, setUserName] = useState('') @@ -85,7 +85,7 @@ const Navbar = ({breadcrumbs}: any) => { if (excludedPaths.includes(match.params.path)) return <> return ( - + {breadcrumb} {index < (breadcrumbs.length - 1) ? ' > ' : ''} diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index e018c7c6..ea40727b 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -91,6 +91,7 @@ const AssignmentDetailPage = () => { } const handleSubmit = async () => { + let response; const contentField = { filepaths : [], form : formData, @@ -113,9 +114,9 @@ const AssignmentDetailPage = () => { submission.append('content', JSON.stringify(contentField)) submission.append('files', file) - var response = await RequestService.postMultipart(`/api/course/${courseId}/assignment/${assignmentId}/submissions`, submission) + response = await RequestService.postMultipart(`/api/course/${courseId}/assignment/${assignmentId}/submissions`, submission); } else { - var response = await RequestService.post(`/api/course/${courseId}/assignment/${assignmentId}/submissions`, submission) + response = await RequestService.post(`/api/course/${courseId}/assignment/${assignmentId}/submissions`, submission); } setAlert({ autoDelete: true, type: 'success', message: 'Submission Sent' }) @@ -124,13 +125,13 @@ const AssignmentDetailPage = () => { await RequestService.post(`/api/grade/${response.id}`, {} ) setAlert({ autoDelete: true, type: 'success', message: 'Submission Graded' }) - fetchData() + await fetchData() } catch (err) { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message setAlert({ autoDelete: false, type: 'error', message }) } finally { setLoading(false) - fetchData() + await fetchData() } } @@ -158,7 +159,7 @@ const AssignmentDetailPage = () => { {assignmentProblems && assignmentProblems.length > 0 ? ( assignmentProblems.map((assignmentProblem, index) => ( - + {`Assignment Problem ${index + 1}`} @@ -192,7 +193,7 @@ const AssignmentDetailPage = () => { {/**Submissions List */}
{submissions.map((submission, index) => ( - + { history.push(`/course/${courseId}/assignment/${assignmentId}/submission/${submission.id}`) }}> diff --git a/devU-client/src/components/pages/assignmentFormPage.tsx b/devU-client/src/components/pages/assignmentFormPage.tsx index 97fc7929..aa2821e4 100644 --- a/devU-client/src/components/pages/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/assignmentFormPage.tsx @@ -7,7 +7,6 @@ import PageWrapper from 'components/shared/layouts/pageWrapper' import RequestService from 'services/request.service' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -// import Button from 'components/shared/inputs/button' import Button from '@mui/material/Button' import {SET_ALERT} from 'redux/types/active.types' diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index a37457e9..bd885648 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -15,7 +15,8 @@ const HomePage = () => { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) - const [courses, setCourses] = useState(new Array()) + const [enrollCourses, setEnrollCourses] = useState(new Array()) + const [pastCourses, setPastCourses] = useState(new Array()) const [assignments, setAssignments] = useState(new Map>()) useEffect(() => { @@ -24,17 +25,29 @@ const HomePage = () => { const fetchData = async () => { try { - const enrolledCourses = await RequestService.get( `/api/courses/user/${userId}` ) + const allCourses = await RequestService.get<{ + activeCourses: UserCourse[]; + pastCourses: UserCourse[] + }>(`/api/courses/user/${userId}`); + const enrolledCourses: UserCourse[] = allCourses.activeCourses; + const pastCourses: UserCourse[] = allCourses.pastCourses; + const coursePromises = enrolledCourses.map(_course => { const course = RequestService.get(`/api/courses/${_course.id}`) // TODO: Optimize out this redundant call const assignments = RequestService.get(`/api/course/${_course.id}/assignments/released`) return Promise.all([course, assignments]) }) + const pastCoursePromises = pastCourses.map(_course => { + const course = RequestService.get(`/api/courses/${_course.id}`) + return Promise.all([course, assignments]) + }) + const pastCourseResults = await Promise.all(pastCoursePromises) const result = await Promise.all(coursePromises) const courses = result.map(([course]) => course) const assignmentsMap = new Map>() result.forEach(([course, assignments]) => assignmentsMap.set(course, assignments)) - setCourses(courses) + setEnrollCourses(courses) + setPastCourses(pastCourseResults.map(([course]) => course)) setAssignments(assignmentsMap) } catch (error) { setError(error) @@ -53,15 +66,30 @@ const HomePage = () => {

My Courses

-
- {courses.map((course) => ( + {enrollCourses && enrollCourses.map((course) => ( ))} + {enrollCourses.length === 0 &&

You do not have current enrollment yet

} +
+ +
+
+

Completed Courses

+
+
+ +
+ {pastCourses && pastCourses.map((course) => ( + + ))} + {pastCourses.length === 0 &&

You do not have completed courses yet

}
+ ) } From 45724ba1e28650360620c7f3c470646ea8920c7e Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Fri, 26 Apr 2024 23:25:59 -0400 Subject: [PATCH 098/400] Updated breadcrumbs for new frontend and backend paths --- devU-client/src/components/misc/navbar.tsx | 28 ++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/devU-client/src/components/misc/navbar.tsx b/devU-client/src/components/misc/navbar.tsx index 1e4d5109..afd4154b 100644 --- a/devU-client/src/components/misc/navbar.tsx +++ b/devU-client/src/components/misc/navbar.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react' import withBreadcrumbs from 'react-router-breadcrumbs-hoc' -import { Link } from 'react-router-dom' +import { Link, useParams } from 'react-router-dom' import styles from './navbar.scss' import RequestService from 'services/request.service' import { Assignment, Course, User } from 'devu-shared-modules' @@ -37,13 +37,14 @@ const CourseBreadcrumb = ({ match }: any) => { const AssignmentBreadcrumb = ({ match }: any) => { const [assignmentName, setAssignmentName] = useState('') + const { courseId } = useParams<{courseId: string}>() useEffect(() => { getAssignment() }, []) const getAssignment = async () => { - const assignment = await RequestService.get( `/api/assignments/${match.params.assignmentId}` ) + const assignment = await RequestService.get( `/api/course/${courseId}/assignments/${match.params.assignmentId}` ) setAssignmentName(assignment.name) } @@ -58,30 +59,33 @@ const DynamicBreadcrumb = ({ match }: any) => { } const routes = [ - { path: '/', breadcrumb: 'Home' }, + { path: '/:any', breadcrumb: 'Home'}, //Only appears once at least one directory deep - { path: '/users/:userId', breadcrumb: UserBreadcrumb}, + { path: '/user/:userId', breadcrumb: UserBreadcrumb}, - { path: '/courses/:courseId', breadcrumb: CourseBreadcrumb}, - { path: '/courses/:courseId/:path', breadcrumb: DynamicBreadcrumb}, + { path: '/course/:courseId', breadcrumb: CourseBreadcrumb}, + { path: '/course/:courseId/:path', breadcrumb: DynamicBreadcrumb}, - { path: '/courses/:courseId/assignments/:assignmentId', breadcrumb: AssignmentBreadcrumb}, - { path: '/courses/:courseId/assignments/:assignmentId/:path', breadcrumb: DynamicBreadcrumb}, + { path: '/course/:courseId/assignment/:assignmentId', breadcrumb: AssignmentBreadcrumb}, + { path: '/course/:courseId/assignment/:assignmentId/:path', breadcrumb: DynamicBreadcrumb}, - { path: '/courses/:courseId/assignments/:assignmentId/submissions/:submissionId', breadcrumb: 'Submission'}, - { path: '/courses/:courseId/assignments/:assignmentId/submissions/:submissionId/feedback', breadcrumb: 'Feedback'}, + { path: '/course/:courseId/assignment/:assignmentId/submission/:submissionId', breadcrumb: 'Submission'}, + { path: '/course/:courseId/assignment/:assignmentId/submission/:submissionId/feedback', breadcrumb: 'Feedback'}, ] const Navbar = ({breadcrumbs}: any) => { const excludedPaths = [ - 'assignments', - 'submissions', + 'assignment', + 'submission', ] return (
{breadcrumbs.map(({breadcrumb, match}: any, index: number) => { + console.log(match.params.path) + console.log(match.url) + console.log(breadcrumb) if (excludedPaths.includes(match.params.path)) return <> return ( From cc651f1827cf6eec86ab116311fa561bac703026 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sat, 27 Apr 2024 01:05:26 -0400 Subject: [PATCH 099/400] update add course form for better looking --- .../components/pages/assignmentFormPage.scss | 7 +++- .../components/pages/courseUpdatePage.scss | 4 ++ .../src/components/pages/courseUpdatePage.tsx | 27 ++++++-------- .../src/components/pages/coursesFormPage.scss | 31 +++++++++++----- .../src/components/pages/coursesFormPage.tsx | 37 ++++++++----------- .../components/shared/inputs/textField.tsx | 31 +++++++++------- devU-client/src/utils/textField.utils.ts | 5 +++ 7 files changed, 81 insertions(+), 61 deletions(-) diff --git a/devU-client/src/components/pages/assignmentFormPage.scss b/devU-client/src/components/pages/assignmentFormPage.scss index 57f00d12..1fa5ad98 100644 --- a/devU-client/src/components/pages/assignmentFormPage.scss +++ b/devU-client/src/components/pages/assignmentFormPage.scss @@ -1,8 +1,11 @@ @import 'variables'; + +h1 { + text-align: center; +} .form { - display: absolute; - background-color: #4e4e4e; + background-color: $list-item-background; border-radius: 10px; padding: 20px; width: 50%; diff --git a/devU-client/src/components/pages/courseUpdatePage.scss b/devU-client/src/components/pages/courseUpdatePage.scss index 29aa4f46..70854761 100644 --- a/devU-client/src/components/pages/courseUpdatePage.scss +++ b/devU-client/src/components/pages/courseUpdatePage.scss @@ -1,5 +1,9 @@ @import 'variables'; +h1 { + text-align: center; +} + .form { display: absolute; background-color: #4e4e4e; diff --git a/devU-client/src/components/pages/courseUpdatePage.tsx b/devU-client/src/components/pages/courseUpdatePage.tsx index 67c8c88c..7b8a0322 100644 --- a/devU-client/src/components/pages/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/courseUpdatePage.tsx @@ -78,23 +78,20 @@ const CourseUpdatePage = ({}) => {

Course Detail Update

-

Required Fields *

- - - - - - - - + + + + +

- +
@@ -103,10 +100,10 @@ const CourseUpdatePage = ({}) => {

- +
-
+
diff --git a/devU-client/src/components/pages/coursesFormPage.scss b/devU-client/src/components/pages/coursesFormPage.scss index b03e4036..c315ec62 100644 --- a/devU-client/src/components/pages/coursesFormPage.scss +++ b/devU-client/src/components/pages/coursesFormPage.scss @@ -1,27 +1,40 @@ @import 'variables'; .form { - display: absolute; - background-color: #4e4e4e; + background-color: $list-item-background; border-radius: 10px; - padding: 20px; + padding: 30px; width: 50%; left: 0; right: 0; - margin-left: auto; - margin-right: auto; + margin-left: auto; + margin-right: auto; + } .datepickerContainer { display: flex; - justify-content: center; + justify-content: space-between; } -.datepickerContainer > * { - margin: 0 10px; -} .submitBtn { display: flex; justify-content: center; } + +.datepicker { + font: inherit; + letter-spacing: inherit; + box-sizing: content-box; + background: none; + height: 1.4375em; + -webkit-tap-highlight-color: transparent; + display: block; + + animation-duration: 10ms; + padding: 16.5px 14px; + border: 1px solid #bbbbbb; + border-radius: 4px; + +} \ No newline at end of file diff --git a/devU-client/src/components/pages/coursesFormPage.tsx b/devU-client/src/components/pages/coursesFormPage.tsx index 22988d0e..5a18c019 100644 --- a/devU-client/src/components/pages/coursesFormPage.tsx +++ b/devU-client/src/components/pages/coursesFormPage.tsx @@ -10,11 +10,9 @@ import RequestService from 'services/request.service' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -// import Button from 'components/shared/inputs/button' import {SET_ALERT} from 'redux/types/active.types' -import styles from '../shared/inputs/textField.scss' import formStyles from './coursesFormPage.scss' -import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; +import {applyMessageToErrorFields, removeClassFromField} from "../../utils/textField.utils"; import Button from '@mui/material/Button' @@ -60,9 +58,9 @@ const EditCourseFormPage = () => { .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message - const newFields = applyStylesToErrorFields(err, formData, styles.errorField) + const newFields = new Map() + Array.isArray(err) ? err.map((e) => applyMessageToErrorFields(newFields, e.param, e.msg)) : newFields setInvalidFields(newFields); - setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => { @@ -73,35 +71,32 @@ const EditCourseFormPage = () => {

Course Form

-

Required Fields *

- - - - - - - - + + + +

- +

- +

- +
-
+
diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 7a7da2ec..102ed68d 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -1,7 +1,6 @@ import React from 'react' -import { toCapitalizedWords } from 'devu-shared-modules' - +import {TextField as MuiTextField} from '@mui/material' import styles from './textField.scss' type Props = { @@ -14,10 +13,11 @@ type Props = { disabled?: true defaultValue?: string value?: string + invalidated?: boolean + helpText?: string } const TextField = ({ - type = 'text', onChange, className = '', label, @@ -26,24 +26,27 @@ const TextField = ({ disabled, defaultValue, value, + invalidated, + helpText, + }: Props) => { const handleChange = (e: React.ChangeEvent) => { if (onChange) onChange(e.target.value, e) } - return (
- {label && } - disabled={disabled} - defaultValue={defaultValue} - value={value} - /> + helperText={helpText} +
) } diff --git a/devU-client/src/utils/textField.utils.ts b/devU-client/src/utils/textField.utils.ts index 6b67c399..5c272347 100644 --- a/devU-client/src/utils/textField.utils.ts +++ b/devU-client/src/utils/textField.utils.ts @@ -44,4 +44,9 @@ export function applyStylesToErrorFields(error: ExpressValidationError[] | Error const sections = extractErrorFields(error) const allFields = initializeFieldClasses(data) return applyClassToMultipleFields(allFields, sections, styles) +} + +export function applyMessageToErrorFields(allFields: Map, errField: string, message: string) { + allFields.set(errField, message) + return allFields } \ No newline at end of file From 8a860aede60567f34c41eb858ff89f99009a9d42 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Sat, 27 Apr 2024 01:12:17 -0400 Subject: [PATCH 100/400] Fix id name for course update path --- devU-api/src/entities/course/course.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-api/src/entities/course/course.controller.ts b/devU-api/src/entities/course/course.controller.ts index ea6d52b3..cb30235c 100644 --- a/devU-api/src/entities/course/course.controller.ts +++ b/devU-api/src/entities/course/course.controller.ts @@ -77,7 +77,7 @@ export async function postAddInstructor(req: Request, res: Response, next: NextF export async function put(req: Request, res: Response, next: NextFunction) { try { - req.body.id = parseInt(req.params.id) + req.body.id = parseInt(req.params.courseId) const results = await CourseService.update(req.body) if (!results.affected) return res.status(404).json(NotFound) From 8e8769563cb6a3234db543f5854f5e2b23362e4c Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sat, 27 Apr 2024 02:06:17 -0400 Subject: [PATCH 101/400] update links for NCAG/CAG and gradebook --- .../src/components/pages/assignmentUpdatePage.tsx | 6 +++--- .../src/components/pages/containerAutoGraderForm.scss | 2 +- .../src/components/pages/containerAutoGraderForm.tsx | 7 +++---- devU-client/src/components/pages/courseUpdatePage.scss | 2 +- devU-client/src/components/pages/coursesFormPage.tsx | 2 +- .../src/components/pages/gradebookInstructorPage.tsx | 10 +++++----- .../src/components/pages/gradebookStudentPage.tsx | 7 +++++-- .../components/pages/nonContainerAutoGraderForm.scss | 2 +- 8 files changed, 20 insertions(+), 18 deletions(-) diff --git a/devU-client/src/components/pages/assignmentUpdatePage.tsx b/devU-client/src/components/pages/assignmentUpdatePage.tsx index 48d8b61b..427bd225 100644 --- a/devU-client/src/components/pages/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/assignmentUpdatePage.tsx @@ -42,9 +42,9 @@ const AssignmentUpdatePage = () => { assignmentId: parseInt(problemFormData.assignmentId), problemName: problemFormData.problemName, maxScore: parseInt(problemFormData.maxScore), - } - - RequestService.post('/api/assignment-problems/', finalProblemFormData) + } + + RequestService.post(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems/`, finalProblemFormData) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Assignment Problem Added' }) }) diff --git a/devU-client/src/components/pages/containerAutoGraderForm.scss b/devU-client/src/components/pages/containerAutoGraderForm.scss index 5837c701..7a7edfa4 100644 --- a/devU-client/src/components/pages/containerAutoGraderForm.scss +++ b/devU-client/src/components/pages/containerAutoGraderForm.scss @@ -2,7 +2,7 @@ .form { display: absolute; - background-color: #4e4e4e; + background-color: $list-item-background; border-radius: 10px; padding: 20px; width: 50%; diff --git a/devU-client/src/components/pages/containerAutoGraderForm.tsx b/devU-client/src/components/pages/containerAutoGraderForm.tsx index d5ab7a07..1700925f 100644 --- a/devU-client/src/components/pages/containerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/containerAutoGraderForm.tsx @@ -1,8 +1,7 @@ import React, {useState} from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' -import styles from './nonContainerAutoGraderForm.scss' +import styles from './ContainerAutoGraderForm.scss' import TextField from 'components/shared/inputs/textField' -// import Button from 'components/shared/inputs/button' import {useActionless} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' import RequestService from 'services/request.service' @@ -15,7 +14,7 @@ import Button from '@mui/material/Button' const ContainerAutoGraderForm = () => { const [setAlert] = useActionless(SET_ALERT) - const {assignmentId} = useParams<{ assignmentId: string }>() + const {courseId, assignmentId} = useParams<{ courseId: string, assignmentId: string }>() const history = useHistory() const [graderFile, setGraderFile] = useState() const [makefile, setMakefile] = useState() @@ -50,7 +49,7 @@ const ContainerAutoGraderForm = () => { if (graderFile) multipart.append('graderFile', graderFile) if (makefile) multipart.append('makefileFile', makefile) - RequestService.postMultipart('/api/container-auto-graders/', multipart) + RequestService.postMultipart(`/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders/`, multipart) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Container Auto-Grader Added' }) history.goBack() diff --git a/devU-client/src/components/pages/courseUpdatePage.scss b/devU-client/src/components/pages/courseUpdatePage.scss index 70854761..7d0f63f8 100644 --- a/devU-client/src/components/pages/courseUpdatePage.scss +++ b/devU-client/src/components/pages/courseUpdatePage.scss @@ -6,7 +6,7 @@ h1 { .form { display: absolute; - background-color: #4e4e4e; + background-color: $list-item-background; border-radius: 10px; padding: 20px; width: 50%; diff --git a/devU-client/src/components/pages/coursesFormPage.tsx b/devU-client/src/components/pages/coursesFormPage.tsx index 5a18c019..88af540d 100644 --- a/devU-client/src/components/pages/coursesFormPage.tsx +++ b/devU-client/src/components/pages/coursesFormPage.tsx @@ -50,7 +50,7 @@ const EditCourseFormPage = () => { endDate : endDate.toISOString(), } - RequestService.post('/api/courses/', finalFormData) + RequestService.post('/api/courses/instructor', finalFormData) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Course Added' }) history.goBack() diff --git a/devU-client/src/components/pages/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebookInstructorPage.tsx index 1ffb23ba..e6278266 100644 --- a/devU-client/src/components/pages/gradebookInstructorPage.tsx +++ b/devU-client/src/components/pages/gradebookInstructorPage.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from 'react' +import React, {useEffect, useState} from 'react' -import { User, UserCourse, Assignment, AssignmentScore } from 'devu-shared-modules' +import {Assignment, AssignmentScore, User, UserCourse} from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' @@ -9,7 +9,7 @@ import ErrorPage from './errorPage' import RequestService from 'services/request.service' import styles from './gradebookPage.scss' -import { useParams } from 'react-router-dom' +import {useParams} from 'react-router-dom' type TableProps = { users: User[] @@ -81,10 +81,10 @@ const GradebookInstructorPage = () => { const fetchData = async () => { try { - const userCourses = await RequestService.get( `/api/user-courses/course/${courseId}` ) + const userCourses = await RequestService.get(`/api/course/${courseId}/user-courses/course/${courseId}`) setUserCourses(userCourses) - const users = await RequestService.get( `/api/users/course/${courseId}?level=student` ) + const users = await RequestService.get(`/api/users/course/${courseId}`) setUsers(users) const assignments = await RequestService.get( `/api/assignments/course/${courseId}` ) diff --git a/devU-client/src/components/pages/gradebookStudentPage.tsx b/devU-client/src/components/pages/gradebookStudentPage.tsx index a249da03..9fca9a95 100644 --- a/devU-client/src/components/pages/gradebookStudentPage.tsx +++ b/devU-client/src/components/pages/gradebookStudentPage.tsx @@ -26,6 +26,7 @@ type AssignmentProps = { const CategoryAssignment = ({assignment, assignmentScore}: AssignmentProps) => { const { courseId } = useParams<{courseId: string}>() + return (
{ const [categories, setCategories] = useState(new Array()) const [assignments, setAssignments] = useState(new Array()) const [assignmentScores, setAssignmentScores] = useState(new Array()) - + const role = useAppSelector((store) => store.roleMode) const { courseId } = useParams<{courseId: string}>() const userId = useAppSelector((store) => store.user.id) @@ -98,7 +99,9 @@ const GradebookStudentPage = () => {

Student Gradebook

- Instructor View + {role.isInstructor() && + Instructor + View}
diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.scss b/devU-client/src/components/pages/nonContainerAutoGraderForm.scss index 5837c701..9b26996e 100644 --- a/devU-client/src/components/pages/nonContainerAutoGraderForm.scss +++ b/devU-client/src/components/pages/nonContainerAutoGraderForm.scss @@ -2,7 +2,7 @@ .form { display: absolute; - background-color: #4e4e4e; + background-color: $list-item-background; border-radius: 10px; padding: 20px; width: 50%; From 3288cae60ce3f2e7ad2e1453c397a369f589c8f0 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Sat, 27 Apr 2024 03:30:33 -0400 Subject: [PATCH 102/400] Fixed some small bugs Fixed some small bugs --- devU-client/src/components/misc/globalToolbar.tsx | 2 +- devU-client/src/components/pages/assignmentDetailPage.tsx | 2 +- devU-client/src/components/pages/containerAutoGraderForm.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devU-client/src/components/misc/globalToolbar.tsx b/devU-client/src/components/misc/globalToolbar.tsx index 61781b69..9f477269 100644 --- a/devU-client/src/components/misc/globalToolbar.tsx +++ b/devU-client/src/components/misc/globalToolbar.tsx @@ -14,7 +14,7 @@ const GlobalToolbar = () => { return (
- Auto Four + DevU
diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index ea40727b..274b301c 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -177,7 +177,7 @@ const AssignmentDetailPage = () => { {containerAutograder && ()} - {assignmentProblems? ( + {assignmentProblems && assignmentProblems.length > 0 ? ( ) : null} diff --git a/devU-client/src/components/pages/containerAutoGraderForm.tsx b/devU-client/src/components/pages/containerAutoGraderForm.tsx index 1700925f..13d7c0e1 100644 --- a/devU-client/src/components/pages/containerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/containerAutoGraderForm.tsx @@ -1,6 +1,6 @@ import React, {useState} from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' -import styles from './ContainerAutoGraderForm.scss' +import styles from './containerAutoGraderForm.scss' import TextField from 'components/shared/inputs/textField' import {useActionless} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' From e770dea5d83a3e05bc37c38c832a24397c840650 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Sat, 27 Apr 2024 11:43:19 -0400 Subject: [PATCH 103/400] bug fixes and minor path changes --- devU-api/src/entities/user/user.service.ts | 2 +- devU-api/src/entities/userCourse/userCourse.controller.ts | 2 +- devU-api/src/entities/userCourse/userCourse.router.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devU-api/src/entities/user/user.service.ts b/devU-api/src/entities/user/user.service.ts index 09a37d18..b32cafd5 100644 --- a/devU-api/src/entities/user/user.service.ts +++ b/devU-api/src/entities/user/user.service.ts @@ -35,7 +35,7 @@ export async function list() { export async function listByCourse(courseId: number, userRole?: string) { const userCourses = await UserCourseService.listByCourse(courseId) const userPromises = userCourses - .filter(uc => !userRole || uc.role === userRole) + // .filter(uc => !userRole || uc.role === userRole) .map(uc => connect().findOne({ id: uc.userId, deletedAt: IsNull() })) return await Promise.all(userPromises) } diff --git a/devU-api/src/entities/userCourse/userCourse.controller.ts b/devU-api/src/entities/userCourse/userCourse.controller.ts index 09c7729b..4e5fbd32 100644 --- a/devU-api/src/entities/userCourse/userCourse.controller.ts +++ b/devU-api/src/entities/userCourse/userCourse.controller.ts @@ -28,7 +28,7 @@ export async function get(req: Request, res: Response, next: NextFunction) { export async function getByCourse(req: Request, res: Response, next: NextFunction) { try { - const id = parseInt(req.params.id) + const id = parseInt(req.params.courseId) const userCourses = await UserCourseService.listByCourse(id) const response = userCourses.map(serialize) diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index 96951dda..2934389e 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -29,7 +29,7 @@ Router.get('/users', UserCourseController.checkEnroll) * schema: * type: integer */ -Router.get('/course/:id', isAuthorized('courseViewAll'), asInt(), UserCourseController.getByCourse) +Router.get('/', isAuthorized('courseViewAll'), UserCourseController.getByCourse) /** * @swagger From 95dc49ad588e6916138e918fee25ecdc25d706d0 Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Sat, 27 Apr 2024 12:12:23 -0400 Subject: [PATCH 104/400] Took a pass at updating the front end paths. 2 paths will need to get the courseId and are currently marked with TODO --- devU-api/src/entities/userCourse/userCourse.router.ts | 5 +++++ .../src/components/pages/assignmentUpdatePage.tsx | 2 +- .../src/components/pages/courseAssignmentsListPage.tsx | 2 +- .../src/components/pages/gradebookInstructorPage.tsx | 6 +++--- .../components/pages/nonContainerAutoGraderForm.tsx | 3 ++- .../src/components/pages/submissionFeedbackPage.tsx | 10 +++++----- .../src/components/pages/userCoursesListPage.tsx | 2 +- .../src/components/pages/userSubmissionsListPage.tsx | 3 ++- 8 files changed, 20 insertions(+), 13 deletions(-) diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index 2934389e..54c3d274 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -10,6 +10,11 @@ import {extractOwnerByPathParam, isAuthorized} from '../../authorization/authori import UserCourseController from './userCourse.controller' const Router = express.Router({ mergeParams: true }) + + +/** + * TODO: Document this + */ Router.get('/users', UserCourseController.checkEnroll) /** * @swagger diff --git a/devU-client/src/components/pages/assignmentUpdatePage.tsx b/devU-client/src/components/pages/assignmentUpdatePage.tsx index 427bd225..10a9898f 100644 --- a/devU-client/src/components/pages/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/assignmentUpdatePage.tsx @@ -118,7 +118,7 @@ const AssignmentUpdatePage = () => { } - RequestService.put(`/api/assignments/${assignmentId}`, finalFormData) + RequestService.put(`/api/course/${courseId}/assignments/${assignmentId}`, finalFormData) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Assignment Updated' }) diff --git a/devU-client/src/components/pages/courseAssignmentsListPage.tsx b/devU-client/src/components/pages/courseAssignmentsListPage.tsx index dfd806ea..e6ccbfeb 100644 --- a/devU-client/src/components/pages/courseAssignmentsListPage.tsx +++ b/devU-client/src/components/pages/courseAssignmentsListPage.tsx @@ -25,7 +25,7 @@ const CourseAssignmentsListPage = () => { const fetchData = async () => { try { - const assignments = await RequestService.get(`/api/assignments/course/${courseId}`) + const assignments = await RequestService.get(`/api/course/${courseId}/assignments`) setAssignments(assignments) } catch (error) { setError(error) diff --git a/devU-client/src/components/pages/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebookInstructorPage.tsx index e6278266..5cd3dd99 100644 --- a/devU-client/src/components/pages/gradebookInstructorPage.tsx +++ b/devU-client/src/components/pages/gradebookInstructorPage.tsx @@ -81,17 +81,17 @@ const GradebookInstructorPage = () => { const fetchData = async () => { try { - const userCourses = await RequestService.get(`/api/course/${courseId}/user-courses/course/${courseId}`) + const userCourses = await RequestService.get(`/api/course/${courseId}/user-courses/`) setUserCourses(userCourses) const users = await RequestService.get(`/api/users/course/${courseId}`) setUsers(users) - const assignments = await RequestService.get( `/api/assignments/course/${courseId}` ) + const assignments = await RequestService.get( `/api/course/${courseId}/assignments` ) assignments.sort((a, b) => (Date.parse(a.startDate) - Date.parse(b.startDate))) //Sort by assignment's start date setAssignments(assignments) - const assignmentScores = await RequestService.get( `/api/assignment-scores/course/${courseId}` ) + const assignmentScores = await RequestService.get( `/api/course/${courseId}/assignment-scores` ) setAssignmentScores(assignmentScores) } catch (error) { diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx index de9ed9f7..51b31b89 100644 --- a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx @@ -59,7 +59,8 @@ const NonContainerAutoGraderForm = () => { correctString: formData.correctString, } - RequestService.post('/api/nonContainerAutoGrader/', finalFormData) + // TODO: Get courseId and update path + RequestService.post(`/api/course/???/nonContainerAutoGrader/`, finalFormData) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Non-Container Auto-Grader Added' }) history.goBack() diff --git a/devU-client/src/components/pages/submissionFeedbackPage.tsx b/devU-client/src/components/pages/submissionFeedbackPage.tsx index a2bb5a34..e8f0477e 100644 --- a/devU-client/src/components/pages/submissionFeedbackPage.tsx +++ b/devU-client/src/components/pages/submissionFeedbackPage.tsx @@ -19,17 +19,17 @@ const SubmissionFeedbackPage = () => { const fetchData = async () => { try { - const submissionScore = (await RequestService.get( `/api/submission-scores?submission=${submissionId}` )).pop() ?? null + const submissionScore = (await RequestService.get( `/api/course/${courseId}/assignment/${assignmentId}/submission-scores?submission=${submissionId}` )).pop() ?? null setSubmissionScore(submissionScore) - const submissionProblemScores = await RequestService.get( `/api/submission-problem-scores/${submissionId}` ) + const submissionProblemScores = await RequestService.get( `/api/course/${courseId}/assignment/${assignmentId}/submission-problem-scores/${submissionId}` ) setSubmissionProblemScores(submissionProblemScores) - const submission = await RequestService.get( `/api/submissions/${submissionId}` ) - const assignment = await RequestService.get( `/api/assignments/${submission.assignmentId}` ) + const submission = await RequestService.get( `/api/course/${courseId}/assignment/${assignmentId}/submissions/${submissionId}` ) + const assignment = await RequestService.get( `/api/course/${courseId}/assignments/${submission.assignmentId}` ) setAssignment(assignment) - const assignmentProblems = await RequestService.get( `/api/assignment-problems/${assignment.id}` ) + const assignmentProblems = await RequestService.get( `/api/course/${courseId}/assignment/${assignmentId}/assignment-problems/${assignment.id}` ) setAssignmentProblems(assignmentProblems) } catch (error) { diff --git a/devU-client/src/components/pages/userCoursesListPage.tsx b/devU-client/src/components/pages/userCoursesListPage.tsx index a6bf4b4d..78a2f65e 100644 --- a/devU-client/src/components/pages/userCoursesListPage.tsx +++ b/devU-client/src/components/pages/userCoursesListPage.tsx @@ -31,7 +31,7 @@ const UserCoursesListPage = () => { const userCourses = await RequestService.get(`/api/courses/user/${userId}`) const coursePromises = userCourses.map(uc => { const course = RequestService.get(`/api/courses/${uc.courseId}`) - const assignments = RequestService.get(`/api/assignments/course/${uc.courseId}`) + const assignments = RequestService.get(`/api/course/${uc.courseId}/assignments/released`) return Promise.all([course, assignments]) }) diff --git a/devU-client/src/components/pages/userSubmissionsListPage.tsx b/devU-client/src/components/pages/userSubmissionsListPage.tsx index 6801aa8c..fb0f14ff 100644 --- a/devU-client/src/components/pages/userSubmissionsListPage.tsx +++ b/devU-client/src/components/pages/userSubmissionsListPage.tsx @@ -59,7 +59,8 @@ const UserCoursesListPage = () => { const courseRequests = submissions.map((s) => RequestService.get(`/api/courses/${s.courseId}`)) const assignmentRequests = submissions.map((s) => - RequestService.get(`/api/assignments/${s.assignmentId}`), + // TODO: Get courseId and update path + RequestService.get(`/api/course/???/assignments/${s.assignmentId}`), ) const courses = await Promise.all(courseRequests) From b39815f198b94a6363ea3469cb9c0a4d75a573ba Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sat, 27 Apr 2024 13:16:54 -0400 Subject: [PATCH 105/400] update course api to handle upcoming, active, past, instructor Courses and display instructor if the course role is instructor --- .../src/entities/course/course.controller.ts | 16 ++++++- .../src/entities/course/course.service.ts | 38 ++++++++++----- .../listItems/userCourseListItem.tsx | 7 +-- devU-client/src/components/pages/homePage.tsx | 48 +++++++++++-------- 4 files changed, 72 insertions(+), 37 deletions(-) diff --git a/devU-api/src/entities/course/course.controller.ts b/devU-api/src/entities/course/course.controller.ts index cb30235c..4845391b 100644 --- a/devU-api/src/entities/course/course.controller.ts +++ b/devU-api/src/entities/course/course.controller.ts @@ -20,8 +20,20 @@ export async function get(req: Request, res: Response, next: NextFunction) { export async function getByUser(req: Request, res: Response, next: NextFunction) { try { const userId = parseInt(req.params.userId) - const {activeCourses, pastCourses} = await CourseService.listByUser(userId) - const response = {activeCourses: activeCourses.map(serialize), pastCourses: pastCourses.map(serialize)} + const { + activeCourses, + pastCourses, + instructorCourses, + upcomingCourses + } = await CourseService.listByUser(userId) + + const response = + { + activeCourses: activeCourses.map(serialize), + pastCourses: pastCourses.map(serialize), + instructorCourses: instructorCourses.map(serialize), + upcomingCourses: upcomingCourses.map(serialize) + } res.status(200).json(response) } catch (err) { diff --git a/devU-api/src/entities/course/course.service.ts b/devU-api/src/entities/course/course.service.ts index d16701ee..749de161 100644 --- a/devU-api/src/entities/course/course.service.ts +++ b/devU-api/src/entities/course/course.service.ts @@ -35,21 +35,37 @@ export async function list() { export async function listByUser(userId: number) { const userCourses = await UserCourseService.listByUser(userId) - let activeCourses: CourseModel[] = [] - let pastCourses: CourseModel[] = [] const date = new Date() - // TODO: There is a more efficient way to do this than a query in a loop.. I'm too rusty on SQL to think of it rn - for (const userCourse of userCourses) { - const course = await connect().findOne({id: userCourse.courseId, deletedAt: IsNull()}) - if (course) { - if (course.endDate > date) { - activeCourses.push(course) - } else { + const activeCourses = [] + const pastCourses = [] + const instructorCourses = [] + const upcomingCourses = [] + + const userCourseIds = userCourses.map(userCourse => userCourse.courseId) + const allCourses = await connect() + .createQueryBuilder('course') + .where('course.id IN (:...ids)', {ids: userCourseIds}) + .andWhere('course.deletedAt IS NULL') + .getMany(); + + for (const course of allCourses) { + const userCourse = userCourses.find(userCourse => userCourse.courseId === course.id); + switch (true) { + case course.startDate > date: + upcomingCourses.push(course) + break + case course.endDate < date: pastCourses.push(course) - } + break + case userCourse && userCourse.role === 'instructor': + instructorCourses.push(course) + break + default: + activeCourses.push(course) } } - return {activeCourses, pastCourses} + + return {activeCourses, pastCourses, instructorCourses, upcomingCourses} } export default { diff --git a/devU-client/src/components/listItems/userCourseListItem.tsx b/devU-client/src/components/listItems/userCourseListItem.tsx index efeb8a04..4689ad5e 100644 --- a/devU-client/src/components/listItems/userCourseListItem.tsx +++ b/devU-client/src/components/listItems/userCourseListItem.tsx @@ -15,12 +15,13 @@ type Props = { course: Course assignments?: Assignment[] past?: boolean + instructor?: boolean } -const UserCourseListItem = ({course, assignments, past = false}: Props) => ( +const UserCourseListItem = ({course, assignments, past = false, instructor = false}: Props) => ( -
{course.name}
+
{instructor ? (course.name + " (Instructor)") : course.name}
{course.number}
Semester: {prettyPrintSemester(course.semester)}
@@ -29,7 +30,7 @@ const UserCourseListItem = ({course, assignments, past = false}: Props) => ( {assignments && assignments.length > 0 ? (assignments.map((assignment) => ( - ))) : (past ?
:
No Assignments Due Yet
)} + ))) : ((past || instructor) ?
:
No Assignments Due Yet
)}
diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index bd885648..d5c47800 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -8,7 +8,7 @@ import UserCourseListItem from "../listItems/userCourseListItem"; import {useAppSelector} from 'redux/hooks' import RequestService from 'services/request.service' -import {Assignment, Course, UserCourse} from 'devu-shared-modules' +import {Assignment, Course} from 'devu-shared-modules' const HomePage = () => { const userId = useAppSelector((store) => store.user.id) @@ -18,37 +18,38 @@ const HomePage = () => { const [enrollCourses, setEnrollCourses] = useState(new Array()) const [pastCourses, setPastCourses] = useState(new Array()) const [assignments, setAssignments] = useState(new Map>()) + const [instructorCourses, setInstructorCourses] = useState(new Array()) useEffect(() => { fetchData() }, []) const fetchData = async () => { + const assignmentMap = new Map>() try { const allCourses = await RequestService.get<{ - activeCourses: UserCourse[]; - pastCourses: UserCourse[] + instructorCourses: Course[]; + activeCourses: Course[]; + pastCourses: Course[]; + upcomingCourses: Course[];//TODO: Add upcoming courses feature }>(`/api/courses/user/${userId}`); - const enrolledCourses: UserCourse[] = allCourses.activeCourses; - const pastCourses: UserCourse[] = allCourses.pastCourses; + const enrolledCourses: Course[] = allCourses.activeCourses; - const coursePromises = enrolledCourses.map(_course => { - const course = RequestService.get(`/api/courses/${_course.id}`) // TODO: Optimize out this redundant call - const assignments = RequestService.get(`/api/course/${_course.id}/assignments/released`) - return Promise.all([course, assignments]) - }) - const pastCoursePromises = pastCourses.map(_course => { - const course = RequestService.get(`/api/courses/${_course.id}`) + const pastCourses: Course[] = allCourses.pastCourses; + const instructorCourses: Course[] = allCourses.instructorCourses; + + const assignmentPromises = enrolledCourses.map((course) => { + const assignments = RequestService.get(`/api/course/${course.id}/assignments/released`) return Promise.all([course, assignments]) }) - const pastCourseResults = await Promise.all(pastCoursePromises) - const result = await Promise.all(coursePromises) - const courses = result.map(([course]) => course) - const assignmentsMap = new Map>() - result.forEach(([course, assignments]) => assignmentsMap.set(course, assignments)) - setEnrollCourses(courses) - setPastCourses(pastCourseResults.map(([course]) => course)) - setAssignments(assignmentsMap) + const assignmentResults = await Promise.all(assignmentPromises) + assignmentResults.forEach(([course, assignments]) => assignmentMap.set(course, assignments)) + + setAssignments(assignmentMap) + setPastCourses(pastCourses) + setEnrollCourses(enrolledCourses) + setInstructorCourses(instructorCourses) + } catch (error) { setError(error) } finally { @@ -67,7 +68,12 @@ const HomePage = () => {

My Courses

- +
+ {instructorCourses && instructorCourses.map((course) => ( + + ))} +
{enrollCourses && enrollCourses.map((course) => ( From 64806c9c0e34836f49590cb2f4ddc28e826f1bd7 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Sat, 27 Apr 2024 13:28:13 -0400 Subject: [PATCH 106/400] Fixed some courseId paths Fixed some courseId paths --- .../src/components/pages/assignmentDetailPage.tsx | 10 +++++----- .../components/pages/nonContainerAutoGraderForm.tsx | 4 ++-- .../src/components/pages/userSubmissionsListPage.tsx | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index 274b301c..6d2d9133 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -1,7 +1,7 @@ import React, {useEffect, useState} from 'react' import {useHistory, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' -import {Assignment, AssignmentProblem, Submission,} from 'devu-shared-modules' +import {Assignment, AssignmentProblem, Submission, ContainerAutoGrader} from 'devu-shared-modules' import RequestService from 'services/request.service' import ErrorPage from './errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' @@ -35,8 +35,8 @@ const AssignmentDetailPage = () => { // const [submissionProblemScores, setSubmissionProblemScores] = useState(new Array()) const [assignment, setAssignment] = useState() - // const [containerAutograder, setContainerAutograder] = useState() - const containerAutograder = false; //TODO: Use the above commented out code to get the container autograder + const [containerAutograder, setContainerAutograder] = useState() + // const containerAutograder = false; //TODO: Use the above commented out code to get the container autograder useEffect(() => { fetchData() @@ -67,8 +67,8 @@ const AssignmentDetailPage = () => { // const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) // setSubmissionProblemScores(submissionProblemScoresReq) - // const containerAutograder = (await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`)).pop() ?? null - // setContainerAutograder(containerAutograder) + const containerAutograder = (await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`)).pop() ?? null + setContainerAutograder(containerAutograder) } catch (err) { setError(err) diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx index 51b31b89..1a327c4f 100644 --- a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx @@ -16,7 +16,7 @@ import Button from '@mui/material/Button' const NonContainerAutoGraderForm = () => { const [setAlert] = useActionless(SET_ALERT) const [invalidFields, setInvalidFields] = useState(new Map()) - const {assignmentId} = useParams<{ assignmentId: string }>() + const {assignmentId, courseId} = useParams<{ assignmentId: string, courseId : string }>() const history = useHistory() const [formData,setFormData] = useState({ @@ -60,7 +60,7 @@ const NonContainerAutoGraderForm = () => { } // TODO: Get courseId and update path - RequestService.post(`/api/course/???/nonContainerAutoGrader/`, finalFormData) + RequestService.post(`/api/course/${courseId}/nonContainerAutoGrader/`, finalFormData) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Non-Container Auto-Grader Added' }) history.goBack() diff --git a/devU-client/src/components/pages/userSubmissionsListPage.tsx b/devU-client/src/components/pages/userSubmissionsListPage.tsx index fb0f14ff..8d2447d8 100644 --- a/devU-client/src/components/pages/userSubmissionsListPage.tsx +++ b/devU-client/src/components/pages/userSubmissionsListPage.tsx @@ -60,7 +60,7 @@ const UserCoursesListPage = () => { const courseRequests = submissions.map((s) => RequestService.get(`/api/courses/${s.courseId}`)) const assignmentRequests = submissions.map((s) => // TODO: Get courseId and update path - RequestService.get(`/api/course/???/assignments/${s.assignmentId}`), + RequestService.get(`/api/course/${s.courseId}/assignments/${s.assignmentId}`), ) const courses = await Promise.all(courseRequests) From 3c25ac3f9d79eb2cd5a4a4314f23a2486af36069 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sat, 27 Apr 2024 13:45:31 -0400 Subject: [PATCH 107/400] update visual looking for the student gradebook --- .../src/components/pages/gradebookPage.scss | 28 +++++++++---------- .../components/pages/gradebookStudentPage.tsx | 12 ++++++-- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/devU-client/src/components/pages/gradebookPage.scss b/devU-client/src/components/pages/gradebookPage.scss index 29f02271..a252e74d 100644 --- a/devU-client/src/components/pages/gradebookPage.scss +++ b/devU-client/src/components/pages/gradebookPage.scss @@ -1,9 +1,9 @@ @import 'variables'; .header { - color: $text-color; - display: flex; - justify-content: space-between; + color: $text-color; + display: flex; + align-items: center; } .categoryName { @@ -15,15 +15,15 @@ color: $text-color } -.button { - justify-content: center; - background-color: $primary; - color: $text-color; - border: 0px; - padding: 10px 40px; - border-radius: 50px; - font-size: 14px; - font-weight: 700; - text-decoration: none; - cursor: pointer; +.smallLine { + width: 50px; /* adjust this value to set the length of the small line */ + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ + margin-right: 10px; /* adjust this value to set the space between the line and the text */ +} + +.largeLine { + flex-grow: 1; + border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ + margin-left: 10px; /* adjust this value to set the space between the line and the text */ + margin-right: 10px; /* add this line to create some space between the line and the button */ } \ No newline at end of file diff --git a/devU-client/src/components/pages/gradebookStudentPage.tsx b/devU-client/src/components/pages/gradebookStudentPage.tsx index 9fca9a95..de127b22 100644 --- a/devU-client/src/components/pages/gradebookStudentPage.tsx +++ b/devU-client/src/components/pages/gradebookStudentPage.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useState} from 'react' -import {Link, useParams} from 'react-router-dom' +import {Link, useHistory, useParams} from 'react-router-dom' import {useAppSelector} from 'redux/hooks' import {Assignment, AssignmentScore} from 'devu-shared-modules' @@ -9,6 +9,7 @@ import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import ErrorPage from './errorPage' import RequestService from 'services/request.service' +import Button from '@mui/material/Button' import styles from './gradebookPage.scss' @@ -67,6 +68,7 @@ const GradebookStudentPage = () => { const role = useAppSelector((store) => store.roleMode) const { courseId } = useParams<{courseId: string}>() const userId = useAppSelector((store) => store.user.id) + const history = useHistory() useEffect(() => { fetchData() @@ -97,11 +99,15 @@ const GradebookStudentPage = () => { return (
+

Student Gradebook

+
{role.isInstructor() && - Instructor - View} + }
From 03de576a2738bf35aa1951045cef1bd9c17e0b77 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Sat, 27 Apr 2024 13:56:04 -0400 Subject: [PATCH 108/400] Updated container autograder paths Updated container autograder paths --- .../entities/containerAutoGrader/containerAutoGrader.router.ts | 2 +- devU-client/src/components/pages/assignmentDetailPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts index 7e0cf42b..3eafb847 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.router.ts @@ -44,7 +44,7 @@ Router.get('/:id', isAuthorized('assignmentViewAll'), asInt(), ContainerAutoGrad /** * @swagger - * /container-auto-graders/assignment/{id}: + * /course/:courseId/assignment/:assignmentId/container-auto-graders: * get: * summary: Retrieve an assignment's container auto grader * tags: diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index 6d2d9133..e52e606a 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -67,7 +67,7 @@ const AssignmentDetailPage = () => { // const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) // setSubmissionProblemScores(submissionProblemScoresReq) - const containerAutograder = (await RequestService.get(`/api/container-auto-graders/assignment/${assignmentId}`)).pop() ?? null + const containerAutograder = (await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`)).pop() ?? null setContainerAutograder(containerAutograder) } catch (err) { From 1151df031b475f1bf84346eeaa6b52202d414008 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Sat, 27 Apr 2024 14:27:33 -0400 Subject: [PATCH 109/400] Updated Assignment Detail Page to not get CAG Updated Assignment Detail Page to not get CAG --- .../pages/assignmentDetailPage.scss | 5 +++++ .../components/pages/assignmentDetailPage.tsx | 22 +++++++++---------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/devU-client/src/components/pages/assignmentDetailPage.scss b/devU-client/src/components/pages/assignmentDetailPage.scss index d2c4847e..96030d7f 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.scss +++ b/devU-client/src/components/pages/assignmentDetailPage.scss @@ -56,3 +56,8 @@ .submissionCard { margin-bottom : 15px; } + +.fileInput { + margin-top: 10px; + margin-bottom: 10px; +} diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index e52e606a..1bb96393 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -1,7 +1,7 @@ import React, {useEffect, useState} from 'react' import {useHistory, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' -import {Assignment, AssignmentProblem, Submission, ContainerAutoGrader} from 'devu-shared-modules' +import {Assignment, AssignmentProblem, Submission, /*ContainerAutoGrader*/} from 'devu-shared-modules' import RequestService from 'services/request.service' import ErrorPage from './errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' @@ -35,7 +35,7 @@ const AssignmentDetailPage = () => { // const [submissionProblemScores, setSubmissionProblemScores] = useState(new Array()) const [assignment, setAssignment] = useState() - const [containerAutograder, setContainerAutograder] = useState() + // const [containerAutograder, setContainerAutograder] = useState() // const containerAutograder = false; //TODO: Use the above commented out code to get the container autograder useEffect(() => { @@ -67,8 +67,8 @@ const AssignmentDetailPage = () => { // const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) // setSubmissionProblemScores(submissionProblemScoresReq) - const containerAutograder = (await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`)).pop() ?? null - setContainerAutograder(containerAutograder) + // const containerAutograder = (await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`)).pop() ?? null + // setContainerAutograder(containerAutograder) } catch (err) { setError(err) @@ -141,13 +141,13 @@ const AssignmentDetailPage = () => {

{assignment?.name}

- {role.isInstructor() && } - {role.isInstructor() && } + {role.isInstructor() && } + {role.isInstructor() && } {role.isInstructor() && } @@ -175,7 +175,7 @@ const AssignmentDetailPage = () => { )} - {containerAutograder && ()} + {assignment?.disableHandins === true && ()} {assignmentProblems && assignmentProblems.length > 0 ? ( From 263e0acc9d8c834bf7fd18d6e0a474b462923d7c Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sat, 27 Apr 2024 14:35:20 -0400 Subject: [PATCH 110/400] fix bug where return errors when user has no enrolled course --- devU-api/src/entities/course/course.service.ts | 5 +++-- devU-client/src/components/pages/homePage.tsx | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/devU-api/src/entities/course/course.service.ts b/devU-api/src/entities/course/course.service.ts index 749de161..06f5eda2 100644 --- a/devU-api/src/entities/course/course.service.ts +++ b/devU-api/src/entities/course/course.service.ts @@ -42,11 +42,12 @@ export async function listByUser(userId: number) { const upcomingCourses = [] const userCourseIds = userCourses.map(userCourse => userCourse.courseId) - const allCourses = await connect() + + const allCourses = userCourseIds.length > 0 ? await connect() .createQueryBuilder('course') .where('course.id IN (:...ids)', {ids: userCourseIds}) .andWhere('course.deletedAt IS NULL') - .getMany(); + .getMany() : []; for (const course of allCourses) { const userCourse = userCourses.find(userCourse => userCourse.courseId === course.id); diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index d5c47800..c9bd305e 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -27,6 +27,7 @@ const HomePage = () => { const fetchData = async () => { const assignmentMap = new Map>() try { + const assignmentMap = new Map>() const allCourses = await RequestService.get<{ instructorCourses: Course[]; activeCourses: Course[]; From b5b44abce559b6a597c83c9619d943fe0ad200ed Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Sat, 27 Apr 2024 14:56:46 -0400 Subject: [PATCH 111/400] Updated NCAG creation path Updated NCAG creation path --- devU-client/src/components/pages/assignmentDetailPage.tsx | 2 +- .../src/components/pages/nonContainerAutoGraderForm.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index 1bb96393..58499d16 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -122,7 +122,7 @@ const AssignmentDetailPage = () => { setAlert({ autoDelete: true, type: 'success', message: 'Submission Sent' }) // Now you can use submissionResponse.id here - await RequestService.post(`/api/grade/${response.id}`, {} ) + await RequestService.post(`/api/course/${courseId}/grade/${response.id}`, {} ) setAlert({ autoDelete: true, type: 'success', message: 'Submission Graded' }) await fetchData() diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx index 1a327c4f..15c78f3c 100644 --- a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx @@ -60,7 +60,7 @@ const NonContainerAutoGraderForm = () => { } // TODO: Get courseId and update path - RequestService.post(`/api/course/${courseId}/nonContainerAutoGrader/`, finalFormData) + RequestService.post(`/api/course/${courseId}/assignment/${assignmentId}/non-container-auto-graders/`, finalFormData) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Non-Container Auto-Grader Added' }) history.goBack() @@ -107,7 +107,7 @@ const NonContainerAutoGraderForm = () => {
- +
From f75b9a91bf7754ec6af6da73210f85fa0a34af3b Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Sat, 27 Apr 2024 15:35:48 -0400 Subject: [PATCH 112/400] Fixed navbar home link --- devU-client/src/components/misc/navbar.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/devU-client/src/components/misc/navbar.tsx b/devU-client/src/components/misc/navbar.tsx index da7e7f7b..5fe89257 100644 --- a/devU-client/src/components/misc/navbar.tsx +++ b/devU-client/src/components/misc/navbar.tsx @@ -59,7 +59,7 @@ const DynamicBreadcrumb = ({ match }: any) => { } const routes = [ - { path: '/:any', breadcrumb: 'Home'}, //Only appears once at least one directory deep + { path: '/:home', breadcrumb: 'Home'}, //Only appears once at least one directory deep { path: '/user/:userId', breadcrumb: UserBreadcrumb}, @@ -83,14 +83,13 @@ const Navbar = ({breadcrumbs}: any) => { return (
{breadcrumbs.map(({breadcrumb, match}: any, index: number) => { - console.log(match.params.path) - console.log(match.url) - console.log(breadcrumb) + let url = match.url if (excludedPaths.includes(match.params.path)) return <> + if (match.params.home) url = '/' return ( - {breadcrumb} + {breadcrumb} {index < (breadcrumbs.length - 1) ? ' > ' : ''} ) From e98398fd7f19d2c66abfbbb8bc0a5ee0f96f46b5 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Sat, 27 Apr 2024 18:32:02 -0400 Subject: [PATCH 113/400] AssignmentDetailPage now displays form questions based on NCAGS, updated routes --- .../nonContainerAutoGrader.router.ts | 13 +++++----- .../components/pages/assignmentDetailPage.tsx | 25 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts index 88696f15..62187e7c 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.router.ts @@ -13,7 +13,7 @@ const Router = express.Router({ mergeParams: true }) /** * @swagger - * /course/:courseId/assignment/:assignmentId/nonContainerAutoGraders: + * /course/:courseId/assignment/:assignmentId/non-container-auto-graders: * get: * summary: Retrieve a list of all nonContainerAutoGrader with the assignment ID * tags: @@ -28,11 +28,12 @@ const Router = express.Router({ mergeParams: true }) * schema: * type: integer */ -Router.get('/', isAuthorized('assignmentViewAll'), nonContainerQuestions.getByAssignmentId) +Router.get('/', isAuthorized('enrolled'), nonContainerQuestions.getByAssignmentId) +// TODO: Authorization set to enrolled temporarily, add proper authorization in context of retrieving questions on assignment detail page /** * @swagger - * /course/:courseId/assignment/:assignmentId/nonContainerAutoGraders/byId/{id}: + * /course/:courseId/assignment/:assignmentId/non-container-auto-graders/byId/{id}: * get: * summary: Retrieve a single non container auto grader * tags: @@ -51,7 +52,7 @@ Router.get('/byId/:id', isAuthorized('assignmentViewAll'), asInt(), nonContainer /** * @swagger - * /course/:courseId/assignment/:assignmentId/nonContainerAutoGraders: + * /course/:courseId/assignment/:assignmentId/non-container-auto-graders: * post: * summary: Create a question * tags: @@ -69,7 +70,7 @@ Router.post('/', isAuthorized('assignmentEditAll'), validator, nonContainerQuest /** * @swagger - * /course/:courseId/assignment/:assignmentId/nonContainerAutoGraders: + * /course/:courseId/assignment/:assignmentId/non-container-auto-graders: * put: * summary: Update a question * tags: @@ -93,7 +94,7 @@ Router.put('/:id', isAuthorized('assignmentEditAll'), asInt(), validator, nonCon /** * @swagger - * /course/:courseId/assignment/:assignmentId/nonContainerAutoGraders/{id}: + * /course/:courseId/assignment/:assignmentId/non-container-auto-graders/{id}: * delete: * summary: Delete a question * tags: diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index e52e606a..e7fdf0b0 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -1,7 +1,7 @@ import React, {useEffect, useState} from 'react' import {useHistory, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' -import {Assignment, AssignmentProblem, Submission, ContainerAutoGrader} from 'devu-shared-modules' +import {Assignment, AssignmentProblem, Submission, NonContainerAutoGrader} from 'devu-shared-modules' import RequestService from 'services/request.service' import ErrorPage from './errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' @@ -35,8 +35,9 @@ const AssignmentDetailPage = () => { // const [submissionProblemScores, setSubmissionProblemScores] = useState(new Array()) const [assignment, setAssignment] = useState() - const [containerAutograder, setContainerAutograder] = useState() + //const [containerAutograder, setContainerAutograder] = useState() // const containerAutograder = false; //TODO: Use the above commented out code to get the container autograder + const [nonContainerAutograders, setNonContainerAutograders] = useState(new Array ()) useEffect(() => { fetchData() @@ -67,8 +68,12 @@ const AssignmentDetailPage = () => { // const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) // setSubmissionProblemScores(submissionProblemScoresReq) - const containerAutograder = (await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`)).pop() ?? null - setContainerAutograder(containerAutograder) + // const containerAutograder = (await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`)).pop() ?? null + // setContainerAutograder(containerAutograder) + + const nonContainers = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/non-container-auto-graders`) + setNonContainerAutograders(nonContainers) + } catch (err) { setError(err) @@ -122,7 +127,7 @@ const AssignmentDetailPage = () => { setAlert({ autoDelete: true, type: 'success', message: 'Submission Sent' }) // Now you can use submissionResponse.id here - await RequestService.post(`/api/grade/${response.id}`, {} ) + await RequestService.post(`/api/course/${courseId}/grade/${response.id}`, {} ) setAlert({ autoDelete: true, type: 'success', message: 'Submission Graded' }) await fetchData() @@ -157,15 +162,15 @@ const AssignmentDetailPage = () => { - {assignmentProblems && assignmentProblems.length > 0 ? ( - assignmentProblems.map((assignmentProblem, index) => ( + {nonContainerAutograders && nonContainerAutograders.length > 0 ? ( + nonContainerAutograders.map((nonContainer, index) => ( {`Assignment Problem ${index + 1}`} - {assignmentProblem.problemName} - + {nonContainer.question} + )) @@ -175,7 +180,7 @@ const AssignmentDetailPage = () => { )} - {containerAutograder && ()} + {assignmentProblems && assignmentProblems.length > 0 ? ( From 4260b101ca07ba2240b1c369ade1bd8ab056037d Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Sat, 27 Apr 2024 18:32:55 -0400 Subject: [PATCH 114/400] Added instructor account jones to populate-db --- devU-api/scripts/populate-db.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/devU-api/scripts/populate-db.ts b/devU-api/scripts/populate-db.ts index 4abe7de4..6608e94f 100644 --- a/devU-api/scripts/populate-db.ts +++ b/devU-api/scripts/populate-db.ts @@ -116,7 +116,7 @@ async function createNonContainerAutoGrader( } console.log('Creating NonContainerAutoGrader for Assignment Id: ', assignmentId) return await SendPOST( - `/course/${courseId}/assignment/${assignmentId}/nonContainerAutoGrader`, + `/course/${courseId}/assignment/${assignmentId}/non-container-auto-graders`, JSON.stringify(problemData), 'admin' ) @@ -205,6 +205,7 @@ async function runCourseAndSubmission() { //Create users const billy = await fetchToken('billy@buffalo.edu', 'billy') const bob = await fetchToken('bob@buffalo.edu', 'bob') + const jones = await fetchToken('jones@buffalo.edu', 'jones') //Create courses const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024')).id @@ -213,8 +214,10 @@ async function runCourseAndSubmission() { //Enroll students await joinCourse(courseId1, billy, 'student') await joinCourse(courseId1, bob, 'student') + await joinCourse(courseId1, jones, 'instructor') await joinCourse(courseId2, billy, 'student') await joinCourse(courseId2, bob, 'student') + await joinCourse(courseId2, jones, 'instructor') //Create assignments const assignment1 = await createAssignment(courseId1, 'Course1 Assignment 1', 'Quiz') From b90086d9b9c8d9cdff0d80ad265d1c7d11342922 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Sat, 27 Apr 2024 18:35:21 -0400 Subject: [PATCH 115/400] Fixed submission detail and feedback pages --- .../src/entities/submission/submission.router.ts | 2 +- .../submissionScore/submissionScore.router.ts | 3 ++- .../submissionScore/submissionScore.service.ts | 1 + .../components/pages/submissionDetailPage.tsx | 16 ++++++++-------- .../components/pages/submissionFeedbackPage.tsx | 13 ++++++------- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index 18ff18f8..79d30c18 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -37,7 +37,7 @@ const upload = Multer() * type: integer * */ -Router.get('/', /*isAuthorized('submissionViewAll'),*/ SubmissionController.get) +Router.get('/', /*isAuthorized('submissionViewAll'),*/ SubmissionController.getByAssignment) // TODO: Authorization diff --git a/devU-api/src/entities/submissionScore/submissionScore.router.ts b/devU-api/src/entities/submissionScore/submissionScore.router.ts index c532ac06..a6727847 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.router.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.router.ts @@ -22,7 +22,8 @@ const Router = express.Router({ mergeParams: true }) * '200': * description: OK */ -Router.get('/', isAuthorized('scoresViewAll'), SubmissionScoreController.get) +Router.get('/', isAuthorized('enrolled'), SubmissionScoreController.get) +// TODO: Allow retrieving score by submission by the submission owner /** * @swagger diff --git a/devU-api/src/entities/submissionScore/submissionScore.service.ts b/devU-api/src/entities/submissionScore/submissionScore.service.ts index d1832a5d..83167b59 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.service.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.service.ts @@ -42,6 +42,7 @@ export async function list(submissionId?: number) { } export async function listByUser(userId: number, assignmentId: number) { + //This doesn't work, the SubmissionScore entity doesn't have userId and assignmentId. const options: FindManyOptions = { where: { userId: userId, diff --git a/devU-client/src/components/pages/submissionDetailPage.tsx b/devU-client/src/components/pages/submissionDetailPage.tsx index 4aef3d48..8e2efd57 100644 --- a/devU-client/src/components/pages/submissionDetailPage.tsx +++ b/devU-client/src/components/pages/submissionDetailPage.tsx @@ -35,19 +35,19 @@ const SubmissionDetailPage = () => { const fetchData = async () => { try { + const submission = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submissions/${submissionId}`) + setSubmission(submission) + const submissionScore = (await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submission-scores?submission=${submissionId}`)).pop() ?? null setSubmissionScore(submissionScore) - const submissionProblemScores = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submission-problem-scores/${submissionId}`) + const submissionProblemScores = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submission-problem-scores/submission/${submissionId}`) setSubmissionProblemScores(submissionProblemScores) - - const submission = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submissions/${submissionId}`) - setSubmission(submission) - - const assignment = await RequestService.get(`/api/course/${courseId}/assignment/${submission.assignmentId}`) + + const assignment = await RequestService.get(`/api/course/${courseId}/assignments/${submission.assignmentId}`) setAssignment(assignment) - const assignmentProblems = await RequestService.get(`/api/course/${courseId}/assignment/${assignment.id}`) + const assignmentProblems = await RequestService.get(`/api/course/${courseId}/assignment/${assignment.id}/assignment-problems`) setAssignmentProblems(assignmentProblems) } catch (error) { @@ -119,7 +119,7 @@ const SubmissionDetailPage = () => { {submissionScore?.score ?? "N/A"} - View + View Feedback
diff --git a/devU-client/src/components/pages/submissionFeedbackPage.tsx b/devU-client/src/components/pages/submissionFeedbackPage.tsx index e8f0477e..5071e2e5 100644 --- a/devU-client/src/components/pages/submissionFeedbackPage.tsx +++ b/devU-client/src/components/pages/submissionFeedbackPage.tsx @@ -4,7 +4,7 @@ import {Link, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import ErrorPage from './errorPage' -import {Assignment, AssignmentProblem, Submission, SubmissionProblemScore, SubmissionScore} from 'devu-shared-modules' +import {Assignment, AssignmentProblem, SubmissionProblemScore, SubmissionScore} from 'devu-shared-modules' import RequestService from 'services/request.service' const SubmissionFeedbackPage = () => { @@ -19,17 +19,16 @@ const SubmissionFeedbackPage = () => { const fetchData = async () => { try { - const submissionScore = (await RequestService.get( `/api/course/${courseId}/assignment/${assignmentId}/submission-scores?submission=${submissionId}` )).pop() ?? null + const submissionScore = (await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submission-scores?submission=${submissionId}`)).pop() ?? null setSubmissionScore(submissionScore) - const submissionProblemScores = await RequestService.get( `/api/course/${courseId}/assignment/${assignmentId}/submission-problem-scores/${submissionId}` ) + const submissionProblemScores = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submission-problem-scores/submission/${submissionId}`) setSubmissionProblemScores(submissionProblemScores) - const submission = await RequestService.get( `/api/course/${courseId}/assignment/${assignmentId}/submissions/${submissionId}` ) - const assignment = await RequestService.get( `/api/course/${courseId}/assignments/${submission.assignmentId}` ) + const assignment = await RequestService.get( `/api/course/${courseId}/assignments/${assignmentId}` ) setAssignment(assignment) - const assignmentProblems = await RequestService.get( `/api/course/${courseId}/assignment/${assignmentId}/assignment-problems/${assignment.id}` ) + const assignmentProblems = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems`) setAssignmentProblems(assignmentProblems) } catch (error) { @@ -61,7 +60,7 @@ const SubmissionFeedbackPage = () => {
{sps.feedback}
))} - View Submission + View Submission Details ) From 24bafec766a4e317752c3b161f0660c869ac5a42 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Sat, 27 Apr 2024 18:42:15 -0400 Subject: [PATCH 116/400] Removed unused variable from homepage --- devU-client/src/components/pages/homePage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index c9bd305e..5b93a1e6 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -25,7 +25,6 @@ const HomePage = () => { }, []) const fetchData = async () => { - const assignmentMap = new Map>() try { const assignmentMap = new Map>() const allCourses = await RequestService.get<{ From 207751150ef8b9b6948dba75434161702d3abfae Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Sat, 27 Apr 2024 19:39:32 -0400 Subject: [PATCH 117/400] Fixed instructor gradebook --- .../entities/assignmentScore/assignmentScore.controller.ts | 4 ++-- devU-api/src/entities/user/user.router.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts b/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts index 09e9b9f5..4f0c89a8 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts @@ -64,8 +64,8 @@ export async function detailByUser(req: Request, res: Response, next: NextFuncti export async function getByCourse(req: Request, res: Response, next: NextFunction) { try { - const userId = parseInt(req.params.id) - const assignmentScores = await AssignmentScoreService.listByCourse(userId) + const courseId = parseInt(req.params.courseId) + const assignmentScores = await AssignmentScoreService.listByCourse(courseId) const response = assignmentScores.map(as => { if (as) return serialize(as) diff --git a/devU-api/src/entities/user/user.router.ts b/devU-api/src/entities/user/user.router.ts index 61bdaf72..67ab2309 100644 --- a/devU-api/src/entities/user/user.router.ts +++ b/devU-api/src/entities/user/user.router.ts @@ -65,7 +65,8 @@ Router.get('/:id', asInt(), UserController.detail) * schema: * type: string */ -Router.get('/course/:id', isAuthorized('courseViewAll'), asInt(), UserController.getByCourse) +Router.get('/course/:id', /* isAuthorized('courseViewAll'), */ asInt(), UserController.getByCourse) +// TODO: Removed authorization for now, fix later /** * @swagger From bad5f18dabcdcbdaff1d637cb9ed42cdb4218793 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sat, 27 Apr 2024 21:09:55 -0400 Subject: [PATCH 118/400] update assignmentForm and assignmentUpdateForm and courseUpdateForm to have better look --- .../components/pages/assignmentFormPage.scss | 23 +++++-- .../components/pages/assignmentFormPage.tsx | 64 ++++++++++-------- .../components/pages/assignmentUpdatePage.tsx | 66 ++++++++++--------- .../components/pages/courseUpdatePage.scss | 31 --------- .../src/components/pages/courseUpdatePage.tsx | 7 +- .../src/components/pages/coursesFormPage.scss | 1 - devU-client/src/components/pages/homePage.tsx | 1 - 7 files changed, 91 insertions(+), 102 deletions(-) delete mode 100644 devU-client/src/components/pages/courseUpdatePage.scss diff --git a/devU-client/src/components/pages/assignmentFormPage.scss b/devU-client/src/components/pages/assignmentFormPage.scss index 1fa5ad98..492f0420 100644 --- a/devU-client/src/components/pages/assignmentFormPage.scss +++ b/devU-client/src/components/pages/assignmentFormPage.scss @@ -17,11 +17,8 @@ h1 { .datepickerContainer { display: flex; - justify-content: center; - } - - .datepickerContainer > * { - margin: 0 10px; + justify-content: space-between; + width: 100%; } .header { @@ -41,4 +38,18 @@ h1 { border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ margin-left: 10px; /* adjust this value to set the space between the line and the text */ margin-right: 10px; /* add this line to create some space between the line and the button */ -} \ No newline at end of file +} + +.datepicker { + font: inherit; + letter-spacing: inherit; + box-sizing: content-box; + background: none; + height: 1.4375em; + -webkit-tap-highlight-color: transparent; + width: 85%; + animation-duration: 10ms; + padding: 16.5px 14px; + border: 1px solid #bbbbbb; + border-radius: 4px; +} diff --git a/devU-client/src/components/pages/assignmentFormPage.tsx b/devU-client/src/components/pages/assignmentFormPage.tsx index aa2821e4..87ea5f34 100644 --- a/devU-client/src/components/pages/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/assignmentFormPage.tsx @@ -11,8 +11,7 @@ import Button from '@mui/material/Button' import {SET_ALERT} from 'redux/types/active.types' -import styles from '../shared/inputs/textField.scss' -import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; +import {applyMessageToErrorFields, removeClassFromField} from "../../utils/textField.utils"; import {useHistory, useParams} from 'react-router-dom' import formStyles from './assignmentFormPage.scss' @@ -75,9 +74,10 @@ const AssignmentCreatePage = () => { }) .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message - const newFields = applyStylesToErrorFields(err, formData, styles.errorField) + const newFields = new Map() + Array.isArray(err) ? err.map((e) => applyMessageToErrorFields(newFields, e.param, e.msg)) : newFields + setInvalidFields(newFields); - setInvalidFields(newFields) setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => { @@ -90,50 +90,56 @@ const AssignmentCreatePage = () => {

Assignment Form

-

Required Field *

- - + + + + + + + + + +
- +
- +
- +

- - - - - - - - - - - -
+
- +

-
- +
+
diff --git a/devU-client/src/components/pages/assignmentUpdatePage.tsx b/devU-client/src/components/pages/assignmentUpdatePage.tsx index 10a9898f..8162db2f 100644 --- a/devU-client/src/components/pages/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/assignmentUpdatePage.tsx @@ -10,13 +10,12 @@ import RequestService from 'services/request.service' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -// import Button from 'components/shared/inputs/button' import Button from '@mui/material/Button' import formStyles from './assignmentFormPage.scss' import {SET_ALERT} from 'redux/types/active.types' import styles from 'components/shared/inputs/textField.scss' -import {applyStylesToErrorFields, removeClassFromField} from 'utils/textField.utils' +import {applyMessageToErrorFields, applyStylesToErrorFields, removeClassFromField} from 'utils/textField.utils' type UrlParams = { assignmentId: string @@ -126,9 +125,9 @@ const AssignmentUpdatePage = () => { }) .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message - const newFields = applyStylesToErrorFields(err, formData, styles.errorField) - - setInvalidFields(newFields) + const newFields = new Map() + Array.isArray(err) ? err.map((e) => applyMessageToErrorFields(newFields, e.param, e.msg)) : newFields + setInvalidFields(newFields); setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => { @@ -158,7 +157,7 @@ const AssignmentUpdatePage = () => { - +
@@ -167,50 +166,55 @@ const AssignmentUpdatePage = () => {



-

Required Field *

+ + + + + - - + + + +
- +
- +
- +

- - - - - - - - - - - -
+
- +

-
- +
+
diff --git a/devU-client/src/components/pages/courseUpdatePage.scss b/devU-client/src/components/pages/courseUpdatePage.scss deleted file mode 100644 index 7d0f63f8..00000000 --- a/devU-client/src/components/pages/courseUpdatePage.scss +++ /dev/null @@ -1,31 +0,0 @@ -@import 'variables'; - -h1 { - text-align: center; -} - -.form { - display: absolute; - background-color: $list-item-background; - border-radius: 10px; - padding: 20px; - width: 50%; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - } - - .datepickerContainer { - display: flex; - justify-content: center; - } - - .datepickerContainer > * { - margin: 0 10px; - } - - .submitBtn { - display: flex; - justify-content: center; - } \ No newline at end of file diff --git a/devU-client/src/components/pages/courseUpdatePage.tsx b/devU-client/src/components/pages/courseUpdatePage.tsx index 7b8a0322..2364a3d3 100644 --- a/devU-client/src/components/pages/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/courseUpdatePage.tsx @@ -11,13 +11,12 @@ import {ExpressValidationError} from 'devu-shared-modules' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -// import Button from 'components/shared/inputs/button' import Button from '@mui/material/Button' import {SET_ALERT} from 'redux/types/active.types' import styles from '../shared/inputs/textField.scss' import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; -import formStyles from './courseUpdatePage.scss' +import formStyles from './coursesFormPage.scss' type UrlParams = { courseId: string @@ -91,12 +90,14 @@ const CourseUpdatePage = ({}) => {

- +

diff --git a/devU-client/src/components/pages/coursesFormPage.scss b/devU-client/src/components/pages/coursesFormPage.scss index c315ec62..6c283fb1 100644 --- a/devU-client/src/components/pages/coursesFormPage.scss +++ b/devU-client/src/components/pages/coursesFormPage.scss @@ -36,5 +36,4 @@ padding: 16.5px 14px; border: 1px solid #bbbbbb; border-radius: 4px; - } \ No newline at end of file diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage.tsx index c9bd305e..5b93a1e6 100644 --- a/devU-client/src/components/pages/homePage.tsx +++ b/devU-client/src/components/pages/homePage.tsx @@ -25,7 +25,6 @@ const HomePage = () => { }, []) const fetchData = async () => { - const assignmentMap = new Map>() try { const assignmentMap = new Map>() const allCourses = await RequestService.get<{ From 65b590f62ac475ca896dca78370020a952c64e42 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Sat, 27 Apr 2024 21:29:55 -0400 Subject: [PATCH 119/400] Fixed broken assignment-scores route --- devU-api/src/entities/assignmentScore/assignmentScore.router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts index 1fa357f8..d16a5f48 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts @@ -29,7 +29,7 @@ const Router = express.Router({ mergeParams: true }) * schema: * type: integer */ -Router.get('/:id', isAuthorized('scoresViewAll'), asInt(), AssignmentScoreController.getByCourse) +Router.get('/', isAuthorized('scoresViewAll'), AssignmentScoreController.getByCourse) /** * @swagger From 8f8a769586607e27d70c4a514c999c06b7a11ca8 Mon Sep 17 00:00:00 2001 From: keiferms3 Date: Sun, 28 Apr 2024 18:56:57 -0400 Subject: [PATCH 120/400] Manually merged grading-entity-update changes to develop --- devU-api/package-lock.json | 70 +++- devU-api/package.json | 2 + .../containerAutoGrader.service.ts | 179 +++++----- .../containerAutoGrader.validator.ts | 40 +-- .../src/entities/grader/grader.controller.ts | 35 +- devU-api/src/entities/grader/grader.router.ts | 42 ++- .../src/entities/grader/grader.service.ts | 326 ++++++++++++------ .../nonContainerAutoGrader.grader.ts | 14 +- .../entities/submission/submission.service.ts | 2 +- devU-api/src/fileStorage.ts | 18 +- devU-api/src/router/courseData.router.ts | 2 - devU-api/src/router/index.ts | 4 + devU-api/src/tango/tango.service.ts | 75 ++-- .../components/pages/assignmentDetailPage.tsx | 2 +- docker-compose.yml | 5 +- 15 files changed, 503 insertions(+), 313 deletions(-) diff --git a/devU-api/package-lock.json b/devU-api/package-lock.json index 927af045..ffd2d9d7 100644 --- a/devU-api/package-lock.json +++ b/devU-api/package-lock.json @@ -43,6 +43,7 @@ "@types/morgan": "^1.9.2", "@types/multer": "^1.4.7", "@types/node": "^15.12.2", + "@types/node-fetch": "^2.6.11", "@types/passport": "^1.0.6", "@types/passport-strategy": "^0.2.35", "@types/swagger-jsdoc": "^6.0.0", @@ -50,6 +51,7 @@ "husky": "^6.0.0", "jest": "^27.0.4", "lint-staged": "^11.0.0", + "node-fetch": "^2.7.0", "npm-run-all": "^4.1.5", "prettier": "^2.3.0", "rimraf": "^3.0.2", @@ -59,7 +61,7 @@ "typescript": "^4.3.2" }, "engines": { - "node": ">=16" + "node": ">=20" } }, "devu-shared-modules": {}, @@ -2149,6 +2151,30 @@ "integrity": "sha512-dvMUE/m2LbXPwlvVuzCyslTEtQ2ZwuuFClDrOQ6mp2CenCg971719PTILZ4I6bTP27xfFFc+o7x2TkLuun/MPw==", "dev": true }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -9271,6 +9297,48 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/devU-api/package.json b/devU-api/package.json index b0379a50..5a6432e3 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -42,6 +42,7 @@ "@types/morgan": "^1.9.2", "@types/multer": "^1.4.7", "@types/node": "^15.12.2", + "@types/node-fetch": "^2.6.11", "@types/passport": "^1.0.6", "@types/passport-strategy": "^0.2.35", "@types/swagger-jsdoc": "^6.0.0", @@ -49,6 +50,7 @@ "husky": "^6.0.0", "jest": "^27.0.4", "lint-staged": "^11.0.0", + "node-fetch": "^2.7.0", "npm-run-all": "^4.1.5", "prettier": "^2.3.0", "rimraf": "^3.0.2", diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index cf200a89..1a2eba31 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -3,141 +3,118 @@ import { getRepository, IsNull } from 'typeorm' import { ContainerAutoGrader, FileUpload } from 'devu-shared-modules' import ContainerAutoGraderModel from './containerAutoGrader.model' -import FileModel from '../../fileUpload/fileUpload.model' +import FileModel from "../../fileUpload/fileUpload.model"; import { uploadFile, downloadFile } from '../../fileStorage' -import { generateFilename } from '../../utils/fileUpload.utils' +import {generateFilename} from "../../utils/fileUpload.utils"; const connect = () => getRepository(ContainerAutoGraderModel) const fileConn = () => getRepository(FileModel) -async function filesUpload( - bucket: string, - file: Express.Multer.File, - containerAutoGrader: ContainerAutoGrader, - filename: string, - userId: number -) { - const Etag: string = await uploadFile(bucket, file, filename) - const assignmentId = containerAutoGrader.assignmentId - - const fileModel: FileUpload = { - etags: Etag, - fieldName: bucket, - originalName: file.originalname, - filename: filename, - assignmentId: assignmentId, - } - //TODO: This is a temporary fix to get the function to pass. CourseId should be modified in the future - fileModel.courseId = 1 - fileModel.userId = userId - - await fileConn().save(fileModel) - - return Etag -} +async function filesUpload(bucket: string, file: Express.Multer.File, containerAutoGrader: ContainerAutoGrader,filename: string, userId: number) { + const Etag: string = await uploadFile(bucket, file,filename) + const assignmentId = containerAutoGrader.assignmentId + + const fileModel: FileUpload = { + etags: Etag, + fieldName: bucket, + originalName: file.originalname, + filename: filename, + assignmentId: assignmentId, + } + //TODO: This is a temporary fix to get the function to pass. CourseId should be modified in the future + fileModel.courseId = 1 + fileModel.userId = userId + + await fileConn().save(fileModel) -export async function create( - containerAutoGrader: ContainerAutoGrader, - graderInputFile: Express.Multer.File, - makefileInputFile: Express.Multer.File | null = null, - userId: number -) { - const existingContainerAutoGrader = await connect().findOne({ - assignmentId: containerAutoGrader.assignmentId, - deletedAt: IsNull(), - }) - if (existingContainerAutoGrader) - throw new Error( - 'Container Auto Grader already exists for this assignment, please update instead of creating a new one' - ) - const bucket: string = 'graders' - const filename: string = generateFilename(graderInputFile.originalname, userId) - await filesUpload(bucket, graderInputFile, containerAutoGrader, filename, userId) - containerAutoGrader.graderFile = filename - - if (makefileInputFile) { - const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) - await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) - containerAutoGrader.makefileFile = makefileFilename - } - - const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader - return await connect().save({ id, assignmentId, graderFile, makefileFile, autogradingImage, timeout }) + return Etag } -export async function update( - containerAutoGrader: ContainerAutoGrader, - graderInputFile: Express.Multer.File | null = null, - makefileInputFile: Express.Multer.File | null = null, - userId: number -) { - if (!containerAutoGrader.id) throw new Error('Missing Id') - if (graderInputFile) { + + +export async function create(containerAutoGrader: ContainerAutoGrader, graderInputFile: Express.Multer.File, makefileInputFile: Express.Multer.File | null = null, userId: number) { + const existingContainerAutoGrader = await connect().findOne({ assignmentId: containerAutoGrader.assignmentId, deletedAt: IsNull() }) + if (existingContainerAutoGrader) throw new Error('Container Auto Grader already exists for this assignment, please update instead of creating a new one') const bucket: string = 'graders' const filename: string = generateFilename(graderInputFile.originalname, userId) await filesUpload(bucket, graderInputFile, containerAutoGrader, filename, userId) containerAutoGrader.graderFile = filename - } - if (makefileInputFile) { - const bucket: string = 'makefiles' - const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) - await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) - containerAutoGrader.makefileFile = makefileFilename - } + if (makefileInputFile) { + const bucket: string = 'makefiles' + const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) + await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) + containerAutoGrader.makefileFile = makefileFilename + } - const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader - return await connect().update(id, { assignmentId, graderFile, makefileFile, autogradingImage, timeout }) + const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader + return await connect().save({ id, assignmentId, graderFile, makefileFile, autogradingImage, timeout }) +} + +export async function update(containerAutoGrader: ContainerAutoGrader, graderInputFile: Express.Multer.File | null = null, makefileInputFile: Express.Multer.File | null = null, userId: number) { + if (!containerAutoGrader.id) throw new Error('Missing Id') + if (graderInputFile) { + const bucket: string = 'graders' + const filename: string = generateFilename(graderInputFile.originalname, userId) + await filesUpload(bucket, graderInputFile, containerAutoGrader, filename, userId) + containerAutoGrader.graderFile = filename + } + + if (makefileInputFile) { + const bucket: string = 'makefiles' + const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) + await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) + containerAutoGrader.makefileFile = makefileFilename + } + + const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader + return await connect().update(id, { assignmentId, graderFile, makefileFile, autogradingImage, timeout }) } export async function _delete(id: number) { - return await connect().softDelete({ id, deletedAt: IsNull() }) + return await connect().softDelete({ id, deletedAt: IsNull() }) } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOne({ id, deletedAt: IsNull() }) } export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().find({ deletedAt: IsNull() }) } //The grader has not changed to the new function, so the fake function keep here for now to avoid error //But need to be deleted when the grader entity changed to the getGraderByAssignmentId export async function getGraderObjectByAssignmentId(assignmentId: number) { - if (!assignmentId) throw new Error('Missing AssignmentId') - return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) -} - -export async function listByAssignmentId(assignmentId: number) { - if (!assignmentId) throw new Error('Missing AssignmentId') - return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) + if (!assignmentId) throw new Error('Missing AssignmentId') + return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) } -export async function getGraderByAssignmentId(assignmentId: number) { - const containerAutoGraders = await connect().findOne({ assignmentId: assignmentId, deletedAt: IsNull() }) - if (!containerAutoGraders) throw new Error('No containerAutoGraders found') +export async function getGraderByAssignmentId(assignmentId: number){ + const containerAutoGraders = await connect().findOne({ assignmentId: assignmentId, deletedAt: IsNull() }) + if (!containerAutoGraders) return {graderData: null, makefileData: null, autogradingImage: null, timeout: null} - const { graderFile, makefileFile, autogradingImage, timeout } = containerAutoGraders - const graderData = await downloadFile('graders', graderFile) - let makefileData + const { graderFile, makefileFile, autogradingImage, timeout } = containerAutoGraders + const graderData = await downloadFile('graders', graderFile) + let makefileData; - if (makefileFile) { - makefileData = await downloadFile('makefiles', makefileFile) - } else { - makefileData = await downloadFile('makefiles', 'defaultMakefile') // Put actual default makefile name here - } + if (makefileFile){ + makefileData = await downloadFile('makefiles', makefileFile) + }else{ + makefileData = await downloadFile('makefiles', 'defaultMakefile') // Put actual default makefile name here + } - return { graderData, makefileData, autogradingImage, timeout } + return {graderData, makefileData, autogradingImage, timeout} } + export default { - create, - retrieve, - update, - _delete, - list, - getGraderObjectByAssignmentId, - listByAssignmentId, -} + create, + retrieve, + update, + _delete, + list, + getGraderByAssignmentId, + getGraderObjectByAssignmentId, +} \ No newline at end of file diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts index 1f264307..48f381f7 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts @@ -1,41 +1,37 @@ -import { check } from 'express-validator' +import {check} from 'express-validator' import validate from '../../middleware/validator/generic.validator' + const assignmentId = check('assignmentId').isNumeric() -const graderFile = check('graderFile') - .optional({ nullable: true }) - .custom(({ req }) => { +const graderFile = check('graderFile').optional({ nullable: true }).custom(({req}) => { const file = req?.files['grader'] if (file !== null) { - if (file.size <= 0) { - throw new Error('File is empty') - } + if (file.size <= 0) { + throw new Error('File is empty') + } } else { - throw new Error('does not have grader file') + throw new Error('does not have grader file') } - }) - .withMessage('Grader file is required') +}).withMessage('Grader file is required') -const makefileFile = check('makefileFile') - .optional({ nullable: true }) - .custom(({ req }) => { +const makefileFile = check('makefileFile').optional({ nullable: true }).custom(({ req }) => { const file = req.files['makefile'] if (file !== null) { - if (file.size <= 0) { - throw new Error('File is empty') - } + if (file.size <= 0) { + throw new Error('File is empty') + } } - }) +}) + const autogradingImage = check('autogradingImage').isString() -const timeout = check('timeout') - .isNumeric() - .custom(value => value > 0) - .withMessage('Timeout should be a positive integer') +const timeout = check('timeout').isNumeric() +.custom((value) => value > 0) +.withMessage('Timeout should be a positive integer') const validator = [assignmentId, graderFile, makefileFile, autogradingImage, timeout, validate] -export default validator +export default validator \ No newline at end of file diff --git a/devU-api/src/entities/grader/grader.controller.ts b/devU-api/src/entities/grader/grader.controller.ts index e6f4ceda..f8f18370 100644 --- a/devU-api/src/entities/grader/grader.controller.ts +++ b/devU-api/src/entities/grader/grader.controller.ts @@ -2,21 +2,30 @@ import { Request, Response, NextFunction } from 'express' import GraderService from './grader.service' -import { GenericResponse, NotFound } from '../../utils/apiResponse.utils' +import { GenericResponse } from '../../utils/apiResponse.utils' //, NotFound -import { serialize } from './grader.serializer' +//import { serialize } from '../grader/grader.serializer' export async function grade(req: Request, res: Response, next: NextFunction) { - try { - const submissionId = parseInt(req.params.id) - const grade = await GraderService.grade(submissionId) - if (!grade || grade.length === 0) return res.status(404).json(NotFound) - const response = serialize(grade) - - res.status(200).json(response) - } catch (err) { - res.status(400).json(new GenericResponse(err.message)) - } + try { + const submissionId = parseInt(req.params.id) + const response = await GraderService.grade(submissionId) //grade + + res.status(200).json(response) + } catch (err) { + res.status(400).json(new GenericResponse(err.message)) + } +} + +export async function tangoCallback(req: Request, res: Response, next: NextFunction) { + try { + const outputFile = req.params.outputFile + const response = await GraderService.tangoCallback(outputFile) + + res.status(200).json(response) + } catch (err) { + res.status(400).json(new GenericResponse(err.message)) + } } -export default { grade } +export default { grade, tangoCallback } diff --git a/devU-api/src/entities/grader/grader.router.ts b/devU-api/src/entities/grader/grader.router.ts index 7db825f5..48ea4fb8 100644 --- a/devU-api/src/entities/grader/grader.router.ts +++ b/devU-api/src/entities/grader/grader.router.ts @@ -4,19 +4,20 @@ import express from 'express' // Middleware //import validator from './grader.validator' import { asInt } from '../../middleware/validator/generic.validator' -import { isAuthorized } from '../../authorization/authorization.middleware' +//import { isAuthorized } from '../../authorization/authorization.middleware' // Controller import GraderController from './grader.controller' +import { isAuthenticated } from '../../authentication/authentication.middleware' -const Router = express.Router({ mergeParams: true }) +const Router = express.Router() /** * @swagger * tags: * - name: Grader - * description: - * /course/:courseId/grade/{id}: + * description: + * /grade/{id}: * post: * summary: Grade a submission, currently only with non-container autograders * tags: @@ -31,12 +32,29 @@ const Router = express.Router({ mergeParams: true }) * schema: * type: integer */ -Router.post('/:id', isAuthorized('enrolled'), asInt(), GraderController.grade) -// TODO: This one might be tricky. Can every student hit this endpoint for their own submissions? That's probably -// the easiest way to handle it. If not, how does this endpoint get hit when they make a submission without -// sacrificing RESTfullness? eg. Sometimes we want to make submissions without running the grader so the front end -// should have control of this endpoint -// -or- do we only let students hit this once per submission. Regrades must be by someone with permission. Then -// add a second endpoint that is /regrade +Router.post('/:id', asInt(), isAuthenticated, /*isAuthorized('enrolled'),*/ GraderController.grade) +// TODO: Add authorization, 'enrolled' was causing issues -export default Router +/** + * @swagger + * tags: + * - name: Grader callback + * description: + * /grade/callback/{outputFilename}: + * post: + * summary: Not directly called by the user. Tells the API that a container grading job has finished and creates relevant entities from the results. + * tags: + * - Grader + * responses: + * '200': + * description: OK + * parameters: + * - name: outputFilename + * in: path + * required: true + * schema: + * type: string + */ +Router.post('/callback/:outputFile', GraderController.tangoCallback) //Unauthorized route so tango can make callback without needing token + +export default Router \ No newline at end of file diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index 722e0971..edcc6d39 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -5,126 +5,246 @@ import nonContainerAutograderService from '../nonContainerAutoGrader/nonContaine import containerAutograderService from '../containerAutoGrader/containerAutoGrader.service' import assignmentProblemService from '../assignmentProblem/assignmentProblem.service' import assignmentScoreService from '../assignmentScore/assignmentScore.service' +import courseService from '../course/course.service' +import { addJob, createCourse, uploadFile, pollJob } from '../../tango/tango.service' -import { SubmissionScore, SubmissionProblemScore, ContainerAutoGrader, AssignmentScore } from 'devu-shared-modules' +import { SubmissionScore, SubmissionProblemScore, AssignmentScore, Submission, NonContainerAutoGrader, AssignmentProblem } from 'devu-shared-modules' import { checkAnswer } from '../nonContainerAutoGrader/nonContainerAutoGrader.grader' import { serialize as serializeNonContainer } from '../nonContainerAutoGrader/nonContainerAutoGrader.serializer' -import { serialize as serializeContainer } from '../containerAutoGrader/containerAutoGrader.serializer' import { serialize as serializeAssignmentScore } from '../assignmentScore/assignmentScore.serializer' +import { serialize as serializeSubmissionScore} from '../submissionScore/submissionScore.serializer' +import { serialize as serializeSubmission } from '../submission/submission.serializer' +import { serialize as serializeAssignmentProblem } from '../assignmentProblem/assignmentProblem.serializer' +import { downloadFile, initializeMinio } from '../../fileStorage' + +async function grade(submissionId: number) { + const submissionModel = await submissionService.retrieve(submissionId) + if (!submissionModel) throw new Error('Submission not found.') + const submission = serializeSubmission(submissionModel) + + const assignmentId = submission.assignmentId + + const content = JSON.parse(submission.content) + const form = content.form + const filepaths: string[] = content.filepaths + + const nonContainerAutograders = (await nonContainerAutograderService.listByAssignmentId(assignmentId)).map(model => serializeNonContainer(model)) + const assignmentProblems = (await assignmentProblemService.list(assignmentId)).map(model => serializeAssignmentProblem(model)) + + let score = 0 + let feedback = '' + + //Run Non-Container Autograders + const ncagResults = await runNonContainerAutograders(form, nonContainerAutograders, assignmentProblems, submissionId) + score += ncagResults.score + feedback += ncagResults.feedback + + //Run Container Autograders + const cagResults = await runContainerAutograders(filepaths, submission, assignmentId) + const jobResponse = cagResults.jobResponse + const containerGrading = cagResults.containerGrading + + //Grading is finished. Create SubmissionScore and AssignmentScore and save to db. + const scoreObj: SubmissionScore = { + submissionId: submissionId, + score: score, //Sum of all SubmissionProblemScore scores + feedback: feedback //Concatination of SubmissionProblemScore feedbacks + } + submissionScoreService.create(scoreObj) + + //If containergrading is true, tangoCallback handles assignmentScore creation + if (containerGrading === false) { + updateAssignmentScore(submission, score) + return {message: `Noncontainer autograding completed successfully`, submissionScore: scoreObj} + } + return {message: `Autograder successfully added job #${jobResponse?.jobId} to the queue with status message ${jobResponse?.statusMsg}`} +} -export async function grade(submissionId: number) { - const submission = await submissionService.retrieve(submissionId) - if (!submission) return null - - const assignmentId = submission.assignmentId +async function runNonContainerAutograders(form: any, nonContainerAutograders: NonContainerAutoGrader[], assignmentProblems: AssignmentProblem[], submissionId: number) { + let score = 0 + let feedback = '' + + for (const question in form) { + const nonContainerGrader = nonContainerAutograders.find(grader => grader.question === question) + const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) + + if (nonContainerGrader && assignmentProblem) { + const [problemScore, problemFeedback] = checkAnswer(form[question], nonContainerGrader) + score += problemScore + feedback += `${problemFeedback}\n` + + const problemScoreObj: SubmissionProblemScore = { + submissionId: submissionId, + assignmentProblemId: assignmentProblem.id ?? 0, //This should never be undefined, not sure why our types have id set as optional + score: problemScore, + feedback: problemFeedback + } + submissionProblemScoreService.create(problemScoreObj) + } + } + return {score, feedback} +} - const content = JSON.parse(submission.content) - const form = content.form - const filepaths = content.filepaths //Using the field name that was written on the whiteboard for now +async function runContainerAutograders(filepaths: string[], submission: Submission, assignmentId: number) { + let containerGrading = true + let jobResponse = null + + const {graderData, makefileData, autogradingImage, timeout} = await containerAutograderService.getGraderByAssignmentId(assignmentId) + if (!graderData || !makefileData || !autogradingImage || !timeout) { + containerGrading = false + } else { + try { + const bucketName = await courseService.retrieve(submission.courseId).then((course) => { + return course ? (course.number + course.semester + course.id).toLowerCase() : 'submission' + }) + initializeMinio(bucketName) + + const labName = `${bucketName}-${submission.assignmentId}` + const optionFiles = [] + const openResponse = await createCourse(labName) + if (openResponse) { + await uploadFile(labName, graderData, "Graderfile") + await uploadFile(labName, makefileData, "Makefile") + + for (const filepath of filepaths){ + const buffer = await downloadFile(bucketName, filepath) + if (await uploadFile(labName, buffer, filepath)) { + optionFiles.push({localFile: filepath, destFile: filepath}) + } + } + const jobOptions = { + image: autogradingImage, + files: [{localFile: "Graderfile", destFile: "autograde.tar"}, + {localFile: "Makefile", destFile: "Makefile"},] + .concat(optionFiles), + jobName: `${labName}-${submission.id}`, + output_file: `${labName}-${submission.id}-output.txt`, + timeout: timeout, + callback_url: `http://api:3001/course/${submission.courseId}/grade/callback/${labName}-${submission.id}-output.txt` + } + jobResponse = await addJob(labName, jobOptions) + } + } catch (e: any) { + throw new Error(e) + } + } + return {containerGrading, jobResponse} +} - const nonContainerAutograders = await nonContainerAutograderService.listByAssignmentId(assignmentId) - const containerAutograders = await containerAutograderService.listByAssignmentId(assignmentId) - const assignmentProblems = await assignmentProblemService.list(assignmentId) - let score = 0 - let feedback = '' - let allScores = [] //This is the return value, the serializer parses it into a GraderInfo object for the controller to return +async function tangoCallback(outputFile: string) { + //Output filename consists of 4 sections separated by hyphens. + and () only for visual clarity, not a part of the filename + //(course.number+course.semester+course.id)-(assignment.id)-(submission.id)-(output.txt) + const filenameSplit = outputFile.split('-') + const labName = `${filenameSplit[0]}-${filenameSplit[1]}` + const assignmentId = Number(filenameSplit[1]) + const submissionId = Number(filenameSplit[2]) - //Run Non-Container Autograders - for (const question in form) { - const nonContainerGrader = nonContainerAutograders.find(grader => grader.question === question) - const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) + try { + const response = await pollJob(labName, outputFile) + if (typeof response !== 'string') throw 'Autograder output file not found' - if (nonContainerGrader && assignmentProblem) { - const [problemScore, problemFeedback] = checkAnswer(form[question], serializeNonContainer(nonContainerGrader)) - score += problemScore - feedback += problemFeedback + '\n' + try { + const splitResponse = response.split(/\r\n|\r|\n/) + var scoresLine = JSON.parse(splitResponse[splitResponse.length - 2]) + } catch { + throw response + } + const scores = scoresLine.scores + + let score = 0 + const assignmentProblems = await assignmentProblemService.list(assignmentId) + const submissionScoreModel = await submissionScoreService.retrieve(submissionId) + const submissionModel = await submissionService.retrieve(submissionId) + if (!submissionModel) throw "Submission not found." + const submission = serializeSubmission(submissionModel) + + for (const question in scores) { + const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) + if (assignmentProblem) { + const problemScoreObj: SubmissionProblemScore = { + submissionId: submissionId, + assignmentProblemId: assignmentProblem.id, + score: Number(scores[question]), + feedback: `Autograder graded ${assignmentProblem.problemName} for ${Number(scores[question])} points` + } + submissionProblemScoreService.create(problemScoreObj) + score += Number(scores[question]) + } + } + if (submissionScoreModel) { //If noncontainer grading has occured + var submissionScore = serializeSubmissionScore(submissionScoreModel) + submissionScore.score = (submissionScore.score ?? 0) + score + score = submissionScore.score + submissionScore.feedback += `\n${response}` + + await submissionScoreService.update(submissionScore) + } else { //If submission is exclusively container graded + var submissionScore: SubmissionScore = { + submissionId: submissionId, + score: score, //Sum of all SubmissionProblemScore scores + feedback: response //Feedback from Tango + } + await submissionScoreService.create(submissionScore) + } + await updateAssignmentScore(submission, score) - const problemScoreObj: SubmissionProblemScore = { - submissionId: submissionId, - assignmentProblemId: assignmentProblem.id, - score: problemScore, - feedback: problemFeedback, - } - allScores.push(await submissionProblemScoreService.create(problemScoreObj)) + return {submissionScore: submissionScore, outputFile: response} + } catch (e: any) { + callbackFailure(assignmentId, submissionId, e) + throw new Error(e) } - } - - //Run Container Autograders - //Mock functionality, this is not finalized!!!! - if (filepaths) { - for (const filepath of filepaths) { - const containerGrader = containerAutograders.find(grader => grader.autogradingImage === filepath) //PLACEHOLDER, I'm just using autogradingImage temporarily to associate graders to files - - if (containerGrader) { - const gradeResults = await mockContainerCheckAnswer(filepath, serializeContainer(containerGrader)) +} - for (const result of gradeResults) { - const problemScoreObj: SubmissionProblemScore = { - submissionId: submissionId, - assignmentProblemId: 1, //PLACEHOLDER, an assignmentProblem must exist in the db for this to work - score: result.score, - feedback: result.feedback, - } - allScores.push(await submissionProblemScoreService.create(problemScoreObj)) - score += result.score - feedback += result.feedback + '\n' +export async function callbackFailure(assignmentId: number, submissionId: number, file: string) { + const assignmentProblems = await assignmentProblemService.list(assignmentId) + const submissionScoreModel = await submissionScoreService.retrieve(submissionId) + const submissionProblemScoreModels = await submissionProblemScoreService.list(submissionId) + + for (const assignmentProblem of assignmentProblems) { + const submissionProblemScore = submissionProblemScoreModels.find((sps) => sps.assignmentProblemId === assignmentProblem.id) + if (!submissionProblemScore) { //If assignmentProblem hasn't already been graded by noncontainer autograder + const problemScoreObj: SubmissionProblemScore = { + submissionId: submissionId, + assignmentProblemId: assignmentProblem.id, + score: 0, + feedback: 'Autograding failed to complete.' + } + submissionProblemScoreService.create(problemScoreObj) } - } } - } - - //Grading is finished. Create SubmissionScore and AssignmentScore and save to db. - const scoreObj: SubmissionScore = { - submissionId: submissionId, - score: score, //Sum of all SubmissionProblemScore scores - feedback: feedback, //Concatination of SubmissionProblemScore feedbacks - } - allScores.push(await submissionScoreService.create(scoreObj)) - - //PLACEHOLDER AssignmentScore logic. This should be customizable, but for now AssignmentScore will simply equal the latest SubmissionScore - const assignmentScoreModel = await assignmentScoreService.retrieveByUser(submission.assignmentId, submission.userId) - if (assignmentScoreModel) { - //If assignmentScore already exists, update existing entity - const assignmentScore = serializeAssignmentScore(assignmentScoreModel) - assignmentScore.score = score - await assignmentScoreService.update(assignmentScore) - } else { - //Otherwise make a new one - const assignmentScore: AssignmentScore = { - assignmentId: submission.assignmentId, - userId: submission.userId, - score: score, + if (submissionScoreModel) { //If noncontainer grading has occured + var submissionScore = serializeSubmissionScore(submissionScoreModel) + submissionScore.score = (submissionScore.score ?? 0) + submissionScore.feedback += `\n${file}` + + submissionScoreService.update(submissionScore) + } else { //If submission is exclusively container graded + var submissionScore: SubmissionScore = { + submissionId: submissionId, + score: 0, + feedback: file + } + submissionScoreService.create(submissionScore) } - await assignmentScoreService.create(assignmentScore) - } - - return allScores } -//Temporary mock function, delete when the container autograder grading function is written -export async function mockContainerCheckAnswer(file: string, containerAutoGrader: ContainerAutoGrader) { - let gradeResults = [] - - //SubmissionProblemScore 1 - gradeResults.push({ - score: 5, - feedback: 'Grader #' + containerAutoGrader.id + ' graded ' + file + ' problem 1 for 5/5 points', - }) - - //SubmissionProblemScore 2 - gradeResults.push({ - score: 5, - feedback: 'Grader #' + containerAutoGrader.id + ' graded ' + file + ' problem 2 for 5/5 points', - }) - - //SubmissionProblemScore 3 - gradeResults.push({ - score: 10, - feedback: 'Grader #' + containerAutoGrader.id + ' graded ' + file + ' problem 3 for 10/10 points', - }) - - return gradeResults +//Currently just sets assignmentscore to the latest submission. Pulled this function out for easy future modification. +async function updateAssignmentScore(submission: Submission, score: number) { + const assignmentScoreModel = await assignmentScoreService.retrieveByUser(submission.assignmentId, submission.userId) + if (assignmentScoreModel) { //If assignmentScore already exists, update existing entity + const assignmentScore = serializeAssignmentScore(assignmentScoreModel) + assignmentScore.score = score + assignmentScoreService.update(assignmentScore) + + } else { //Otherwise create a new one + const assignmentScore: AssignmentScore = { + assignmentId: submission.assignmentId, + userId: submission.userId, + score: score, + } + assignmentScoreService.create(assignmentScore) + } } -export default { grade } +export default { grade, tangoCallback } \ No newline at end of file diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts index c4642f40..fea1ef50 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts @@ -11,21 +11,15 @@ export function checkAnswer(studentAnswer: string, nonContainerAutoGrader: NonCo const isMatch: boolean = pattern.test(studentAnswer) if (isMatch) { - return [ - nonContainerAutoGrader.score, - `Grader #${nonContainerAutoGrader.id} graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`, - ] + return [nonContainerAutoGrader.score, `Autograder graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`] } } else { // if no regex is set use normal string matching if (studentAnswer === nonContainerAutoGrader.correctString) { - return [ - nonContainerAutoGrader.score, - `Grader #${nonContainerAutoGrader.id} graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`, - ] + return [nonContainerAutoGrader.score, `Autograder graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`] } } // default value to return if all conditions fail to execute // i.e. the answer is incorrect or improperly formatted - return [0, `Grader #${nonContainerAutoGrader.id} graded "${nonContainerAutoGrader.question}" for 0 points`] -} + return [0, `Autograder graded "${nonContainerAutoGrader.question}" for 0 points`] +} \ No newline at end of file diff --git a/devU-api/src/entities/submission/submission.service.ts b/devU-api/src/entities/submission/submission.service.ts index 77ea5f74..bb1e5a30 100644 --- a/devU-api/src/entities/submission/submission.service.ts +++ b/devU-api/src/entities/submission/submission.service.ts @@ -69,4 +69,4 @@ export default { _delete, list, listByAssignment, -} +} \ No newline at end of file diff --git a/devU-api/src/fileStorage.ts b/devU-api/src/fileStorage.ts index 26a7f30d..91390e78 100644 --- a/devU-api/src/fileStorage.ts +++ b/devU-api/src/fileStorage.ts @@ -30,12 +30,12 @@ export async function initializeMinio(inputBucketName?: string) { } }) return - } else { + }else{ for (const bucketName of Object.values(BucketNames)) { const bucketExists = await minioClient.bucketExists(bucketName) - + if (bucketExists) continue - + minioClient.makeBucket(bucketName, 'us-east-1', function (err) { if (err) { throw new Error(`Error creating MinIO bucket '${bucketName}'`) @@ -45,11 +45,12 @@ export async function initializeMinio(inputBucketName?: string) { } } -export async function uploadFile(bucketName: string, file: Express.Multer.File, filename: string): Promise { + +export async function uploadFile(bucketName: string, file: Express.Multer.File, filename:string): Promise { return new Promise((resolve, reject) => { minioClient.putObject(bucketName, filename, file.buffer, (err, etag) => { if (err) { - reject(new Error('File failed to upload because' + err.message)) + reject(new Error('File failed to upload because '+err.message)) } else { resolve(etag.etag) } @@ -63,10 +64,9 @@ export async function downloadFile(bucketName: string, filename: string): Promis minioClient.getObject(bucketName, filename, (err, dataStream) => { if (err) { - reject(new Error('File failed to download from MinIO because' + err.message)) + reject(new Error('File failed to download from MinIO because '+err.message)) } - - dataStream.on('data', (chunk: any) => { + dataStream.on('data', (chunk:any) => { fileData.push(chunk) }) @@ -75,4 +75,4 @@ export async function downloadFile(bucketName: string, filename: string): Promis }) }) }) -} +} \ No newline at end of file diff --git a/devU-api/src/router/courseData.router.ts b/devU-api/src/router/courseData.router.ts index 4026c768..037e8005 100644 --- a/devU-api/src/router/courseData.router.ts +++ b/devU-api/src/router/courseData.router.ts @@ -9,7 +9,6 @@ import assignmentProblem from '../entities/assignmentProblem/assignmentProblem.r import submissionProblemScore from '../entities/submissionProblemScore/submissionProblemScore.router' import deadlineExtensions from '../entities/deadlineExtensions/deadlineExtensions.router' import fileUpload from '../fileUpload/fileUpload.router' -import grader from '../entities/grader/grader.router' import categories from '../entities/category/category.router' import categoryScores from '../entities/categoryScore/categoryScore.router' import courseScores from '../entities/courseScore/courseScore.router' @@ -39,7 +38,6 @@ Router.use('/categories', categories) Router.use('/category-scores', categoryScores) Router.use('/course-scores', courseScores) Router.use('/file-upload', fileUpload) -Router.use('/grade', grader) Router.use('/roles', role) Router.use('/user-courses', userCourse) diff --git a/devU-api/src/router/index.ts b/devU-api/src/router/index.ts index c742b60b..69ce90e4 100644 --- a/devU-api/src/router/index.ts +++ b/devU-api/src/router/index.ts @@ -9,6 +9,7 @@ import logout from '../authentication/logout/logout.router' import status from '../status/status.router' import users from '../entities/user/user.router' import courseRoutes from './courseData.router' +import grader from '../entities/grader/grader.router' import { isAuthenticated } from '../authentication/authentication.middleware' @@ -20,6 +21,9 @@ const Router = express.Router() // TODO: Decide if we want to pull the course object (And return a 404 if not found) in middleware here or let it // happen later... do this check in isAuthorized +//Has to be pulled out of course router due to unauthenticated route +Router.use('/course/:courseId/grade', asInt('courseId'), grader) + Router.use('/course/:courseId', isAuthenticated, asInt('courseId'), courseRoutes) Router.use('/c/:courseId', isAuthenticated, asInt('courseId'), courseRoutes) diff --git a/devU-api/src/tango/tango.service.ts b/devU-api/src/tango/tango.service.ts index 103b91fc..3aa2bc44 100644 --- a/devU-api/src/tango/tango.service.ts +++ b/devU-api/src/tango/tango.service.ts @@ -1,62 +1,69 @@ import './tango.types' +import fetch from 'node-fetch' -const tangoHost = `http://${process.env.TANGO_KEY ?? 'localhost:3000'}` +const tangoHost = `http://tango:3000` const tangoKey = process.env.TANGO_KEY ?? 'test' // for more info https://docs.autolabproject.com/tango-rest/ /** - * Opens a directory for a given course and lab. - * @param courselab - The combination of the course name and the lab name. + * Opens a directory for a given course. + * @param course - The course name. */ -export async function openDirectory(courselab: string): Promise { - const url = `${tangoHost}/open/${tangoKey}/${courselab}/` +export async function createCourse(course: string): Promise { + const url = `${tangoHost}/open/${tangoKey}/${course}/` const response = await fetch(url, { method: 'GET' }) - return response.ok ? await response.json() : null + return response.ok ? await response.json() as OpenResponse : null } /** - * Uploads a file to the server for a given course and lab. - * @param courselab - The combination of the course name and the lab name. + * Uploads a file to the server for a given course. + * @param course - The course name. * @param fileName - The file name, used to identify the file when uploaded * @param file - The file to be uploaded. */ -export async function uploadFile(courselab: string, file: File, fileName: string): Promise { - const url = `${tangoHost}/upload/${tangoKey}/${courselab}/` - const formData = new FormData() - formData.append('file', file) - const response = await fetch(url, { method: 'POST', body: formData, headers: { filename: fileName } }) - return response.ok ? await response.json() : null +export async function uploadFile(course: string, file: Buffer, fileName: string): Promise { + const url = `${tangoHost}/upload/${tangoKey}/${course}/` + const response = await fetch(url, { method: 'POST', body: file, headers: { 'filename': fileName } }) + return response.ok ? await response.json() as UploadResponse : null } /** - * Adds a job to the server for a given course and lab. - * @param courselab - The combination of the course name and the lab name. + * Adds a job to the server for a given course. + * @param course - The course name. * @param job - The job request object. */ -export async function addJob(courselab: string, job: AddJobRequest): Promise { - const url = `${tangoHost}/addJob/${tangoKey}/${courselab}/` +export async function addJob(course: string, job: AddJobRequest): Promise { + const url = `${tangoHost}/addJob/${tangoKey}/${course}/` const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(job), }) - return response.ok ? await response.json() : null + return response.ok ? await response.json() as AddJobResponse : null } /** * Polls the server for the status of a job. - * @param courselab - The combination of the course name and the lab name. + * @param course - The course name. * @param outputFile - The name of the output file. */ -export async function pollJob( - courselab: string, - outputFile: string -): Promise { - const url = `${tangoHost}/poll/${tangoKey}/${courselab}/${outputFile}/` +export async function pollJob(course: string, outputFile: string): Promise { //PollSuccessResponse + const url = `${tangoHost}/poll/${tangoKey}/${course}/${outputFile}/` const response = await fetch(url, { method: 'GET' }) - const data = await response.json() - return response.ok ? (data as PollSuccessResponse) : (data as PollFailureResponse) + + return response.headers.get('Content-Type')?.includes('application/json') + ? (await response.json()) as PollFailureResponse + : (await response.text()) as PollSuccessResponse +} + +/** + * Pings the tango server. + */ +export async function tangoHelloWorld(): Promise { + const url = `${tangoHost}/` + const response = await fetch(url, { method: 'GET' }) + return response.ok } /** @@ -65,7 +72,7 @@ export async function pollJob( export async function getInfo(): Promise { const url = `${tangoHost}/info/${tangoKey}/` const response = await fetch(url, { method: 'GET' }) - return response.ok ? await response.json() : null + return response.ok ? await response.json() as InfoResponse: null } /** @@ -75,7 +82,7 @@ export async function getInfo(): Promise { export async function getPoolInfo(image: string): Promise { const url = `${tangoHost}/pool/${tangoKey}/${image}/` const response = await fetch(url, { method: 'GET' }) - return response.ok ? await response.json() : null + return response.ok ? await response.json() as Object: null } /** @@ -84,18 +91,14 @@ export async function getPoolInfo(image: string): Promise { * @param num - The number of instances to pre-allocate. * @param request - The request object. */ -export async function preallocateInstances( - image: string, - num: number, - request: PreallocRequest -): Promise { +export async function preallocateInstances(image: string, num: number, request: PreallocRequest): Promise { const url = `${tangoHost}/prealloc/${tangoKey}/${image}/${num}/` const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }) - return response.ok ? await response.json() : null + return response.ok ? await response.json() as PreallocResponse: null } /** @@ -107,4 +110,4 @@ export async function getJobs(deadjobs: number): Promise<{} | null> { const url = `${tangoHost}/jobs/${tangoKey}/${deadjobs}/` const response = await fetch(url, { method: 'POST' }) return response.ok ? {} : null -} +} \ No newline at end of file diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignmentDetailPage.tsx index a8d176db..75aa8563 100644 --- a/devU-client/src/components/pages/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignmentDetailPage.tsx @@ -180,7 +180,7 @@ const AssignmentDetailPage = () => { )} - {assignment?.disableHandins === true && ()} + {!(assignment?.disableHandins) && ()} {assignmentProblems && assignmentProblems.length > 0 ? ( diff --git a/docker-compose.yml b/docker-compose.yml index 094b2909..8cd165a8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: - ./devU-api/config:/config api: # Runs the API + container_name: api build: context: . dockerfile: api.Dockerfile @@ -95,7 +96,7 @@ services: # Path to volumes within the Tango container. Does not need to be modified. # - DOCKER_VOLUME_PATH # Modify the below to be the path to volumes on your host machine -# - DOCKER_TANGO_HOST_VOLUME_PATH +# - DOCKER_TANGO_HOST_VOLUME_PATH= depends_on: - redis @@ -104,7 +105,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ./logs/tango/:/var/log/tango/ - ./logs/tangonginx:/var/log/nginx -# - ./Tango/volumes:/opt/TangoService/Tango/volumes +# - ./tango_files:/opt/TangoService/Tango/volumes # certbot: # container_name: certbot From b691541b0fe72feb504df9ed2fa9defb2245874c Mon Sep 17 00:00:00 2001 From: jessehartloff Date: Mon, 29 Apr 2024 00:15:07 -0400 Subject: [PATCH 121/400] add optional Makefile to all populate DB CAGs; add default path for Tango envvar to document what needs to be configured --- devU-api/scripts/populate-db.ts | 4 ++-- docker-compose.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/devU-api/scripts/populate-db.ts b/devU-api/scripts/populate-db.ts index 6608e94f..cdb1b7a4 100644 --- a/devU-api/scripts/populate-db.ts +++ b/devU-api/scripts/populate-db.ts @@ -240,11 +240,11 @@ async function runCourseAndSubmission() { await createNonContainerAutoGrader(courseId2, assignmentId3, problemName2, 10, '/^[^Bb]+$/', true) //ContainerAutoGrader + makefile = new File(['This is a test makefile'], 'makefile') file = new File(['This is a test grader file'], 'grader.code') - await createContainerAutoGrader(courseId1, assignmentId2, 'NewestImageInTheWorld', 300, file) + await createContainerAutoGrader(courseId1, assignmentId2, 'NewestImageInTheWorld', 300, file, makefile) file = new File(['This is another test grader file'], 'grader.code') - makefile = new File(['This is a test makefile'], 'makefile') await createContainerAutoGrader(courseId2, assignmentId4, 'OldestImageInTheWorld', 300, file, makefile) //Create submissions diff --git a/docker-compose.yml b/docker-compose.yml index 8cd165a8..97fa563c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -96,7 +96,7 @@ services: # Path to volumes within the Tango container. Does not need to be modified. # - DOCKER_VOLUME_PATH # Modify the below to be the path to volumes on your host machine -# - DOCKER_TANGO_HOST_VOLUME_PATH= + - DOCKER_TANGO_HOST_VOLUME_PATH=/absolute/path/to/tango_files depends_on: - redis @@ -105,7 +105,7 @@ services: - /var/run/docker.sock:/var/run/docker.sock - ./logs/tango/:/var/log/tango/ - ./logs/tangonginx:/var/log/nginx -# - ./tango_files:/opt/TangoService/Tango/volumes + - ./tango_files:/opt/TangoService/Tango/volumes # certbot: # container_name: certbot From 5a150450c8caeb5f63aaffc775db7ac029d22464 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Mon, 29 Apr 2024 02:15:38 -0400 Subject: [PATCH 122/400] update disable toggle when in course page,(it will auto switch to user's role for the course) --- .../src/components/utils/roleToggle.tsx | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/devU-client/src/components/utils/roleToggle.tsx b/devU-client/src/components/utils/roleToggle.tsx index 8e7b2e95..42907809 100644 --- a/devU-client/src/components/utils/roleToggle.tsx +++ b/devU-client/src/components/utils/roleToggle.tsx @@ -1,22 +1,53 @@ import Switch from '@mui/material/Switch' import FormControlLabel from '@mui/material/FormControlLabel' -import { updateUserRole } from '../../redux/role.redux' -import { useDispatch, useSelector } from 'react-redux' -import { RootState } from '../../redux/store' -import React from 'react' +import {updateUserRole} from '../../redux/role.redux' +import {useDispatch, useSelector} from 'react-redux' +import {RootState} from '../../redux/store' +import React, {useEffect} from 'react' +import {useParams} from "react-router-dom"; +import RequestService from 'services/request.service' +import {UserCourse} from "devu-shared-modules"; const RoleToggle = () => { const dispatch = useDispatch(); const userRole = useSelector((state: RootState) => state.roleMode.userRole); + const [hasCoursePath, setHasCoursePath] = React.useState(false) + const {courseId} = useParams<{ courseId: string }>() + + useEffect(() => { + const path = window.location.pathname + const hasCoursePath = path.includes('/course/') + setHasCoursePath(hasCoursePath) + + if (hasCoursePath) { + RequestService.get(`/api/course/${courseId}/user-courses/users`) + .then((response) => { + const userCourses: UserCourse = response; + if (userCourses.role === 'instructor') { + handleToggle() + dispatch(updateUserRole('Instructor')); + } else { + handleToggle() + dispatch(updateUserRole('Student')); + } + }) + .catch(error => { + console.error(error) + }) + } + + }, [courseId, dispatch]); const handleToggle = () => { const newRole = userRole === 'Student' ? 'Instructor' : 'Student'; dispatch(updateUserRole(newRole)); }; + return ( Date: Mon, 29 Apr 2024 02:24:05 -0400 Subject: [PATCH 123/400] add TODO for the temporary solution --- devU-client/src/components/utils/roleToggle.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devU-client/src/components/utils/roleToggle.tsx b/devU-client/src/components/utils/roleToggle.tsx index 42907809..f8bbc40c 100644 --- a/devU-client/src/components/utils/roleToggle.tsx +++ b/devU-client/src/components/utils/roleToggle.tsx @@ -18,7 +18,7 @@ const RoleToggle = () => { const path = window.location.pathname const hasCoursePath = path.includes('/course/') setHasCoursePath(hasCoursePath) - + //TODO: This is a temporary solution to get the user role for the course. The whole toggle switch should be removed in the future if (hasCoursePath) { RequestService.get(`/api/course/${courseId}/user-courses/users`) .then((response) => { @@ -36,7 +36,7 @@ const RoleToggle = () => { }) } - }, [courseId, dispatch]); + }, []); const handleToggle = () => { const newRole = userRole === 'Student' ? 'Instructor' : 'Student'; From b885ede0eb3ad200ceeacd7b7f08663546f71151 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Fri, 3 May 2024 10:59:18 -0400 Subject: [PATCH 124/400] update front-end pages to specific folder --- .../assignmentDetailPage.scss | 0 .../assignmentDetailPage.tsx | 0 .../pages/courseAssignmentsListPage.scss | 26 ---- .../pages/courseAssignmentsListPage.tsx | 66 -------- .../pages/{ => courses}/courseDetailPage.scss | 0 .../pages/{ => courses}/courseDetailPage.tsx | 0 .../pages/{ => courses}/coursePreviewPage.tsx | 0 .../pages/{ => errorPage}/errorPage.scss | 0 .../pages/{ => errorPage}/errorPage.tsx | 0 .../assignments}/assignmentFormPage.scss | 0 .../assignments}/assignmentFormPage.tsx | 0 .../assignments}/assignmentUpdatePage.tsx | 0 .../containers}/containerAutoGraderForm.scss | 0 .../containers}/containerAutoGraderForm.tsx | 0 .../nonContainerAutoGraderForm.scss | 0 .../nonContainerAutoGraderForm.tsx | 0 .../{ => forms/courses}/courseUpdatePage.tsx | 0 .../{ => forms/courses}/coursesFormPage.scss | 0 .../{ => forms/courses}/coursesFormPage.tsx | 0 .../gradebookInstructorPage.tsx | 0 .../pages/{ => gradebook}/gradebookPage.scss | 0 .../{ => gradebook}/gradebookStudentPage.tsx | 0 .../pages/{ => homePage}/homePage.scss | 0 .../pages/{ => homePage}/homePage.tsx | 0 .../courses}/coursesListPage.scss | 0 .../courses}/coursesListPage.tsx | 0 .../submissionDetailPage.tsx | 0 .../submissionFeedbackPage.tsx | 0 .../components/pages/userCoursesListPage.scss | 26 ---- .../components/pages/userCoursesListPage.tsx | 89 ----------- .../pages/userSubmissionsListPage.scss | 48 ------ .../pages/userSubmissionsListPage.tsx | 143 ------------------ .../pages/{ => users}/userDetailPage.scss | 0 .../pages/{ => users}/userDetailPage.tsx | 0 34 files changed, 398 deletions(-) rename devU-client/src/components/pages/{ => assignments}/assignmentDetailPage.scss (100%) rename devU-client/src/components/pages/{ => assignments}/assignmentDetailPage.tsx (100%) delete mode 100644 devU-client/src/components/pages/courseAssignmentsListPage.scss delete mode 100644 devU-client/src/components/pages/courseAssignmentsListPage.tsx rename devU-client/src/components/pages/{ => courses}/courseDetailPage.scss (100%) rename devU-client/src/components/pages/{ => courses}/courseDetailPage.tsx (100%) rename devU-client/src/components/pages/{ => courses}/coursePreviewPage.tsx (100%) rename devU-client/src/components/pages/{ => errorPage}/errorPage.scss (100%) rename devU-client/src/components/pages/{ => errorPage}/errorPage.tsx (100%) rename devU-client/src/components/pages/{ => forms/assignments}/assignmentFormPage.scss (100%) rename devU-client/src/components/pages/{ => forms/assignments}/assignmentFormPage.tsx (100%) rename devU-client/src/components/pages/{ => forms/assignments}/assignmentUpdatePage.tsx (100%) rename devU-client/src/components/pages/{ => forms/containers}/containerAutoGraderForm.scss (100%) rename devU-client/src/components/pages/{ => forms/containers}/containerAutoGraderForm.tsx (100%) rename devU-client/src/components/pages/{ => forms/containers}/nonContainerAutoGraderForm.scss (100%) rename devU-client/src/components/pages/{ => forms/containers}/nonContainerAutoGraderForm.tsx (100%) rename devU-client/src/components/pages/{ => forms/courses}/courseUpdatePage.tsx (100%) rename devU-client/src/components/pages/{ => forms/courses}/coursesFormPage.scss (100%) rename devU-client/src/components/pages/{ => forms/courses}/coursesFormPage.tsx (100%) rename devU-client/src/components/pages/{ => gradebook}/gradebookInstructorPage.tsx (100%) rename devU-client/src/components/pages/{ => gradebook}/gradebookPage.scss (100%) rename devU-client/src/components/pages/{ => gradebook}/gradebookStudentPage.tsx (100%) rename devU-client/src/components/pages/{ => homePage}/homePage.scss (100%) rename devU-client/src/components/pages/{ => homePage}/homePage.tsx (100%) rename devU-client/src/components/pages/{ => listPages/courses}/coursesListPage.scss (100%) rename devU-client/src/components/pages/{ => listPages/courses}/coursesListPage.tsx (100%) rename devU-client/src/components/pages/{ => submissions}/submissionDetailPage.tsx (100%) rename devU-client/src/components/pages/{ => submissions}/submissionFeedbackPage.tsx (100%) delete mode 100644 devU-client/src/components/pages/userCoursesListPage.scss delete mode 100644 devU-client/src/components/pages/userCoursesListPage.tsx delete mode 100644 devU-client/src/components/pages/userSubmissionsListPage.scss delete mode 100644 devU-client/src/components/pages/userSubmissionsListPage.tsx rename devU-client/src/components/pages/{ => users}/userDetailPage.scss (100%) rename devU-client/src/components/pages/{ => users}/userDetailPage.tsx (100%) diff --git a/devU-client/src/components/pages/assignmentDetailPage.scss b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss similarity index 100% rename from devU-client/src/components/pages/assignmentDetailPage.scss rename to devU-client/src/components/pages/assignments/assignmentDetailPage.scss diff --git a/devU-client/src/components/pages/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx similarity index 100% rename from devU-client/src/components/pages/assignmentDetailPage.tsx rename to devU-client/src/components/pages/assignments/assignmentDetailPage.tsx diff --git a/devU-client/src/components/pages/courseAssignmentsListPage.scss b/devU-client/src/components/pages/courseAssignmentsListPage.scss deleted file mode 100644 index 6c5ccb87..00000000 --- a/devU-client/src/components/pages/courseAssignmentsListPage.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import 'variables'; - -.assignmentName { - color : $text-color; - font-size: 14px; - text-decoration: none; -} - -.button { - justify-content: center; - background-color: $primary; - color: $text-color; - border: 0px; - padding: 10px 40px; - border-radius: 50px; - font-size: 14px; - font-weight: 700; - text-decoration: none; - cursor: pointer; -} - -.header { - color: $text-color; - display: flex; - justify-content: space-between; -} \ No newline at end of file diff --git a/devU-client/src/components/pages/courseAssignmentsListPage.tsx b/devU-client/src/components/pages/courseAssignmentsListPage.tsx deleted file mode 100644 index e6ccbfeb..00000000 --- a/devU-client/src/components/pages/courseAssignmentsListPage.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { useEffect, useState } from 'react' - -import PageWrapper from 'components/shared/layouts/pageWrapper' -import { Assignment } from 'devu-shared-modules' -import RequestService from 'services/request.service' -import ErrorPage from './errorPage' -import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import { Link, useParams } from 'react-router-dom' - -import styles from './courseAssignmentsListPage.scss' -import { useAppSelector } from '../../redux/hooks' - - -const CourseAssignmentsListPage = () => { - - const role = useAppSelector((store) => store.roleMode) - const { courseId } = useParams<{ courseId: string }>() - const [error, setError] = useState(null) - const [loading, setLoading] = useState(true) - const [assignments, setAssignments] = useState(new Array()) - - useEffect(() => { - fetchData() - }, []) - - const fetchData = async () => { - try { - const assignments = await RequestService.get(`/api/course/${courseId}/assignments`) - setAssignments(assignments) - } catch (error) { - setError(error) - } finally { - setLoading(false) - } - } - - if (loading) return - if (error) return - - return ( - -
-

Course Assignments List Page

- -
- {( role.isInstructor() && - Add Assignments - )} -
-
- - - {assignments.map(assignment => ( -
- - {assignment.name} -
- ))} -
- ) -} - -export default CourseAssignmentsListPage diff --git a/devU-client/src/components/pages/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss similarity index 100% rename from devU-client/src/components/pages/courseDetailPage.scss rename to devU-client/src/components/pages/courses/courseDetailPage.scss diff --git a/devU-client/src/components/pages/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx similarity index 100% rename from devU-client/src/components/pages/courseDetailPage.tsx rename to devU-client/src/components/pages/courses/courseDetailPage.tsx diff --git a/devU-client/src/components/pages/coursePreviewPage.tsx b/devU-client/src/components/pages/courses/coursePreviewPage.tsx similarity index 100% rename from devU-client/src/components/pages/coursePreviewPage.tsx rename to devU-client/src/components/pages/courses/coursePreviewPage.tsx diff --git a/devU-client/src/components/pages/errorPage.scss b/devU-client/src/components/pages/errorPage/errorPage.scss similarity index 100% rename from devU-client/src/components/pages/errorPage.scss rename to devU-client/src/components/pages/errorPage/errorPage.scss diff --git a/devU-client/src/components/pages/errorPage.tsx b/devU-client/src/components/pages/errorPage/errorPage.tsx similarity index 100% rename from devU-client/src/components/pages/errorPage.tsx rename to devU-client/src/components/pages/errorPage/errorPage.tsx diff --git a/devU-client/src/components/pages/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss similarity index 100% rename from devU-client/src/components/pages/assignmentFormPage.scss rename to devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss diff --git a/devU-client/src/components/pages/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx similarity index 100% rename from devU-client/src/components/pages/assignmentFormPage.tsx rename to devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx diff --git a/devU-client/src/components/pages/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx similarity index 100% rename from devU-client/src/components/pages/assignmentUpdatePage.tsx rename to devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx diff --git a/devU-client/src/components/pages/containerAutoGraderForm.scss b/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.scss similarity index 100% rename from devU-client/src/components/pages/containerAutoGraderForm.scss rename to devU-client/src/components/pages/forms/containers/containerAutoGraderForm.scss diff --git a/devU-client/src/components/pages/containerAutoGraderForm.tsx b/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.tsx similarity index 100% rename from devU-client/src/components/pages/containerAutoGraderForm.tsx rename to devU-client/src/components/pages/forms/containers/containerAutoGraderForm.tsx diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.scss b/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.scss similarity index 100% rename from devU-client/src/components/pages/nonContainerAutoGraderForm.scss rename to devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.scss diff --git a/devU-client/src/components/pages/nonContainerAutoGraderForm.tsx b/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.tsx similarity index 100% rename from devU-client/src/components/pages/nonContainerAutoGraderForm.tsx rename to devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.tsx diff --git a/devU-client/src/components/pages/courseUpdatePage.tsx b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx similarity index 100% rename from devU-client/src/components/pages/courseUpdatePage.tsx rename to devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx diff --git a/devU-client/src/components/pages/coursesFormPage.scss b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss similarity index 100% rename from devU-client/src/components/pages/coursesFormPage.scss rename to devU-client/src/components/pages/forms/courses/coursesFormPage.scss diff --git a/devU-client/src/components/pages/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx similarity index 100% rename from devU-client/src/components/pages/coursesFormPage.tsx rename to devU-client/src/components/pages/forms/courses/coursesFormPage.tsx diff --git a/devU-client/src/components/pages/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx similarity index 100% rename from devU-client/src/components/pages/gradebookInstructorPage.tsx rename to devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx diff --git a/devU-client/src/components/pages/gradebookPage.scss b/devU-client/src/components/pages/gradebook/gradebookPage.scss similarity index 100% rename from devU-client/src/components/pages/gradebookPage.scss rename to devU-client/src/components/pages/gradebook/gradebookPage.scss diff --git a/devU-client/src/components/pages/gradebookStudentPage.tsx b/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx similarity index 100% rename from devU-client/src/components/pages/gradebookStudentPage.tsx rename to devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx diff --git a/devU-client/src/components/pages/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss similarity index 100% rename from devU-client/src/components/pages/homePage.scss rename to devU-client/src/components/pages/homePage/homePage.scss diff --git a/devU-client/src/components/pages/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx similarity index 100% rename from devU-client/src/components/pages/homePage.tsx rename to devU-client/src/components/pages/homePage/homePage.tsx diff --git a/devU-client/src/components/pages/coursesListPage.scss b/devU-client/src/components/pages/listPages/courses/coursesListPage.scss similarity index 100% rename from devU-client/src/components/pages/coursesListPage.scss rename to devU-client/src/components/pages/listPages/courses/coursesListPage.scss diff --git a/devU-client/src/components/pages/coursesListPage.tsx b/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx similarity index 100% rename from devU-client/src/components/pages/coursesListPage.tsx rename to devU-client/src/components/pages/listPages/courses/coursesListPage.tsx diff --git a/devU-client/src/components/pages/submissionDetailPage.tsx b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx similarity index 100% rename from devU-client/src/components/pages/submissionDetailPage.tsx rename to devU-client/src/components/pages/submissions/submissionDetailPage.tsx diff --git a/devU-client/src/components/pages/submissionFeedbackPage.tsx b/devU-client/src/components/pages/submissions/submissionFeedbackPage.tsx similarity index 100% rename from devU-client/src/components/pages/submissionFeedbackPage.tsx rename to devU-client/src/components/pages/submissions/submissionFeedbackPage.tsx diff --git a/devU-client/src/components/pages/userCoursesListPage.scss b/devU-client/src/components/pages/userCoursesListPage.scss deleted file mode 100644 index 47c94174..00000000 --- a/devU-client/src/components/pages/userCoursesListPage.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import 'variables'; - -.coursesContainer { - display: grid; - grid-template-columns: repeat(auto-fill, 500px); - gap: 20px; /* adjust this value to set the space between the cards */ -} - -.header { - color: $text-color; - display: flex; - align-items: center; -} - -.smallLine { - width: 50px; /* adjust this value to set the length of the small line */ - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-right: 10px; /* adjust this value to set the space between the line and the text */ -} - -.largeLine { - flex-grow: 1; - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-left: 10px; /* adjust this value to set the space between the line and the text */ - margin-right: 10px; /* add this line to create some space between the line and the button */ -} \ No newline at end of file diff --git a/devU-client/src/components/pages/userCoursesListPage.tsx b/devU-client/src/components/pages/userCoursesListPage.tsx deleted file mode 100644 index 78a2f65e..00000000 --- a/devU-client/src/components/pages/userCoursesListPage.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React, {useEffect, useState} from 'react' - -import PageWrapper from 'components/shared/layouts/pageWrapper' -import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import ErrorPage from './errorPage' -import styles from './userCoursesListPage.scss' -import UserCourseListItem from "../listItems/userCourseListItem"; -import {Link} from 'react-router-dom' - -import {useAppSelector} from 'redux/hooks' -import RequestService from 'services/request.service' -import {Assignment, Course, UserCourse} from 'devu-shared-modules' - - -const UserCoursesListPage = () => { - const userId = useAppSelector((store) => store.user.id) - const role = useAppSelector((store) => store.roleMode) - - - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - const [courses, setCourses] = useState(new Array()) - const [assignments, setAssignments] = useState(new Map>()) - - useEffect(() => { - fetchData() - }, []) - - const fetchData = async () => { - try { - const userCourses = await RequestService.get(`/api/courses/user/${userId}`) - const coursePromises = userCourses.map(uc => { - const course = RequestService.get(`/api/courses/${uc.courseId}`) - const assignments = RequestService.get(`/api/course/${uc.courseId}/assignments/released`) - return Promise.all([course, assignments]) - - }) - const result = await Promise.all(coursePromises) - const courses = result.map(([course]) => course) - const assignmentsMap = new Map>() - result.forEach(([course, assignments]) => assignmentsMap.set(course, assignments)) - setCourses(courses) - setAssignments(assignmentsMap) - } catch (error) { - setError(error) - } finally { - setLoading(false) - } - } - - - if (loading) return - if (error) return - - return ( - -
-
-

My Courses

-
- - -
- {role.isInstructor() && ( - - Add Courses - - )} -
-
- -
- {courses.map((course) => ( - - - ))} -
- -
-
-

Previous Courses

-
-
- -
- ) -} - -export default UserCoursesListPage diff --git a/devU-client/src/components/pages/userSubmissionsListPage.scss b/devU-client/src/components/pages/userSubmissionsListPage.scss deleted file mode 100644 index 2e5a0758..00000000 --- a/devU-client/src/components/pages/userSubmissionsListPage.scss +++ /dev/null @@ -1,48 +0,0 @@ -@import 'variables'; - -.filters { - display: flex; - justify-content: flex-end; - - margin-bottom: 1rem; - - gap: 0.25rem; -} - -.dropdown { - width: 200px; -} - -.header { - color: $text-color; - display: flex; - justify-content: space-between; - align-items: center; -} - -@media (max-width: 700px) { - .dropdown { - width: 100%; - } - - .header { - display: block; - } - - .filters { - flex-direction: column; - } -} - -.smallLine { - width: 50px; /* adjust this value to set the length of the small line */ - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-right: 10px; /* adjust this value to set the space between the line and the text */ -} - -.largeLine { - flex-grow: 1; - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-left: 10px; /* adjust this value to set the space between the line and the text */ - margin-right: 10px; /* add this line to create some space between the line and the button */ -} \ No newline at end of file diff --git a/devU-client/src/components/pages/userSubmissionsListPage.tsx b/devU-client/src/components/pages/userSubmissionsListPage.tsx deleted file mode 100644 index 8d2447d8..00000000 --- a/devU-client/src/components/pages/userSubmissionsListPage.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import React, {useEffect, useState} from 'react' -import {Assignment, Course, Submission} from 'devu-shared-modules' - -import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import PageWrapper from 'components/shared/layouts/pageWrapper' -import SubmissionListItem from 'components/listItems/submissionListItem' -import Dropdown, {Option} from 'components/shared/inputs/dropdown' -import ErrorPage from './errorPage' - -import RequestService from 'services/request.service' -import LocalStorageService from 'services/localStorage.service' - -import styles from './userSubmissionsListPage.scss' -import {useAppSelector} from 'redux/hooks' - -const ORDER_BY_STORAGE_KEY = 'submissions_order_by' -const GROUP_BY_STORAGE_KEY = 'submissions_group_by' - -type OrderBy = 'submittedAt' | 'dueAt' | 'assignmentName' | 'courseName' -type GroupBy = 'id' | 'assignmentId' | 'courseId' - -const orderByOptions: Option[] = [ - { label: 'Submitted At', value: 'submittedAt' }, - { label: 'Due At', value: 'dueAt' }, - { label: 'Assignment', value: 'assignmentName' }, - { label: 'Course', value: 'courseName' }, -] - -const groupByOptions: Option[] = [ - { label: 'None', value: 'id' }, - { label: 'Assignment', value: 'assignmentId' }, - { label: 'Course', value: 'courseId' }, -] - -const UserCoursesListPage = () => { - const defaultOrderBy = LocalStorageService.get(ORDER_BY_STORAGE_KEY) || 'submittedAt' - const defaultGroupBy = LocalStorageService.get(GROUP_BY_STORAGE_KEY) || 'id' - - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - const [submissions, setSubmissions] = useState(new Array()) - const [courses, setCourses] = useState>({}) - const [assignments, setAssignments] = useState>({}) - - const [orderBy, setOrderBy] = useState(defaultOrderBy) - const [groupBy, setGroupBy] = useState(defaultGroupBy) - - const userId = useAppSelector((store) => store.user.id) - - useEffect(() => { - fetchData(orderBy, groupBy) - }, []) - - const fetchData = async (orderBy: OrderBy, groupBy: GroupBy) => { - try { - const submissions = await RequestService.get( - `/api/submissions?orderBy=${orderBy}&groupBy=${groupBy}&user=${userId}`, - ) - - const courseRequests = submissions.map((s) => RequestService.get(`/api/courses/${s.courseId}`)) - const assignmentRequests = submissions.map((s) => - // TODO: Get courseId and update path - RequestService.get(`/api/course/${s.courseId}/assignments/${s.assignmentId}`), - ) - - const courses = await Promise.all(courseRequests) - const assignments = await Promise.all(assignmentRequests) - - // Mapify course & assignment ids so we can look them up more easilly with our submission - const courseMap: Record = {} - for (const course of courses) courseMap[course.id || ''] = course - - const assignmentMap: Record = {} - for (const assignment of assignments) assignmentMap[assignment.id || ''] = assignment - - setSubmissions(submissions) - setAssignments(assignmentMap) - setCourses(courseMap) - } catch (error) { - setError(error) - } finally { - setLoading(false) - } - } - - const handleFilterChange = (updatedOrderBy: OrderBy) => { - setOrderBy(updatedOrderBy) - fetchData(updatedOrderBy, groupBy) - - LocalStorageService.set(ORDER_BY_STORAGE_KEY, updatedOrderBy) - } - - const handleGroupByChange = (updatedGroupBy: GroupBy) => { - setGroupBy(updatedGroupBy) - fetchData(orderBy, updatedGroupBy) - - LocalStorageService.set(GROUP_BY_STORAGE_KEY, updatedGroupBy) - } - - if (loading) return - if (error) return - - const defaultOrderByOption = orderByOptions.find((o) => o.value === orderBy) - const defaultGroupByOption = groupByOptions.find((o) => o.value === groupBy) - - - return ( - -
-
- -

My Submissions

-
-
- - -
-
- {submissions.map((submission) => ( - - ))} -
- ) -} - -export default UserCoursesListPage diff --git a/devU-client/src/components/pages/userDetailPage.scss b/devU-client/src/components/pages/users/userDetailPage.scss similarity index 100% rename from devU-client/src/components/pages/userDetailPage.scss rename to devU-client/src/components/pages/users/userDetailPage.scss diff --git a/devU-client/src/components/pages/userDetailPage.tsx b/devU-client/src/components/pages/users/userDetailPage.tsx similarity index 100% rename from devU-client/src/components/pages/userDetailPage.tsx rename to devU-client/src/components/pages/users/userDetailPage.tsx From 14c99cd3655c54f8c3aeadfe71b04373a8c25609 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Fri, 3 May 2024 11:00:29 -0400 Subject: [PATCH 125/400] update: when update forms, the related information will display on the field --- devU-client/src/components/app.tsx | 2 +- .../src/components/authenticatedRouter.tsx | 36 ++--- .../src/components/forms/editUserForm.tsx | 6 +- devU-client/src/components/misc/navbar.tsx | 2 +- .../assignments/assignmentDetailPage.scss | 4 + .../assignments/assignmentDetailPage.tsx | 11 +- .../pages/courses/courseDetailPage.tsx | 2 +- .../pages/courses/coursePreviewPage.tsx | 8 +- .../forms/assignments/assignmentFormPage.tsx | 2 +- .../assignments/assignmentProblemFormPage.tsx | 92 +++++++++++ .../assignments/assignmentUpdatePage.tsx | 147 +++++++----------- .../containers/containerAutoGraderForm.tsx | 6 +- .../containers/nonContainerAutoGraderForm.tsx | 35 ++++- .../pages/forms/courses/courseUpdatePage.tsx | 42 +++-- .../pages/forms/courses/coursesFormPage.tsx | 7 +- .../gradebook/gradebookInstructorPage.tsx | 2 +- .../pages/gradebook/gradebookStudentPage.tsx | 2 +- .../components/pages/homePage/homePage.tsx | 4 +- .../listPages/courses/coursesListPage.tsx | 6 +- .../submissions/submissionDetailPage.tsx | 6 +- .../submissions/submissionFeedbackPage.tsx | 2 +- .../components/pages/users/userDetailPage.tsx | 2 +- .../components/shared/inputs/textField.tsx | 18 +-- 23 files changed, 271 insertions(+), 173 deletions(-) create mode 100644 devU-client/src/components/pages/forms/assignments/assignmentProblemFormPage.tsx diff --git a/devU-client/src/components/app.tsx b/devU-client/src/components/app.tsx index 3413fb81..eb8f611e 100644 --- a/devU-client/src/components/app.tsx +++ b/devU-client/src/components/app.tsx @@ -7,7 +7,7 @@ import { SET_USER } from 'redux/types/user.types' import Alert from 'components/shared/alerts/alert' import AuthenticatedRouter from 'components/authenticatedRouter' import AuthProvider from 'components/pages/authProvider' -import ErrorPage from 'components/pages/errorPage' +import ErrorPage from 'components/pages/errorPage/errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import history from 'services/history.service' diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index 6c3f2bfb..b1ee0ff6 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -1,24 +1,24 @@ import React from 'react' import {Route, Switch} from 'react-router-dom' -import AssignmentDetailPage from 'components/pages/assignmentDetailPage' -import AssignmentCreatePage from 'components/pages/assignmentFormPage' -import AssignmentUpdatePage from 'components/pages/assignmentUpdatePage' -import CourseAssignmentsListPage from 'components/pages/courseAssignmentsListPage' -import CourseDetailPage from 'components/pages/courseDetailPage' -import EditCourseFormPage from 'components/pages/coursesFormPage' -import CourseUpdatePage from 'components/pages/courseUpdatePage' -import HomePage from 'components/pages/homePage' +import AssignmentDetailPage from 'components/pages/assignments/assignmentDetailPage' +import AssignmentCreatePage from 'components/pages/forms/assignments/assignmentFormPage' +import AssignmentUpdatePage from 'components/pages/forms/assignments/assignmentUpdatePage' +import CourseDetailPage from 'components/pages/courses/courseDetailPage' +import EditCourseFormPage from 'components/pages/forms/courses/coursesFormPage' +import CourseUpdatePage from 'components/pages/forms/courses/courseUpdatePage' +import HomePage from 'components/pages/homePage/homePage' import NotFoundPage from 'components/pages/notFoundPage' -import SubmissionDetailPage from 'components/pages/submissionDetailPage' -import UserDetailPage from 'components/pages/userDetailPage' -import NonContainerAutoGraderForm from './pages/nonContainerAutoGraderForm' -import GradebookStudentPage from './pages/gradebookStudentPage' -import GradebookInstructorPage from './pages/gradebookInstructorPage' -import SubmissionFeedbackPage from './pages/submissionFeedbackPage' -import ContainerAutoGraderForm from './pages/containerAutoGraderForm' -import CoursePreviewPage from './pages/coursePreviewPage' -import CoursesListPage from "./pages/coursesListPage"; +import SubmissionDetailPage from 'components/pages/submissions/submissionDetailPage' +import UserDetailPage from 'components/pages/users/userDetailPage' +import NonContainerAutoGraderForm from './pages/forms/containers/nonContainerAutoGraderForm' +import GradebookStudentPage from './pages/gradebook/gradebookStudentPage' +import GradebookInstructorPage from './pages/gradebook/gradebookInstructorPage' +import SubmissionFeedbackPage from './pages/submissions/submissionFeedbackPage' +import ContainerAutoGraderForm from './pages/forms/containers/containerAutoGraderForm' +import CoursePreviewPage from './pages/courses/coursePreviewPage' +import CoursesListPage from "./pages/listPages/courses/coursesListPage"; +import AssignmentProblemFormPage from './pages/forms/assignments/assignmentProblemFormPage' const AuthenticatedRouter = () => ( @@ -34,7 +34,6 @@ const AuthenticatedRouter = () => ( - @@ -42,6 +41,7 @@ const AuthenticatedRouter = () => ( + diff --git a/devU-client/src/components/forms/editUserForm.tsx b/devU-client/src/components/forms/editUserForm.tsx index 1f935776..9ecc5afb 100644 --- a/devU-client/src/components/forms/editUserForm.tsx +++ b/devU-client/src/components/forms/editUserForm.tsx @@ -49,10 +49,10 @@ const EditUserForm = ({ user, onSubmit }: Props) => { id='preferredName' defaultValue={user.preferredName} /> - - + + ) diff --git a/devU-client/src/components/misc/navbar.tsx b/devU-client/src/components/misc/navbar.tsx index 5fe89257..cbc24074 100644 --- a/devU-client/src/components/misc/navbar.tsx +++ b/devU-client/src/components/misc/navbar.tsx @@ -84,7 +84,7 @@ const Navbar = ({breadcrumbs}: any) => {
{breadcrumbs.map(({breadcrumb, match}: any, index: number) => { let url = match.url - if (excludedPaths.includes(match.params.path)) return <> + if (excludedPaths.includes(match.params.path)) return
if (match.params.home) url = '/' return ( diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss index 96030d7f..7f46726f 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss @@ -39,6 +39,8 @@ margin-top: 10px; margin-left: 10px; margin-right: 10px; + background-color: $list-item-background; + color:$list-item-background } .accordionDetails { @@ -46,6 +48,8 @@ flex-direction: column; align-items: center; justify-content: center; + background-color: $list-item-background; + color:$text-color; } .textField { diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 75aa8563..8f7f39c8 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -3,7 +3,7 @@ import {useHistory, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' import {Assignment, AssignmentProblem, Submission, NonContainerAutoGrader, /*ContainerAutoGrader*/} from 'devu-shared-modules' import RequestService from 'services/request.service' -import ErrorPage from './errorPage' +import ErrorPage from '../errorPage/errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import {useActionless, useAppSelector} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' @@ -16,7 +16,7 @@ import Button from '@mui/material/Button' import Grid from '@mui/material/Unstable_Grid2' import styles from './assignmentDetailPage.scss' -import {prettyPrintDateTime} from "../../utils/date.utils"; +import {prettyPrintDateTime} from "../../../utils/date.utils"; const AssignmentDetailPage = () => { const [setAlert] = useActionless(SET_ALERT) @@ -75,7 +75,7 @@ const AssignmentDetailPage = () => { setNonContainerAutograders(nonContainers) - } catch (err) { + } catch (err:any) { setError(err) const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message setAlert({autoDelete: false, type: 'error', message}) @@ -153,6 +153,9 @@ const AssignmentDetailPage = () => { {role.isInstructor() && } + {role.isInstructor() && } {role.isInstructor() && } @@ -166,7 +169,7 @@ const AssignmentDetailPage = () => { nonContainerAutograders.map((nonContainer, index) => ( - {`Assignment Problem ${index + 1}`} + {`Assignment Problem ${index + 1}`} {nonContainer.question} diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index d788531f..80331b25 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -17,7 +17,7 @@ import Stack from '@mui/material/Stack' import styles from './courseDetailPage.scss' -import {SET_ALERT} from "../../redux/types/active.types"; +import {SET_ALERT} from "../../../redux/types/active.types"; import {useActionless, useAppSelector} from "redux/hooks"; const CourseDetailPage = () => { diff --git a/devU-client/src/components/pages/courses/coursePreviewPage.tsx b/devU-client/src/components/pages/courses/coursePreviewPage.tsx index bef2cd3e..9aa40aca 100644 --- a/devU-client/src/components/pages/courses/coursePreviewPage.tsx +++ b/devU-client/src/components/pages/courses/coursePreviewPage.tsx @@ -4,11 +4,11 @@ import {Course, UserCourse} from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' -import {useActionless, useAppSelector} from "../../redux/hooks" -import RequestService from "../../services/request.service" -import {SET_ALERT} from "../../redux/types/active.types" +import {useActionless, useAppSelector} from "../../../redux/hooks" +import RequestService from "../../../services/request.service" +import {SET_ALERT} from "../../../redux/types/active.types" import {useHistory, useParams} from "react-router-dom" -import LoadingOverlay from "../shared/loaders/loadingOverlay" +import LoadingOverlay from "../../shared/loaders/loadingOverlay" import Button from '@mui/material/Button' import styles from "./courseDetailPage.scss" diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index 87ea5f34..b60984bf 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -11,7 +11,7 @@ import Button from '@mui/material/Button' import {SET_ALERT} from 'redux/types/active.types' -import {applyMessageToErrorFields, removeClassFromField} from "../../utils/textField.utils"; +import {applyMessageToErrorFields, removeClassFromField} from "../../../../utils/textField.utils"; import {useHistory, useParams} from 'react-router-dom' import formStyles from './assignmentFormPage.scss' diff --git a/devU-client/src/components/pages/forms/assignments/assignmentProblemFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentProblemFormPage.tsx new file mode 100644 index 00000000..34b32608 --- /dev/null +++ b/devU-client/src/components/pages/forms/assignments/assignmentProblemFormPage.tsx @@ -0,0 +1,92 @@ +import React, { useState} from 'react' +import {ExpressValidationError} from 'devu-shared-modules' +import 'react-datepicker/dist/react-datepicker.css' +import {useHistory, useParams} from 'react-router-dom' + +import PageWrapper from 'components/shared/layouts/pageWrapper' + +import RequestService from 'services/request.service' + +import {useActionless} from 'redux/hooks' +import TextField from 'components/shared/inputs/textField' +import Button from '@mui/material/Button' +import formStyles from './assignmentFormPage.scss' + +import {SET_ALERT} from 'redux/types/active.types' +import styles from 'components/shared/inputs/textField.scss' +import { applyStylesToErrorFields, removeClassFromField} from 'utils/textField.utils' + + + + +const AssignmentProblemFormPage = () => { + const [setAlert] = useActionless(SET_ALERT) + const { courseId, assignmentId } = useParams<{courseId : string ;assignmentId : string}>() + const history = useHistory() + const [FormData,setFormData] = useState({ + assignmentId: assignmentId, + problemName: '', + maxScore: '', + }) + + const [InvalidFields, setnvalidFields] = useState(new Map()) + + const handleProblemChange = (value : String, e : React.ChangeEvent) => { + const key = e.target.id + const newInvalidFields = removeClassFromField(InvalidFields, key) + setnvalidFields(newInvalidFields) + setFormData(prevState => ({...prevState,[key] : value})) + } + + const handleSubmit = () => { + const finalProblemFormData = { + assignmentId: parseInt(FormData.assignmentId), + problemName: FormData.problemName, + maxScore: parseInt(FormData.maxScore), + } + RequestService.post(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems/`, finalProblemFormData) + .then(() => { + setAlert({ autoDelete: true, type: 'success', message: 'Assignment Problem Added' }) + history.goBack() + }) + .catch((err: ExpressValidationError[] | Error) => { + const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message + const newFields = applyStylesToErrorFields(err, FormData, styles.errorField) + + setnvalidFields(newFields) + setAlert({ autoDelete: false, type: 'error', message }) + }) + .finally(() => { + setFormData({ + assignmentId: assignmentId, + problemName: '', + maxScore: '', + }) + }) + } + + return( +
+
+

Assignment Detail Update

+
+
+
+ + + + + + +
+ +
+
+
) +} + +export default AssignmentProblemFormPage \ No newline at end of file diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index 8162db2f..7370f968 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -1,5 +1,5 @@ -import React, {useState} from 'react' -import {ExpressValidationError} from 'devu-shared-modules' +import React, {useEffect, useState} from 'react' +import {ExpressValidationError, Assignment} from 'devu-shared-modules' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' import {useHistory, useParams} from 'react-router-dom' @@ -14,8 +14,7 @@ import Button from '@mui/material/Button' import formStyles from './assignmentFormPage.scss' import {SET_ALERT} from 'redux/types/active.types' -import styles from 'components/shared/inputs/textField.scss' -import {applyMessageToErrorFields, applyStylesToErrorFields, removeClassFromField} from 'utils/textField.utils' +import {applyMessageToErrorFields, removeClassFromField} from 'utils/textField.utils' type UrlParams = { assignmentId: string @@ -24,69 +23,25 @@ type UrlParams = { const AssignmentUpdatePage = () => { const { assignmentId } = useParams() as UrlParams const { courseId } = useParams<{courseId : string}>() - - - const [toggleForm,setToggleForm] = useState(false) - const [problemFormData,setProblemFormData] = useState({ - assignmentId: assignmentId, - problemName: '', - maxScore: '', - }) - const [problemInvalidFields, setProblemInvalidFields] = useState(new Map()) - - const toggleProblemForm = () => { setToggleForm(!toggleForm) } - - const handleSubmit = () => { - const finalProblemFormData = { - assignmentId: parseInt(problemFormData.assignmentId), - problemName: problemFormData.problemName, - maxScore: parseInt(problemFormData.maxScore), - } - - RequestService.post(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems/`, finalProblemFormData) - .then(() => { - setAlert({ autoDelete: true, type: 'success', message: 'Assignment Problem Added' }) - }) - .catch((err: ExpressValidationError[] | Error) => { - const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message - const newFields = applyStylesToErrorFields(err, problemFormData, styles.errorField) - - setProblemInvalidFields(newFields) - setAlert({ autoDelete: false, type: 'error', message }) - }) - .finally(() => { - setProblemFormData({ - assignmentId: assignmentId, - problemName: '', - maxScore: '', - }) - }) - } - - const handleProblemChange = (value : String, e : React.ChangeEvent) => { - const key = e.target.id - const newInvalidFields = removeClassFromField(problemInvalidFields, key) - setProblemInvalidFields(newInvalidFields) - setProblemFormData(prevState => ({...prevState,[key] : value})) - } - - const [setAlert] = useActionless(SET_ALERT) + const [startDate, setStartDate] = useState(new Date()) + const [endDate, setEndDate] = useState(new Date()) + const [dueDate, setDueDate] = useState(new Date()) + const [invalidFields, setInvalidFields] = useState(new Map()) + const history = useHistory() - const [formData, setFormData] = useState({ - courseId: courseId, + const [formData, setFormData] = useState({ + courseId: parseInt(courseId), name: '', - categoryName: null, - description: null, + categoryName: '', + description: '', maxFileSize: 0, maxSubmissions: null, disableHandins: false, + dueDate: "", + endDate: "", + startDate: "", }) - const [startDate, setStartDate] = useState(new Date()) - const [endDate, setEndDate] = useState(new Date()) - const [dueDate, setDueDate] = useState(new Date()) - const [invalidFields, setInvalidFields] = useState(new Map()) - const history = useHistory() const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id @@ -102,6 +57,27 @@ const AssignmentUpdatePage = () => { const handleEndDateChange = (date : Date) => {setEndDate(date)} const handleDueDateChange = (date: Date) => {setDueDate(date)} + useEffect(() => { + RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`).then((res) => { + const assignment:Assignment =res + setFormData({ + name: assignment.name, + categoryName: assignment.categoryName, + description: assignment.description, + maxFileSize: assignment.maxFileSize, + maxSubmissions: assignment.maxSubmissions, + disableHandins: assignment.disableHandins, + startDate: assignment.startDate, + endDate: assignment.endDate, + dueDate: assignment.dueDate, + courseId: assignment.courseId + }); + setStartDate(new Date(res.startDate)); + setDueDate(new Date(res.dueDate)); + setEndDate(new Date(res.endDate)); + }); + }, []); + const handleAssignmentUpdate = () => { const finalFormData = { courseId: formData.courseId, @@ -140,50 +116,31 @@ const AssignmentUpdatePage = () => {

Assignment Detail Update

- -
- - {toggleForm && ( -
-

Required Field *

- - - - - - - -
- -
-
- )} - -



+ invalidated={!!invalidFields.get("name")} helpText={invalidFields.get("name")} + defaultValue={formData.name}/> + invalidated={!!invalidFields.get("categoryName")} + helpText={invalidFields.get("categoryName")} + defaultValue={formData.categoryName}/> + invalidated={!!invalidFields.get("description")} + helpText={invalidFields.get("description")} + defaultValue={formData.description?formData.description:undefined}/> + invalidated={!!invalidFields.get("maxFileSize")} + helpText={invalidFields.get("maxFileSize")} + defaultValue={formData.maxFileSize?formData.maxFileSize.toString():""}/> + invalidated={!!invalidFields.get("maxSubmission")} + helpText={invalidFields.get("maxSubmission")} + defaultValue={formData.maxSubmissions?(formData.maxSubmissions).toString():undefined}/>
@@ -207,13 +164,13 @@ const AssignmentUpdatePage = () => {
+ onChange={handleCheckbox} className={formStyles.submitBtn}/>

-
diff --git a/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.tsx b/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.tsx index 13d7c0e1..3d4555ca 100644 --- a/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.tsx @@ -5,9 +5,9 @@ import TextField from 'components/shared/inputs/textField' import {useActionless} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' import RequestService from 'services/request.service' -import {ExpressValidationError} from "../../../devu-shared-modules"; -import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; -import textStyles from "../shared/inputs/textField.scss"; +import {ExpressValidationError} from "devu-shared-modules"; +import {applyStylesToErrorFields, removeClassFromField} from "../../../../utils/textField.utils"; +import textStyles from "../../../shared/inputs/textField.scss"; import {useHistory, useParams} from 'react-router-dom' import Button from '@mui/material/Button' diff --git a/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.tsx b/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.tsx index 15c78f3c..cd6d5e68 100644 --- a/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.tsx @@ -1,22 +1,24 @@ -import React, {useState} from 'react' +import React, {useState, useEffect} from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' import styles from './nonContainerAutoGraderForm.scss' import TextField from 'components/shared/inputs/textField' -// import Button from 'components/shared/inputs/button' import {useActionless} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' import RequestService from 'services/request.service' -import textStyles from '../shared/inputs/textField.scss' -import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; -import {ExpressValidationError} from "../../../devu-shared-modules"; +import textStyles from '../../../shared/inputs/textField.scss' +import {applyStylesToErrorFields, removeClassFromField} from "../../../../utils/textField.utils"; +import {AssignmentProblem, ExpressValidationError} from "devu-shared-modules"; import {useHistory, useParams} from 'react-router-dom' +import {Accordion, AccordionDetails, AccordionSummary, Typography} from '@mui/material' import Button from '@mui/material/Button' + const NonContainerAutoGraderForm = () => { const [setAlert] = useActionless(SET_ALERT) const [invalidFields, setInvalidFields] = useState(new Map()) const {assignmentId, courseId} = useParams<{ assignmentId: string, courseId : string }>() + const [assignmentProblems, setAssignmentProblems] = useState() const history = useHistory() const [formData,setFormData] = useState({ @@ -44,6 +46,15 @@ const NonContainerAutoGraderForm = () => { const toggleRegex = (e: React.ChangeEvent) => { setFormData(prevState => ({...prevState,isRegex : e.target.checked})) } + + useEffect(() => { + RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems/`) + .then((res) => { + const problems = res as AssignmentProblem[] + setAssignmentProblems(problems) + }) + }, []) + const handleSubmit = () => { if(!validateNumber(formData.assignmentId) || !validateNumber(formData.score)){ @@ -112,7 +123,21 @@ const NonContainerAutoGraderForm = () => {

Existing Problems

+
+ {assignmentProblems?.map((problem:AssignmentProblem,index) => ( + + + {`Assignment Problem Question ${index + 1}`} + + + Problem Name:{problem.problemName} + Max Score:{problem.maxScore} + + + ))} +
+




) } diff --git a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx index 2364a3d3..58c01c04 100644 --- a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx @@ -1,4 +1,4 @@ -import React, {useState} from 'react' +import React, {useEffect, useState} from 'react' import {useHistory, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -13,8 +13,10 @@ import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import Button from '@mui/material/Button' import {SET_ALERT} from 'redux/types/active.types' -import styles from '../shared/inputs/textField.scss' -import {applyStylesToErrorFields, removeClassFromField} from "../../utils/textField.utils"; +import { + applyMessageToErrorFields, + removeClassFromField +} from "../../../../utils/textField.utils"; import formStyles from './coursesFormPage.scss' @@ -23,7 +25,6 @@ type UrlParams = { } const CourseUpdatePage = ({}) => { - const [setAlert] = useActionless(SET_ALERT) const history = useHistory() const [formData,setFormData] = useState({ @@ -36,7 +37,21 @@ const CourseUpdatePage = ({}) => { const [invalidFields, setInvalidFields] = useState(new Map()) const { courseId } = useParams() as UrlParams - + useEffect(() => { + let isMounted = false; + if (!isMounted){ + RequestService.get(`/api/courses/${courseId}`).then((res) => { + setFormData({ + name: res.name, + number: res.number, + semester: res.semester, + }); + setStartDate(new Date(res.startDate)); + setEndDate(new Date(res.endDate)); + isMounted = true; + }); + } + }, []); const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id const newInvalidFields = removeClassFromField(invalidFields, key) @@ -54,7 +69,6 @@ const CourseUpdatePage = ({}) => { startDate : startDate.toISOString(), endDate : endDate.toISOString(), } - RequestService.put(`/api/courses/${courseId}`, finalFormData) .then(() => { @@ -63,9 +77,9 @@ const CourseUpdatePage = ({}) => { }) .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message - const newInvalidFields = applyStylesToErrorFields(err, formData, styles.errorField) - setInvalidFields(newInvalidFields) - + const newFields = new Map() + Array.isArray(err) ? err.map((e) => applyMessageToErrorFields(newFields, e.param, e.msg)) : newFields + setInvalidFields(newFields); setAlert({ autoDelete: false, type: 'error', message }) }) .finally(() => { @@ -79,11 +93,13 @@ const CourseUpdatePage = ({}) => {
+ invalidated={!!invalidFields.get("name")} helpText={invalidFields.get("name")} + defaultValue={formData.name}/> + invalidated={!!invalidFields.get("number")} helpText={invalidFields.get("number")}/> + placeholder='Ex. f2022, w2023, s2024' invalidated={!!invalidFields.get("semester")} + helpText={invalidFields.get("semester")}/>
@@ -103,7 +119,7 @@ const CourseUpdatePage = ({}) => {
- +
diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx index 88af540d..3fd06420 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx @@ -12,7 +12,7 @@ import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import {SET_ALERT} from 'redux/types/active.types' import formStyles from './coursesFormPage.scss' -import {applyMessageToErrorFields, removeClassFromField} from "../../utils/textField.utils"; +import {applyMessageToErrorFields, removeClassFromField} from "../../../../utils/textField.utils"; import Button from '@mui/material/Button' @@ -74,9 +74,10 @@ const EditCourseFormPage = () => { + invalidated={!!invalidFields.get("number")} helpText={invalidFields.get("number")}/> + placeholder='Ex. f2022, w2023, s2024' invalidated={!!invalidFields.get("semester")} + helpText={invalidFields.get("semester")} />
diff --git a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx index 5cd3dd99..fbb4dae0 100644 --- a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx @@ -4,7 +4,7 @@ import {Assignment, AssignmentScore, User, UserCourse} from 'devu-shared-modules import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import ErrorPage from './errorPage' +import ErrorPage from '../errorPage/errorPage' import RequestService from 'services/request.service' diff --git a/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx b/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx index de127b22..46f9654b 100644 --- a/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx @@ -6,7 +6,7 @@ import {Assignment, AssignmentScore} from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import ErrorPage from './errorPage' +import ErrorPage from '../errorPage/errorPage' import RequestService from 'services/request.service' import Button from '@mui/material/Button' diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 5b93a1e6..b549b6b1 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -2,9 +2,9 @@ import React, {useEffect, useState} from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import ErrorPage from './errorPage' +import ErrorPage from '../errorPage/errorPage' import styles from './homePage.scss' -import UserCourseListItem from "../listItems/userCourseListItem"; +import UserCourseListItem from "../../listItems/userCourseListItem"; import {useAppSelector} from 'redux/hooks' import RequestService from 'services/request.service' diff --git a/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx b/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx index d67de2b5..1f64db33 100644 --- a/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx +++ b/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx @@ -3,11 +3,11 @@ import {Course, UserCourse} from 'devu-shared-modules' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import PageWrapper from 'components/shared/layouts/pageWrapper' import Dropdown, {Option} from 'components/shared/inputs/dropdown' -import ErrorPage from './errorPage' +import ErrorPage from '../../errorPage/errorPage' import RequestService from 'services/request.service' import styles from './coursesListPage.scss' -import CourseListItem from "../listItems/courseListItem"; -import {useAppSelector} from "../../redux/hooks"; +import CourseListItem from "../../../listItems/courseListItem"; +import {useAppSelector} from "../../../../redux/hooks"; import Button from "@mui/material/Button"; import {useHistory} from "react-router-dom"; diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx index 8e2efd57..c941cc8b 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx @@ -2,12 +2,12 @@ import React, {useEffect, useState} from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import ErrorPage from './errorPage' +import ErrorPage from '../errorPage/errorPage' import RequestService from 'services/request.service' import {Assignment, AssignmentProblem, Submission, SubmissionProblemScore, SubmissionScore} from 'devu-shared-modules' import {Link, useParams} from 'react-router-dom' -import Button from '../shared/inputs/button' -import TextField from '../shared/inputs/textField' +import Button from '../../shared/inputs/button' +import TextField from '../../shared/inputs/textField' import {useActionless} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' diff --git a/devU-client/src/components/pages/submissions/submissionFeedbackPage.tsx b/devU-client/src/components/pages/submissions/submissionFeedbackPage.tsx index 5071e2e5..5543e54f 100644 --- a/devU-client/src/components/pages/submissions/submissionFeedbackPage.tsx +++ b/devU-client/src/components/pages/submissions/submissionFeedbackPage.tsx @@ -3,7 +3,7 @@ import {Link, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import ErrorPage from './errorPage' +import ErrorPage from '../errorPage/errorPage' import {Assignment, AssignmentProblem, SubmissionProblemScore, SubmissionScore} from 'devu-shared-modules' import RequestService from 'services/request.service' diff --git a/devU-client/src/components/pages/users/userDetailPage.tsx b/devU-client/src/components/pages/users/userDetailPage.tsx index ee02a084..09b1322a 100644 --- a/devU-client/src/components/pages/users/userDetailPage.tsx +++ b/devU-client/src/components/pages/users/userDetailPage.tsx @@ -8,7 +8,7 @@ import { UPDATE_USER } from 'redux/types/user.types' import RequestService from 'services/request.service' -import ErrorPage from 'components/pages/errorPage' +import ErrorPage from 'components/pages/errorPage/errorPage' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import EditUserForm from 'components/forms/editUserForm' diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 102ed68d..4a4c4a7f 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -10,11 +10,12 @@ type Props = { className?: string placeholder?: string id?: string - disabled?: true + disabled?: boolean defaultValue?: string value?: string invalidated?: boolean helpText?: string + variant?: 'outlined' | 'standard' | 'filled' } const TextField = ({ @@ -26,9 +27,9 @@ const TextField = ({ disabled, defaultValue, value, - invalidated, - helpText, - + invalidated, + helpText, + variant = 'outlined' }: Props) => { const handleChange = (e: React.ChangeEvent) => { if (onChange) onChange(e.target.value, e) @@ -36,17 +37,16 @@ const TextField = ({ return (
- disabled={disabled} - helperText={helpText} - + onChange={handleChange}/>
) } From edde3de3850b7bbcb3100dd6f4eb04b7a22533ef Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sat, 11 May 2024 05:23:00 -0400 Subject: [PATCH 126/400] Update dependencies for Typeorm and Minio --- devU-api/README.md | 8 +- devU-api/package.json | 6 +- devU-api/src/database.ts | 20 +- .../entities/assignment/assignment.router.ts | 12 +- .../entities/assignment/assignment.service.ts | 15 +- .../assignmentProblem.service.ts | 9 +- .../assignmentScore/assignmentScore.router.ts | 2 +- .../assignmentScore.service.ts | 15 +- .../src/entities/category/category.service.ts | 11 +- .../categoryScore/categoryScore.service.ts | 13 +- .../containerAutoGrader.service.ts | 186 ++++---- .../containerAutoGrader.validator.ts | 40 +- .../src/entities/course/course.controller.ts | 28 +- .../src/entities/course/course.service.ts | 102 +++-- .../courseScore/courseScore.service.ts | 9 +- .../deadlineExtensions.service.ts | 9 +- .../src/entities/grader/grader.controller.ts | 32 +- devU-api/src/entities/grader/grader.router.ts | 6 +- .../src/entities/grader/grader.service.ts | 425 ++++++++++-------- .../nonContainerAutoGrader.grader.ts | 12 +- .../nonContainerAutoGrader.service.ts | 11 +- devU-api/src/entities/role/role.service.ts | 13 +- .../entities/submission/submission.router.ts | 3 +- .../entities/submission/submission.service.ts | 16 +- .../submissionProblemScore.service.ts | 9 +- .../submissionScore.service.ts | 7 +- devU-api/src/entities/user/user.service.ts | 13 +- .../userCourse/userCourse.controller.ts | 6 +- .../entities/userCourse/userCourse.router.ts | 6 +- .../entities/userCourse/userCourse.service.ts | 28 +- devU-api/src/fileStorage.ts | 65 +-- devU-api/src/fileUpload/fileUpload.service.ts | 21 +- devU-api/src/index.ts | 19 +- devU-api/src/model/index.ts | 1 - devU-api/src/tango/tango.service.ts | 31 +- 35 files changed, 637 insertions(+), 572 deletions(-) diff --git a/devU-api/README.md b/devU-api/README.md index 5c1a1cb3..cee3b4ac 100644 --- a/devU-api/README.md +++ b/devU-api/README.md @@ -97,7 +97,7 @@ npm run generate-config Run the initial migrations to setup our DB schema ``` -npm run typeorm -- migration:run +npm run typeorm -- migration:run -d src/database.ts ``` Once you've got all the dependencies installed you can run the project via @@ -257,19 +257,19 @@ I wouldn't recommend digging that far down as the of tests should be more human- If the schema needs to be updated, you can do so by updating the models and running ``` -npm run typeorm -- migration:generate -n generatedMigrationName +npm run typeorm migration:generate -- -d src/database src/migration/ ``` Doing so will attempt to create an auto migration from any changes within the `src/models` directory and add it to `src/migrations`. If an auto migration is generated for you (always check your auto migrations), you can run it with the above migration command ``` -npm run typeorm -- migration:run +npm run typeorm -- migration:run -d src/database.ts ``` And revert the latest migration with ``` -npm run typeorm -- migration:revert +npm run typeorm -- migration:revert -d src/database.ts ``` ### Configuration Options diff --git a/devU-api/package.json b/devU-api/package.json index 5a6432e3..6907e91d 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -5,7 +5,7 @@ "scripts": { "start": "ts-node-dev src/index.ts", "update-shared": "npm update devu-shared-modules", - "typeorm": "ts-node ./node_modules/typeorm/cli.js --config src/database.ts", + "typeorm": "typeorm-ts-node-commonjs", "test": "jest --passWithNoTests", "clean": "rimraf build/*", "lint": "tsc", @@ -70,7 +70,7 @@ "express-validator": "^6.14.2", "helmet": "^4.6.0", "jsonwebtoken": "^9.0.2", - "minio": "^7.0.18", + "minio": "^8.0.0", "morgan": "^1.10.0", "multer": "^1.4.2", "passport": "^0.6.0", @@ -80,6 +80,6 @@ "regex-parser": "^2.3.0", "swagger-jsdoc": "^6.1.0", "swagger-ui-express": "^4.1.6", - "typeorm": "0.2.32" + "typeorm": "0.3.20" } } diff --git a/devU-api/src/database.ts b/devU-api/src/database.ts index a2dfbc83..293cf8fc 100644 --- a/devU-api/src/database.ts +++ b/devU-api/src/database.ts @@ -1,8 +1,8 @@ -import { ConnectionOptions, Repository } from 'typeorm' +import { DataSource, DataSourceOptions, Repository } from 'typeorm' import environment from './environment' -const typeORMConfiguration: ConnectionOptions = { +const typeORMConfiguration: DataSourceOptions = { type: 'postgres', host: environment.dbHost, username: environment.dbUsername, @@ -15,14 +15,12 @@ const typeORMConfiguration: ConnectionOptions = { entities: [`${__dirname}/**/*.model.{ts,js}`], migrations: [`${__dirname}/migration/**/*.{ts,js}`], subscribers: [`${__dirname}/migration/**/*.{ts,js}`], - cli: { - entitiesDir: `./src/model`, - migrationsDir: `./src/migration`, - subscribersDir: `./src/subscriber`, - }, } +const dataSource = new DataSource(typeORMConfiguration) + export default typeORMConfiguration +export { dataSource } /* This function is used to group the data by the specified column @@ -47,11 +45,5 @@ export async function groupBy( orders = Object.keys(filteredOrders).length === 0 ? { id: 'ASC' } : filteredOrders - return await connection.find({ - where: { - [filter.index]: filter.value, - }, - order: orders, - withDeleted: false, - }) + return await connection.findBy({ ...orders }) } diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index 9150266d..976757ce 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -75,7 +75,7 @@ Router.get('/', isAuthorized('assignmentViewAll'), AssignmentsController.getByCo * schema: * type: integer */ -Router.get('/:assignmentId', asInt("assignmentId"), isAuthorizedByAssignmentStatus, AssignmentsController.detail) +Router.get('/:assignmentId', asInt('assignmentId'), isAuthorizedByAssignmentStatus, AssignmentsController.detail) /** * @swagger @@ -130,7 +130,13 @@ Router.post('/', isAuthorized('assignmentEditAll'), validator, AssignmentsContro * schema: * $ref: '#/components/schemas/Assignment' */ -Router.put('/:assignmentId', isAuthorized('assignmentEditAll'), asInt("assignmentId"), validator, AssignmentsController.put) +Router.put( + '/:assignmentId', + isAuthorized('assignmentEditAll'), + asInt('assignmentId'), + validator, + AssignmentsController.put +) /** * @swagger @@ -155,6 +161,6 @@ Router.put('/:assignmentId', isAuthorized('assignmentEditAll'), asInt("assignmen * schema: * type: integer */ -Router.delete('/:assignmentId', isAuthorized('assignmentEditAll'), asInt("assignmentId"), AssignmentsController._delete) +Router.delete('/:assignmentId', isAuthorized('assignmentEditAll'), asInt('assignmentId'), AssignmentsController._delete) export default Router diff --git a/devU-api/src/entities/assignment/assignment.service.ts b/devU-api/src/entities/assignment/assignment.service.ts index b3b2fc9e..707cc186 100644 --- a/devU-api/src/entities/assignment/assignment.service.ts +++ b/devU-api/src/entities/assignment/assignment.service.ts @@ -1,10 +1,11 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import AssignmentModel from './assignment.model' import { Assignment } from 'devu-shared-modules' -const connect = () => getRepository(AssignmentModel) +const connect = () => dataSource.getRepository(AssignmentModel) export async function create(assignment: Assignment) { return await connect().save(assignment) @@ -44,24 +45,24 @@ export async function _delete(id: number) { } export async function retrieve(id: number, courseId: number) { - return await connect().findOne({ id: id, courseId: courseId, deletedAt: IsNull() }) + return await connect().findOneBy({ id: id, courseId: courseId, deletedAt: IsNull() }) } export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().findBy({ deletedAt: IsNull() }) } export async function listByCourse(courseId: number) { - return await connect().find({ courseId: courseId, deletedAt: IsNull() }) + return await connect().findBy({ courseId: courseId, deletedAt: IsNull() }) } export async function listByCourseReleased(courseId: number) { // TODO: filter by start date after current time - return await connect().find({ courseId: courseId, deletedAt: IsNull() }) + return await connect().findBy({ courseId: courseId, deletedAt: IsNull() }) } export async function isReleased(id: number) { - const assignment = await connect().findOne({ id, deletedAt: IsNull() }) + const assignment = await connect().findOneBy({ id, deletedAt: IsNull() }) if (!assignment) { return false diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.service.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.service.ts index c8e844ce..7eff05e6 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.service.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.service.ts @@ -1,10 +1,11 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import AssignmentProblemModel from './assignmentProblem.model' import { AssignmentProblem } from 'devu-shared-modules' -const connect = () => getRepository(AssignmentProblemModel) +const connect = () => dataSource.getRepository(AssignmentProblemModel) export async function create(assignmentProblem: AssignmentProblem) { return await connect().save(assignmentProblem) @@ -21,12 +22,12 @@ export async function _delete(id: number) { } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } // Retrieve all the assignmentProblems linked to a particular assignment by assignmentId export async function list(assignmentId: number) { - return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) + return await connect().findBy({ assignmentId: assignmentId, deletedAt: IsNull() }) } export default { diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts index d16a5f48..77c70ad1 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.router.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.router.ts @@ -73,7 +73,7 @@ Router.get( '/user/:userId', extractOwnerByPathParam('userId'), isAuthorized('scoresViewAll', 'scoresViewSelfReleased'), - asInt("userId"), + asInt('userId'), AssignmentScoreController.getByUser ) diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.service.ts b/devU-api/src/entities/assignmentScore/assignmentScore.service.ts index 97555ca5..d3fe7925 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.service.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.service.ts @@ -1,4 +1,5 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import AssignmentScoreModel from './assignmentScore.model' @@ -6,7 +7,7 @@ import { AssignmentScore } from 'devu-shared-modules' import AssignmentService from '../assignment/assignment.service' -const connect = () => getRepository(AssignmentScoreModel) +const connect = () => dataSource.getRepository(AssignmentScoreModel) export async function create(assignmentScore: AssignmentScore) { return await connect().save(assignmentScore) @@ -24,26 +25,26 @@ export async function _delete(id: number) { } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } export async function list(assignmentId: number) { // TODO: There's no way this is right. Test to verify that it should be 'assignmentId': assignmentId - return await connect().find({ assignmentId, deletedAt: IsNull() }) + return await connect().findBy({ assignmentId, deletedAt: IsNull() }) } export async function retrieveByUser(assignmentId: number, userId: number) { //TODO: This can't be right.. can it? - return await connect().findOne({ assignmentId, userId, deletedAt: IsNull() }) + return await connect().findOneBy({ assignmentId, userId, deletedAt: IsNull() }) } export async function listByUser(userId: number) { - return await connect().find({ userId, deletedAt: IsNull() }) + return await connect().findBy({ userId, deletedAt: IsNull() }) } export async function listByCourse(courseId: number) { const assignments = await AssignmentService.listByCourse(courseId) - const assignmentScorePromises = assignments.map(a => connect().find({ assignmentId: a.id, deletedAt: IsNull() })) + const assignmentScorePromises = assignments.map(a => connect().findBy({ assignmentId: a.id, deletedAt: IsNull() })) return (await Promise.all(assignmentScorePromises)).reduce((a, b) => a.concat(b), []) //Must flatten 2D array resulting from array promises } diff --git a/devU-api/src/entities/category/category.service.ts b/devU-api/src/entities/category/category.service.ts index 219b6c6b..eea4edd3 100644 --- a/devU-api/src/entities/category/category.service.ts +++ b/devU-api/src/entities/category/category.service.ts @@ -1,10 +1,11 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import CategoryModel from './category.model' import { Category } from 'devu-shared-modules' -const connect = () => getRepository(CategoryModel) +const connect = () => dataSource.getRepository(CategoryModel) export async function create(category: Category) { return await connect().save(category) @@ -21,16 +22,16 @@ export async function _delete(id: number) { } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().findBy({ deletedAt: IsNull() }) } export async function listByCourse(courseId: number) { // TODO? - return await connect().find({ courseId, deletedAt: IsNull() }) + return await connect().findBy({ courseId, deletedAt: IsNull() }) } export default { diff --git a/devU-api/src/entities/categoryScore/categoryScore.service.ts b/devU-api/src/entities/categoryScore/categoryScore.service.ts index 1196a3ba..4aab8638 100644 --- a/devU-api/src/entities/categoryScore/categoryScore.service.ts +++ b/devU-api/src/entities/categoryScore/categoryScore.service.ts @@ -1,10 +1,11 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import CategoryScoreModel from './categoryScore.model' import { CategoryScore } from 'devu-shared-modules' -const connect = () => getRepository(CategoryScoreModel) +const connect = () => dataSource.getRepository(CategoryScoreModel) export async function create(categoryScore: CategoryScore) { return await connect().save(categoryScore) @@ -23,19 +24,19 @@ export async function _delete(id: number) { } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } // Retrieve all the categoryScores linked to a particular category (TODO: This endpoint doesn't have a path) // export async function listByCategory(categoryId: number) { -// return await connect().find({ categoryId: categoryId, deletedAt: IsNull() }) +// return await connect().findBy({ categoryId: categoryId, deletedAt: IsNull() }) export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().findBy({ deletedAt: IsNull() }) } export async function listByCourse(courseId: number) { - return await connect().find({ courseId, deletedAt: IsNull() }) + return await connect().findBy({ courseId, deletedAt: IsNull() }) } export default { diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts index 1a2eba31..a84d96ad 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.service.ts @@ -1,120 +1,140 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import { ContainerAutoGrader, FileUpload } from 'devu-shared-modules' import ContainerAutoGraderModel from './containerAutoGrader.model' -import FileModel from "../../fileUpload/fileUpload.model"; +import FileModel from '../../fileUpload/fileUpload.model' import { uploadFile, downloadFile } from '../../fileStorage' -import {generateFilename} from "../../utils/fileUpload.utils"; - -const connect = () => getRepository(ContainerAutoGraderModel) -const fileConn = () => getRepository(FileModel) - -async function filesUpload(bucket: string, file: Express.Multer.File, containerAutoGrader: ContainerAutoGrader,filename: string, userId: number) { - const Etag: string = await uploadFile(bucket, file,filename) - const assignmentId = containerAutoGrader.assignmentId - - const fileModel: FileUpload = { - etags: Etag, - fieldName: bucket, - originalName: file.originalname, - filename: filename, - assignmentId: assignmentId, - } - //TODO: This is a temporary fix to get the function to pass. CourseId should be modified in the future - fileModel.courseId = 1 - fileModel.userId = userId - - await fileConn().save(fileModel) - - return Etag +import { generateFilename } from '../../utils/fileUpload.utils' + +const connect = () => dataSource.getRepository(ContainerAutoGraderModel) +const fileConn = () => dataSource.getRepository(FileModel) + +async function filesUpload( + bucket: string, + file: Express.Multer.File, + containerAutoGrader: ContainerAutoGrader, + filename: string, + userId: number +) { + const Etag: string = await uploadFile(bucket, file, filename) + const assignmentId = containerAutoGrader.assignmentId + + const fileModel: FileUpload = { + etags: Etag, + fieldName: bucket, + originalName: file.originalname, + filename: filename, + assignmentId: assignmentId, + } + //TODO: This is a temporary fix to get the function to pass. CourseId should be modified in the future + fileModel.courseId = 1 + fileModel.userId = userId + + await fileConn().save(fileModel) + + return Etag } +export async function create( + containerAutoGrader: ContainerAutoGrader, + graderInputFile: Express.Multer.File, + makefileInputFile: Express.Multer.File | null = null, + userId: number +) { + const existingContainerAutoGrader = await connect().findOneBy({ + assignmentId: containerAutoGrader.assignmentId, + deletedAt: IsNull(), + }) + if (existingContainerAutoGrader) + throw new Error( + 'Container Auto Grader already exists for this assignment, please update instead of creating a new one' + ) + const bucket: string = 'graders' + const filename: string = generateFilename(graderInputFile.originalname, userId) + await filesUpload(bucket, graderInputFile, containerAutoGrader, filename, userId) + containerAutoGrader.graderFile = filename + + if (makefileInputFile) { + const bucket: string = 'makefiles' + const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) + await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) + containerAutoGrader.makefileFile = makefileFilename + } + + const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader + return await connect().save({ id, assignmentId, graderFile, makefileFile, autogradingImage, timeout }) +} - -export async function create(containerAutoGrader: ContainerAutoGrader, graderInputFile: Express.Multer.File, makefileInputFile: Express.Multer.File | null = null, userId: number) { - const existingContainerAutoGrader = await connect().findOne({ assignmentId: containerAutoGrader.assignmentId, deletedAt: IsNull() }) - if (existingContainerAutoGrader) throw new Error('Container Auto Grader already exists for this assignment, please update instead of creating a new one') +export async function update( + containerAutoGrader: ContainerAutoGrader, + graderInputFile: Express.Multer.File | null = null, + makefileInputFile: Express.Multer.File | null = null, + userId: number +) { + if (!containerAutoGrader.id) throw new Error('Missing Id') + if (graderInputFile) { const bucket: string = 'graders' const filename: string = generateFilename(graderInputFile.originalname, userId) await filesUpload(bucket, graderInputFile, containerAutoGrader, filename, userId) containerAutoGrader.graderFile = filename + } - if (makefileInputFile) { - const bucket: string = 'makefiles' - const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) - await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) - containerAutoGrader.makefileFile = makefileFilename - } - - const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader - return await connect().save({ id, assignmentId, graderFile, makefileFile, autogradingImage, timeout }) -} + if (makefileInputFile) { + const bucket: string = 'makefiles' + const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) + await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) + containerAutoGrader.makefileFile = makefileFilename + } -export async function update(containerAutoGrader: ContainerAutoGrader, graderInputFile: Express.Multer.File | null = null, makefileInputFile: Express.Multer.File | null = null, userId: number) { - if (!containerAutoGrader.id) throw new Error('Missing Id') - if (graderInputFile) { - const bucket: string = 'graders' - const filename: string = generateFilename(graderInputFile.originalname, userId) - await filesUpload(bucket, graderInputFile, containerAutoGrader, filename, userId) - containerAutoGrader.graderFile = filename - } - - if (makefileInputFile) { - const bucket: string = 'makefiles' - const makefileFilename: string = generateFilename(makefileInputFile.originalname, userId) - await filesUpload(bucket, makefileInputFile, containerAutoGrader, makefileFilename, userId) - containerAutoGrader.makefileFile = makefileFilename - } - - const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader - return await connect().update(id, { assignmentId, graderFile, makefileFile, autogradingImage, timeout }) + const { id, assignmentId, graderFile, makefileFile, autogradingImage, timeout } = containerAutoGrader + return await connect().update(id, { assignmentId, graderFile, makefileFile, autogradingImage, timeout }) } export async function _delete(id: number) { - return await connect().softDelete({ id, deletedAt: IsNull() }) + return await connect().softDelete({ id, deletedAt: IsNull() }) } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().findBy({ deletedAt: IsNull() }) } //The grader has not changed to the new function, so the fake function keep here for now to avoid error //But need to be deleted when the grader entity changed to the getGraderByAssignmentId export async function getGraderObjectByAssignmentId(assignmentId: number) { - if (!assignmentId) throw new Error('Missing AssignmentId') - return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) + if (!assignmentId) throw new Error('Missing AssignmentId') + return await connect().findBy({ assignmentId: assignmentId, deletedAt: IsNull() }) } -export async function getGraderByAssignmentId(assignmentId: number){ - const containerAutoGraders = await connect().findOne({ assignmentId: assignmentId, deletedAt: IsNull() }) - if (!containerAutoGraders) return {graderData: null, makefileData: null, autogradingImage: null, timeout: null} +export async function getGraderByAssignmentId(assignmentId: number) { + const containerAutoGraders = await connect().findOneBy({ assignmentId: assignmentId, deletedAt: IsNull() }) + if (!containerAutoGraders) return { graderData: null, makefileData: null, autogradingImage: null, timeout: null } - const { graderFile, makefileFile, autogradingImage, timeout } = containerAutoGraders - const graderData = await downloadFile('graders', graderFile) - let makefileData; + const { graderFile, makefileFile, autogradingImage, timeout } = containerAutoGraders + const graderData = await downloadFile('graders', graderFile) + let makefileData - if (makefileFile){ - makefileData = await downloadFile('makefiles', makefileFile) - }else{ - makefileData = await downloadFile('makefiles', 'defaultMakefile') // Put actual default makefile name here - } + if (makefileFile) { + makefileData = await downloadFile('makefiles', makefileFile) + } else { + makefileData = await downloadFile('makefiles', 'defaultMakefile') // Put actual default makefile name here + } - return {graderData, makefileData, autogradingImage, timeout} + return { graderData, makefileData, autogradingImage, timeout } } - export default { - create, - retrieve, - update, - _delete, - list, - getGraderByAssignmentId, - getGraderObjectByAssignmentId, -} \ No newline at end of file + create, + retrieve, + update, + _delete, + list, + getGraderByAssignmentId, + getGraderObjectByAssignmentId, +} diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts index 48f381f7..1f264307 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.validator.ts @@ -1,37 +1,41 @@ -import {check} from 'express-validator' +import { check } from 'express-validator' import validate from '../../middleware/validator/generic.validator' - const assignmentId = check('assignmentId').isNumeric() -const graderFile = check('graderFile').optional({ nullable: true }).custom(({req}) => { +const graderFile = check('graderFile') + .optional({ nullable: true }) + .custom(({ req }) => { const file = req?.files['grader'] if (file !== null) { - if (file.size <= 0) { - throw new Error('File is empty') - } + if (file.size <= 0) { + throw new Error('File is empty') + } } else { - throw new Error('does not have grader file') + throw new Error('does not have grader file') } -}).withMessage('Grader file is required') + }) + .withMessage('Grader file is required') -const makefileFile = check('makefileFile').optional({ nullable: true }).custom(({ req }) => { +const makefileFile = check('makefileFile') + .optional({ nullable: true }) + .custom(({ req }) => { const file = req.files['makefile'] if (file !== null) { - if (file.size <= 0) { - throw new Error('File is empty') - } + if (file.size <= 0) { + throw new Error('File is empty') + } } -}) - + }) const autogradingImage = check('autogradingImage').isString() -const timeout = check('timeout').isNumeric() -.custom((value) => value > 0) -.withMessage('Timeout should be a positive integer') +const timeout = check('timeout') + .isNumeric() + .custom(value => value > 0) + .withMessage('Timeout should be a positive integer') const validator = [assignmentId, graderFile, makefileFile, autogradingImage, timeout, validate] -export default validator \ No newline at end of file +export default validator diff --git a/devU-api/src/entities/course/course.controller.ts b/devU-api/src/entities/course/course.controller.ts index 4845391b..0f24f3ba 100644 --- a/devU-api/src/entities/course/course.controller.ts +++ b/devU-api/src/entities/course/course.controller.ts @@ -1,10 +1,10 @@ -import {NextFunction, Request, Response} from 'express' +import { NextFunction, Request, Response } from 'express' import CourseService from './course.service' -import {GenericResponse, NotFound, Updated} from '../../utils/apiResponse.utils' +import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' -import {serialize} from './course.serializer' +import { serialize } from './course.serializer' import UserCourseService from '../userCourse/userCourse.service' export async function get(req: Request, res: Response, next: NextFunction) { @@ -20,20 +20,14 @@ export async function get(req: Request, res: Response, next: NextFunction) { export async function getByUser(req: Request, res: Response, next: NextFunction) { try { const userId = parseInt(req.params.userId) - const { - activeCourses, - pastCourses, - instructorCourses, - upcomingCourses - } = await CourseService.listByUser(userId) - - const response = - { - activeCourses: activeCourses.map(serialize), - pastCourses: pastCourses.map(serialize), - instructorCourses: instructorCourses.map(serialize), - upcomingCourses: upcomingCourses.map(serialize) - } + const { activeCourses, pastCourses, instructorCourses, upcomingCourses } = await CourseService.listByUser(userId) + + const response = { + activeCourses: activeCourses.map(serialize), + pastCourses: pastCourses.map(serialize), + instructorCourses: instructorCourses.map(serialize), + upcomingCourses: upcomingCourses.map(serialize), + } res.status(200).json(response) } catch (err) { diff --git a/devU-api/src/entities/course/course.service.ts b/devU-api/src/entities/course/course.service.ts index 06f5eda2..d2971618 100644 --- a/devU-api/src/entities/course/course.service.ts +++ b/devU-api/src/entities/course/course.service.ts @@ -1,79 +1,83 @@ -import {getRepository, IsNull} from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import CourseModel from './course.model' -import {Course} from 'devu-shared-modules' -import {initializeMinio} from '../../fileStorage' -import UserCourseService from "../userCourse/userCourse.service"; +import { Course } from 'devu-shared-modules' +import { initializeMinio } from '../../fileStorage' +import UserCourseService from '../userCourse/userCourse.service' -const connect = () => getRepository(CourseModel) +const connect = () => dataSource.getRepository(CourseModel) export async function create(course: Course) { - const output = await connect().save(course) - const bucketName = (course.number + course.semester + course.id).replace(/ /g, '-').toLowerCase() - await initializeMinio(bucketName) - return output + const output = await connect().save(course) + const bucketName = (course.number + course.semester + course.id).replace(/ /g, '-').toLowerCase() + await initializeMinio(bucketName) + return output } export async function update(course: Course) { - const {id, name, semester, number, startDate, endDate} = course - if (!id) throw new Error('Missing Id') - return await connect().update(id, {name, semester, number, startDate, endDate}) + const { id, name, semester, number, startDate, endDate } = course + if (!id) throw new Error('Missing Id') + return await connect().update(id, { name, semester, number, startDate, endDate }) } export async function _delete(id: number) { - return await connect().softDelete({id, deletedAt: IsNull()}) + return await connect().softDelete({ id, deletedAt: IsNull() }) } export async function retrieve(id: number) { - return await connect().findOne({id, deletedAt: IsNull()}) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } export async function list() { - return await connect().find({deletedAt: IsNull()}) + return await connect().findBy({ deletedAt: IsNull() }) } export async function listByUser(userId: number) { - const userCourses = await UserCourseService.listByUser(userId) - const date = new Date() - const activeCourses = [] - const pastCourses = [] - const instructorCourses = [] - const upcomingCourses = [] + const userCourses = await UserCourseService.listByUser(userId) + const date = new Date() + const activeCourses = [] + const pastCourses = [] + const instructorCourses = [] + const upcomingCourses = [] - const userCourseIds = userCourses.map(userCourse => userCourse.courseId) + const userCourseIds = userCourses.map(userCourse => userCourse.courseId) - const allCourses = userCourseIds.length > 0 ? await connect() - .createQueryBuilder('course') - .where('course.id IN (:...ids)', {ids: userCourseIds}) - .andWhere('course.deletedAt IS NULL') - .getMany() : []; + const allCourses = + userCourseIds.length > 0 + ? await connect() + .createQueryBuilder('course') + .where('course.id IN (:...ids)', { ids: userCourseIds }) + .andWhere('course.deletedAt IS NULL') + .getMany() + : [] - for (const course of allCourses) { - const userCourse = userCourses.find(userCourse => userCourse.courseId === course.id); - switch (true) { - case course.startDate > date: - upcomingCourses.push(course) - break - case course.endDate < date: - pastCourses.push(course) - break - case userCourse && userCourse.role === 'instructor': - instructorCourses.push(course) - break - default: - activeCourses.push(course) - } + for (const course of allCourses) { + const userCourse = userCourses.find(userCourse => userCourse.courseId === course.id) + switch (true) { + case course.startDate > date: + upcomingCourses.push(course) + break + case course.endDate < date: + pastCourses.push(course) + break + case userCourse && userCourse.role === 'instructor': + instructorCourses.push(course) + break + default: + activeCourses.push(course) } + } - return {activeCourses, pastCourses, instructorCourses, upcomingCourses} + return { activeCourses, pastCourses, instructorCourses, upcomingCourses } } export default { - create, - retrieve, - update, - _delete, - list, - listByUser, + create, + retrieve, + update, + _delete, + list, + listByUser, } diff --git a/devU-api/src/entities/courseScore/courseScore.service.ts b/devU-api/src/entities/courseScore/courseScore.service.ts index 391d1e02..f9e7837e 100644 --- a/devU-api/src/entities/courseScore/courseScore.service.ts +++ b/devU-api/src/entities/courseScore/courseScore.service.ts @@ -1,10 +1,11 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import CourseScoreModel from './courseScore.model' import { CourseScore } from 'devu-shared-modules' -const connect = () => getRepository(CourseScoreModel) +const connect = () => dataSource.getRepository(CourseScoreModel) export async function create(courseScore: CourseScore) { return await connect().save(courseScore) @@ -23,12 +24,12 @@ export async function _delete(id: number) { } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } // Retrieve all the courseScores linked to a particular course export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().findBy({ deletedAt: IsNull() }) } export default { diff --git a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.service.ts b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.service.ts index d218ae52..8d15e32b 100644 --- a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.service.ts +++ b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.service.ts @@ -1,10 +1,11 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import DeadlineExtension from './deadlineExtensions.model' import { DeadlineExtensions } from 'devu-shared-modules' -const connect = () => getRepository(DeadlineExtension) +const connect = () => dataSource.getRepository(DeadlineExtension) export async function create(assignment: DeadlineExtensions) { return await connect().save(assignment) @@ -30,11 +31,11 @@ export async function _delete(id: number) { } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().findBy({ deletedAt: IsNull() }) } export default { diff --git a/devU-api/src/entities/grader/grader.controller.ts b/devU-api/src/entities/grader/grader.controller.ts index f8f18370..824d243c 100644 --- a/devU-api/src/entities/grader/grader.controller.ts +++ b/devU-api/src/entities/grader/grader.controller.ts @@ -7,25 +7,25 @@ import { GenericResponse } from '../../utils/apiResponse.utils' //, NotFound //import { serialize } from '../grader/grader.serializer' export async function grade(req: Request, res: Response, next: NextFunction) { - try { - const submissionId = parseInt(req.params.id) - const response = await GraderService.grade(submissionId) //grade - - res.status(200).json(response) - } catch (err) { - res.status(400).json(new GenericResponse(err.message)) - } + try { + const submissionId = parseInt(req.params.id) + const response = await GraderService.grade(submissionId) //grade + + res.status(200).json(response) + } catch (err) { + res.status(400).json(new GenericResponse(err.message)) + } } export async function tangoCallback(req: Request, res: Response, next: NextFunction) { - try { - const outputFile = req.params.outputFile - const response = await GraderService.tangoCallback(outputFile) - - res.status(200).json(response) - } catch (err) { - res.status(400).json(new GenericResponse(err.message)) - } + try { + const outputFile = req.params.outputFile + const response = await GraderService.tangoCallback(outputFile) + + res.status(200).json(response) + } catch (err) { + res.status(400).json(new GenericResponse(err.message)) + } } export default { grade, tangoCallback } diff --git a/devU-api/src/entities/grader/grader.router.ts b/devU-api/src/entities/grader/grader.router.ts index 48ea4fb8..33783097 100644 --- a/devU-api/src/entities/grader/grader.router.ts +++ b/devU-api/src/entities/grader/grader.router.ts @@ -16,7 +16,7 @@ const Router = express.Router() * @swagger * tags: * - name: Grader - * description: + * description: * /grade/{id}: * post: * summary: Grade a submission, currently only with non-container autograders @@ -39,7 +39,7 @@ Router.post('/:id', asInt(), isAuthenticated, /*isAuthorized('enrolled'),*/ Grad * @swagger * tags: * - name: Grader callback - * description: + * description: * /grade/callback/{outputFilename}: * post: * summary: Not directly called by the user. Tells the API that a container grading job has finished and creates relevant entities from the results. @@ -57,4 +57,4 @@ Router.post('/:id', asInt(), isAuthenticated, /*isAuthorized('enrolled'),*/ Grad */ Router.post('/callback/:outputFile', GraderController.tangoCallback) //Unauthorized route so tango can make callback without needing token -export default Router \ No newline at end of file +export default Router diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index edcc6d39..c4031c43 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -8,243 +8,270 @@ import assignmentScoreService from '../assignmentScore/assignmentScore.service' import courseService from '../course/course.service' import { addJob, createCourse, uploadFile, pollJob } from '../../tango/tango.service' -import { SubmissionScore, SubmissionProblemScore, AssignmentScore, Submission, NonContainerAutoGrader, AssignmentProblem } from 'devu-shared-modules' +import { + SubmissionScore, + SubmissionProblemScore, + AssignmentScore, + Submission, + NonContainerAutoGrader, + AssignmentProblem, +} from 'devu-shared-modules' import { checkAnswer } from '../nonContainerAutoGrader/nonContainerAutoGrader.grader' import { serialize as serializeNonContainer } from '../nonContainerAutoGrader/nonContainerAutoGrader.serializer' import { serialize as serializeAssignmentScore } from '../assignmentScore/assignmentScore.serializer' -import { serialize as serializeSubmissionScore} from '../submissionScore/submissionScore.serializer' +import { serialize as serializeSubmissionScore } from '../submissionScore/submissionScore.serializer' import { serialize as serializeSubmission } from '../submission/submission.serializer' import { serialize as serializeAssignmentProblem } from '../assignmentProblem/assignmentProblem.serializer' import { downloadFile, initializeMinio } from '../../fileStorage' async function grade(submissionId: number) { - const submissionModel = await submissionService.retrieve(submissionId) - if (!submissionModel) throw new Error('Submission not found.') - const submission = serializeSubmission(submissionModel) + const submissionModel = await submissionService.retrieve(submissionId) + if (!submissionModel) throw new Error('Submission not found.') + const submission = serializeSubmission(submissionModel) - const assignmentId = submission.assignmentId + const assignmentId = submission.assignmentId - const content = JSON.parse(submission.content) - const form = content.form - const filepaths: string[] = content.filepaths + const content = JSON.parse(submission.content) + const form = content.form + const filepaths: string[] = content.filepaths - const nonContainerAutograders = (await nonContainerAutograderService.listByAssignmentId(assignmentId)).map(model => serializeNonContainer(model)) - const assignmentProblems = (await assignmentProblemService.list(assignmentId)).map(model => serializeAssignmentProblem(model)) + const nonContainerAutograders = (await nonContainerAutograderService.listByAssignmentId(assignmentId)).map(model => + serializeNonContainer(model) + ) + const assignmentProblems = (await assignmentProblemService.list(assignmentId)).map(model => + serializeAssignmentProblem(model) + ) - let score = 0 - let feedback = '' - - //Run Non-Container Autograders - const ncagResults = await runNonContainerAutograders(form, nonContainerAutograders, assignmentProblems, submissionId) - score += ncagResults.score - feedback += ncagResults.feedback - - //Run Container Autograders - const cagResults = await runContainerAutograders(filepaths, submission, assignmentId) - const jobResponse = cagResults.jobResponse - const containerGrading = cagResults.containerGrading - - //Grading is finished. Create SubmissionScore and AssignmentScore and save to db. - const scoreObj: SubmissionScore = { - submissionId: submissionId, - score: score, //Sum of all SubmissionProblemScore scores - feedback: feedback //Concatination of SubmissionProblemScore feedbacks - } - submissionScoreService.create(scoreObj) - - //If containergrading is true, tangoCallback handles assignmentScore creation - if (containerGrading === false) { - updateAssignmentScore(submission, score) - return {message: `Noncontainer autograding completed successfully`, submissionScore: scoreObj} - } - return {message: `Autograder successfully added job #${jobResponse?.jobId} to the queue with status message ${jobResponse?.statusMsg}`} + let score = 0 + let feedback = '' + + //Run Non-Container Autograders + const ncagResults = await runNonContainerAutograders(form, nonContainerAutograders, assignmentProblems, submissionId) + score += ncagResults.score + feedback += ncagResults.feedback + + //Run Container Autograders + const cagResults = await runContainerAutograders(filepaths, submission, assignmentId) + const jobResponse = cagResults.jobResponse + const containerGrading = cagResults.containerGrading + + //Grading is finished. Create SubmissionScore and AssignmentScore and save to db. + const scoreObj: SubmissionScore = { + submissionId: submissionId, + score: score, //Sum of all SubmissionProblemScore scores + feedback: feedback, //Concatination of SubmissionProblemScore feedbacks + } + submissionScoreService.create(scoreObj) + + //If containergrading is true, tangoCallback handles assignmentScore creation + if (containerGrading === false) { + updateAssignmentScore(submission, score) + return { message: `Noncontainer autograding completed successfully`, submissionScore: scoreObj } + } + return { + message: `Autograder successfully added job #${jobResponse?.jobId} to the queue with status message ${jobResponse?.statusMsg}`, + } } -async function runNonContainerAutograders(form: any, nonContainerAutograders: NonContainerAutoGrader[], assignmentProblems: AssignmentProblem[], submissionId: number) { - let score = 0 - let feedback = '' - - for (const question in form) { - const nonContainerGrader = nonContainerAutograders.find(grader => grader.question === question) - const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) - - if (nonContainerGrader && assignmentProblem) { - const [problemScore, problemFeedback] = checkAnswer(form[question], nonContainerGrader) - score += problemScore - feedback += `${problemFeedback}\n` - - const problemScoreObj: SubmissionProblemScore = { - submissionId: submissionId, - assignmentProblemId: assignmentProblem.id ?? 0, //This should never be undefined, not sure why our types have id set as optional - score: problemScore, - feedback: problemFeedback - } - submissionProblemScoreService.create(problemScoreObj) - } +async function runNonContainerAutograders( + form: any, + nonContainerAutograders: NonContainerAutoGrader[], + assignmentProblems: AssignmentProblem[], + submissionId: number +) { + let score = 0 + let feedback = '' + + for (const question in form) { + const nonContainerGrader = nonContainerAutograders.find(grader => grader.question === question) + const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) + + if (nonContainerGrader && assignmentProblem) { + const [problemScore, problemFeedback] = checkAnswer(form[question], nonContainerGrader) + score += problemScore + feedback += `${problemFeedback}\n` + + const problemScoreObj: SubmissionProblemScore = { + submissionId: submissionId, + assignmentProblemId: assignmentProblem.id ?? 0, //This should never be undefined, not sure why our types have id set as optional + score: problemScore, + feedback: problemFeedback, + } + submissionProblemScoreService.create(problemScoreObj) } - return {score, feedback} + } + return { score, feedback } } async function runContainerAutograders(filepaths: string[], submission: Submission, assignmentId: number) { - let containerGrading = true - let jobResponse = null + let containerGrading = true + let jobResponse = null - const {graderData, makefileData, autogradingImage, timeout} = await containerAutograderService.getGraderByAssignmentId(assignmentId) - if (!graderData || !makefileData || !autogradingImage || !timeout) { - containerGrading = false - } else { - try { - const bucketName = await courseService.retrieve(submission.courseId).then((course) => { - return course ? (course.number + course.semester + course.id).toLowerCase() : 'submission' - }) - initializeMinio(bucketName) - - const labName = `${bucketName}-${submission.assignmentId}` - const optionFiles = [] - const openResponse = await createCourse(labName) - if (openResponse) { - await uploadFile(labName, graderData, "Graderfile") - await uploadFile(labName, makefileData, "Makefile") - - for (const filepath of filepaths){ - const buffer = await downloadFile(bucketName, filepath) - if (await uploadFile(labName, buffer, filepath)) { - optionFiles.push({localFile: filepath, destFile: filepath}) - } - } - const jobOptions = { - image: autogradingImage, - files: [{localFile: "Graderfile", destFile: "autograde.tar"}, - {localFile: "Makefile", destFile: "Makefile"},] - .concat(optionFiles), - jobName: `${labName}-${submission.id}`, - output_file: `${labName}-${submission.id}-output.txt`, - timeout: timeout, - callback_url: `http://api:3001/course/${submission.courseId}/grade/callback/${labName}-${submission.id}-output.txt` - } - jobResponse = await addJob(labName, jobOptions) - } - } catch (e: any) { - throw new Error(e) + const { graderData, makefileData, autogradingImage, timeout } = + await containerAutograderService.getGraderByAssignmentId(assignmentId) + if (!graderData || !makefileData || !autogradingImage || !timeout) { + containerGrading = false + } else { + try { + const bucketName = await courseService.retrieve(submission.courseId).then(course => { + return course ? (course.number + course.semester + course.id).toLowerCase() : 'submission' + }) + initializeMinio(bucketName) + + const labName = `${bucketName}-${submission.assignmentId}` + const optionFiles = [] + const openResponse = await createCourse(labName) + if (openResponse) { + await uploadFile(labName, graderData, 'Graderfile') + await uploadFile(labName, makefileData, 'Makefile') + + for (const filepath of filepaths) { + const buffer = await downloadFile(bucketName, filepath) + if (await uploadFile(labName, buffer, filepath)) { + optionFiles.push({ localFile: filepath, destFile: filepath }) + } + } + const jobOptions = { + image: autogradingImage, + files: [ + { localFile: 'Graderfile', destFile: 'autograde.tar' }, + { localFile: 'Makefile', destFile: 'Makefile' }, + ].concat(optionFiles), + jobName: `${labName}-${submission.id}`, + output_file: `${labName}-${submission.id}-output.txt`, + timeout: timeout, + callback_url: `http://api:3001/course/${submission.courseId}/grade/callback/${labName}-${submission.id}-output.txt`, } + jobResponse = await addJob(labName, jobOptions) + } + } catch (e: any) { + throw new Error(e) } - return {containerGrading, jobResponse} + } + return { containerGrading, jobResponse } } - async function tangoCallback(outputFile: string) { - //Output filename consists of 4 sections separated by hyphens. + and () only for visual clarity, not a part of the filename - //(course.number+course.semester+course.id)-(assignment.id)-(submission.id)-(output.txt) - const filenameSplit = outputFile.split('-') - const labName = `${filenameSplit[0]}-${filenameSplit[1]}` - const assignmentId = Number(filenameSplit[1]) - const submissionId = Number(filenameSplit[2]) + //Output filename consists of 4 sections separated by hyphens. + and () only for visual clarity, not a part of the filename + //(course.number+course.semester+course.id)-(assignment.id)-(submission.id)-(output.txt) + const filenameSplit = outputFile.split('-') + const labName = `${filenameSplit[0]}-${filenameSplit[1]}` + const assignmentId = Number(filenameSplit[1]) + const submissionId = Number(filenameSplit[2]) + + try { + const response = await pollJob(labName, outputFile) + if (typeof response !== 'string') throw 'Autograder output file not found' try { - const response = await pollJob(labName, outputFile) - if (typeof response !== 'string') throw 'Autograder output file not found' - - try { - const splitResponse = response.split(/\r\n|\r|\n/) - var scoresLine = JSON.parse(splitResponse[splitResponse.length - 2]) - } catch { - throw response - } - const scores = scoresLine.scores - - let score = 0 - const assignmentProblems = await assignmentProblemService.list(assignmentId) - const submissionScoreModel = await submissionScoreService.retrieve(submissionId) - const submissionModel = await submissionService.retrieve(submissionId) - if (!submissionModel) throw "Submission not found." - const submission = serializeSubmission(submissionModel) - - for (const question in scores) { - const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) - if (assignmentProblem) { - const problemScoreObj: SubmissionProblemScore = { - submissionId: submissionId, - assignmentProblemId: assignmentProblem.id, - score: Number(scores[question]), - feedback: `Autograder graded ${assignmentProblem.problemName} for ${Number(scores[question])} points` - } - submissionProblemScoreService.create(problemScoreObj) - score += Number(scores[question]) - } - } - if (submissionScoreModel) { //If noncontainer grading has occured - var submissionScore = serializeSubmissionScore(submissionScoreModel) - submissionScore.score = (submissionScore.score ?? 0) + score - score = submissionScore.score - submissionScore.feedback += `\n${response}` - - await submissionScoreService.update(submissionScore) - } else { //If submission is exclusively container graded - var submissionScore: SubmissionScore = { - submissionId: submissionId, - score: score, //Sum of all SubmissionProblemScore scores - feedback: response //Feedback from Tango - } - await submissionScoreService.create(submissionScore) + const splitResponse = response.split(/\r\n|\r|\n/) + var scoresLine = JSON.parse(splitResponse[splitResponse.length - 2]) + } catch { + throw response + } + const scores = scoresLine.scores + + let score = 0 + const assignmentProblems = await assignmentProblemService.list(assignmentId) + const submissionScoreModel = await submissionScoreService.retrieve(submissionId) + const submissionModel = await submissionService.retrieve(submissionId) + if (!submissionModel) throw 'Submission not found.' + const submission = serializeSubmission(submissionModel) + + for (const question in scores) { + const assignmentProblem = assignmentProblems.find(problem => problem.problemName === question) + if (assignmentProblem) { + const problemScoreObj: SubmissionProblemScore = { + submissionId: submissionId, + assignmentProblemId: assignmentProblem.id, + score: Number(scores[question]), + feedback: `Autograder graded ${assignmentProblem.problemName} for ${Number(scores[question])} points`, } - await updateAssignmentScore(submission, score) + submissionProblemScoreService.create(problemScoreObj) + score += Number(scores[question]) + } + } + if (submissionScoreModel) { + //If noncontainer grading has occured + var submissionScore = serializeSubmissionScore(submissionScoreModel) + submissionScore.score = (submissionScore.score ?? 0) + score + score = submissionScore.score + submissionScore.feedback += `\n${response}` - return {submissionScore: submissionScore, outputFile: response} - } catch (e: any) { - callbackFailure(assignmentId, submissionId, e) - throw new Error(e) + await submissionScoreService.update(submissionScore) + } else { + //If submission is exclusively container graded + var submissionScore: SubmissionScore = { + submissionId: submissionId, + score: score, //Sum of all SubmissionProblemScore scores + feedback: response, //Feedback from Tango + } + await submissionScoreService.create(submissionScore) } + await updateAssignmentScore(submission, score) + + return { submissionScore: submissionScore, outputFile: response } + } catch (e: any) { + callbackFailure(assignmentId, submissionId, e) + throw new Error(e) + } } export async function callbackFailure(assignmentId: number, submissionId: number, file: string) { - const assignmentProblems = await assignmentProblemService.list(assignmentId) - const submissionScoreModel = await submissionScoreService.retrieve(submissionId) - const submissionProblemScoreModels = await submissionProblemScoreService.list(submissionId) - - for (const assignmentProblem of assignmentProblems) { - const submissionProblemScore = submissionProblemScoreModels.find((sps) => sps.assignmentProblemId === assignmentProblem.id) - if (!submissionProblemScore) { //If assignmentProblem hasn't already been graded by noncontainer autograder - const problemScoreObj: SubmissionProblemScore = { - submissionId: submissionId, - assignmentProblemId: assignmentProblem.id, - score: 0, - feedback: 'Autograding failed to complete.' - } - submissionProblemScoreService.create(problemScoreObj) - } + const assignmentProblems = await assignmentProblemService.list(assignmentId) + const submissionScoreModel = await submissionScoreService.retrieve(submissionId) + const submissionProblemScoreModels = await submissionProblemScoreService.list(submissionId) + + for (const assignmentProblem of assignmentProblems) { + const submissionProblemScore = submissionProblemScoreModels.find( + sps => sps.assignmentProblemId === assignmentProblem.id + ) + if (!submissionProblemScore) { + //If assignmentProblem hasn't already been graded by noncontainer autograder + const problemScoreObj: SubmissionProblemScore = { + submissionId: submissionId, + assignmentProblemId: assignmentProblem.id, + score: 0, + feedback: 'Autograding failed to complete.', + } + submissionProblemScoreService.create(problemScoreObj) } - if (submissionScoreModel) { //If noncontainer grading has occured - var submissionScore = serializeSubmissionScore(submissionScoreModel) - submissionScore.score = (submissionScore.score ?? 0) - submissionScore.feedback += `\n${file}` - - submissionScoreService.update(submissionScore) - } else { //If submission is exclusively container graded - var submissionScore: SubmissionScore = { - submissionId: submissionId, - score: 0, - feedback: file - } - submissionScoreService.create(submissionScore) + } + if (submissionScoreModel) { + //If noncontainer grading has occured + var submissionScore = serializeSubmissionScore(submissionScoreModel) + submissionScore.score = submissionScore.score ?? 0 + submissionScore.feedback += `\n${file}` + + submissionScoreService.update(submissionScore) + } else { + //If submission is exclusively container graded + var submissionScore: SubmissionScore = { + submissionId: submissionId, + score: 0, + feedback: file, } + submissionScoreService.create(submissionScore) + } } //Currently just sets assignmentscore to the latest submission. Pulled this function out for easy future modification. async function updateAssignmentScore(submission: Submission, score: number) { - const assignmentScoreModel = await assignmentScoreService.retrieveByUser(submission.assignmentId, submission.userId) - if (assignmentScoreModel) { //If assignmentScore already exists, update existing entity - const assignmentScore = serializeAssignmentScore(assignmentScoreModel) - assignmentScore.score = score - assignmentScoreService.update(assignmentScore) - - } else { //Otherwise create a new one - const assignmentScore: AssignmentScore = { - assignmentId: submission.assignmentId, - userId: submission.userId, - score: score, - } - assignmentScoreService.create(assignmentScore) + const assignmentScoreModel = await assignmentScoreService.retrieveByUser(submission.assignmentId, submission.userId) + if (assignmentScoreModel) { + //If assignmentScore already exists, update existing entity + const assignmentScore = serializeAssignmentScore(assignmentScoreModel) + assignmentScore.score = score + assignmentScoreService.update(assignmentScore) + } else { + //Otherwise create a new one + const assignmentScore: AssignmentScore = { + assignmentId: submission.assignmentId, + userId: submission.userId, + score: score, } + assignmentScoreService.create(assignmentScore) + } } -export default { grade, tangoCallback } \ No newline at end of file +export default { grade, tangoCallback } diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts index fea1ef50..43fab9a7 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.grader.ts @@ -11,15 +11,21 @@ export function checkAnswer(studentAnswer: string, nonContainerAutoGrader: NonCo const isMatch: boolean = pattern.test(studentAnswer) if (isMatch) { - return [nonContainerAutoGrader.score, `Autograder graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`] + return [ + nonContainerAutoGrader.score, + `Autograder graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`, + ] } } else { // if no regex is set use normal string matching if (studentAnswer === nonContainerAutoGrader.correctString) { - return [nonContainerAutoGrader.score, `Autograder graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`] + return [ + nonContainerAutoGrader.score, + `Autograder graded "${nonContainerAutoGrader.question}" for ${nonContainerAutoGrader.score} points`, + ] } } // default value to return if all conditions fail to execute // i.e. the answer is incorrect or improperly formatted return [0, `Autograder graded "${nonContainerAutoGrader.question}" for 0 points`] -} \ No newline at end of file +} diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts index 99eab720..ca786586 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.service.ts @@ -1,8 +1,9 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import NonContainerAutoGraderModel from './nonContainerAutoGrader.model' import { NonContainerAutoGrader } from 'devu-shared-modules' -const connect = () => getRepository(NonContainerAutoGraderModel) +const connect = () => dataSource.getRepository(NonContainerAutoGraderModel) export async function create(nonContainerQuestion: NonContainerAutoGrader) { return await connect().save(nonContainerQuestion) @@ -20,17 +21,17 @@ export async function _delete(id: number) { } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } // Retrieve all the nonContainerQuestions linked to a particular assignment by assignmentId export async function listByAssignmentId(assignmentId: number) { if (!assignmentId) throw new Error('Missing AssignmentId') - return await connect().find({ assignmentId: assignmentId, deletedAt: IsNull() }) + return await connect().findBy({ assignmentId: assignmentId, deletedAt: IsNull() }) } export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().findBy({ deletedAt: IsNull() }) } export default { diff --git a/devU-api/src/entities/role/role.service.ts b/devU-api/src/entities/role/role.service.ts index 79e4da51..bc97caa6 100644 --- a/devU-api/src/entities/role/role.service.ts +++ b/devU-api/src/entities/role/role.service.ts @@ -1,11 +1,12 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import { Role as RoleType } from 'devu-shared-modules' import Role from './role.model' import Defaults from './role.defaults' -const connect = () => getRepository(Role) +const connect = () => dataSource.getRepository(Role) export async function create(role: RoleType) { return await connect().save(role) @@ -63,11 +64,11 @@ export async function _delete(id: number) { } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } export async function retrieveByCourseAndName(courseId: number, name: string) { - return await connect().findOne({ courseId: courseId, name: name, deletedAt: IsNull() }) + return await connect().findOneBy({ courseId: courseId, name: name, deletedAt: IsNull() }) } export function retrieveDefaultByName(name: string) { @@ -75,11 +76,11 @@ export function retrieveDefaultByName(name: string) { } export async function listAll() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().findBy({ deletedAt: IsNull() }) } export async function listByCourse(courseId: number) { - return await connect().find({ courseId: courseId, deletedAt: IsNull() }) + return await connect().findBy({ courseId: courseId, deletedAt: IsNull() }) } export default { diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index 79d30c18..c8b4517c 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -40,7 +40,6 @@ const upload = Multer() Router.get('/', /*isAuthorized('submissionViewAll'),*/ SubmissionController.getByAssignment) // TODO: Authorization - Router.get('/assignments/:assignmentId', asInt('assignmentId'), SubmissionController.getByAssignment) /** @@ -79,7 +78,7 @@ Router.get('/:id', isAuthorized('enrolled'), asInt(), SubmissionController.detai * schema: * type: integer */ -Router.get('/user/:userId', isAuthorized('enrolled'), asInt("userId"), SubmissionController.listByUser) +Router.get('/user/:userId', isAuthorized('enrolled'), asInt('userId'), SubmissionController.listByUser) // TODO: submissionViewAll or enrolled/self /** diff --git a/devU-api/src/entities/submission/submission.service.ts b/devU-api/src/entities/submission/submission.service.ts index bb1e5a30..137b060c 100644 --- a/devU-api/src/entities/submission/submission.service.ts +++ b/devU-api/src/entities/submission/submission.service.ts @@ -1,4 +1,5 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import SubmissionModel from '../submission/submission.model' import FileModel from '../../fileUpload/fileUpload.model' @@ -8,13 +9,14 @@ import { FileUpload, Submission } from 'devu-shared-modules' import { uploadFile } from '../../fileStorage' import { groupBy } from '../../database' -const submissionConn = () => getRepository(SubmissionModel) -const fileConn = () => getRepository(FileModel) +const submissionConn = () => dataSource.getRepository(SubmissionModel) +const fileConn = () => dataSource.getRepository(FileModel) export async function create(submission: Submission, file?: Express.Multer.File | undefined) { if (file) { - const bucket: string = await getRepository(CourseModel) - .findOne({ id: submission.courseId }) + const bucket: string = await dataSource + .getRepository(CourseModel) + .findOneBy({ id: submission.courseId }) .then(course => { if (course) { return (course.number + course.semester + course.id).replace(/ /g, '-').toLowerCase() @@ -50,7 +52,7 @@ export async function _delete(id: number) { } export async function retrieve(id: number) { - return await submissionConn().findOne({ id, deletedAt: IsNull() }) + return await submissionConn().findOneBy({ id, deletedAt: IsNull() }) } export async function list(query: any, id: number) { @@ -69,4 +71,4 @@ export default { _delete, list, listByAssignment, -} \ No newline at end of file +} diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts index 2f088109..630e799c 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.service.ts @@ -1,10 +1,11 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import { SubmissionProblemScore } from 'devu-shared-modules' import SubmissionProblemScoreModel from './submissionProblemScore.model' -const connect = () => getRepository(SubmissionProblemScoreModel) +const connect = () => dataSource.getRepository(SubmissionProblemScoreModel) export async function create(submissionProblemScore: SubmissionProblemScore) { return await connect().save(submissionProblemScore) @@ -24,11 +25,11 @@ export async function _delete(id: number) { export async function retrieve(id: number, courseId: number) { // TODO: Submission Problem Score doesn't know its courseId (neither does submission score). Best way to ensure the request is authorized based on course permissions? - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } export async function list(submissionId: number) { - return await connect().find({ submissionId: submissionId, deletedAt: IsNull() }) + return await connect().findBy({ submissionId: submissionId, deletedAt: IsNull() }) } export default { diff --git a/devU-api/src/entities/submissionScore/submissionScore.service.ts b/devU-api/src/entities/submissionScore/submissionScore.service.ts index 83167b59..82a676d6 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.service.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.service.ts @@ -1,10 +1,11 @@ -import { FindManyOptions, getRepository, IsNull } from 'typeorm' +import { FindManyOptions, IsNull } from 'typeorm' +import { dataSource } from '../../database' import SubmissionScoreModel from '../submissionScore/submissionScore.model' import { SubmissionScore } from 'devu-shared-modules' -const connect = () => getRepository(SubmissionScoreModel) +const connect = () => dataSource.getRepository(SubmissionScoreModel) export async function create(submissionScore: SubmissionScore) { return await connect().save(submissionScore) @@ -28,7 +29,7 @@ export async function _delete(id: number) { } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } export async function list(submissionId?: number) { diff --git a/devU-api/src/entities/user/user.service.ts b/devU-api/src/entities/user/user.service.ts index b32cafd5..fa2b5841 100644 --- a/devU-api/src/entities/user/user.service.ts +++ b/devU-api/src/entities/user/user.service.ts @@ -1,4 +1,5 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import UserModel from './user.model' @@ -6,7 +7,7 @@ import { User } from 'devu-shared-modules' import UserCourseService from '../userCourse/userCourse.service' -const connect = () => getRepository(UserModel) +const connect = () => dataSource.getRepository(UserModel) export async function create(user: User) { return await connect().save(user) @@ -25,25 +26,25 @@ export async function _delete(id: number) { } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } export async function list() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().findBy({ deletedAt: IsNull() }) } export async function listByCourse(courseId: number, userRole?: string) { const userCourses = await UserCourseService.listByCourse(courseId) const userPromises = userCourses // .filter(uc => !userRole || uc.role === userRole) - .map(uc => connect().findOne({ id: uc.userId, deletedAt: IsNull() })) + .map(uc => connect().findOneBy({ id: uc.userId, deletedAt: IsNull() })) return await Promise.all(userPromises) } export async function ensure(userInfo: User) { const { externalId, email } = userInfo - const user = await connect().findOne({ externalId }) + const user = await connect().findOneBy({ externalId }) if (user) return { user, isNewUser: false } diff --git a/devU-api/src/entities/userCourse/userCourse.controller.ts b/devU-api/src/entities/userCourse/userCourse.controller.ts index 4e5fbd32..3eee4ffb 100644 --- a/devU-api/src/entities/userCourse/userCourse.controller.ts +++ b/devU-api/src/entities/userCourse/userCourse.controller.ts @@ -1,9 +1,9 @@ -import {NextFunction, Request, Response} from 'express' +import { NextFunction, Request, Response } from 'express' import UserCourseService from './userCourse.service' -import {serialize} from './userCourse.serializer' +import { serialize } from './userCourse.serializer' -import {GenericResponse, NotFound, Updated} from '../../utils/apiResponse.utils' +import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' export async function getAll(req: Request, res: Response, next: NextFunction) { try { diff --git a/devU-api/src/entities/userCourse/userCourse.router.ts b/devU-api/src/entities/userCourse/userCourse.router.ts index 54c3d274..c781c49c 100644 --- a/devU-api/src/entities/userCourse/userCourse.router.ts +++ b/devU-api/src/entities/userCourse/userCourse.router.ts @@ -3,15 +3,14 @@ import express from 'express' // Middleware import validator from './userCourse.validator' -import {asInt} from '../../middleware/validator/generic.validator' -import {extractOwnerByPathParam, isAuthorized} from '../../authorization/authorization.middleware' +import { asInt } from '../../middleware/validator/generic.validator' +import { extractOwnerByPathParam, isAuthorized } from '../../authorization/authorization.middleware' // Controller import UserCourseController from './userCourse.controller' const Router = express.Router({ mergeParams: true }) - /** * TODO: Document this */ @@ -76,7 +75,6 @@ Router.get('/:id', isAuthorized('courseViewAll'), asInt(), UserCourseController. * type: integer */ - Router.get( '/user/:userId', extractOwnerByPathParam('userId'), diff --git a/devU-api/src/entities/userCourse/userCourse.service.ts b/devU-api/src/entities/userCourse/userCourse.service.ts index abe71825..88baae89 100644 --- a/devU-api/src/entities/userCourse/userCourse.service.ts +++ b/devU-api/src/entities/userCourse/userCourse.service.ts @@ -1,14 +1,15 @@ -import { getRepository, IsNull } from 'typeorm' +import { IsNull } from 'typeorm' +import { dataSource } from '../../database' import { UserCourse as UserCourseType } from 'devu-shared-modules' import UserCourse from './userCourse.model' -const connect = () => getRepository(UserCourse) +const connect = () => dataSource.getRepository(UserCourse) export async function create(userCourse: UserCourseType) { const userId = userCourse.userId - const hasEnrolled = await connect().findOne({ userId, courseId: userCourse.courseId }) + const hasEnrolled = await connect().findOneBy({ userId, courseId: userCourse.courseId }) if (hasEnrolled) throw new Error('User already enrolled in course') return await connect().save(userCourse) } @@ -16,7 +17,7 @@ export async function create(userCourse: UserCourseType) { export async function update(userCourse: UserCourseType, currentUser: number) { const { courseId, role, dropped } = userCourse if (!courseId) throw new Error('Missing course Id') - const userCourseData = await connect().findOne({ courseId, userId: currentUser }) + const userCourseData = await connect().findOneBy({ courseId, userId: currentUser }) if (!userCourseData) throw new Error('User not enrolled in course') userCourseData.role = role userCourseData.dropped = dropped @@ -25,43 +26,40 @@ export async function update(userCourse: UserCourseType, currentUser: number) { } export async function _delete(courseId: number, userId: number) { - const userCourse = await connect().findOne({ courseId, userId }) + const userCourse = await connect().findOneBy({ courseId, userId }) if (!userCourse) throw new Error('User Not Found in Course') return await connect().softDelete({ courseId, userId, deletedAt: IsNull() }) } export async function retrieve(id: number) { - return await connect().findOne({ id, deletedAt: IsNull() }) + return await connect().findOneBy({ id, deletedAt: IsNull() }) } export async function retrieveByCourseAndUser(courseId: number, userId: number) { - return await connect().findOne({ courseId: courseId, userId: userId, deletedAt: IsNull() }) + return await connect().findOneBy({ courseId: courseId, userId: userId, deletedAt: IsNull() }) } export async function list(userId: number) { // TODO: look into/test this - return await connect().find({ userId, deletedAt: IsNull() }) + return await connect().findBy({ userId, deletedAt: IsNull() }) } export async function listAll() { - return await connect().find({ deletedAt: IsNull() }) + return await connect().findBy({ deletedAt: IsNull() }) } export async function listByCourse(courseId: number) { // TODO: look into/test this - return await connect().find({ courseId, deletedAt: IsNull() }) + return await connect().findBy({ courseId, deletedAt: IsNull() }) } export async function listByUser(userId: number) { - return await connect().find({ userId: userId, dropped: false, deletedAt: IsNull() }) + return await connect().findBy({ userId: userId, dropped: false, deletedAt: IsNull() }) } export async function checkIfEnrolled(userId: number, courseId: number) { - return await connect().findOne({ userId, courseId, dropped: false, deletedAt: IsNull() }) + return await connect().findOneBy({ userId, courseId, dropped: false, deletedAt: IsNull() }) } - - - export default { create, retrieve, diff --git a/devU-api/src/fileStorage.ts b/devU-api/src/fileStorage.ts index 91390e78..c8d4a3a6 100644 --- a/devU-api/src/fileStorage.ts +++ b/devU-api/src/fileStorage.ts @@ -24,55 +24,58 @@ export async function initializeMinio(inputBucketName?: string) { if (bucketExists) return - minioClient.makeBucket(inputBucketName, 'us-east-1', function (err) { - if (err) { - throw new Error(`Error creating MinIO bucket '${inputBucketName}'`) - } + await minioClient.makeBucket(inputBucketName, 'us-east-1').catch(err => { + throw new Error(`Error creating MinIO bucket '${inputBucketName}'`) }) return - }else{ + } else { for (const bucketName of Object.values(BucketNames)) { const bucketExists = await minioClient.bucketExists(bucketName) - + if (bucketExists) continue - - minioClient.makeBucket(bucketName, 'us-east-1', function (err) { - if (err) { - throw new Error(`Error creating MinIO bucket '${bucketName}'`) - } + + minioClient.makeBucket(bucketName, 'us-east-1').catch(err => { + throw new Error(`Error creating MinIO bucket '${bucketName}'`) }) } } } - -export async function uploadFile(bucketName: string, file: Express.Multer.File, filename:string): Promise { - return new Promise((resolve, reject) => { - minioClient.putObject(bucketName, filename, file.buffer, (err, etag) => { - if (err) { - reject(new Error('File failed to upload because '+err.message)) - } else { - resolve(etag.etag) - } - }) - }) +export async function uploadFile(bucketName: string, file: Express.Multer.File, filename: string): Promise { + try { + const objInfo = await minioClient.putObject(bucketName, filename, file.buffer) + return objInfo.etag + } catch (err: Error | any) { + if (err) { + throw new Error('File failed to upload because ' + err) + } else { + throw err + } + } } export async function downloadFile(bucketName: string, filename: string): Promise { - return new Promise((resolve, reject) => { + try { + const dataStream = await minioClient.getObject(bucketName, filename) const fileData: Buffer[] = [] - - minioClient.getObject(bucketName, filename, (err, dataStream) => { - if (err) { - reject(new Error('File failed to download from MinIO because '+err.message)) - } - dataStream.on('data', (chunk:any) => { + return new Promise((resolve, reject) => { + dataStream.on('data', (chunk: Buffer) => { fileData.push(chunk) }) dataStream.on('end', () => { resolve(Buffer.concat(fileData)) }) + + dataStream.on('error', (err: any) => { + reject(new Error('File failed to download from MinIO because ' + err)) + }) }) - }) -} \ No newline at end of file + } catch (err: Error | any) { + if (err) { + throw new Error('File failed to download because ' + err) + } else { + throw err + } + } +} diff --git a/devU-api/src/fileUpload/fileUpload.service.ts b/devU-api/src/fileUpload/fileUpload.service.ts index 98b10c04..c3f0e746 100644 --- a/devU-api/src/fileUpload/fileUpload.service.ts +++ b/devU-api/src/fileUpload/fileUpload.service.ts @@ -1,7 +1,7 @@ import { FileUpload } from '../../devu-shared-modules' import { generateFilename } from '../utils/fileUpload.utils' -import { minioClient, uploadFile } from '../fileStorage' +import { minioClient, uploadFile, downloadFile } from '../fileStorage' export async function create(files: Express.Multer.File[], bucketName: string, userId: number) { try { @@ -34,24 +34,13 @@ export async function create(files: Express.Multer.File[], bucketName: string, u export async function retrieve(bucketName: string, fileName: string): Promise { return new Promise((resolve, reject) => { - minioClient.getObject(bucketName, fileName, function (err, dataStream) { - if (err) { - reject(err) - } - - let file: Buffer[] = [] - dataStream.on('data', function (chunk: Buffer) { - file.push(chunk) - }) - - dataStream.on('end', function () { - resolve(Buffer.concat(file)) + downloadFile(bucketName, fileName) + .then(data => { + resolve(data) }) - - dataStream.on('error', function (err: Error) { + .catch(err => { reject(err) }) - }) }) } diff --git a/devU-api/src/index.ts b/devU-api/src/index.ts index f5cd0034..0b5e71b0 100644 --- a/devU-api/src/index.ts +++ b/devU-api/src/index.ts @@ -2,17 +2,15 @@ import 'reflect-metadata' import express from 'express' -import bodyParser from 'body-parser' import cors from 'cors' import helmet from 'helmet' import morgan from 'morgan' -import { createConnection } from 'typeorm' import cookieParser from 'cookie-parser' import passport from 'passport' +import { dataSource } from './database' import environment from './environment' -import connectionInfo from './database' import { initializeMinio } from './fileStorage' // Middleware @@ -25,11 +23,20 @@ import './utils/passport.utils' const app = express() initializeMinio() - .then(() => createConnection(connectionInfo)) + .then(() => + dataSource + .initialize() + .then(() => { + console.log('Data Source has been initialized!') + }) + .catch(err => { + console.error('Error during Data Source initialization', err) + }) + ) .then(_connection => { app.use(helmet()) - app.use(bodyParser.urlencoded({ extended: true })) - app.use(bodyParser.json()) + app.use(express.urlencoded({ extended: true })) + app.use(express.json()) app.use(cookieParser()) app.use(cors({ origin: environment.clientUrl, credentials: true })) app.use(morgan('combined')) diff --git a/devU-api/src/model/index.ts b/devU-api/src/model/index.ts index 0e2a286b..b8b1226a 100644 --- a/devU-api/src/model/index.ts +++ b/devU-api/src/model/index.ts @@ -19,7 +19,6 @@ type Models = | AssignmentModel | AssignmentProblemModel | AssignmentScoreModel - | CategoryModel | CategoryScoreModel | ContainerAutoGraderModel | CourseModel diff --git a/devU-api/src/tango/tango.service.ts b/devU-api/src/tango/tango.service.ts index 3aa2bc44..df26730f 100644 --- a/devU-api/src/tango/tango.service.ts +++ b/devU-api/src/tango/tango.service.ts @@ -13,7 +13,7 @@ const tangoKey = process.env.TANGO_KEY ?? 'test' export async function createCourse(course: string): Promise { const url = `${tangoHost}/open/${tangoKey}/${course}/` const response = await fetch(url, { method: 'GET' }) - return response.ok ? await response.json() as OpenResponse : null + return response.ok ? ((await response.json()) as OpenResponse) : null } /** @@ -24,8 +24,8 @@ export async function createCourse(course: string): Promise */ export async function uploadFile(course: string, file: Buffer, fileName: string): Promise { const url = `${tangoHost}/upload/${tangoKey}/${course}/` - const response = await fetch(url, { method: 'POST', body: file, headers: { 'filename': fileName } }) - return response.ok ? await response.json() as UploadResponse : null + const response = await fetch(url, { method: 'POST', body: file, headers: { filename: fileName } }) + return response.ok ? ((await response.json()) as UploadResponse) : null } /** @@ -40,7 +40,7 @@ export async function addJob(course: string, job: AddJobRequest): Promise { //PollSuccessResponse +export async function pollJob(course: string, outputFile: string): Promise { + //PollSuccessResponse const url = `${tangoHost}/poll/${tangoKey}/${course}/${outputFile}/` const response = await fetch(url, { method: 'GET' }) - return response.headers.get('Content-Type')?.includes('application/json') - ? (await response.json()) as PollFailureResponse - : (await response.text()) as PollSuccessResponse + return response.headers.get('Content-Type')?.includes('application/json') + ? ((await response.json()) as PollFailureResponse) + : ((await response.text()) as PollSuccessResponse) } /** @@ -72,7 +73,7 @@ export async function tangoHelloWorld(): Promise { export async function getInfo(): Promise { const url = `${tangoHost}/info/${tangoKey}/` const response = await fetch(url, { method: 'GET' }) - return response.ok ? await response.json() as InfoResponse: null + return response.ok ? ((await response.json()) as InfoResponse) : null } /** @@ -82,7 +83,7 @@ export async function getInfo(): Promise { export async function getPoolInfo(image: string): Promise { const url = `${tangoHost}/pool/${tangoKey}/${image}/` const response = await fetch(url, { method: 'GET' }) - return response.ok ? await response.json() as Object: null + return response.ok ? ((await response.json()) as Object) : null } /** @@ -91,14 +92,18 @@ export async function getPoolInfo(image: string): Promise { * @param num - The number of instances to pre-allocate. * @param request - The request object. */ -export async function preallocateInstances(image: string, num: number, request: PreallocRequest): Promise { +export async function preallocateInstances( + image: string, + num: number, + request: PreallocRequest +): Promise { const url = `${tangoHost}/prealloc/${tangoKey}/${image}/${num}/` const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }) - return response.ok ? await response.json() as PreallocResponse: null + return response.ok ? ((await response.json()) as PreallocResponse) : null } /** @@ -110,4 +115,4 @@ export async function getJobs(deadjobs: number): Promise<{} | null> { const url = `${tangoHost}/jobs/${tangoKey}/${deadjobs}/` const response = await fetch(url, { method: 'POST' }) return response.ok ? {} : null -} \ No newline at end of file +} From 1b331a42625ed825f27fc1957608b253ec1b5cb7 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sat, 11 May 2024 05:24:37 -0400 Subject: [PATCH 127/400] Update .gitignore to avoid add package-lock.json --- .gitignore | 4 +++- devU-api/.gitignore | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e0c4dad5..195f41c7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ node_modules/ ./docker-compose.yml /.idea /.vscode -/logs \ No newline at end of file +/logs +/*/node_modules +/*/package-lock.json \ No newline at end of file diff --git a/devU-api/.gitignore b/devU-api/.gitignore index 1410eb81..e8113070 100644 --- a/devU-api/.gitignore +++ b/devU-api/.gitignore @@ -3,6 +3,7 @@ build/ env/ .idea/ /devu-shared-modules +/package-lock.json config/*.yml !config/test.yml From 584f7ac3a688d12eb6d8c39caf7f33bf77872730 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sat, 11 May 2024 05:46:04 -0400 Subject: [PATCH 128/400] Update run local for devU-client to avoid issue causing error because of Node Version --- devU-api/package.json | 2 +- devU-client/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/devU-api/package.json b/devU-api/package.json index 6907e91d..8db25dd1 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -56,7 +56,7 @@ "rimraf": "^3.0.2", "ts-jest": "^27.0.2", "ts-node": "^10.0.0", - "ts-node-dev": "^1.1.6", + "ts-node-dev": "^2.0.0", "typescript": "^4.3.2" }, "dependencies": { diff --git a/devU-client/package.json b/devU-client/package.json index 14ab416b..32705846 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -1,7 +1,7 @@ { "scripts": { "update-shared": "npm update devu-shared-modules", - "local": "cross-env NODE_ENV=local webpack-dev-server -d --open --mode development", + "local": "cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=local webpack-dev-server -d --open --mode development", "start": "cross-env NODE_ENV=development webpack-dev-server -d --open --mode development", "prod": "cross-env NODE_ENV=production webpack-dev-server -d --open --mode development", "build": "cross-env NODE_ENV=production webpack -p --mode=production", From 5e79975331627e5a2a2c3d40f4f48afde09a3061 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sat, 11 May 2024 21:54:50 -0400 Subject: [PATCH 129/400] update more files with the new npm module to avoid critical vulnerabilities --- devU-api/package.json | 6 ++---- .../authentication/authentication.service.ts | 1 + devU-api/src/database.ts | 19 +------------------ .../assignment/assignment.controller.ts | 4 +++- .../assignmentProblem.controller.ts | 4 +++- .../assignmentScore.controller.ts | 4 +++- .../entities/category/category.controller.ts | 4 +++- .../containerAutoGrader.controller.ts | 4 +++- .../src/entities/course/course.controller.ts | 8 ++++++-- .../deadlineExtensions.controller.ts | 4 +++- .../src/entities/grader/grader.controller.ts | 8 ++++++-- .../src/entities/grader/grader.service.ts | 6 +++--- .../nonContainerAutoGrader.controller.ts | 4 +++- devU-api/src/entities/role/role.controller.ts | 4 +++- .../entities/submission/submission.service.ts | 4 +--- .../submissionProblemScore.controller.ts | 4 +++- .../submissionScore.controller.ts | 4 +++- devU-api/src/entities/user/user.controller.ts | 4 +++- .../userCourse/userCourse.controller.ts | 4 +++- devU-api/src/fileStorage.ts | 2 +- devU-api/src/index.ts | 3 +-- 21 files changed, 58 insertions(+), 47 deletions(-) diff --git a/devU-api/package.json b/devU-api/package.json index 8db25dd1..6c206ca3 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -34,11 +34,9 @@ "@types/cookie-parser": "^1.4.2", "@types/cors": "^2.8.10", "@types/express": "^4.17.12", - "@types/helmet": "^4.0.0", - "@types/jest": "^26.0.24", + "@types/jest": "^27.0.00", "@types/jsonwebtoken": "^8.5.1", "@types/jws": "^3.2.3", - "@types/minio": "^7.0.8", "@types/morgan": "^1.9.2", "@types/multer": "^1.4.7", "@types/node": "^15.12.2", @@ -72,7 +70,7 @@ "jsonwebtoken": "^9.0.2", "minio": "^8.0.0", "morgan": "^1.10.0", - "multer": "^1.4.2", + "multer": "^1.4.5-lts.1", "passport": "^0.6.0", "passport-saml": "^3.2.2", "pg": "^8.4.0", diff --git a/devU-api/src/authentication/authentication.service.ts b/devU-api/src/authentication/authentication.service.ts index c04c0db9..476687e3 100644 --- a/devU-api/src/authentication/authentication.service.ts +++ b/devU-api/src/authentication/authentication.service.ts @@ -46,6 +46,7 @@ export function createRefreshToken(user: UserModel): string { export function validateJwt(token: string): TokenType | null { try { + // @ts-ignore const verificationKey = getVerificationKey(jws.decode(token).header.kid) const payload: unknown = jwt.verify(token, verificationKey, { ...jwtOptions, algorithms: ['RS256'] }) diff --git a/devU-api/src/database.ts b/devU-api/src/database.ts index 293cf8fc..f547688f 100644 --- a/devU-api/src/database.ts +++ b/devU-api/src/database.ts @@ -1,4 +1,4 @@ -import { DataSource, DataSourceOptions, Repository } from 'typeorm' +import { DataSource, DataSourceOptions } from 'typeorm' import environment from './environment' @@ -30,20 +30,3 @@ export { dataSource } @param filter: the filter object @returns the grouped data */ -export async function groupBy( - connection: Repository, - columnList: string[], - query: any, - filter: { index: string; value: number } -) { - let orders = query - // The filteredOrders currently only filters the orders by the columnList, any other orders are removed - // and only set to 'ASC' since no input is provided for the order - const filteredOrders = Object.entries(orders) - .filter(([key]) => columnList.includes(orders[key])) - .reduce((acc, [key]) => ({ ...acc, [orders[key]]: 'ASC' }), {}) - - orders = Object.keys(filteredOrders).length === 0 ? { id: 'ASC' } : filteredOrders - - return await connection.findBy({ ...orders }) -} diff --git a/devU-api/src/entities/assignment/assignment.controller.ts b/devU-api/src/entities/assignment/assignment.controller.ts index 319e4c95..4bc85386 100644 --- a/devU-api/src/entities/assignment/assignment.controller.ts +++ b/devU-api/src/entities/assignment/assignment.controller.ts @@ -54,7 +54,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts b/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts index 2de14416..ef6553ac 100644 --- a/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts +++ b/devU-api/src/entities/assignmentProblem/assignmentProblem.controller.ts @@ -40,7 +40,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts b/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts index 4f0c89a8..575c41d7 100644 --- a/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts +++ b/devU-api/src/entities/assignmentScore/assignmentScore.controller.ts @@ -84,7 +84,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/category/category.controller.ts b/devU-api/src/entities/category/category.controller.ts index 3fb84369..afe5e505 100644 --- a/devU-api/src/entities/category/category.controller.ts +++ b/devU-api/src/entities/category/category.controller.ts @@ -52,7 +52,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts index f3aa5b79..2e489398 100644 --- a/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts +++ b/devU-api/src/entities/containerAutoGrader/containerAutoGrader.controller.ts @@ -63,7 +63,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/course/course.controller.ts b/devU-api/src/entities/course/course.controller.ts index 0f24f3ba..6e5b349a 100644 --- a/devU-api/src/entities/course/course.controller.ts +++ b/devU-api/src/entities/course/course.controller.ts @@ -57,7 +57,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } @@ -77,7 +79,9 @@ export async function postAddInstructor(req: Request, res: Response, next: NextF res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.controller.ts b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.controller.ts index 21080475..b2e1ee11 100644 --- a/devU-api/src/entities/deadlineExtensions/deadlineExtensions.controller.ts +++ b/devU-api/src/entities/deadlineExtensions/deadlineExtensions.controller.ts @@ -39,7 +39,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/grader/grader.controller.ts b/devU-api/src/entities/grader/grader.controller.ts index 824d243c..df373c6f 100644 --- a/devU-api/src/entities/grader/grader.controller.ts +++ b/devU-api/src/entities/grader/grader.controller.ts @@ -13,7 +13,9 @@ export async function grade(req: Request, res: Response, next: NextFunction) { res.status(200).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } @@ -24,7 +26,9 @@ export async function tangoCallback(req: Request, res: Response, next: NextFunct res.status(200).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/grader/grader.service.ts b/devU-api/src/entities/grader/grader.service.ts index c4031c43..35f2bc52 100644 --- a/devU-api/src/entities/grader/grader.service.ts +++ b/devU-api/src/entities/grader/grader.service.ts @@ -64,8 +64,8 @@ async function grade(submissionId: number) { submissionScoreService.create(scoreObj) //If containergrading is true, tangoCallback handles assignmentScore creation - if (containerGrading === false) { - updateAssignmentScore(submission, score) + if (!containerGrading) { + await updateAssignmentScore(submission, score) return { message: `Noncontainer autograding completed successfully`, submissionScore: scoreObj } } return { @@ -116,7 +116,7 @@ async function runContainerAutograders(filepaths: string[], submission: Submissi const bucketName = await courseService.retrieve(submission.courseId).then(course => { return course ? (course.number + course.semester + course.id).toLowerCase() : 'submission' }) - initializeMinio(bucketName) + await initializeMinio(bucketName) const labName = `${bucketName}-${submission.assignmentId}` const optionFiles = [] diff --git a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts index 085eb27c..a0cea1dc 100644 --- a/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts +++ b/devU-api/src/entities/nonContainerAutoGrader/nonContainerAutoGrader.controller.ts @@ -47,7 +47,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/role/role.controller.ts b/devU-api/src/entities/role/role.controller.ts index 172a592d..45921b5a 100644 --- a/devU-api/src/entities/role/role.controller.ts +++ b/devU-api/src/entities/role/role.controller.ts @@ -65,7 +65,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/submission/submission.service.ts b/devU-api/src/entities/submission/submission.service.ts index 137b060c..e7a9ba09 100644 --- a/devU-api/src/entities/submission/submission.service.ts +++ b/devU-api/src/entities/submission/submission.service.ts @@ -7,7 +7,6 @@ import CourseModel from '../course/course.model' import { FileUpload, Submission } from 'devu-shared-modules' import { uploadFile } from '../../fileStorage' -import { groupBy } from '../../database' const submissionConn = () => dataSource.getRepository(SubmissionModel) const fileConn = () => dataSource.getRepository(FileModel) @@ -56,9 +55,8 @@ export async function retrieve(id: number) { } export async function list(query: any, id: number) { - const OrderByMappings = ['id', 'createdAt', 'updatedAt', 'courseId', 'assignmentId', 'submittedBy'] - return await groupBy(submissionConn(), OrderByMappings, query, { index: 'submittedBy', value: id }) + return await submissionConn().findBy({ ...query, submittedBy: id, deletedAt: IsNull() }) } export async function listByAssignment(assignmentId: number, id: number) { diff --git a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts index 378ea987..5bba0e9f 100644 --- a/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts +++ b/devU-api/src/entities/submissionProblemScore/submissionProblemScore.controller.ts @@ -44,7 +44,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/submissionScore/submissionScore.controller.ts b/devU-api/src/entities/submissionScore/submissionScore.controller.ts index bd8506cb..30166b32 100644 --- a/devU-api/src/entities/submissionScore/submissionScore.controller.ts +++ b/devU-api/src/entities/submissionScore/submissionScore.controller.ts @@ -55,7 +55,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/user/user.controller.ts b/devU-api/src/entities/user/user.controller.ts index 03d5aa3b..b8117343 100644 --- a/devU-api/src/entities/user/user.controller.ts +++ b/devU-api/src/entities/user/user.controller.ts @@ -55,7 +55,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/entities/userCourse/userCourse.controller.ts b/devU-api/src/entities/userCourse/userCourse.controller.ts index 3eee4ffb..4c98af8b 100644 --- a/devU-api/src/entities/userCourse/userCourse.controller.ts +++ b/devU-api/src/entities/userCourse/userCourse.controller.ts @@ -77,7 +77,9 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { - res.status(400).json(new GenericResponse(err.message)) + if (err instanceof Error) { + res.status(400).json(new GenericResponse(err.message)) + } } } diff --git a/devU-api/src/fileStorage.ts b/devU-api/src/fileStorage.ts index c8d4a3a6..f0f153ca 100644 --- a/devU-api/src/fileStorage.ts +++ b/devU-api/src/fileStorage.ts @@ -73,7 +73,7 @@ export async function downloadFile(bucketName: string, filename: string): Promis }) } catch (err: Error | any) { if (err) { - throw new Error('File failed to download because ' + err) + throw new Error(filename + " and " + bucketName +' File failed to download because ' + err) } else { throw err } diff --git a/devU-api/src/index.ts b/devU-api/src/index.ts index 0b5e71b0..bc19bc17 100644 --- a/devU-api/src/index.ts +++ b/devU-api/src/index.ts @@ -24,8 +24,7 @@ const app = express() initializeMinio() .then(() => - dataSource - .initialize() + dataSource.initialize() .then(() => { console.log('Data Source has been initialized!') }) From a3faeddca6a35a1c0cf1cff697f13f44e4a24fa2 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sat, 11 May 2024 21:57:51 -0400 Subject: [PATCH 130/400] Update .gitignore --- devU-api/.gitignore | 2 +- devU-shared/.gitignore | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/devU-api/.gitignore b/devU-api/.gitignore index e8113070..7c464a98 100644 --- a/devU-api/.gitignore +++ b/devU-api/.gitignore @@ -3,7 +3,7 @@ build/ env/ .idea/ /devu-shared-modules -/package-lock.json +package-lock.json config/*.yml !config/test.yml diff --git a/devU-shared/.gitignore b/devU-shared/.gitignore index ea6872ba..13fff090 100644 --- a/devU-shared/.gitignore +++ b/devU-shared/.gitignore @@ -1,6 +1,6 @@ node_modules/ ../devU-api/devu-shared-modules/ - +package-lock.json # Build Files *.js *.d.ts From df706a9256d7cf5531a4d9b9ac275ac8c9f12f76 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sun, 12 May 2024 02:30:06 -0400 Subject: [PATCH 131/400] fix docker compose errors --- api.Dockerfile | 2 +- devU-client/package.json | 16 ++++++++-------- devU-client/webpack.config.js | 5 ++++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/api.Dockerfile b/api.Dockerfile index 4b252993..efd6b72f 100644 --- a/api.Dockerfile +++ b/api.Dockerfile @@ -24,5 +24,5 @@ ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.2.1/wait RUN chmod +x /wait # TypeORM Migrations -CMD /wait && npm run typeorm -- migration:run && npm start +CMD /wait && npm run typeorm -- migration:run -d src/database.ts && npm start diff --git a/devU-client/package.json b/devU-client/package.json index 32705846..711c542e 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -1,13 +1,13 @@ { "scripts": { "update-shared": "npm update devu-shared-modules", - "local": "cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=local webpack-dev-server -d --open --mode development", + "local": "cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=local webpack-dev-server --mode development", "start": "cross-env NODE_ENV=development webpack-dev-server -d --open --mode development", "prod": "cross-env NODE_ENV=production webpack-dev-server -d --open --mode development", "build": "cross-env NODE_ENV=production webpack -p --mode=production", - "build-development": "cross-env NODE_ENV=development webpack -p --mode=production", + "build-development": "cross-env NODE_ENV=development webpack --mode=production", "build-all": "concurrently \"npm run build-development\" \"npm run build\"", - "build-local": "cross-env NODE_ENV=local webpack -p --mode=production", + "build-local": "cross-env NODE_ENV=local webpack --mode=production", "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", "pre-commit": "lint-staged", "dev-backend": "docker compose -f ../docker-compose.yml --profile dev-client up -d", @@ -65,7 +65,7 @@ "dotenv-webpack": "^7.0.2", "file-loader": "^5.1.0", "fs": "^0.0.1-security", - "html-loader": "^0.5.5", + "html-loader": "^5.0.0", "html-webpack-injector": "^1.1.4", "html-webpack-plugin": "^4.5.2", "husky": "^6.0.0", @@ -75,9 +75,9 @@ "sass-loader": "^10.2.0", "style-loader": "^2.0.0", "ts-loader": "^8.0.15", - "webpack": "^4.42.0", - "webpack-bundle-analyzer": "^3.6.1", - "webpack-cli": "^3.3.11", - "webpack-dev-server": "^3.10.3" + "webpack": "^5.91.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.0.4" } } diff --git a/devU-client/webpack.config.js b/devU-client/webpack.config.js index b30bd2dc..ac5c6e01 100644 --- a/devU-client/webpack.config.js +++ b/devU-client/webpack.config.js @@ -89,6 +89,7 @@ module.exports = () => { publicPath: "/", }, optimization: { + nodeEnv: `${env}`, usedExports: true, runtimeChunk: 'single', splitChunks: { @@ -103,7 +104,9 @@ module.exports = () => { devServer: { hot: true, port: process.env.PORT || 9000, - contentBase: path.join(__dirname, "dist"), + static: { + directory: path.join(__dirname, "dist"), + }, historyApiFallback: true, }, devtool: 'inline-source-map', From b07ce8e6b1c1cebd3c0c8b327689d66fa3422d62 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sun, 12 May 2024 02:31:48 -0400 Subject: [PATCH 132/400] delete lock.json --- devU-api/package-lock.json | 12019 ------------------- devU-client/package-lock.json | 20264 -------------------------------- 2 files changed, 32283 deletions(-) delete mode 100644 devU-api/package-lock.json delete mode 100644 devU-client/package-lock.json diff --git a/devU-api/package-lock.json b/devU-api/package-lock.json deleted file mode 100644 index ffd2d9d7..00000000 --- a/devU-api/package-lock.json +++ /dev/null @@ -1,12019 +0,0 @@ -{ - "name": "devu-api", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "devu-api", - "version": "1.0.0", - "dependencies": { - "body-parser": "^1.19.0", - "colors": "^1.4.0", - "config": "^3.3.6", - "cookie-parser": "^1.4.5", - "cors": "^2.8.5", - "devu-shared-modules": "./devu-shared-modules", - "express": "^4.17.1", - "express-validator": "^6.14.2", - "helmet": "^4.6.0", - "jsonwebtoken": "^9.0.2", - "minio": "^7.0.18", - "morgan": "^1.10.0", - "multer": "^1.4.2", - "passport": "^0.6.0", - "passport-saml": "^3.2.2", - "pg": "^8.4.0", - "reflect-metadata": "^0.1.10", - "regex-parser": "^2.3.0", - "swagger-jsdoc": "^6.1.0", - "swagger-ui-express": "^4.1.6", - "typeorm": "0.2.32" - }, - "devDependencies": { - "@types/config": "^0.0.38", - "@types/cookie-parser": "^1.4.2", - "@types/cors": "^2.8.10", - "@types/express": "^4.17.12", - "@types/helmet": "^4.0.0", - "@types/jest": "^26.0.24", - "@types/jsonwebtoken": "^8.5.1", - "@types/jws": "^3.2.3", - "@types/minio": "^7.0.8", - "@types/morgan": "^1.9.2", - "@types/multer": "^1.4.7", - "@types/node": "^15.12.2", - "@types/node-fetch": "^2.6.11", - "@types/passport": "^1.0.6", - "@types/passport-strategy": "^0.2.35", - "@types/swagger-jsdoc": "^6.0.0", - "@types/swagger-ui-express": "^4.1.2", - "husky": "^6.0.0", - "jest": "^27.0.4", - "lint-staged": "^11.0.0", - "node-fetch": "^2.7.0", - "npm-run-all": "^4.1.5", - "prettier": "^2.3.0", - "rimraf": "^3.0.2", - "ts-jest": "^27.0.2", - "ts-node": "^10.0.0", - "ts-node-dev": "^1.1.6", - "typescript": "^4.3.2" - }, - "engines": { - "node": ">=20" - } - }, - "devu-shared-modules": {}, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", - "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.6", - "call-me-maybe": "^1.0.1", - "js-yaml": "^4.1.0" - } - }, - "node_modules/@apidevtools/openapi-schemas": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", - "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/@apidevtools/swagger-methods": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", - "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" - }, - "node_modules/@apidevtools/swagger-parser": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.2.tgz", - "integrity": "sha512-JFxcEyp8RlNHgBCE98nwuTkZT6eNFPc1aosWV6wPcQph72TSEEu1k3baJD4/x1qznU+JiDdz8F5pTwabZh+Dhg==", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^9.0.6", - "@apidevtools/openapi-schemas": "^2.0.4", - "@apidevtools/swagger-methods": "^3.0.2", - "@jsdevtools/ono": "^7.1.3", - "call-me-maybe": "^1.0.1", - "z-schema": "^4.2.3" - }, - "peerDependencies": { - "openapi-types": ">=7" - } - }, - "node_modules/@babel/code-frame": { - "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.23.4", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.7.tgz", - "integrity": "sha512-nS6dZaISCXJ3+518CWiBfEr//gHyMO02uDxBkXTKZDN5POruCnOZ1N4YBRZDCabwF8nZMWBpRxIicmXtBs+fvw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.6.tgz", - "integrity": "sha512-gJnOEWSqTk96qG5BoIrl5bVtc23DCycmIePPYnamY9RboYdI4nFy5vAQMSl81O5K/W0sLDWfGysnOECC+KUUCA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.14.5", - "@babel/helper-compilation-targets": "^7.14.5", - "@babel/helper-module-transforms": "^7.14.5", - "@babel/helpers": "^7.14.6", - "@babel/parser": "^7.14.6", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/core/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/generator": { - "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.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz", - "integrity": "sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.14.5", - "@babel/helper-validator-option": "^7.14.5", - "browserslist": "^4.16.6", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "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", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.14.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.14.7.tgz", - "integrity": "sha512-TMUt4xKxJn6ccjcOW7c4hlwyJArizskAhoSTOCkA0uZ+KghIaci0Qg9R043kUMWI9mtQfgny+NQ5QATnZ+paaA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz", - "integrity": "sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.5.tgz", - "integrity": "sha512-iXpX4KW8LVODuAieD7MzhNjmM6dzYY5tfRqT+R9HDXWl0jPn/djKmA+G9s/2C2T9zggw5tK1QNqZ70USfedOwA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.14.5", - "@babel/helper-replace-supers": "^7.14.5", - "@babel/helper-simple-access": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/helper-validator-identifier": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz", - "integrity": "sha512-IqiLIrODUOdnPU9/F8ib1Fx2ohlgDhxnIDU7OEVi+kAbEZcyiF7BLU8W6PfvPi9LzztjS7kcbzbmL7oG8kD6VA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz", - "integrity": "sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.5.tgz", - "integrity": "sha512-3i1Qe9/8x/hCHINujn+iuHy+mMRLoc77b2nI9TB0zjH1hvn9qGlXjWlggdwUcju36PkPCy/lpM7LLUdcTyH4Ow==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.14.5", - "@babel/helper-optimise-call-expression": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz", - "integrity": "sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", - "dev": true, - "dependencies": { - "@babel/types": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "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" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.14.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.6.tgz", - "integrity": "sha512-yesp1ENQBiLI+iYHSJdoZKUtRpfTlL1grDIX9NRlAVppljLw/4tTyYupIB7uIYmC3stW/imAv8EqaKaS/ibmeA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.14.5", - "@babel/traverse": "^7.14.5", - "@babel/types": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "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" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.14.5.tgz", - "integrity": "sha512-u6OXzDaIXjEstBRRoBCQ/uKQKlbuaeE5in0RvWdA4pN6AhqxTIwUsnHPU1CFZA/amYObMsuWhYfRl3Ch90HD0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", - "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", - "dev": true, - "dependencies": { - "@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.23.9", - "@babel/types": "^7.23.9", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/traverse/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", - "dev": true, - "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "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", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.0.6.tgz", - "integrity": "sha512-fMlIBocSHPZ3JxgWiDNW/KPj6s+YRd0hicb33IrmelCcjXo/pXPwvuiKFmZz+XuqI/1u7nbUK10zSsWL/1aegg==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.0.6", - "jest-util": "^27.0.6", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/console/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/console/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/console/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.0.6.tgz", - "integrity": "sha512-SsYBm3yhqOn5ZLJCtccaBcvD/ccTLCeuDv8U41WJH/V1MW5eKUkeMHT9U+Pw/v1m1AIWlnIW/eM2XzQr0rEmow==", - "dev": true, - "dependencies": { - "@jest/console": "^27.0.6", - "@jest/reporters": "^27.0.6", - "@jest/test-result": "^27.0.6", - "@jest/transform": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.0.6", - "jest-config": "^27.0.6", - "jest-haste-map": "^27.0.6", - "jest-message-util": "^27.0.6", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.0.6", - "jest-resolve-dependencies": "^27.0.6", - "jest-runner": "^27.0.6", - "jest-runtime": "^27.0.6", - "jest-snapshot": "^27.0.6", - "jest-util": "^27.0.6", - "jest-validate": "^27.0.6", - "jest-watcher": "^27.0.6", - "micromatch": "^4.0.4", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/core/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/core/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.0.6.tgz", - "integrity": "sha512-4XywtdhwZwCpPJ/qfAkqExRsERW+UaoSRStSHCCiQTUpoYdLukj+YJbQSFrZjhlUDRZeNiU9SFH0u7iNimdiIg==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/node": "*", - "jest-mock": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/environment/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/environment/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/environment/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/fake-timers": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.0.6.tgz", - "integrity": "sha512-sqd+xTWtZ94l3yWDKnRTdvTeZ+A/V7SSKrxsrOKSqdyddb9CeNRF8fbhAU0D7ZJBpTTW2nbp6MftmKJDZfW2LQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "@sinonjs/fake-timers": "^7.0.2", - "@types/node": "*", - "jest-message-util": "^27.0.6", - "jest-mock": "^27.0.6", - "jest-util": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/fake-timers/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/fake-timers/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/fake-timers/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.0.6.tgz", - "integrity": "sha512-DdTGCP606rh9bjkdQ7VvChV18iS7q0IMJVP1piwTWyWskol4iqcVwthZmoJEf7obE1nc34OpIyoVGPeqLC+ryw==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.0.6", - "@jest/types": "^27.0.6", - "expect": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/globals/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/globals/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/globals/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/globals/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.0.6.tgz", - "integrity": "sha512-TIkBt09Cb2gptji3yJXb3EE+eVltW6BjO7frO7NEfjI9vSIYoISi5R3aI3KpEDXlB1xwB+97NXIqz84qYeYsfA==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.0.6", - "@jest/test-result": "^27.0.6", - "@jest/transform": "^27.0.6", - "@jest/types": "^27.0.6", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^27.0.6", - "jest-resolve": "^27.0.6", - "jest-util": "^27.0.6", - "jest-worker": "^27.0.6", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^8.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/reporters/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/reporters/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/source-map": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.6.tgz", - "integrity": "sha512-Fek4mi5KQrqmlY07T23JRi0e7Z9bXTOOD86V/uS0EIW4PClvPDqZOyFlLpNJheS6QI0FNX1CgmPjtJ4EA/2M+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.0.6.tgz", - "integrity": "sha512-ja/pBOMTufjX4JLEauLxE3LQBPaI2YjGFtXexRAjt1I/MbfNlMx0sytSX3tn5hSLzQsR3Qy2rd0hc1BWojtj9w==", - "dev": true, - "dependencies": { - "@jest/console": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/test-result/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/test-result/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/test-result/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/test-result/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/test-result/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.0.6.tgz", - "integrity": "sha512-bISzNIApazYOlTHDum9PwW22NOyDa6VI31n6JucpjTVM0jD6JDgqEZ9+yn575nDdPF0+4csYDxNNW13NvFQGZA==", - "dev": true, - "dependencies": { - "@jest/test-result": "^27.0.6", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.0.6", - "jest-runtime": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.0.6.tgz", - "integrity": "sha512-rj5Dw+mtIcntAUnMlW/Vju5mr73u8yg+irnHwzgtgoeI6cCPOvUwQ0D1uQtc/APmWgvRweEb1g05pkUpxH3iCA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.0.6", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.0.6", - "jest-regex-util": "^27.0.6", - "jest-util": "^27.0.6", - "micromatch": "^4.0.4", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/transform/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/transform/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", - "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@sqltools/formatter": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", - "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" - }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - }, - "node_modules/@types/babel__core": { - "version": "7.1.15", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.15.tgz", - "integrity": "sha512-bxlMKPDbY8x5h6HBwVzEOk2C8fb6SLfYQ5Jw3uBYuYF1lfWk/kbLd81la82vrIkBb0l+JdmrZaDikPrNxpS/Ew==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.3.tgz", - "integrity": "sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.14.2.tgz", - "integrity": "sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz", - "integrity": "sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg==", - "dev": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/config": { - "version": "0.0.38", - "resolved": "https://registry.npmjs.org/@types/config/-/config-0.0.38.tgz", - "integrity": "sha512-z2WizAfIFgSv8SQfQ8c0LlbDAcK47D/o93XW6bxZ9t3bs4fmmfAPjk1nhAIBTG84PBBCHfSPM+8g7vhLdbFokg==", - "dev": true - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cookie-parser": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz", - "integrity": "sha512-uwcY8m6SDQqciHsqcKDGbo10GdasYsPCYkH3hVegj9qAah6pX5HivOnOuI3WYmyQMnOATV39zv/Ybs0bC/6iVg==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "dev": true - }, - "node_modules/@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dev": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz", - "integrity": "sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/helmet": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/helmet/-/helmet-4.0.0.tgz", - "integrity": "sha512-ONIn/nSNQA57yRge3oaMQESef/6QhoeX7llWeDli0UZIfz8TQMkfNPTXA8VnnyeA1WUjG2pGqdjEIueYonMdfQ==", - "deprecated": "This is a stub types definition. helmet provides its own type definitions, so you do not need this installed.", - "dev": true, - "dependencies": { - "helmet": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "26.0.24", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz", - "integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==", - "dev": true, - "dependencies": { - "jest-diff": "^26.0.0", - "pretty-format": "^26.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz", - "integrity": "sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg==" - }, - "node_modules/@types/jsonwebtoken": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz", - "integrity": "sha512-4L8msWK31oXwdtC81RmRBAULd0ShnAHjBuKT9MRQpjP0piNrZdXyTRcKY9/UIfhGeKIT4PvF5amOOUbbT/9Wpg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/jws": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@types/jws/-/jws-3.2.4.tgz", - "integrity": "sha512-aqtH4dPw1wUjFZaeMD1ak/pf8iXlu/odFe+trJrvw0g1sTh93i+SCykg0Ek8C6B7rVK3oBORbfZAsKO7P10etg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true - }, - "node_modules/@types/minio": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/@types/minio/-/minio-7.0.8.tgz", - "integrity": "sha512-3l9JTgbgZQMQFSRIzK8vDvPhM8MOkyqzftD0+5ltd8sMvodz9WswmzxpzNhI6vZJ19iqZfgzDMyZ4e/lNexOvA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/morgan": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz", - "integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/multer": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", - "integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/node": { - "version": "15.14.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.2.tgz", - "integrity": "sha512-dvMUE/m2LbXPwlvVuzCyslTEtQ2ZwuuFClDrOQ6mp2CenCg971719PTILZ4I6bTP27xfFFc+o7x2TkLuun/MPw==", - "dev": true - }, - "node_modules/@types/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", - "dev": true, - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/@types/node-fetch/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true - }, - "node_modules/@types/passport": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz", - "integrity": "sha512-JtswU8N3kxBYgo+n9of7C97YQBT+AYPP2aBfNGTzABqPAZnK/WOAaKfh3XesUYMZRrXFuoPc2Hv0/G/nQFveHw==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/passport-strategy": { - "version": "0.2.35", - "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz", - "integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==", - "dev": true, - "dependencies": { - "@types/express": "*", - "@types/passport": "*" - } - }, - "node_modules/@types/prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-eI5Yrz3Qv4KPUa/nSIAi0h+qX0XyewOliug5F2QAtuRg6Kjg6jfmxe1GIwoIRhZspD1A0RP8ANrPwvEXXtRFog==", - "dev": true - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "node_modules/@types/serve-static": { - "version": "1.13.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", - "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dev": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true - }, - "node_modules/@types/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", - "dev": true - }, - "node_modules/@types/strip-json-comments": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", - "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", - "dev": true - }, - "node_modules/@types/swagger-jsdoc": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.1.tgz", - "integrity": "sha512-+MUpcbyxD528dECUBCEVm6abNuORdbuGjbrUdHDeAQ+rkPuo2a+L4N02WJHF3bonSSE6SJ3dUJwF2V6+cHnf0w==", - "dev": true - }, - "node_modules/@types/swagger-ui-express": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.3.tgz", - "integrity": "sha512-jqCjGU/tGEaqIplPy3WyQg+Nrp6y80DCFnDEAvVKWkJyv0VivSSDCChkppHRHAablvInZe6pijDFMnavtN0vqA==", - "dev": true, - "dependencies": { - "@types/express": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true - }, - "node_modules/@types/zen-observable": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", - "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" - }, - "node_modules/@xmldom/xmldom": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.8.tgz", - "integrity": "sha512-PrJx38EfpitFhwmILRl37jAdBlsww6AZ6rRVK4QS7T7RHLhX7mSs647sTmgr9GIxe3qjXdesmomEgbgaokrVFg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@zxing/text-encoding": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", - "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", - "optional": true - }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.4.1.tgz", - "integrity": "sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "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/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/app-root-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "node_modules/available-typed-arrays": { - "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==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/babel-jest": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.0.6.tgz", - "integrity": "sha512-iTJyYLNc4wRofASmofpOc5NK9QunwMk+TLFgGXsTFS8uEqmd8wdI7sga0FPe2oVH3b5Agt/EAK1QjPEuKL8VfA==", - "dev": true, - "dependencies": { - "@jest/transform": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^27.0.6", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-jest/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/babel-jest/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/babel-jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/babel-jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.0.6.tgz", - "integrity": "sha512-CewFeM9Vv2gM7Yr9n5eyyLVPRSiBnk6lKZRjgwYnGKSl9M14TMn2vkN02wTF04OGuSDLEzlWiMzvjXuW9mB6Gw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.0.6.tgz", - "integrity": "sha512-WObA0/Biw2LrVVwZkF/2GqbOdzhKD6Fkdwhoy9ASIrOWr/zodcSpQh72JOkEn6NWyjmnPDjNSqaGN4KnpKzhXw==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^27.0.6", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/binary-extensions": { - "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" - } - }, - "node_modules/block-stream2": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", - "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", - "dependencies": { - "readable-stream": "^3.4.0" - } - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-or-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", - "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==" - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "node_modules/buffer-writer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", - "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/busboy": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", - "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", - "dependencies": { - "dicer": "0.2.5", - "readable-stream": "1.1.x" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/busboy/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/busboy/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz", - "integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", - "set-function-length": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001243", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001243.tgz", - "integrity": "sha512-vNxw9mkTBtkmLFnJRv/2rhs1yufpDfCkBZexG3Y0xdOH2Z/eE/85E4Dl5j1YUN34nZVsSp6vVRFQRrez9wJMRA==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "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/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", - "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", - "dev": true - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.1.tgz", - "integrity": "sha512-jVamGdJPDeuQilKhvVn1h3knuMOZzr8QDnpk+M9aMlCaMkTDd6fBWPhiDqFvFZ07pL0liqabAiuy8SY4jGHeaw==", - "dev": true - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cli-highlight/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cli-highlight/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cli-highlight/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/cli-highlight/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-highlight/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "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/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", - "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/config": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/config/-/config-3.3.6.tgz", - "integrity": "sha512-Hj5916C5HFawjYJat1epbyY2PlAgLpBtDUlr0MxGLgo3p5+7kylyvnRY18PqJHgnNWXcdd0eWDemT7eYWuFgwg==", - "dependencies": { - "json5": "^2.1.1" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-parser": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz", - "integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==", - "dependencies": { - "cookie": "0.4.0", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dev": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cosmiconfig/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cosmiconfig/node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/decimal.js": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", - "integrity": "sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==", - "dev": true - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz", - "integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.2", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/devu-shared-modules": { - "resolved": "devu-shared-modules", - "link": true - }, - "node_modules/dicer": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", - "integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==", - "dependencies": { - "readable-stream": "1.1.x", - "streamsearch": "0.1.2" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/dicer/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/dicer/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "engines": { - "node": ">=10" - } - }, - "node_modules/dynamic-dedupe": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", - "dev": true, - "dependencies": { - "xtend": "^4.0.0" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "node_modules/electron-to-chromium": { - "version": "1.3.772", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.772.tgz", - "integrity": "sha512-X/6VRCXWALzdX+RjCtBU6cyg8WZgoxm9YA02COmDOiNJEZ59WkQggDbWZ4t/giHi/3GS+cvdrP6gbLISANAGYA==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "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==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", - "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.10.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/execa/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.0.6.tgz", - "integrity": "sha512-psNLt8j2kwg42jGBDSfAlU49CEZxejN1f1PlANWDZqIhBOVU/c2Pm888FcjWJzFewhIsNWfZJeLjUjtKGiPuSw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "ansi-styles": "^5.0.0", - "jest-get-type": "^27.0.6", - "jest-matcher-utils": "^27.0.6", - "jest-message-util": "^27.0.6", - "jest-regex-util": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/expect/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/expect/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/expect/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/expect/node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/expect/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/expect/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/expect/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/expect/node_modules/jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/expect/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express-validator": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-6.14.2.tgz", - "integrity": "sha512-8XfAUrQ6Y7dIIuy9KcUPCfG/uCbvREctrxf5EeeME+ulanJ4iiW71lWmm9r4YcKKYOCBMan0WpVg7FtHu4Z4Wg==", - "dependencies": { - "lodash": "^4.17.21", - "validator": "^13.7.0" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fast-xml-parser": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz", - "integrity": "sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/figlet": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.7.0.tgz", - "integrity": "sha512-gO8l3wvqo0V7wEFLXPbkX83b7MVjRrk1oRLfYlZXol8nEpb/ON9pcKLI4qpBv5YtOTfrINtqb7b40iYY2FTWFg==", - "bin": { - "figlet": "bin/index.js" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dependencies": { - "get-intrinsic": "^1.2.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "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==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/helmet": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", - "integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "engines": { - "node": "*" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/http-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/husky": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz", - "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", - "dev": true, - "bin": { - "husky": "lib/bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-ci": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", - "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.1.1" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.0.6.tgz", - "integrity": "sha512-EjV8aETrsD0wHl7CKMibKwQNQc3gIRBXlTikBmmHUeVMKaPFxdcUIBfoDqTSXDoGJIivAYGqCWVlzCSaVjPQsA==", - "dev": true, - "dependencies": { - "@jest/core": "^27.0.6", - "import-local": "^3.0.2", - "jest-cli": "^27.0.6" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.0.6.tgz", - "integrity": "sha512-BuL/ZDauaq5dumYh5y20sn4IISnf1P9A0TDswTxUi84ORGtVa86ApuBHqICL0vepqAnZiY6a7xeSPWv2/yy4eA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-changed-files/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-changed-files/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.0.6.tgz", - "integrity": "sha512-OJlsz6BBeX9qR+7O9lXefWoc2m9ZqcZ5Ohlzz0pTEAG4xMiZUJoacY8f4YDHxgk0oKYxj277AfOk9w6hZYvi1Q==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.0.6", - "@jest/test-result": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.0.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.0.6", - "jest-matcher-utils": "^27.0.6", - "jest-message-util": "^27.0.6", - "jest-runtime": "^27.0.6", - "jest-snapshot": "^27.0.6", - "jest-util": "^27.0.6", - "pretty-format": "^27.0.6", - "slash": "^3.0.0", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-circus/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-circus/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/pretty-format": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", - "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-circus/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.0.6.tgz", - "integrity": "sha512-JZRR3I1Plr2YxPBhgqRspDE2S5zprbga3swYNrvY3HfQGu7p/GjyLOqwrYad97tX3U3mzT53TPHVmozacfP/3w==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^27.0.6", - "@jest/types": "^27.0.6", - "babel-jest": "^27.0.6", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "jest-circus": "^27.0.6", - "jest-environment-jsdom": "^27.0.6", - "jest-environment-node": "^27.0.6", - "jest-get-type": "^27.0.6", - "jest-jasmine2": "^27.0.6", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.0.6", - "jest-runner": "^27.0.6", - "jest-util": "^27.0.6", - "jest-validate": "^27.0.6", - "micromatch": "^4.0.4", - "pretty-format": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-config/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-config/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-config/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-config/node_modules/pretty-format": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", - "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-config/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-docblock": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.6.tgz", - "integrity": "sha512-Fid6dPcjwepTFraz0YxIMCi7dejjJ/KL9FBjPYhBp4Sv1Y9PdhImlKZqYU555BlN4TQKaTc+F2Av1z+anVyGkA==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.0.6.tgz", - "integrity": "sha512-m6yKcV3bkSWrUIjxkE9OC0mhBZZdhovIW5ergBYirqnkLXkyEn3oUUF/QZgyecA1cF1QFyTE8bRRl8Tfg1pfLA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "jest-util": "^27.0.6", - "pretty-format": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-each/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-each/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/pretty-format": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", - "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-each/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.0.6.tgz", - "integrity": "sha512-FvetXg7lnXL9+78H+xUAsra3IeZRTiegA3An01cWeXBspKXUhAwMM9ycIJ4yBaR0L7HkoMPaZsozCLHh4T8fuw==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.0.6", - "@jest/fake-timers": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/node": "*", - "jest-mock": "^27.0.6", - "jest-util": "^27.0.6", - "jsdom": "^16.6.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-environment-jsdom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-environment-jsdom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.0.6.tgz", - "integrity": "sha512-+Vi6yLrPg/qC81jfXx3IBlVnDTI6kmRr08iVa2hFCWmJt4zha0XW7ucQltCAPhSR0FEKEoJ3i+W4E6T0s9is0w==", - "dev": true, - "dependencies": { - "@jest/environment": "^27.0.6", - "@jest/fake-timers": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/node": "*", - "jest-mock": "^27.0.6", - "jest-util": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-environment-node/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-node/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-environment-node/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-haste-map": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.0.6.tgz", - "integrity": "sha512-4ldjPXX9h8doB2JlRzg9oAZ2p6/GpQUNAeiYXqcpmrKbP0Qev0wdZlxSMOmz8mPOEnt4h6qIzXFLDi8RScX/1w==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.0.6", - "jest-serializer": "^27.0.6", - "jest-util": "^27.0.6", - "jest-worker": "^27.0.6", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-haste-map/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-haste-map/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-haste-map/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-haste-map/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-haste-map/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.0.6.tgz", - "integrity": "sha512-cjpH2sBy+t6dvCeKBsHpW41mjHzXgsavaFMp+VWRf0eR4EW8xASk1acqmljFtK2DgyIECMv2yCdY41r2l1+4iA==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^27.0.6", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.0.6", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.0.6", - "jest-matcher-utils": "^27.0.6", - "jest-message-util": "^27.0.6", - "jest-runtime": "^27.0.6", - "jest-snapshot": "^27.0.6", - "jest-util": "^27.0.6", - "pretty-format": "^27.0.6", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-jasmine2/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-jasmine2/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-jasmine2/node_modules/pretty-format": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", - "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-jasmine2/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-jasmine2/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-leak-detector": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.0.6.tgz", - "integrity": "sha512-2/d6n2wlH5zEcdctX4zdbgX8oM61tb67PQt4Xh8JFAIy6LRKUnX528HulkaG6nD5qDl5vRV1NXejCe1XRCH5gQ==", - "dev": true, - "dependencies": { - "jest-get-type": "^27.0.6", - "pretty-format": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-leak-detector/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-leak-detector/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-leak-detector/node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-leak-detector/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-leak-detector/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-leak-detector/node_modules/jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", - "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-leak-detector/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.0.6.tgz", - "integrity": "sha512-OFgF2VCQx9vdPSYTHWJ9MzFCehs20TsyFi6bIHbk5V1u52zJOnvF0Y/65z3GLZHKRuTgVPY4Z6LVePNahaQ+tA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.0.6", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-matcher-utils/node_modules/diff-sequences": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", - "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/jest-diff": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.6.tgz", - "integrity": "sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.0.6", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", - "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.0.6.tgz", - "integrity": "sha512-rBxIs2XK7rGy+zGxgi+UJKP6WqQ+KrBbD1YMj517HYN3v2BG66t3Xan3FWqYHKZwjdB700KiAJ+iES9a0M+ixw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.0.6", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.0.6", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", - "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.0.6.tgz", - "integrity": "sha512-lzBETUoK8cSxts2NYXSBWT+EJNzmUVtVVwS1sU9GwE1DLCfGsngg+ZVSIe0yd0ZSm+y791esiuo+WSwpXJQ5Bw==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "@types/node": "*" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-mock/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-mock/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-mock/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-mock/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.6.tgz", - "integrity": "sha512-SUhPzBsGa1IKm8hx2F4NfTGGp+r7BXJ4CulsZ1k2kI+mGLG+lxGrs76veN2LF/aUdGosJBzKgXmNCw+BzFqBDQ==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.0.6.tgz", - "integrity": "sha512-yKmIgw2LgTh7uAJtzv8UFHGF7Dm7XfvOe/LQ3Txv101fLM8cx2h1QVwtSJ51Q/SCxpIiKfVn6G2jYYMDNHZteA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "chalk": "^4.0.0", - "escalade": "^3.1.1", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.0.6", - "jest-validate": "^27.0.6", - "resolve": "^1.20.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.0.6.tgz", - "integrity": "sha512-mg9x9DS3BPAREWKCAoyg3QucCr0n6S8HEEsqRCKSPjPcu9HzRILzhdzY3imsLoZWeosEbJZz6TKasveczzpJZA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "jest-regex-util": "^27.0.6", - "jest-snapshot": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve-dependencies/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve-dependencies/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-resolve/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-resolve/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-resolve/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.0.6.tgz", - "integrity": "sha512-W3Bz5qAgaSChuivLn+nKOgjqNxM7O/9JOJoKDCqThPIg2sH/d4A/lzyiaFgnb9V1/w29Le11NpzTJSzga1vyYQ==", - "dev": true, - "dependencies": { - "@jest/console": "^27.0.6", - "@jest/environment": "^27.0.6", - "@jest/test-result": "^27.0.6", - "@jest/transform": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-docblock": "^27.0.6", - "jest-environment-jsdom": "^27.0.6", - "jest-environment-node": "^27.0.6", - "jest-haste-map": "^27.0.6", - "jest-leak-detector": "^27.0.6", - "jest-message-util": "^27.0.6", - "jest-resolve": "^27.0.6", - "jest-runtime": "^27.0.6", - "jest-util": "^27.0.6", - "jest-worker": "^27.0.6", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runner/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runner/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runner/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.0.6.tgz", - "integrity": "sha512-BhvHLRVfKibYyqqEFkybsznKwhrsu7AWx2F3y9G9L95VSIN3/ZZ9vBpm/XCS2bS+BWz3sSeNGLzI3TVQ0uL85Q==", - "dev": true, - "dependencies": { - "@jest/console": "^27.0.6", - "@jest/environment": "^27.0.6", - "@jest/fake-timers": "^27.0.6", - "@jest/globals": "^27.0.6", - "@jest/source-map": "^27.0.6", - "@jest/test-result": "^27.0.6", - "@jest/transform": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.0.6", - "jest-message-util": "^27.0.6", - "jest-mock": "^27.0.6", - "jest-regex-util": "^27.0.6", - "jest-resolve": "^27.0.6", - "jest-snapshot": "^27.0.6", - "jest-util": "^27.0.6", - "jest-validate": "^27.0.6", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^16.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-runtime/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-runtime/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-serializer": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.6.tgz", - "integrity": "sha512-PtGdVK9EGC7dsaziskfqaAPib6wTViY3G8E5wz9tLVPhHyiDNTZn/xjZ4khAw+09QkoOVpn7vF5nPSN6dtBexA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.0.6.tgz", - "integrity": "sha512-NTHaz8He+ATUagUgE7C/UtFcRoHqR2Gc+KDfhQIyx+VFgwbeEMjeP+ILpUTLosZn/ZtbNdCF5LkVnN/l+V751A==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/parser": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.0.6", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.0.6", - "jest-get-type": "^27.0.6", - "jest-haste-map": "^27.0.6", - "jest-matcher-utils": "^27.0.6", - "jest-message-util": "^27.0.6", - "jest-resolve": "^27.0.6", - "jest-util": "^27.0.6", - "natural-compare": "^1.4.0", - "pretty-format": "^27.0.6", - "semver": "^7.3.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-snapshot/node_modules/diff-sequences": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", - "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/jest-diff": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.6.tgz", - "integrity": "sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.0.6", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", - "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.0.6.tgz", - "integrity": "sha512-1JjlaIh+C65H/F7D11GNkGDDZtDfMEM8EBXsvd+l/cxtgQ6QhxuloOaiayt89DxUvDarbVhqI98HhgrM1yliFQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-util/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-util/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-util/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-util/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.0.6.tgz", - "integrity": "sha512-yhZZOaMH3Zg6DC83n60pLmdU1DQE46DW+KLozPiPbSbPhlXXaiUTDlhHQhHFpaqIFRrInko1FHXjTRpjWRuWfA==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.6", - "leven": "^3.1.0", - "pretty-format": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-validate/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-validate/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", - "dev": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/pretty-format": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", - "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", - "dev": true, - "dependencies": { - "@jest/types": "^27.0.6", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-validate/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.0.6.tgz", - "integrity": "sha512-/jIoKBhAP00/iMGnTwUBLgvxkn7vsOweDrOTSPzc7X9uOyUtJIDthQBTI1EXz90bdkrxorUZVhJwiB69gcHtYQ==", - "dev": true, - "dependencies": { - "@jest/test-result": "^27.0.6", - "@jest/types": "^27.0.6", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.0.6", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-watcher/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest-watcher/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.6.tgz", - "integrity": "sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/jest/node_modules/@jest/types": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", - "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/jest/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/jest-cli": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.0.6.tgz", - "integrity": "sha512-qUUVlGb9fdKir3RDE+B10ULI+LQrz+MCflEH2UJyoUjoHHCbxDrMxSzjQAPUMsic4SncI62ofYCcAvW6+6rhhg==", - "dev": true, - "dependencies": { - "@jest/core": "^27.0.6", - "@jest/test-result": "^27.0.6", - "@jest/types": "^27.0.6", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "jest-config": "^27.0.6", - "jest-util": "^27.0.6", - "jest-validate": "^27.0.6", - "prompts": "^2.0.1", - "yargs": "^16.0.3" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.6.0.tgz", - "integrity": "sha512-Ty1vmF4NHJkolaEmdjtxTfSfkdb8Ywarwf63f+F8/mDD1uLSSWDxDuMiZxiPhwunLrn9LOSVItWj4bLYsLN3Dg==", - "dev": true, - "dependencies": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.5", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-stream/-/json-stream-1.0.0.tgz", - "integrity": "sha1-GjhU4o0rvuqzHMfd9oPS3cVlJwg=" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "node_modules/lint-staged": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-11.0.0.tgz", - "integrity": "sha512-3rsRIoyaE8IphSUtO1RVTFl1e0SLBtxxUOPBtHxQgBHS5/i6nqvjcUfNioMa4BU9yGnPzbO+xkfLtXtxBpCzjw==", - "dev": true, - "dependencies": { - "chalk": "^4.1.1", - "cli-truncate": "^2.1.0", - "commander": "^7.2.0", - "cosmiconfig": "^7.0.0", - "debug": "^4.3.1", - "dedent": "^0.7.0", - "enquirer": "^2.3.6", - "execa": "^5.0.0", - "listr2": "^3.8.2", - "log-symbols": "^4.1.0", - "micromatch": "^4.0.4", - "normalize-path": "^3.0.0", - "please-upgrade-node": "^3.2.0", - "string-argv": "0.3.1", - "stringify-object": "^3.3.0" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/lint-staged/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/lint-staged/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/lint-staged/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/lint-staged/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/lint-staged/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/lint-staged/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.10.0.tgz", - "integrity": "sha512-eP40ZHihu70sSmqFNbNy2NL1YwImmlMmPh9WO5sLmPDleurMHt3n+SwEWNu2kzKScexZnkyFtc1VI0z/TGlmpw==", - "dev": true, - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^1.2.2", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rxjs": "^6.6.7", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - } - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-update/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "dependencies": { - "tmpl": "1.0.x" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" - }, - "node_modules/minio": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minio/-/minio-7.1.3.tgz", - "integrity": "sha512-xPrLjWkTT5E7H7VnzOjF//xBp9I40jYB4aWhb2xTFopXXfw+Wo82DDWngdUju7Doy3Wk7R8C4LAgwhLHHnf0wA==", - "dependencies": { - "async": "^3.2.4", - "block-stream2": "^2.1.0", - "browser-or-node": "^2.1.1", - "buffer-crc32": "^0.2.13", - "fast-xml-parser": "^4.2.2", - "ipaddr.js": "^2.0.1", - "json-stream": "^1.0.0", - "lodash": "^4.17.21", - "mime-types": "^2.1.35", - "query-string": "^7.1.3", - "through2": "^4.0.2", - "web-encoding": "^1.1.5", - "xml": "^1.0.1", - "xml2js": "^0.5.0" - }, - "engines": { - "node": "^16 || ^18 || >=20" - } - }, - "node_modules/minio/node_modules/ipaddr.js": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", - "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/minio/node_modules/xml2js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", - "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/minio/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "dependencies": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/multer": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", - "integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", - "deprecated": "Multer 1.x is affected by CVE-2022-24434. This is fixed in v1.4.4-lts.1 which drops support for versions of Node.js before 6. Please upgrade to at least Node.js 6 and version 1.4.4-lts.1 of Multer. If you need support for older versions of Node.js, we are open to accepting patches that would fix the CVE on the main 1.x release line, whilst maintaining compatibility with Node.js 0.10.", - "dependencies": { - "append-field": "^1.0.0", - "busboy": "^0.2.11", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "on-finished": "^2.3.0", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/multer/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node_modules/node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/node-releases": { - "version": "1.1.73", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.73.tgz", - "integrity": "sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==", - "dev": true - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "bin": { - "npm-run-all": "bin/npm-run-all/index.js", - "run-p": "bin/run-p/index.js", - "run-s": "bin/run-s/index.js" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "peer": true - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/packet-reader": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", - "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parent-require": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", - "integrity": "sha512-2MXDNZC4aXdkkap+rBBMv0lUsfJqvX5/2FiYYnfCnorZt3Pk06/IOR5KeaoghgS2w07MLWgjbsnyaq6PdHn2LQ==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/passport": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", - "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", - "dependencies": { - "passport-strategy": "1.x.x", - "pause": "0.0.1", - "utils-merge": "^1.0.1" - }, - "engines": { - "node": ">= 0.4.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jaredhanson" - } - }, - "node_modules/passport-saml": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/passport-saml/-/passport-saml-3.2.4.tgz", - "integrity": "sha512-JSgkFXeaexLNQh1RrOvJAgjLnZzH/S3HbX/mWAk+i7aulnjqUe7WKnPl1NPnJWqP7Dqsv0I2Xm6KIFHkftk0HA==", - "deprecated": "For versions >= 4, please use scopped package @node-saml/passport-saml", - "dependencies": { - "@xmldom/xmldom": "^0.7.6", - "debug": "^4.3.2", - "passport-strategy": "^1.0.0", - "xml-crypto": "^2.1.3", - "xml-encryption": "^2.0.0", - "xml2js": "^0.4.23", - "xmlbuilder": "^15.1.1" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/passport-saml/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/passport-saml/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/passport-strategy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pause": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" - }, - "node_modules/pg": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.6.0.tgz", - "integrity": "sha512-qNS9u61lqljTDFvmk/N66EeGq3n6Ujzj0FFyNMGQr6XuEv4tgNTXvJQTfJdcvGit5p5/DWPu+wj920hAJFI+QQ==", - "dependencies": { - "buffer-writer": "2.0.0", - "packet-reader": "1.0.0", - "pg-connection-string": "^2.5.0", - "pg-pool": "^3.3.0", - "pg-protocol": "^1.5.0", - "pg-types": "^2.1.0", - "pgpass": "1.x" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "pg-native": ">=2.0.0" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-connection-string": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", - "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" - }, - "node_modules/pg-int8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", - "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/pg-pool": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.3.0.tgz", - "integrity": "sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg==", - "peerDependencies": { - "pg": ">=8.0" - } - }, - "node_modules/pg-protocol": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", - "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" - }, - "node_modules/pg-types": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", - "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", - "dependencies": { - "pg-int8": "1.0.1", - "postgres-array": "~2.0.0", - "postgres-bytea": "~1.0.0", - "postgres-date": "~1.0.4", - "postgres-interval": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pgpass": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", - "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", - "dependencies": { - "split2": "^3.1.1" - } - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true, - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "dependencies": { - "node-modules-regexp": "^1.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "dependencies": { - "semver-compare": "^1.0.0" - } - }, - "node_modules/postgres-array": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", - "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", - "engines": { - "node": ">=4" - } - }, - "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-date": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", - "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postgres-interval": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", - "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", - "dependencies": { - "xtend": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.2.tgz", - "integrity": "sha512-lnJzDfJ66zkMy58OL5/NY5zp70S7Nz6KqcKkXYzn2tMVrNxvbqaBpg7H3qHaLxCJ5lNMsGuM8+ohS7cZrthdLQ==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/pretty-format/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/pretty-format/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/query-string": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", - "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", - "dependencies": { - "decode-uri-component": "^0.2.2", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" - }, - "node_modules/regex-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", - "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/send/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "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==", - "dependencies": { - "define-data-property": "^1.1.2", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", - "dev": true - }, - "node_modules/side-channel": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz", - "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", - "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==", - "dev": true - }, - "node_modules/split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", - "engines": { - "node": ">=4" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", - "dev": true, - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.padend": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz", - "integrity": "sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" - }, - "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/supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/swagger-jsdoc": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.1.0.tgz", - "integrity": "sha512-xgep5M8Gq31MxpCbQLvJZpNqHfGPfI+sILCzujZbEXIQp2COtkZgoGASs0gacRs4xHmLDH+GuMGdorPITSG4tA==", - "dependencies": { - "commander": "6.2.0", - "doctrine": "3.0.0", - "glob": "7.1.6", - "lodash.mergewith": "^4.6.2", - "swagger-parser": "10.0.2", - "yaml": "2.0.0-1" - }, - "bin": { - "swagger-jsdoc": "bin/swagger-jsdoc.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/swagger-jsdoc/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/swagger-parser": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.2.tgz", - "integrity": "sha512-9jHkHM+QXyLGFLk1DkXBwV+4HyNm0Za3b8/zk/+mjr8jgOSiqm3FOTHBSDsBjtn9scdL+8eWcHdupp2NLM8tDw==", - "dependencies": { - "@apidevtools/swagger-parser": "10.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/swagger-ui-dist": { - "version": "4.14.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.14.2.tgz", - "integrity": "sha512-kOIU7Ts3TrXDLb3/c9jRe4qGp8O3bRT19FFJA8wJfrRFkcK/4atPn3krhtBVJ57ZkNNofworXHxuYwmaisXBdg==" - }, - "node_modules/swagger-ui-express": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.5.0.tgz", - "integrity": "sha512-DHk3zFvsxrkcnurGvQlAcLuTDacAVN1JHKDgcba/gr2NFRE4HGwP1YeHIXMiGznkWR4AeS7X5vEblNn4QljuNA==", - "dependencies": { - "swagger-ui-dist": ">=4.11.0" - }, - "engines": { - "node": ">= v0.10.32" - }, - "peerDependencies": { - "express": ">=4.0.0" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-jest": { - "version": "27.0.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.3.tgz", - "integrity": "sha512-U5rdMjnYam9Ucw+h0QvtNDbc5+88nxt7tbIvqaZUhFrfG4+SkWhMXjejCLVGcpILTPuV+H3W/GZDZrnZFpPeXw==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "buffer-from": "1.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^27.0.0", - "json5": "2.x", - "lodash": "4.x", - "make-error": "1.x", - "mkdirp": "1.x", - "semver": "7.x", - "yargs-parser": "20.x" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "peerDependencies": { - "jest": "^27.0.0", - "typescript": ">=3.8 <5.0" - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node-dev": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-1.1.8.tgz", - "integrity": "sha512-Q/m3vEwzYwLZKmV6/0VlFxcZzVV/xcgOt+Tx/VjaaRHyiBcFlV0541yrT09QjzzCxlDZ34OzKjrFAynlmtflEg==", - "dev": true, - "dependencies": { - "chokidar": "^3.5.1", - "dynamic-dedupe": "^0.3.0", - "minimist": "^1.2.5", - "mkdirp": "^1.0.4", - "resolve": "^1.0.0", - "rimraf": "^2.6.1", - "source-map-support": "^0.5.12", - "tree-kill": "^1.2.2", - "ts-node": "^9.0.0", - "tsconfig": "^7.0.0" - }, - "bin": { - "ts-node-dev": "lib/bin.js", - "tsnd": "lib/bin.js" - }, - "engines": { - "node": ">=0.8.0" - }, - "peerDependencies": { - "node-notifier": "*", - "typescript": "*" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/ts-node-dev/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/ts-node-dev/node_modules/ts-node": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", - "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" - } - }, - "node_modules/ts-node/node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/tsconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", - "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", - "dev": true, - "dependencies": { - "@types/strip-bom": "^3.0.0", - "@types/strip-json-comments": "0.0.30", - "strip-bom": "^3.0.0", - "strip-json-comments": "^2.0.0" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typeorm": { - "version": "0.2.32", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.32.tgz", - "integrity": "sha512-LOBZKZ9As3f8KRMPCUT2H0JZbZfWfkcUnO3w/1BFAbL/X9+cADTF6bczDGGaKVENJ3P8SaKheKmBgpt5h1x+EQ==", - "dependencies": { - "@sqltools/formatter": "^1.2.2", - "app-root-path": "^3.0.0", - "buffer": "^6.0.3", - "chalk": "^4.1.0", - "cli-highlight": "^2.1.10", - "debug": "^4.3.1", - "dotenv": "^8.2.0", - "glob": "^7.1.6", - "js-yaml": "^4.0.0", - "mkdirp": "^1.0.4", - "reflect-metadata": "^0.1.13", - "sha.js": "^2.4.11", - "tslib": "^2.1.0", - "xml2js": "^0.4.23", - "yargonaut": "^1.1.4", - "yargs": "^16.2.0", - "zen-observable-ts": "^1.0.0" - }, - "bin": { - "typeorm": "cli.js" - }, - "funding": { - "url": "https://opencollective.com/typeorm" - } - }, - "node_modules/typeorm/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/typeorm/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/typeorm/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/typeorm/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/typeorm/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/typeorm/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/typeorm/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/typeorm/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.0.0.tgz", - "integrity": "sha512-LkmXi8UUNxnCC+JlH7/fsfsKr5AU110l+SYGJimWNkWhxbN5EyeOtm1MJ0hhvqMMOhGwBj1Fp70Yv9i+hX0QAg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/validator": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", - "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "dependencies": { - "makeerror": "1.0.x" - } - }, - "node_modules/web-encoding": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", - "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", - "dependencies": { - "util": "^0.12.3" - }, - "optionalDependencies": { - "@zxing/text-encoding": "0.9.0" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", - "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", - "dev": true, - "dependencies": { - "lodash": "^4.7.0", - "tr46": "^2.1.0", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "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==", - "dependencies": { - "available-typed-arrays": "^1.0.6", - "call-bind": "^1.0.5", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "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/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/wrap-ansi/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", - "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", - "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=" - }, - "node_modules/xml-crypto": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-2.1.4.tgz", - "integrity": "sha512-ModFeGOy67L/XXHcuepnYGF7DASEDw7fhvy+qIs1ORoH55G1IIr+fN0kaMtttwvmNFFMskD9AHro8wx352/mUg==", - "dependencies": { - "@xmldom/xmldom": "^0.7.0", - "xpath": "0.0.32" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/xml-encryption": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-2.0.0.tgz", - "integrity": "sha512-4Av83DdvAgUQQMfi/w8G01aJshbEZP9ewjmZMpS9t3H+OCZBDvyK4GJPnHGfWiXlArnPbYvR58JB9qF2x9Ds+Q==", - "dependencies": { - "@xmldom/xmldom": "^0.7.0", - "escape-html": "^1.0.3", - "xpath": "0.0.32" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "engines": { - "node": ">=8.0" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "node_modules/xpath": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", - "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/yaml": { - "version": "2.0.0-1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", - "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargonaut": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz", - "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==", - "dependencies": { - "chalk": "^1.1.1", - "figlet": "^1.1.1", - "parent-require": "^1.0.0" - } - }, - "node_modules/yargonaut/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargonaut/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargonaut/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargonaut/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yargonaut/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/z-schema": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-4.2.4.tgz", - "integrity": "sha512-YvBeW5RGNeNzKOUJs3rTL4+9rpcvHXt5I051FJbOcitV8bl40pEfcG0Q+dWSwS0/BIYrMZ/9HHoqLllMkFhD0w==", - "dependencies": { - "lodash.get": "^4.4.2", - "lodash.isequal": "^4.5.0", - "validator": "^13.6.0" - }, - "bin": { - "z-schema": "bin/z-schema" - }, - "engines": { - "node": ">=6.0.0" - }, - "optionalDependencies": { - "commander": "^2.7.1" - } - }, - "node_modules/z-schema/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "optional": true - }, - "node_modules/zen-observable": { - "version": "0.8.15", - "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", - "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" - }, - "node_modules/zen-observable-ts": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", - "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", - "dependencies": { - "@types/zen-observable": "0.8.3", - "zen-observable": "0.8.15" - } - } - } -} diff --git a/devU-client/package-lock.json b/devU-client/package-lock.json deleted file mode 100644 index 45623b33..00000000 --- a/devU-client/package-lock.json +++ /dev/null @@ -1,20264 +0,0 @@ -{ - "name": "devU-client", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "dependencies": { - "@emotion/react": "^11.11.4", - "@emotion/styled": "^11.11.5", - "@fontsource/roboto": "^5.0.12", - "@fortawesome/free-regular-svg-icons": "^5.15.3", - "@fortawesome/free-solid-svg-icons": "^5.15.3", - "@fortawesome/react-fontawesome": "^0.1.14", - "@mui/material": "^5.15.15", - "color-hash": "^2.0.1", - "devu-shared-modules": "./devu-shared-modules", - "history": "^4.10.1", - "moment": "^2.29.1", - "npm-run-all": "^4.1.5", - "query-string": "^7.0.0", - "react": "^17.0.2", - "react-datepicker": "^4.1.1", - "react-dom": "^17.0.1", - "react-redux": "^7.2.4", - "react-router-breadcrumbs-hoc": "^4.1.0", - "react-router-dom": "^5.2.0", - "react-select": "^4.3.1", - "react-toggle": "^4.1.2", - "redux": "^4.1.0", - "redux-devtools-extension": "^2.13.9", - "shortid": "^2.2.16", - "typescript": "^4.3.2" - }, - "devDependencies": { - "@types/color-hash": "^1.0.1", - "@types/js-cookie": "^2.2.6", - "@types/react": "^17.0.2", - "@types/react-datepicker": "^4.1.1", - "@types/react-dom": "^17.0.0", - "@types/react-router-dom": "^5.1.7", - "@types/react-select": "^4.0.17", - "@types/react-toggle": "^4.0.2", - "@types/shortid": "^0.0.29", - "cross-env": "^7.0.3", - "css-loader": "^5.2.6", - "dotenv": "^10.0.0", - "dotenv-webpack": "^7.0.2", - "file-loader": "^5.1.0", - "fs": "^0.0.1-security", - "html-loader": "^0.5.5", - "html-webpack-injector": "^1.1.4", - "html-webpack-plugin": "^4.5.2", - "husky": "^6.0.0", - "lint-staged": "^11.0.0", - "prettier": "^2.3.0", - "sass": "^1.34.0", - "sass-loader": "^10.2.0", - "style-loader": "^2.0.0", - "ts-loader": "^8.0.15", - "webpack": "^4.42.0", - "webpack-bundle-analyzer": "^3.6.1", - "webpack-cli": "^3.3.11", - "webpack-dev-server": "^3.10.3" - } - }, - "devu-shared-modules": {}, - "node_modules/@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dependencies": { - "@babel/highlight": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "dependencies": { - "@babel/types": "^7.24.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "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==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/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==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/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==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/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==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/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==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", - "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@emotion/babel-plugin/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" - }, - "node_modules/@emotion/react": { - "version": "11.11.4", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", - "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/serialize": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", - "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", - "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" - }, - "node_modules/@emotion/styled": { - "version": "11.11.5", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz", - "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/is-prop-valid": "^1.2.2", - "@emotion/serialize": "^1.1.4", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1" - }, - "peerDependencies": { - "@emotion/react": "^11.0.0-rc.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" - }, - "node_modules/@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", - "dependencies": { - "@floating-ui/utils": "^0.2.1" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", - "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", - "dependencies": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", - "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", - "dependencies": { - "@floating-ui/dom": "^1.6.1" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" - }, - "node_modules/@fontsource/roboto": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.12.tgz", - "integrity": "sha512-x0o17jvgoSSbS9OZnUX2+xJmVRvVCfeaYJjkS7w62iN7CuJWtMf5vJj8LqgC7ibqIkitOHVW+XssRjgrcHn62g==" - }, - "node_modules/@fortawesome/fontawesome-common-types": { - "version": "0.2.35", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz", - "integrity": "sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw==", - "hasInstallScript": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/fontawesome-svg-core": { - "version": "1.2.35", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.35.tgz", - "integrity": "sha512-uLEXifXIL7hnh2sNZQrIJWNol7cTVIzwI+4qcBIq9QWaZqUblm0IDrtSqbNg+3SQf8SMGHkiSigD++rHmCHjBg==", - "hasInstallScript": true, - "peer": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "^0.2.35" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-regular-svg-icons": { - "version": "5.15.3", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.3.tgz", - "integrity": "sha512-q4/p8Xehy9qiVTdDWHL4Z+o5PCLRChePGZRTXkl+/Z7erDVL8VcZUuqzJjs6gUz6czss4VIPBRdCz6wP37/zMQ==", - "hasInstallScript": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "^0.2.35" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/free-solid-svg-icons": { - "version": "5.15.3", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz", - "integrity": "sha512-XPeeu1IlGYqz4VWGRAT5ukNMd4VHUEEJ7ysZ7pSSgaEtNvSo+FLurybGJVmiqkQdK50OkSja2bfZXOeyMGRD8Q==", - "hasInstallScript": true, - "dependencies": { - "@fortawesome/fontawesome-common-types": "^0.2.35" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@fortawesome/react-fontawesome": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz", - "integrity": "sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA==", - "dependencies": { - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "@fortawesome/fontawesome-svg-core": "^1.2.32", - "react": ">=16.x" - } - }, - "node_modules/@mui/base": { - "version": "5.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", - "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@floating-ui/react-dom": "^2.0.8", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "@popperjs/core": "^2.11.8", - "clsx": "^2.1.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/core-downloads-tracker": { - "version": "5.15.15", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz", - "integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - } - }, - "node_modules/@mui/material": { - "version": "5.15.15", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz", - "integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.40", - "@mui/core-downloads-tracker": "^5.15.15", - "@mui/system": "^5.15.15", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "@types/react-transition-group": "^4.4.10", - "clsx": "^2.1.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1", - "react-is": "^18.2.0", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/material/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==" - }, - "node_modules/@mui/private-theming": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz", - "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.15.14", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/styled-engine": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", - "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@emotion/cache": "^11.11.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/system": { - "version": "5.15.15", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz", - "integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.15.14", - "@mui/styled-engine": "^5.15.14", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "clsx": "^2.1.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/types": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", - "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/utils": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz", - "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@types/prop-types": "^15.7.11", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/utils/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==" - }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@types/color-hash": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/color-hash/-/color-hash-1.0.1.tgz", - "integrity": "sha512-vCASMW58k1TVDTjc72mNXI4fz+EEgcXLhCuR8oix4W3wwlUtPli9LQ8OjDiA5/ENi/HhWUblF4ZdizmVGnY3dQ==", - "dev": true - }, - "node_modules/@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "dev": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/history": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz", - "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==", - "dev": true - }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "node_modules/@types/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==", - "dev": true - }, - "node_modules/@types/js-cookie": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.6.tgz", - "integrity": "sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw==", - "dev": true - }, - "node_modules/@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", - "dev": true - }, - "node_modules/@types/node": { - "version": "15.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", - "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", - "dev": true - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "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==" - }, - "node_modules/@types/react": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.11.tgz", - "integrity": "sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-datepicker": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.19.6.tgz", - "integrity": "sha512-uH5fzxt9eXxnc+hDCy/iRSFqU2+9lR/q2lAmaG4WILMai1o3IOdpcV+VSypzBFJLTEC2jrfeDXcdol0CJVMq4g==", - "dev": true, - "dependencies": { - "@popperjs/core": "^2.9.2", - "@types/react": "*", - "date-fns": "^2.0.1", - "react-popper": "^2.2.5" - } - }, - "node_modules/@types/react-dom": { - "version": "17.0.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.7.tgz", - "integrity": "sha512-Wd5xvZRlccOrCTej8jZkoFZuZRKHzanDDv1xglI33oBNFMWrqOSzrvWFw7ngSiZjrpJAzPKFtX7JvuXpkNmQHA==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-redux": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz", - "integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==", - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, - "node_modules/@types/react-router": { - "version": "5.1.15", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.15.tgz", - "integrity": "sha512-z3UlMG/x91SFEVmmvykk9FLTliDvfdIUky4k2rCfXWQ0NKbrP8o9BTCaCTPuYsB8gDkUnUmkcA2vYlm2DR+HAA==", - "dev": true, - "dependencies": { - "@types/history": "*", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.7.tgz", - "integrity": "sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg==", - "dev": true, - "dependencies": { - "@types/history": "*", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "node_modules/@types/react-select": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-4.0.17.tgz", - "integrity": "sha512-ZK5wcBhJaqC8ntQl0CJvK2KXNNsk1k5flM7jO+vNPPlceRzdJQazA6zTtQUyNr6exp5yrAiwiudtYxgGlgGHLg==", - "dev": true, - "dependencies": { - "@emotion/serialize": "^1.0.0", - "@types/react": "*", - "@types/react-dom": "*", - "@types/react-transition-group": "*" - } - }, - "node_modules/@types/react-toggle": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/react-toggle/-/react-toggle-4.0.2.tgz", - "integrity": "sha512-sHqfoKFnL0YU2+OC4meNEC8Ptx9FE8/+nFeFvNcdBa6ANA8KpAzj3R9JN8GtrvlLgjKDoYgI7iILgXYcTPo2IA==", - "dev": true, - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/scheduler": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", - "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==" - }, - "node_modules/@types/shortid": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", - "integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=", - "dev": true - }, - "node_modules/@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true - }, - "node_modules/@types/tapable": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.7.tgz", - "integrity": "sha512-0VBprVqfgFD7Ehb2vd8Lh9TG3jP98gvr8rgehQqzztZNI7o8zS8Ad4jyZneKELphpuE212D8J70LnSNQSyO6bQ==", - "dev": true - }, - "node_modules/@types/uglify-js": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.0.tgz", - "integrity": "sha512-EGkrJD5Uy+Pg0NUR8uA4bJ5WMfljyad0G+784vLCNUkD+QwOJXUbBYExXfVGf7YtyzdQp3L/XMYcliB987kL5Q==", - "dev": true, - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/@types/webpack": { - "version": "4.41.29", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.29.tgz", - "integrity": "sha512-6pLaORaVNZxiB3FSHbyBiWM7QdazAWda1zvAq4SbZObZqHSDbWLi62iFdblVea6SK9eyBIVp5yHhKt/yNQdR7Q==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/tapable": "^1", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "anymatch": "^3.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/@types/webpack-sources": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-2.1.0.tgz", - "integrity": "sha512-LXn/oYIpBeucgP1EIJbKQ2/4ZmpvRl+dlrFdX7+94SKRUV3Evy3FsfMZY318vGhkWUS5MPhtOM3w1/hCOAOXcg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.7.3" - } - }, - "node_modules/@types/webpack-sources/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "dev": true, - "dependencies": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "node_modules/@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, - "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true, - "peerDependencies": { - "ajv": ">=5.0.0" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, - "node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.1", - "util": "0.10.3" - } - }, - "node_modules/assert/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "node_modules/assert/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "dependencies": { - "inherits": "2.0.1" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ast-types": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", - "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "dependencies": { - "lodash": "^4.17.14" - } - }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "node_modules/bfj": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", - "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", - "dev": true, - "dependencies": { - "bluebird": "^3.5.5", - "check-types": "^8.0.3", - "hoopy": "^0.1.4", - "tryer": "^1.0.1" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "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" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "node_modules/bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", - "dev": true - }, - "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dev": true, - "dependencies": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, - "dependencies": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - } - }, - "node_modules/bonjour/node_modules/array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "dependencies": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/browserify-sign/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "node_modules/buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "node_modules/buffer/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "dependencies": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "node_modules/cacache/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/cacache/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/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", - "dev": true, - "dependencies": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/check-types": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", - "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", - "dev": true - }, - "node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.1" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, - "node_modules/clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "dependencies": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/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/cliui/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/cliui/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-hash": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-hash/-/color-hash-2.0.1.tgz", - "integrity": "sha512-/wIYAQ3xL9ruURLmDbxAsXEsivaOfwWDUVy+zbWJZL3bnNQIDNSmmqbkNzeTOQvDdiz11Kb010UlJN7hUXLg/w==" - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, - "node_modules/commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "node_modules/copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "dependencies": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "node_modules/cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/css-loader": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.6.tgz", - "integrity": "sha512-0wyN5vXMQZu6BvjbrPdUJvkCzGEO24HC7IS7nW4llc6BBFC+zwR9CKtYGv63Puzsg10L/o12inMY5/2ByzfD6w==", - "dev": true, - "dependencies": { - "icss-utils": "^5.1.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.15", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^3.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.27.0 || ^5.0.0" - } - }, - "node_modules/css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", - "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", - "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" - }, - "node_modules/cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", - "dev": true - }, - "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "node_modules/deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, - "dependencies": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", - "dev": true, - "dependencies": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/default-gateway/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/default-gateway/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/default-gateway/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/default-gateway/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/default-gateway/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/default-gateway/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/default-gateway/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", - "dev": true, - "dependencies": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/del/node_modules/p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/del/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true - }, - "node_modules/devu-shared-modules": { - "resolved": "devu-shared-modules", - "link": true - }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, - "node_modules/dns-packet": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", - "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", - "dev": true, - "dependencies": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, - "dependencies": { - "buffer-indexof": "^1.0.0" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dev": true, - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", - "dev": true, - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true, - "engines": { - "node": ">=0.4", - "npm": ">=1.2" - } - }, - "node_modules/domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", - "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==", - "dev": true, - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", - "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", - "dev": true, - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dot-case/node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/dot-case/node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/dot-case/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/dotenv-defaults": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", - "integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==", - "dev": true, - "dependencies": { - "dotenv": "^8.2.0" - } - }, - "node_modules/dotenv-defaults/node_modules/dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/dotenv-webpack": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-7.0.3.tgz", - "integrity": "sha512-O0O9pOEwrk+n1zzR3T2uuXRlw64QxHSPeNN1GaiNBloQFNaCUL9V8jxSVz4jlXXFP/CIqK8YecWf8BAvsSgMjw==", - "dev": true, - "dependencies": { - "dotenv-defaults": "^2.0.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "webpack": "^4 || ^5" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "node_modules/ejs": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", - "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", - "dev": true, - "hasInstallScript": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "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/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", - "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.10.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es6-templates": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/es6-templates/-/es6-templates-0.2.3.tgz", - "integrity": "sha1-XLmsn7He1usSOTQrgdeSu7QHjuQ=", - "dev": true, - "dependencies": { - "recast": "~0.11.12", - "through": "~2.3.6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", - "dev": true, - "dependencies": { - "original": "^1.0.0" - }, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dev": true, - "dependencies": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "dev": true - }, - "node_modules/file-loader": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-5.1.0.tgz", - "integrity": "sha512-u/VkLGskw3Ue59nyOwUwXI/6nuBCo7KBkniB/l7ICwr/7cPNGsL1WCXUp3GB0qgOOKU1TiP49bv4DZF/LJqprg==", - "dev": true, - "dependencies": { - "loader-utils": "^1.4.0", - "schema-utils": "^2.5.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/file-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/file-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/file-loader/node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "node_modules/filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" - }, - "node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/findup-sync/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/findup-sync/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/follow-redirects": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", - "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=", - "dev": true - }, - "node_modules/fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "dependencies": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/globby/node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" - }, - "node_modules/gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "dev": true, - "dependencies": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/gzip-size/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-base/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/hash-base/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", - "dev": true, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/html-entities": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", - "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", - "dev": true - }, - "node_modules/html-loader": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-0.5.5.tgz", - "integrity": "sha512-7hIW7YinOYUpo//kSYcPB6dCKoceKLmOwjEMmhIobHuWGDVl0Nwe4l68mdG/Ru0wcUxQjVMEoZpkalZ/SE7zog==", - "dev": true, - "dependencies": { - "es6-templates": "^0.2.3", - "fastparse": "^1.1.1", - "html-minifier": "^3.5.8", - "loader-utils": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/html-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/html-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/html-minifier": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", - "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", - "dev": true, - "dependencies": { - "camel-case": "3.0.x", - "clean-css": "4.2.x", - "commander": "2.17.x", - "he": "1.2.x", - "param-case": "2.1.x", - "relateurl": "0.2.x", - "uglify-js": "3.4.x" - }, - "bin": { - "html-minifier": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", - "dev": true, - "dependencies": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", - "he": "^1.2.0", - "param-case": "^3.0.3", - "relateurl": "^0.2.7", - "terser": "^4.6.3" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/html-minifier-terser/node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/html-minifier-terser/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/html-minifier-terser/node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/html-minifier-terser/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/html-webpack-injector": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/html-webpack-injector/-/html-webpack-injector-1.1.4.tgz", - "integrity": "sha512-R+HeAYzPeL3dKIr5/a7a2S6R4fy2yHetKiB7cz5rXjwlnU5tghuy58kCBsKA/Qoj94MAgCYwllHmvYqy2nJSdg==", - "dev": true - }, - "node_modules/html-webpack-plugin": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz", - "integrity": "sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A==", - "dev": true, - "dependencies": { - "@types/html-minifier-terser": "^5.0.0", - "@types/tapable": "^1.0.5", - "@types/webpack": "^4.41.8", - "html-minifier-terser": "^5.0.1", - "loader-utils": "^1.2.3", - "lodash": "^4.17.20", - "pretty-error": "^2.1.1", - "tapable": "^1.1.3", - "util.promisify": "1.0.0" - }, - "engines": { - "node": ">=6.9" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/html-webpack-plugin/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/html-webpack-plugin/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true - }, - "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-errors/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", - "dev": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "dev": true, - "dependencies": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/http-proxy-middleware/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/husky": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz", - "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", - "dev": true, - "bin": { - "husky": "lib/bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "dev": true, - "dependencies": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "node_modules/internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dev": true, - "dependencies": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "node_modules/is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", - "dependencies": { - "call-bind": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "dev": true, - "dependencies": { - "is-path-inside": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "dev": true, - "dependencies": { - "path-is-inside": "^1.0.2" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", - "dependencies": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", - "dev": true - }, - "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", - "dev": true - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klona": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", - "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, - "node_modules/lint-staged": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-11.0.0.tgz", - "integrity": "sha512-3rsRIoyaE8IphSUtO1RVTFl1e0SLBtxxUOPBtHxQgBHS5/i6nqvjcUfNioMa4BU9yGnPzbO+xkfLtXtxBpCzjw==", - "dev": true, - "dependencies": { - "chalk": "^4.1.1", - "cli-truncate": "^2.1.0", - "commander": "^7.2.0", - "cosmiconfig": "^7.0.0", - "debug": "^4.3.1", - "dedent": "^0.7.0", - "enquirer": "^2.3.6", - "execa": "^5.0.0", - "listr2": "^3.8.2", - "log-symbols": "^4.1.0", - "micromatch": "^4.0.4", - "normalize-path": "^3.0.0", - "please-upgrade-node": "^3.2.0", - "string-argv": "0.3.1", - "stringify-object": "^3.3.0" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/lint-staged/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/listr2": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.10.0.tgz", - "integrity": "sha512-eP40ZHihu70sSmqFNbNy2NL1YwImmlMmPh9WO5sLmPDleurMHt3n+SwEWNu2kzKScexZnkyFtc1VI0z/TGlmpw==", - "dev": true, - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^1.2.2", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rxjs": "^6.6.7", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - } - }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/loglevel": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", - "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/loglevel" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", - "dev": true - }, - "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/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "node_modules/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", - "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.31", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", - "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", - "dev": true, - "dependencies": { - "mime-db": "1.48.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" - }, - "peerDependencies": { - "prop-types": "^15.0.0", - "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "node_modules/mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "dependencies": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "engines": { - "node": "*" - } - }, - "node_modules/move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "dependencies": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, - "dependencies": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "node_modules/nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", - "dev": true, - "optional": true - }, - "node_modules/nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node_modules/no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", - "dev": true, - "dependencies": { - "lower-case": "^1.1.1" - } - }, - "node_modules/node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "dependencies": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "node_modules/node-libs-browser/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "bin": { - "npm-run-all": "bin/npm-run-all/index.js", - "run-p": "bin/run-p/index.js", - "run-s": "bin/run-s/index.js" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm-run-all/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==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/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==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/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==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/npm-run-all/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "node_modules/npm-run-all/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/npm-run-all/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/npm-run-all/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all/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==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", - "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", - "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true, - "bin": { - "opener": "bin/opener-bin.js" - } - }, - "node_modules/opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "dev": true, - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "dev": true, - "dependencies": { - "url-parse": "^1.4.3" - } - }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", - "dev": true, - "dependencies": { - "retry": "^0.12.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dev": true, - "dependencies": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "node_modules/param-case": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", - "dev": true, - "dependencies": { - "no-case": "^2.2.0" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/pascal-case/node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/pascal-case/node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/pascal-case/node_modules/tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "engines": { - "node": ">=4" - } - }, - "node_modules/pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "dependencies": { - "pinkie": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "dependencies": { - "semver-compare": "^1.0.0" - } - }, - "node_modules/portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "dev": true, - "dependencies": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "engines": { - "node": ">= 0.12.0" - } - }, - "node_modules/portfinder/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.1.tgz", - "integrity": "sha512-9qH0MGjsSm+fjxOi3GnwViL1otfi7qkj+l/WX5gcRGmZNGsIcqc+A5fBkE6PUobEQK4APqYVaES+B3Uti98TCw==", - "dev": true, - "dependencies": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map-js": "^0.6.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dev": true, - "dependencies": { - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", - "dev": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", - "dev": true - }, - "node_modules/prettier": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", - "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/pretty-error": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", - "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^2.0.4" - } - }, - "node_modules/private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/query-string": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.0.tgz", - "integrity": "sha512-Iy7moLybliR5ZgrK/1R3vjrXq03S13Vz4Rbm5Jg3EFq1LUmQppto0qtXz4vqZ386MSRjZgnTSZ9QC+NZOSd/XA==", - "dependencies": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dev": true, - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-datepicker": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.25.0.tgz", - "integrity": "sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==", - "dependencies": { - "@popperjs/core": "^2.11.8", - "classnames": "^2.2.6", - "date-fns": "^2.30.0", - "prop-types": "^15.7.2", - "react-onclickoutside": "^6.13.0", - "react-popper": "^2.3.0" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17 || ^18", - "react-dom": "^16.9.0 || ^17 || ^18" - } - }, - "node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "peerDependencies": { - "react": "17.0.2" - } - }, - "node_modules/react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" - }, - "node_modules/react-input-autosize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz", - "integrity": "sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==", - "dependencies": { - "prop-types": "^15.5.8" - }, - "peerDependencies": { - "react": "^16.3.0 || ^17.0.0" - } - }, - "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==" - }, - "node_modules/react-onclickoutside": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", - "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==", - "funding": { - "type": "individual", - "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" - }, - "peerDependencies": { - "react": "^15.5.x || ^16.x || ^17.x || ^18.x", - "react-dom": "^15.5.x || ^16.x || ^17.x || ^18.x" - } - }, - "node_modules/react-popper": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", - "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", - "dependencies": { - "react-fast-compare": "^3.0.1", - "warning": "^4.0.2" - }, - "peerDependencies": { - "@popperjs/core": "^2.0.0", - "react": "^16.8.0 || ^17 || ^18", - "react-dom": "^16.8.0 || ^17 || ^18" - } - }, - "node_modules/react-redux": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", - "integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/react-redux": "^7.1.16", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.13.1" - }, - "peerDependencies": { - "react": "^16.8.3 || ^17" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router-breadcrumbs-hoc": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/react-router-breadcrumbs-hoc/-/react-router-breadcrumbs-hoc-4.1.0.tgz", - "integrity": "sha512-HZn352JkMzi/1Mp9H6v78V9f7sjnWRf/dXeFVLDDbxEUK7QJCj1JUBJuEHC+B3tq1+uRCASNm3ofEOaG33tAKw==", - "peerDependencies": { - "react": ">=16.8", - "react-router-dom": ">=5" - } - }, - "node_modules/react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", - "dependencies": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-select": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.1.tgz", - "integrity": "sha512-HBBd0dYwkF5aZk1zP81Wx5UsLIIT2lSvAY2JiJo199LjoLHoivjn9//KsmvQMEFGNhe58xyuOITjfxKCcGc62Q==", - "dependencies": { - "@babel/runtime": "^7.12.0", - "@emotion/cache": "^11.4.0", - "@emotion/react": "^11.1.1", - "memoize-one": "^5.0.0", - "prop-types": "^15.6.0", - "react-input-autosize": "^3.0.0", - "react-transition-group": "^4.3.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/react-toggle": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.1.2.tgz", - "integrity": "sha512-4Ohw31TuYQdhWfA6qlKafeXx3IOH7t4ZHhmRdwsm1fQREwOBGxJT+I22sgHqR/w8JRdk+AeMCJXPImEFSrNXow==", - "dependencies": { - "classnames": "^2.2.5" - }, - "peerDependencies": { - "prop-types": ">= 15.3.0 < 18", - "react": ">= 15.3.0 < 18", - "react-dom": ">= 15.3.0 < 18" - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg/node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/recast": { - "version": "0.11.23", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", - "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", - "dev": true, - "dependencies": { - "ast-types": "0.9.6", - "esprima": "~3.1.0", - "private": "~0.1.5", - "source-map": "~0.5.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/recast/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redux": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.0.tgz", - "integrity": "sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/redux-devtools-extension": { - "version": "2.13.9", - "resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz", - "integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==", - "peerDependencies": { - "redux": "^3.1.0 || ^4.0.0" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "node_modules/renderkid": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.6.tgz", - "integrity": "sha512-GIis2GBr/ho0pFNf57D4XM4+PgnQuTii0WCPjEZmZfKivzUfGuRdjN2aQYtYMiNggHmNyBve+thFnNR1iBRcKg==", - "dev": true, - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.0" - } - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-dir/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "dependencies": { - "aproba": "^1.1.1" - } - }, - "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sass": { - "version": "1.34.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.34.1.tgz", - "integrity": "sha512-scLA7EIZM+MmYlej6sdVr0HRbZX5caX5ofDT9asWnUJj21oqgsC+1LuNfm0eg+vM0fCTZHhwImTiCU0sx9h9CQ==", - "dev": true, - "dependencies": { - "chokidar": ">=3.0.0 <4.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/sass-loader": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.2.0.tgz", - "integrity": "sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw==", - "dev": true, - "dependencies": { - "klona": "^2.0.4", - "loader-utils": "^2.0.0", - "neo-async": "^2.6.2", - "schema-utils": "^3.0.0", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0", - "sass": "^1.3.0", - "webpack": "^4.36.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "fibers": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", - "dev": true - }, - "node_modules/selfsigned": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", - "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", - "dev": true, - "dependencies": { - "node-forge": "^0.10.0" - } - }, - "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true - }, - "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dev": true, - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==" - }, - "node_modules/shortid": { - "version": "2.2.16", - "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz", - "integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==", - "dependencies": { - "nanoid": "^2.1.0" - } - }, - "node_modules/shortid/node_modules/nanoid": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz", - "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==" - }, - "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", - "dev": true, - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/sockjs-client": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.1.tgz", - "integrity": "sha512-VnVAb663fosipI/m6pqRXakEOw7nvd7TUgdr3PlR/8V2I95QIdwT8L4nMxhyU8SmDBHYXU1TOElaKOmKLfYzeQ==", - "dev": true, - "dependencies": { - "debug": "^3.2.6", - "eventsource": "^1.0.7", - "faye-websocket": "^0.11.3", - "inherits": "^2.0.4", - "json3": "^3.3.3", - "url-parse": "^1.5.1" - } - }, - "node_modules/sockjs-client/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", - "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==" - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/spdy-transport/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "engines": { - "node": ">=6" - } - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ssri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", - "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", - "dev": true, - "dependencies": { - "figgy-pudding": "^3.5.1" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "node_modules/strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", - "engines": { - "node": ">=4" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", - "dev": true, - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string.prototype.padend": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz", - "integrity": "sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/style-loader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", - "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", - "dev": true, - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "dev": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", - "dev": true, - "dependencies": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "engines": { - "node": ">= 6.9.0" - }, - "peerDependencies": { - "webpack": "^4.0.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, - "node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", - "dev": true - }, - "node_modules/ts-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.3.0.tgz", - "integrity": "sha512-MgGly4I6cStsJy27ViE32UoqxPTN9Xly4anxxVyaIWR+9BGxboV4EyJBGfR3RePV7Ksjj3rHmPZJeIt+7o4Vag==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^2.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "*" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "node_modules/typescript": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", - "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/uglify-js": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", - "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", - "dev": true, - "dependencies": { - "commander": "~2.19.0", - "source-map": "~0.6.1" - }, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/uglify-js/node_modules/commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", - "dev": true - }, - "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", - "dev": true - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/watchpack": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", - "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" - }, - "optionalDependencies": { - "chokidar": "^3.4.1", - "watchpack-chokidar2": "^2.0.1" - } - }, - "node_modules/watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "dev": true, - "optional": true, - "dependencies": { - "chokidar": "^2.1.8" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "optional": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "optional": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", - "dev": true, - "optional": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "optional": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "optional": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "optional": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "optional": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "optional": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "optional": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/watchpack-chokidar2/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "optional": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/webpack": { - "version": "4.46.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", - "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.5.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - }, - "webpack-command": { - "optional": true - } - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz", - "integrity": "sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1", - "bfj": "^6.1.1", - "chalk": "^2.4.1", - "commander": "^2.18.0", - "ejs": "^2.6.1", - "express": "^4.16.3", - "filesize": "^3.6.1", - "gzip-size": "^5.0.0", - "lodash": "^4.17.19", - "mkdirp": "^0.5.1", - "opener": "^1.5.1", - "ws": "^6.0.0" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 6.14.4" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/webpack-bundle-analyzer/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/webpack-bundle-analyzer/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/webpack-bundle-analyzer/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/webpack-bundle-analyzer/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-bundle-analyzer/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/webpack-cli": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz", - "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2", - "cross-spawn": "^6.0.5", - "enhanced-resolve": "^4.1.1", - "findup-sync": "^3.0.0", - "global-modules": "^2.0.0", - "import-local": "^2.0.0", - "interpret": "^1.4.0", - "loader-utils": "^1.4.0", - "supports-color": "^6.1.0", - "v8-compile-cache": "^2.1.1", - "yargs": "^13.3.2" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=6.11.5" - }, - "peerDependencies": { - "webpack": "4.x.x" - } - }, - "node_modules/webpack-cli/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/webpack-cli/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/webpack-cli/node_modules/chalk/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/webpack-cli/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/webpack-cli/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/webpack-cli/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/webpack-cli/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-cli/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/webpack-cli/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack-cli/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-cli/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/webpack-cli/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-cli/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-cli/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-cli/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", - "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", - "dev": true, - "dependencies": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-middleware/node_modules/memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack-dev-server": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz", - "integrity": "sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ==", - "dev": true, - "dependencies": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.3.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.8", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.26", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.8", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "^0.3.21", - "sockjs-client": "^1.5.0", - "spdy": "^4.0.2", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "^13.3.2" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 6.11.5" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/webpack-dev-server/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", - "dev": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "optionalDependencies": { - "fsevents": "^1.2.7" - } - }, - "node_modules/webpack-dev-server/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/webpack-dev-server/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/webpack-dev-server/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-dev-server/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/webpack-dev-server/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/webpack-dev-server/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-dev-server/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dev": true, - "dependencies": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-log/node_modules/ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/webpack/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/webpack/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/webpack/node_modules/memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "node_modules/webpack/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/webpack/node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "node_modules/worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "dependencies": { - "errno": "~0.1.7" - } - }, - "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/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "dev": true, - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "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/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "dependencies": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - } - }, - "node_modules/yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@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==", - "requires": { - "@babel/types": "^7.24.0" - } - }, - "@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==" - }, - "@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "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==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "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==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/runtime": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", - "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==", - "requires": { - "regenerator-runtime": "^0.14.0" - } - }, - "@babel/types": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", - "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", - "requires": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - } - }, - "@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", - "requires": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - } - } - }, - "@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "requires": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "stylis": "4.2.0" - } - }, - "@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" - }, - "@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "requires": { - "@emotion/memoize": "^0.8.1" - } - }, - "@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" - }, - "@emotion/react": { - "version": "11.11.4", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", - "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", - "requires": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "hoist-non-react-statics": "^3.3.1" - } - }, - "@emotion/serialize": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", - "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", - "requires": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", - "csstype": "^3.0.2" - } - }, - "@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" - }, - "@emotion/styled": { - "version": "11.11.5", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz", - "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==", - "requires": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/is-prop-valid": "^1.2.2", - "@emotion/serialize": "^1.1.4", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1" - } - }, - "@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" - }, - "@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", - "requires": {} - }, - "@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" - }, - "@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" - }, - "@floating-ui/core": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", - "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", - "requires": { - "@floating-ui/utils": "^0.2.1" - } - }, - "@floating-ui/dom": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", - "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", - "requires": { - "@floating-ui/core": "^1.0.0", - "@floating-ui/utils": "^0.2.0" - } - }, - "@floating-ui/react-dom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", - "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", - "requires": { - "@floating-ui/dom": "^1.6.1" - } - }, - "@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" - }, - "@fontsource/roboto": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.12.tgz", - "integrity": "sha512-x0o17jvgoSSbS9OZnUX2+xJmVRvVCfeaYJjkS7w62iN7CuJWtMf5vJj8LqgC7ibqIkitOHVW+XssRjgrcHn62g==" - }, - "@fortawesome/fontawesome-common-types": { - "version": "0.2.35", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz", - "integrity": "sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw==" - }, - "@fortawesome/fontawesome-svg-core": { - "version": "1.2.35", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.35.tgz", - "integrity": "sha512-uLEXifXIL7hnh2sNZQrIJWNol7cTVIzwI+4qcBIq9QWaZqUblm0IDrtSqbNg+3SQf8SMGHkiSigD++rHmCHjBg==", - "peer": true, - "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.35" - } - }, - "@fortawesome/free-regular-svg-icons": { - "version": "5.15.3", - "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.3.tgz", - "integrity": "sha512-q4/p8Xehy9qiVTdDWHL4Z+o5PCLRChePGZRTXkl+/Z7erDVL8VcZUuqzJjs6gUz6czss4VIPBRdCz6wP37/zMQ==", - "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.35" - } - }, - "@fortawesome/free-solid-svg-icons": { - "version": "5.15.3", - "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz", - "integrity": "sha512-XPeeu1IlGYqz4VWGRAT5ukNMd4VHUEEJ7ysZ7pSSgaEtNvSo+FLurybGJVmiqkQdK50OkSja2bfZXOeyMGRD8Q==", - "requires": { - "@fortawesome/fontawesome-common-types": "^0.2.35" - } - }, - "@fortawesome/react-fontawesome": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz", - "integrity": "sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA==", - "requires": { - "prop-types": "^15.7.2" - } - }, - "@mui/base": { - "version": "5.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", - "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", - "requires": { - "@babel/runtime": "^7.23.9", - "@floating-ui/react-dom": "^2.0.8", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "@popperjs/core": "^2.11.8", - "clsx": "^2.1.0", - "prop-types": "^15.8.1" - } - }, - "@mui/core-downloads-tracker": { - "version": "5.15.15", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.15.tgz", - "integrity": "sha512-aXnw29OWQ6I5A47iuWEI6qSSUfH6G/aCsW9KmW3LiFqr7uXZBK4Ks+z8G+qeIub8k0T5CMqlT2q0L+ZJTMrqpg==" - }, - "@mui/material": { - "version": "5.15.15", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.15.tgz", - "integrity": "sha512-3zvWayJ+E1kzoIsvwyEvkTUKVKt1AjchFFns+JtluHCuvxgKcLSRJTADw37k0doaRtVAsyh8bz9Afqzv+KYrIA==", - "requires": { - "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.40", - "@mui/core-downloads-tracker": "^5.15.15", - "@mui/system": "^5.15.15", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "@types/react-transition-group": "^4.4.10", - "clsx": "^2.1.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1", - "react-is": "^18.2.0", - "react-transition-group": "^4.4.5" - }, - "dependencies": { - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - } - } - }, - "@mui/private-theming": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.15.14.tgz", - "integrity": "sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==", - "requires": { - "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.15.14", - "prop-types": "^15.8.1" - } - }, - "@mui/styled-engine": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.15.14.tgz", - "integrity": "sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==", - "requires": { - "@babel/runtime": "^7.23.9", - "@emotion/cache": "^11.11.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - } - }, - "@mui/system": { - "version": "5.15.15", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.15.15.tgz", - "integrity": "sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==", - "requires": { - "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.15.14", - "@mui/styled-engine": "^5.15.14", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "clsx": "^2.1.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - } - }, - "@mui/types": { - "version": "7.2.14", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.14.tgz", - "integrity": "sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==", - "requires": {} - }, - "@mui/utils": { - "version": "5.15.14", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.15.14.tgz", - "integrity": "sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==", - "requires": { - "@babel/runtime": "^7.23.9", - "@types/prop-types": "^15.7.11", - "prop-types": "^15.8.1", - "react-is": "^18.2.0" - }, - "dependencies": { - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - } - } - }, - "@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" - }, - "@types/color-hash": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/color-hash/-/color-hash-1.0.1.tgz", - "integrity": "sha512-vCASMW58k1TVDTjc72mNXI4fz+EEgcXLhCuR8oix4W3wwlUtPli9LQ8OjDiA5/ENi/HhWUblF4ZdizmVGnY3dQ==", - "dev": true - }, - "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "dev": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/history": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz", - "integrity": "sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA==", - "dev": true - }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "@types/html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==", - "dev": true - }, - "@types/js-cookie": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.6.tgz", - "integrity": "sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", - "dev": true - }, - "@types/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", - "dev": true - }, - "@types/node": { - "version": "15.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", - "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", - "dev": true - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "@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==" - }, - "@types/react": { - "version": "17.0.11", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.11.tgz", - "integrity": "sha512-yFRQbD+whVonItSk7ZzP/L+gPTJVBkL/7shLEF+i9GC/1cV3JmUxEQz6+9ylhUpWSDuqo1N9qEvqS6vTj4USUA==", - "requires": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-datepicker": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-4.19.6.tgz", - "integrity": "sha512-uH5fzxt9eXxnc+hDCy/iRSFqU2+9lR/q2lAmaG4WILMai1o3IOdpcV+VSypzBFJLTEC2jrfeDXcdol0CJVMq4g==", - "dev": true, - "requires": { - "@popperjs/core": "^2.9.2", - "@types/react": "*", - "date-fns": "^2.0.1", - "react-popper": "^2.2.5" - } - }, - "@types/react-dom": { - "version": "17.0.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.7.tgz", - "integrity": "sha512-Wd5xvZRlccOrCTej8jZkoFZuZRKHzanDDv1xglI33oBNFMWrqOSzrvWFw7ngSiZjrpJAzPKFtX7JvuXpkNmQHA==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/react-redux": { - "version": "7.1.16", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz", - "integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==", - "requires": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, - "@types/react-router": { - "version": "5.1.15", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.15.tgz", - "integrity": "sha512-z3UlMG/x91SFEVmmvykk9FLTliDvfdIUky4k2rCfXWQ0NKbrP8o9BTCaCTPuYsB8gDkUnUmkcA2vYlm2DR+HAA==", - "dev": true, - "requires": { - "@types/history": "*", - "@types/react": "*" - } - }, - "@types/react-router-dom": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.7.tgz", - "integrity": "sha512-D5mHD6TbdV/DNHYsnwBTv+y73ei+mMjrkGrla86HthE4/PVvL1J94Bu3qABU+COXzpL23T1EZapVVpwHuBXiUg==", - "dev": true, - "requires": { - "@types/history": "*", - "@types/react": "*", - "@types/react-router": "*" - } - }, - "@types/react-select": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@types/react-select/-/react-select-4.0.17.tgz", - "integrity": "sha512-ZK5wcBhJaqC8ntQl0CJvK2KXNNsk1k5flM7jO+vNPPlceRzdJQazA6zTtQUyNr6exp5yrAiwiudtYxgGlgGHLg==", - "dev": true, - "requires": { - "@emotion/serialize": "^1.0.0", - "@types/react": "*", - "@types/react-dom": "*", - "@types/react-transition-group": "*" - } - }, - "@types/react-toggle": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/react-toggle/-/react-toggle-4.0.2.tgz", - "integrity": "sha512-sHqfoKFnL0YU2+OC4meNEC8Ptx9FE8/+nFeFvNcdBa6ANA8KpAzj3R9JN8GtrvlLgjKDoYgI7iILgXYcTPo2IA==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", - "requires": { - "@types/react": "*" - } - }, - "@types/scheduler": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz", - "integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==" - }, - "@types/shortid": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", - "integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=", - "dev": true - }, - "@types/source-list-map": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", - "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", - "dev": true - }, - "@types/tapable": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.7.tgz", - "integrity": "sha512-0VBprVqfgFD7Ehb2vd8Lh9TG3jP98gvr8rgehQqzztZNI7o8zS8Ad4jyZneKELphpuE212D8J70LnSNQSyO6bQ==", - "dev": true - }, - "@types/uglify-js": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.0.tgz", - "integrity": "sha512-EGkrJD5Uy+Pg0NUR8uA4bJ5WMfljyad0G+784vLCNUkD+QwOJXUbBYExXfVGf7YtyzdQp3L/XMYcliB987kL5Q==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - } - }, - "@types/webpack": { - "version": "4.41.29", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.29.tgz", - "integrity": "sha512-6pLaORaVNZxiB3FSHbyBiWM7QdazAWda1zvAq4SbZObZqHSDbWLi62iFdblVea6SK9eyBIVp5yHhKt/yNQdR7Q==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/tapable": "^1", - "@types/uglify-js": "*", - "@types/webpack-sources": "*", - "anymatch": "^3.0.0", - "source-map": "^0.6.0" - } - }, - "@types/webpack-sources": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-2.1.0.tgz", - "integrity": "sha512-LXn/oYIpBeucgP1EIJbKQ2/4ZmpvRl+dlrFdX7+94SKRUV3Evy3FsfMZY318vGhkWUS5MPhtOM3w1/hCOAOXcg==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/source-list-map": "*", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", - "dev": true, - "requires": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "dev": true - }, - "@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "dev": true, - "requires": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", - "dev": true - }, - "@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true, - "requires": {} - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - } - }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "ast-types": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", - "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "requires": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "bfj": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz", - "integrity": "sha512-BmBJa4Lip6BPRINSZ0BPEIfB1wUY/9rwbwvIHQA1KjX9om29B6id0wnWXq7m3bn5JrUVjeOTnVuhPT1FiHwPGw==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "check-types": "^8.0.3", - "hoopy": "^0.1.4", - "tryer": "^1.0.1" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", - "dev": true - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dev": true, - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, - "requires": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - }, - "dependencies": { - "array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - } - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "~1.0.5" - } - }, - "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true - }, - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - }, - "dependencies": { - "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, - "requires": { - "yallist": "^3.0.2" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - } - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", - "dev": true, - "requires": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "check-types": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-8.0.3.tgz", - "integrity": "sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==", - "dev": true - }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" - }, - "clean-css": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", - "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", - "dev": true, - "requires": { - "source-map": "~0.6.0" - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "requires": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - } - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - } - } - }, - "clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==" - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-hash": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-hash/-/color-hash-2.0.1.tgz", - "integrity": "sha512-/wIYAQ3xL9ruURLmDbxAsXEsivaOfwWDUVy+zbWJZL3bnNQIDNSmmqbkNzeTOQvDdiz11Kb010UlJN7hUXLg/w==" - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true - }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - } - }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.1" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "css-loader": { - "version": "5.2.6", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.6.tgz", - "integrity": "sha512-0wyN5vXMQZu6BvjbrPdUJvkCzGEO24HC7IS7nW4llc6BBFC+zwR9CKtYGv63Puzsg10L/o12inMY5/2ByzfD6w==", - "dev": true, - "requires": { - "icss-utils": "^5.1.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.15", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^3.0.0", - "semver": "^7.3.5" - } - }, - "css-select": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", - "integrity": "sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^5.0.0", - "domhandler": "^4.2.0", - "domutils": "^2.6.0", - "nth-check": "^2.0.0" - } - }, - "css-what": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.1.tgz", - "integrity": "sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg==", - "dev": true - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true - }, - "csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" - }, - "cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", - "dev": true - }, - "date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "requires": { - "@babel/runtime": "^7.21.0" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", - "dev": true, - "requires": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", - "dev": true - }, - "detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true - }, - "devu-shared-modules": { - "version": "file:devu-shared-modules" - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, - "dns-packet": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", - "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", - "dev": true, - "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, - "requires": { - "buffer-indexof": "^1.0.0" - } - }, - "dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dev": true, - "requires": { - "utila": "~0.4" - } - }, - "dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "requires": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "dom-serializer": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.2.tgz", - "integrity": "sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, - "domelementtype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", - "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==", - "dev": true - }, - "domhandler": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.2.0.tgz", - "integrity": "sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==", - "dev": true, - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.7.0.tgz", - "integrity": "sha512-8eaHa17IwJUPAiB+SoTYBo5mCdeMgdcAoXJ59m6DT1vw+5iLS3gNoqYaRowaBKtGVrOF1Jz4yDTgYKLK2kvfJg==", - "dev": true, - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - }, - "dependencies": { - "lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "requires": { - "tslib": "^2.0.3" - } - }, - "no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - } - } - }, - "dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "dev": true - }, - "dotenv-defaults": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz", - "integrity": "sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg==", - "dev": true, - "requires": { - "dotenv": "^8.2.0" - }, - "dependencies": { - "dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", - "dev": true - } - } - }, - "dotenv-webpack": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-7.0.3.tgz", - "integrity": "sha512-O0O9pOEwrk+n1zzR3T2uuXRlw64QxHSPeNN1GaiNBloQFNaCUL9V8jxSVz4jlXXFP/CIqK8YecWf8BAvsSgMjw==", - "dev": true, - "requires": { - "dotenv-defaults": "^2.0.2" - } - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "ejs": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", - "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", - "dev": true - }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "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 - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true - }, - "errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", - "dev": true, - "requires": { - "prr": "~1.0.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", - "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.10.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-templates": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/es6-templates/-/es6-templates-0.2.3.tgz", - "integrity": "sha1-XLmsn7He1usSOTQrgdeSu7QHjuQ=", - "dev": true, - "requires": { - "recast": "~0.11.12", - "through": "~2.3.6" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, - "eventsource": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz", - "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==", - "dev": true, - "requires": { - "original": "^1.0.0" - } - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dev": true, - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fastparse": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", - "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true - }, - "faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "dev": true - }, - "file-loader": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-5.1.0.tgz", - "integrity": "sha512-u/VkLGskw3Ue59nyOwUwXI/6nuBCo7KBkniB/l7ICwr/7cPNGsL1WCXUp3GB0qgOOKU1TiP49bv4DZF/LJqprg==", - "dev": true, - "requires": { - "loader-utils": "^1.4.0", - "schema-utils": "^2.5.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "filesize": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", - "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=" - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "dev": true, - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "follow-redirects": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", - "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", - "dev": true - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=", - "dev": true - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "requires": { - "global-prefix": "^3.0.0" - } - }, - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "dependencies": { - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - } - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" - }, - "gzip-size": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", - "integrity": "sha512-FNHi6mmoHvs1mxZAds4PpdCS6QG8B4C1krxJsMutgxl5t3+GlRTzzI3NEkifXx2pVsOvJdOGSmIgDhQ55FwdPA==", - "dev": true, - "requires": { - "duplexer": "^0.1.1", - "pify": "^4.0.1" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } - } - }, - "handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "requires": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - } - }, - "homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } - }, - "hoopy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", - "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", - "dev": true - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "html-entities": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", - "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", - "dev": true - }, - "html-loader": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-0.5.5.tgz", - "integrity": "sha512-7hIW7YinOYUpo//kSYcPB6dCKoceKLmOwjEMmhIobHuWGDVl0Nwe4l68mdG/Ru0wcUxQjVMEoZpkalZ/SE7zog==", - "dev": true, - "requires": { - "es6-templates": "^0.2.3", - "fastparse": "^1.1.1", - "html-minifier": "^3.5.8", - "loader-utils": "^1.1.0", - "object-assign": "^4.1.1" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "html-minifier": { - "version": "3.5.21", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", - "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", - "dev": true, - "requires": { - "camel-case": "3.0.x", - "clean-css": "4.2.x", - "commander": "2.17.x", - "he": "1.2.x", - "param-case": "2.1.x", - "relateurl": "0.2.x", - "uglify-js": "3.4.x" - } - }, - "html-minifier-terser": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", - "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", - "dev": true, - "requires": { - "camel-case": "^4.1.1", - "clean-css": "^4.2.3", - "commander": "^4.1.1", - "he": "^1.2.0", - "param-case": "^3.0.3", - "relateurl": "^0.2.7", - "terser": "^4.6.3" - }, - "dependencies": { - "camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "requires": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "requires": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - } - } - }, - "html-webpack-injector": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/html-webpack-injector/-/html-webpack-injector-1.1.4.tgz", - "integrity": "sha512-R+HeAYzPeL3dKIr5/a7a2S6R4fy2yHetKiB7cz5rXjwlnU5tghuy58kCBsKA/Qoj94MAgCYwllHmvYqy2nJSdg==", - "dev": true - }, - "html-webpack-plugin": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz", - "integrity": "sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A==", - "dev": true, - "requires": { - "@types/html-minifier-terser": "^5.0.0", - "@types/tapable": "^1.0.5", - "@types/webpack": "^4.41.8", - "html-minifier-terser": "^5.0.1", - "loader-utils": "^1.2.3", - "lodash": "^4.17.20", - "pretty-error": "^2.1.1", - "tapable": "^1.1.3", - "util.promisify": "1.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - } - } - }, - "htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "http-parser-js": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", - "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==", - "dev": true - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "dev": true, - "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true - }, - "husky": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz", - "integrity": "sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "requires": {} - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "dev": true, - "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, - "internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dev": true, - "requires": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - } - }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true - }, - "is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==" - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==" - }, - "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==" - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==" - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, - "is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "dev": true, - "requires": { - "is-path-inside": "^2.1.0" - } - }, - "is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "dev": true, - "requires": { - "path-is-inside": "^1.0.2" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - } - }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==" - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", - "dev": true - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "klona": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz", - "integrity": "sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==", - "dev": true - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, - "lint-staged": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-11.0.0.tgz", - "integrity": "sha512-3rsRIoyaE8IphSUtO1RVTFl1e0SLBtxxUOPBtHxQgBHS5/i6nqvjcUfNioMa4BU9yGnPzbO+xkfLtXtxBpCzjw==", - "dev": true, - "requires": { - "chalk": "^4.1.1", - "cli-truncate": "^2.1.0", - "commander": "^7.2.0", - "cosmiconfig": "^7.0.0", - "debug": "^4.3.1", - "dedent": "^0.7.0", - "enquirer": "^2.3.6", - "execa": "^5.0.0", - "listr2": "^3.8.2", - "log-symbols": "^4.1.0", - "micromatch": "^4.0.4", - "normalize-path": "^3.0.0", - "please-upgrade-node": "^3.2.0", - "string-argv": "0.3.1", - "stringify-object": "^3.3.0" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - } - } - }, - "listr2": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.10.0.tgz", - "integrity": "sha512-eP40ZHihu70sSmqFNbNy2NL1YwImmlMmPh9WO5sLmPDleurMHt3n+SwEWNu2kzKScexZnkyFtc1VI0z/TGlmpw==", - "dev": true, - "requires": { - "cli-truncate": "^2.1.0", - "colorette": "^1.2.2", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rxjs": "^6.6.7", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - } - } - }, - "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "requires": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "loglevel": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", - "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", - "dev": true - }, - "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, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" - }, - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", - "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.31", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", - "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", - "dev": true, - "requires": { - "mime-db": "1.48.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mini-create-react-context": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", - "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", - "requires": { - "@babel/runtime": "^7.12.1", - "tiny-warning": "^1.0.3" - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" - }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, - "requires": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - } - }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", - "dev": true, - "optional": true - }, - "nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", - "dev": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", - "dev": true, - "requires": { - "lower-case": "^1.1.1" - } - }, - "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true - }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - } - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "requires": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "dependencies": { - "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==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "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==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "nth-check": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", - "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", - "dev": true, - "requires": { - "boolbase": "^1.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==" - }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true - }, - "opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "dev": true, - "requires": { - "is-wsl": "^1.1.0" - } - }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "dev": true, - "requires": { - "url-parse": "^1.4.3" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", - "dev": true, - "requires": { - "retry": "^0.12.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dev": true, - "requires": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "param-case": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", - "dev": true, - "requires": { - "no-case": "^2.2.0" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - }, - "dependencies": { - "lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "requires": { - "tslib": "^2.0.3" - } - }, - "no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "tslib": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", - "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", - "dev": true - } - } - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "requires": { - "isarray": "0.0.1" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - }, - "pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==" - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "requires": { - "semver-compare": "^1.0.0" - } - }, - "portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "dev": true, - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.1.tgz", - "integrity": "sha512-9qH0MGjsSm+fjxOi3GnwViL1otfi7qkj+l/WX5gcRGmZNGsIcqc+A5fBkE6PUobEQK4APqYVaES+B3Uti98TCw==", - "dev": true, - "requires": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map-js": "^0.6.2" - } - }, - "postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "requires": {} - }, - "postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "dev": true, - "requires": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.4" - } - }, - "postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "requires": { - "icss-utils": "^5.0.0" - } - }, - "postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", - "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", - "dev": true - }, - "prettier": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz", - "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==", - "dev": true - }, - "pretty-error": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", - "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", - "dev": true, - "requires": { - "lodash": "^4.17.20", - "renderkid": "^2.0.4" - } - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - } - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true - }, - "query-string": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.0.0.tgz", - "integrity": "sha512-Iy7moLybliR5ZgrK/1R3vjrXq03S13Vz4Rbm5Jg3EFq1LUmQppto0qtXz4vqZ386MSRjZgnTSZ9QC+NZOSd/XA==", - "requires": { - "decode-uri-component": "^0.2.0", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - } - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dev": true, - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "react-datepicker": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.25.0.tgz", - "integrity": "sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==", - "requires": { - "@popperjs/core": "^2.11.8", - "classnames": "^2.2.6", - "date-fns": "^2.30.0", - "prop-types": "^15.7.2", - "react-onclickoutside": "^6.13.0", - "react-popper": "^2.3.0" - } - }, - "react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - } - }, - "react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" - }, - "react-input-autosize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz", - "integrity": "sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==", - "requires": { - "prop-types": "^15.5.8" - } - }, - "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==" - }, - "react-onclickoutside": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", - "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==", - "requires": {} - }, - "react-popper": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", - "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", - "requires": { - "react-fast-compare": "^3.0.1", - "warning": "^4.0.2" - } - }, - "react-redux": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz", - "integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==", - "requires": { - "@babel/runtime": "^7.12.1", - "@types/react-redux": "^7.1.16", - "hoist-non-react-statics": "^3.3.2", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.13.1" - } - }, - "react-router": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", - "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", - "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "mini-create-react-context": "^0.4.0", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - } - }, - "react-router-breadcrumbs-hoc": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/react-router-breadcrumbs-hoc/-/react-router-breadcrumbs-hoc-4.1.0.tgz", - "integrity": "sha512-HZn352JkMzi/1Mp9H6v78V9f7sjnWRf/dXeFVLDDbxEUK7QJCj1JUBJuEHC+B3tq1+uRCASNm3ofEOaG33tAKw==", - "requires": {} - }, - "react-router-dom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", - "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", - "requires": { - "@babel/runtime": "^7.1.2", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.2.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - } - }, - "react-select": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/react-select/-/react-select-4.3.1.tgz", - "integrity": "sha512-HBBd0dYwkF5aZk1zP81Wx5UsLIIT2lSvAY2JiJo199LjoLHoivjn9//KsmvQMEFGNhe58xyuOITjfxKCcGc62Q==", - "requires": { - "@babel/runtime": "^7.12.0", - "@emotion/cache": "^11.4.0", - "@emotion/react": "^11.1.1", - "memoize-one": "^5.0.0", - "prop-types": "^15.6.0", - "react-input-autosize": "^3.0.0", - "react-transition-group": "^4.3.0" - } - }, - "react-toggle": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.1.2.tgz", - "integrity": "sha512-4Ohw31TuYQdhWfA6qlKafeXx3IOH7t4ZHhmRdwsm1fQREwOBGxJT+I22sgHqR/w8JRdk+AeMCJXPImEFSrNXow==", - "requires": { - "classnames": "^2.2.5" - } - }, - "react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "requires": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "dependencies": { - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "requires": { - "pify": "^3.0.0" - } - } - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "recast": { - "version": "0.11.23", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", - "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", - "dev": true, - "requires": { - "ast-types": "0.9.6", - "esprima": "~3.1.0", - "private": "~0.1.5", - "source-map": "~0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "redux": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.1.0.tgz", - "integrity": "sha512-uI2dQN43zqLWCt6B/BMGRMY6db7TTY4qeHHfGeKb3EOhmOKjU3KdWvNLJyqaHRksv/ErdNH7cFZWg9jXtewy4g==", - "requires": { - "@babel/runtime": "^7.9.2" - } - }, - "redux-devtools-extension": { - "version": "2.13.9", - "resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz", - "integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==", - "requires": {} - }, - "regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", - "dev": true - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "renderkid": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.6.tgz", - "integrity": "sha512-GIis2GBr/ho0pFNf57D4XM4+PgnQuTii0WCPjEZmZfKivzUfGuRdjN2aQYtYMiNggHmNyBve+thFnNR1iBRcKg==", - "dev": true, - "requires": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.0" - } - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - } - } - }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "dependencies": { - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "dev": true, - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "requires": { - "aproba": "^1.1.1" - } - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sass": { - "version": "1.34.1", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.34.1.tgz", - "integrity": "sha512-scLA7EIZM+MmYlej6sdVr0HRbZX5caX5ofDT9asWnUJj21oqgsC+1LuNfm0eg+vM0fCTZHhwImTiCU0sx9h9CQ==", - "dev": true, - "requires": { - "chokidar": ">=3.0.0 <4.0.0" - } - }, - "sass-loader": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.2.0.tgz", - "integrity": "sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw==", - "dev": true, - "requires": { - "klona": "^2.0.4", - "loader-utils": "^2.0.0", - "neo-async": "^2.6.2", - "schema-utils": "^3.0.0", - "semver": "^7.3.2" - } - }, - "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", - "dev": true - }, - "selfsigned": { - "version": "1.10.11", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz", - "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==", - "dev": true, - "requires": { - "node-forge": "^0.10.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", - "dev": true - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } - } - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - } - } - }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - } - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==" - }, - "shortid": { - "version": "2.2.16", - "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.16.tgz", - "integrity": "sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==", - "requires": { - "nanoid": "^2.1.0" - }, - "dependencies": { - "nanoid": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz", - "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==" - } - } - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "sockjs": { - "version": "0.3.21", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", - "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", - "dev": true, - "requires": { - "faye-websocket": "^0.11.3", - "uuid": "^3.4.0", - "websocket-driver": "^0.7.4" - } - }, - "sockjs-client": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.1.tgz", - "integrity": "sha512-VnVAb663fosipI/m6pqRXakEOw7nvd7TUgdr3PlR/8V2I95QIdwT8L4nMxhyU8SmDBHYXU1TOElaKOmKLfYzeQ==", - "dev": true, - "requires": { - "debug": "^3.2.6", - "eventsource": "^1.0.7", - "faye-websocket": "^0.11.3", - "inherits": "^2.0.4", - "json3": "^3.3.3", - "url-parse": "^1.5.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-js": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", - "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz", - "integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==" - }, - "spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - } - }, - "spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "ssri": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", - "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, - "stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "string-argv": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", - "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string.prototype.padend": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.2.tgz", - "integrity": "sha512-/AQFLdYvePENU3W5rgurfWSMU6n+Ww8n/3cUt7E+vPBB/D7YDG8x+qjoFs4M/alR2bW7Qg6xMjVwWUOvuQ0XpQ==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "dev": true, - "requires": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "style-loader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", - "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", - "dev": true, - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - } - }, - "stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true - }, - "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", - "dev": true, - "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "dependencies": { - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, - "timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "requires": { - "setimmediate": "^1.0.4" - } - }, - "tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true - }, - "tryer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", - "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", - "dev": true - }, - "ts-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.3.0.tgz", - "integrity": "sha512-MgGly4I6cStsJy27ViE32UoqxPTN9Xly4anxxVyaIWR+9BGxboV4EyJBGfR3RePV7Ksjj3rHmPZJeIt+7o4Vag==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^2.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "typescript": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz", - "integrity": "sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==" - }, - "uglify-js": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", - "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", - "dev": true, - "requires": { - "commander": "~2.19.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", - "dev": true - } - } - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - } - } - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true - }, - "upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, - "url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "watchpack": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", - "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", - "dev": true, - "requires": { - "chokidar": "^3.4.1", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0", - "watchpack-chokidar2": "^2.0.1" - } - }, - "watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "dev": true, - "optional": true, - "requires": { - "chokidar": "^2.1.8" - }, - "dependencies": { - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "optional": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "optional": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "optional": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "optional": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "optional": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "optional": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "optional": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "requires": { - "minimalistic-assert": "^1.0.0" - } - }, - "webpack": { - "version": "4.46.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", - "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.5.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" - }, - "dependencies": { - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "webpack-bundle-analyzer": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.9.0.tgz", - "integrity": "sha512-Ob8amZfCm3rMB1ScjQVlbYYUEJyEjdEtQ92jqiFUYt5VkEeO2v5UMbv49P/gnmCZm3A6yaFQzCBvpZqN4MUsdA==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1", - "bfj": "^6.1.1", - "chalk": "^2.4.1", - "commander": "^2.18.0", - "ejs": "^2.6.1", - "express": "^4.16.3", - "filesize": "^3.6.1", - "gzip-size": "^5.0.0", - "lodash": "^4.17.19", - "mkdirp": "^0.5.1", - "opener": "^1.5.1", - "ws": "^6.0.0" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "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, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "webpack-cli": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz", - "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "cross-spawn": "^6.0.5", - "enhanced-resolve": "^4.1.1", - "findup-sync": "^3.0.0", - "global-modules": "^2.0.0", - "import-local": "^2.0.0", - "interpret": "^1.4.0", - "loader-utils": "^1.4.0", - "supports-color": "^6.1.0", - "v8-compile-cache": "^2.1.1", - "yargs": "^13.3.2" - }, - "dependencies": { - "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, - "requires": { - "color-convert": "^1.9.0" - } - }, - "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, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "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, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "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, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "webpack-dev-middleware": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", - "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", - "dev": true, - "requires": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "dependencies": { - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "mime": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", - "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", - "dev": true - } - } - }, - "webpack-dev-server": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz", - "integrity": "sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ==", - "dev": true, - "requires": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.3.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.8", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.26", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.8", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "^0.3.21", - "sockjs-client": "^1.5.0", - "spdy": "^4.0.2", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "^13.3.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - } - } - }, - "webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dev": true, - "requires": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true - } - } - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "requires": { - "errno": "~0.1.7" - } - }, - "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, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } -} From 4e58eec2fa1fce97969012c21ad051ad266ac2a6 Mon Sep 17 00:00:00 2001 From: SantarinX Date: Sun, 12 May 2024 03:19:05 -0400 Subject: [PATCH 133/400] remove NODE_OPTIONS=--openssl-legacy-provider because webpack 5 already fix this issue --- devU-client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-client/package.json b/devU-client/package.json index 711c542e..76a4c0b8 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -1,7 +1,7 @@ { "scripts": { "update-shared": "npm update devu-shared-modules", - "local": "cross-env NODE_OPTIONS=--openssl-legacy-provider NODE_ENV=local webpack-dev-server --mode development", + "local": "cross-env NODE_ENV=local webpack-dev-server --mode development", "start": "cross-env NODE_ENV=development webpack-dev-server -d --open --mode development", "prod": "cross-env NODE_ENV=production webpack-dev-server -d --open --mode development", "build": "cross-env NODE_ENV=production webpack -p --mode=production", From 69e3cd882de6711b96ad14ac8a98b450d3369862 Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Thu, 5 Sep 2024 14:35:32 -0400 Subject: [PATCH 134/400] remove package-lock.json copy from api and client Dockerfiles -These files were removed from the repo, so it causes file not found errors --- api.Dockerfile | 2 +- client.Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api.Dockerfile b/api.Dockerfile index efd6b72f..2068a951 100644 --- a/api.Dockerfile +++ b/api.Dockerfile @@ -12,7 +12,7 @@ FROM node:20 WORKDIR /app -COPY ./devU-api/package.json ./devU-api/package-lock.json ./ +COPY ./devU-api/package.json ./ RUN npm install diff --git a/client.Dockerfile b/client.Dockerfile index eec9df6e..e74eeddb 100644 --- a/client.Dockerfile +++ b/client.Dockerfile @@ -12,7 +12,7 @@ FROM node:16 WORKDIR /app -COPY ./devU-client/package.json ./devU-client/package-lock.json ./ +COPY ./devU-client/package.json ./ RUN npm install From 27e28bacaab5cd36759237688db07623d8332de1 Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Thu, 5 Sep 2024 16:07:42 -0400 Subject: [PATCH 135/400] WebPack deprecated hash. Changed to fullhash --- devU-api/package.json | 4 ++-- devU-client/webpack.config.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devU-api/package.json b/devU-api/package.json index 6c206ca3..19003969 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -1,7 +1,7 @@ { "name": "devu-api", "version": "1.0.0", - "description": "Auto-grading 4.0 API", + "description": "DevU API", "scripts": { "start": "ts-node-dev src/index.ts", "update-shared": "npm update devu-shared-modules", @@ -78,6 +78,6 @@ "regex-parser": "^2.3.0", "swagger-jsdoc": "^6.1.0", "swagger-ui-express": "^4.1.6", - "typeorm": "0.3.20" + "typeorm": "^0.3.20" } } diff --git a/devU-client/webpack.config.js b/devU-client/webpack.config.js index ac5c6e01..40955bec 100644 --- a/devU-client/webpack.config.js +++ b/devU-client/webpack.config.js @@ -85,7 +85,7 @@ module.exports = () => { }, output: { path: path.join(__dirname, `/dist/${env}`), - filename: "[name]-[hash:5].js", + filename: "[name]-[fullhash:5].js", publicPath: "/", }, optimization: { From ca60f2496a4c3f1ea7c51eb297744dc78a3ba7a2 Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Thu, 5 Sep 2024 16:23:39 -0400 Subject: [PATCH 136/400] TS fixes on the client -add "any" type to errors -Update fontawesome versions -Use isNaN instead of === NaN --- devU-client/package.json | 12 ++++++------ .../pages/assignments/assignmentDetailPage.tsx | 2 +- .../pages/gradebook/gradebookInstructorPage.tsx | 2 +- .../pages/gradebook/gradebookStudentPage.tsx | 2 +- .../src/components/pages/homePage/homePage.tsx | 2 +- .../pages/submissions/submissionDetailPage.tsx | 2 +- .../pages/submissions/submissionFeedbackPage.tsx | 2 +- devU-client/src/services/validation.service.ts | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/devU-client/package.json b/devU-client/package.json index 76a4c0b8..f8f029f3 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -23,12 +23,12 @@ }, "private": true, "dependencies": { - "@emotion/react": "^11.11.4", - "@emotion/styled": "^11.11.5", - "@fontsource/roboto": "^5.0.12", - "@fortawesome/free-regular-svg-icons": "^5.15.3", - "@fortawesome/free-solid-svg-icons": "^5.15.3", - "@fortawesome/react-fontawesome": "^0.1.14", + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "@fontsource/roboto": "^5.0.14", + "@fortawesome/free-regular-svg-icons": "^6.6.0", + "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@fortawesome/react-fontawesome": "^0.2.2", "@mui/material": "^5.15.15", "color-hash": "^2.0.1", "devu-shared-modules": "./devu-shared-modules", diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 8f7f39c8..f2420559 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -131,7 +131,7 @@ const AssignmentDetailPage = () => { setAlert({ autoDelete: true, type: 'success', message: 'Submission Graded' }) await fetchData() - } catch (err) { + } catch (err: any) { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message setAlert({ autoDelete: false, type: 'error', message }) } finally { diff --git a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx index fbb4dae0..44575ab3 100644 --- a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx @@ -94,7 +94,7 @@ const GradebookInstructorPage = () => { const assignmentScores = await RequestService.get( `/api/course/${courseId}/assignment-scores` ) setAssignmentScores(assignmentScores) - } catch (error) { + } catch (error: any) { setError(error) } finally { setLoading(false) diff --git a/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx b/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx index 46f9654b..2a9d9f84 100644 --- a/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx @@ -86,7 +86,7 @@ const GradebookStudentPage = () => { const categories = [... new Set(assignments.map(a => a.categoryName))] //Get all unique categories from assignments setCategories(categories) - } catch (error) { + } catch (error: any) { setError(error) } finally { setLoading(false) diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index b549b6b1..255cc51b 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -50,7 +50,7 @@ const HomePage = () => { setEnrollCourses(enrolledCourses) setInstructorCourses(instructorCourses) - } catch (error) { + } catch (error: any) { setError(error) } finally { setLoading(false) diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx index c941cc8b..c2bff140 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx @@ -50,7 +50,7 @@ const SubmissionDetailPage = () => { const assignmentProblems = await RequestService.get(`/api/course/${courseId}/assignment/${assignment.id}/assignment-problems`) setAssignmentProblems(assignmentProblems) - } catch (error) { + } catch (error: any) { setError(error) } finally { setLoading(false) diff --git a/devU-client/src/components/pages/submissions/submissionFeedbackPage.tsx b/devU-client/src/components/pages/submissions/submissionFeedbackPage.tsx index 5543e54f..0851e743 100644 --- a/devU-client/src/components/pages/submissions/submissionFeedbackPage.tsx +++ b/devU-client/src/components/pages/submissions/submissionFeedbackPage.tsx @@ -31,7 +31,7 @@ const SubmissionFeedbackPage = () => { const assignmentProblems = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems`) setAssignmentProblems(assignmentProblems) - } catch (error) { + } catch (error: any) { setError(error) } finally { setLoading(false) diff --git a/devU-client/src/services/validation.service.ts b/devU-client/src/services/validation.service.ts index 7e1b0809..c6f3dd90 100644 --- a/devU-client/src/services/validation.service.ts +++ b/devU-client/src/services/validation.service.ts @@ -30,7 +30,7 @@ function stringValidator(value: any): string { } function numberValidator(value: any): string { - if (parseInt(value) === NaN) return NOT_A_NUMBER + if (isNaN(parseInt(value))) return NOT_A_NUMBER return '' } @@ -42,7 +42,7 @@ function booleanValidator(value: any): string { function datetimeValidator(value: any): string { if (!value) return REQUIRED_ERROR if (typeof value === 'number') return INVALID_DATE - if (Date.parse(value) === NaN) return INVALID_DATE + if (isNaN(Date.parse(value))) return INVALID_DATE return '' } From 94d4ad63137613921c85ee02e968ad0d22da9561 Mon Sep 17 00:00:00 2001 From: epicseven-cups Date: Sat, 7 Sep 2024 21:37:13 -0400 Subject: [PATCH 137/400] fixed race conditions that exist on generating the configs --- api.Dockerfile | 10 ++++++++++ docker-compose.yml => compose.yml | 15 +-------------- devU-api/scripts/Dockerfile | 12 ------------ 3 files changed, 11 insertions(+), 26 deletions(-) rename docker-compose.yml => compose.yml (87%) delete mode 100644 devU-api/scripts/Dockerfile diff --git a/api.Dockerfile b/api.Dockerfile index 2068a951..4a1bc307 100644 --- a/api.Dockerfile +++ b/api.Dockerfile @@ -1,3 +1,12 @@ +FROM python:alpine AS config + +WORKDIR /stage +COPY devU-api/config ./config +COPY devU-api/scripts/generateConfig.sh ./generateConfig.sh +RUN apk add --no-cache bash jq openssl \ + && pip install yq +RUN ./generateConfig.sh ./default.yml + FROM node:20 as module_builder WORKDIR /tmp @@ -18,6 +27,7 @@ RUN npm install COPY ./devU-api . +COPY --from=config /stage/default.yml ./config/default.yml COPY --from=module_builder /tmp/devu-shared-modules ./devu-shared-modules ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.2.1/wait /wait diff --git a/docker-compose.yml b/compose.yml similarity index 87% rename from docker-compose.yml rename to compose.yml index 97fa563c..7196d098 100644 --- a/docker-compose.yml +++ b/compose.yml @@ -11,14 +11,6 @@ services: - '5432:5432' expose: - '5432' - config: - # Generates a config file for the API - build: - context: devU-api/scripts - command: "./generateConfig.sh config/default.yml" - image: ubautograding/devtools - volumes: - - ./devU-api/config:/config api: # Runs the API container_name: api @@ -29,11 +21,6 @@ services: TANGO_KEY: devutangokey # TODO: load in from env file. for now this is defined in tango section below WAIT_HOSTS: db:5432 dev: 0 # value here is irrelevant; just here to make sure dev env exists - depends_on: - db: - condition: service_started - config: - condition: service_completed_successfully ports: - '3001:3001' profiles: @@ -113,4 +100,4 @@ services: # volumes: # - ./ssl/certbot/conf:/etc/letsencrypt # - ./ssl/certbot/www:/var/www/certbot - restart: unless-stopped + restart: unless-stopped \ No newline at end of file diff --git a/devU-api/scripts/Dockerfile b/devU-api/scripts/Dockerfile deleted file mode 100644 index c2191d27..00000000 --- a/devU-api/scripts/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -# This is the Dockerfile for ubautograding/devtools and can be extended as -# necessary. Currently it only contains the generateConfig utility. - -# GenerateConfig can be used as follows: -#docker run -v $(pwd)/config:/config --rm ubautograding/devtools /generateConfig.sh config/default.yml - -FROM docker.io/python:alpine - -RUN apk add --no-cache bash jq openssl \ - && pip install yq - -COPY ./generateConfig.sh /generateConfig.sh \ No newline at end of file From d66c1472e6882fc5ffc4cceaee5d0e1ad446f6fb Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Fri, 13 Sep 2024 16:31:01 -0400 Subject: [PATCH 138/400] changed input color in textField component to change based on light/dark mode #1 --- .../components/shared/inputs/textField.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 4a4c4a7f..55910511 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -1,7 +1,10 @@ import React from 'react' import {TextField as MuiTextField} from '@mui/material' +import { SxProps, Theme } from '@mui/material' + import styles from './textField.scss' +import { getCssVariables } from 'utils/theme.utils' type Props = { type?: 'text' | 'email' | 'password' | 'search' @@ -16,6 +19,7 @@ type Props = { invalidated?: boolean helpText?: string variant?: 'outlined' | 'standard' | 'filled' + sx?: SxProps; } const TextField = ({ @@ -29,11 +33,15 @@ const TextField = ({ value, invalidated, helpText, - variant = 'outlined' + variant = 'outlined', + sx }: Props) => { const handleChange = (e: React.ChangeEvent) => { if (onChange) onChange(e.target.value, e) } + + const cssVariables = getCssVariables(); + return (
+ onChange={handleChange} + sx={{ + ...sx, + "& .MuiOutlinedInput-input" : { + color: cssVariables.textColor + } + }} + />
) } From 997d7038a9b986fcb5018200b0b483dd9ce63878 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Fri, 13 Sep 2024 16:47:34 -0400 Subject: [PATCH 139/400] text field label color changed, doesn't work for theme toggle #1 --- .../src/components/shared/inputs/textField.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 55910511..b710de11 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -40,7 +40,7 @@ const TextField = ({ if (onChange) onChange(e.target.value, e) } - const cssVariables = getCssVariables(); + const colors = getCssVariables(); return (
@@ -58,8 +58,14 @@ const TextField = ({ sx={{ ...sx, "& .MuiOutlinedInput-input" : { - color: cssVariables.textColor - } + color: colors.textColor + }, + "& .MuiInputLabel-outlined" : { + color: colors.textColor + }, + "& .MuiOutlinedInput-notchedOutline" : { + color: colors.textColor + }, }} />
From 29561efd9d92f266c95f7c96105205f10497a435 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Sat, 14 Sep 2024 16:55:05 -0400 Subject: [PATCH 140/400] non-disabled text fields change color on theme toggle #1 --- .../components/shared/inputs/textField.tsx | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index b710de11..3b2d059d 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -1,10 +1,10 @@ -import React from 'react' +import React, { useState, useEffect } from 'react' import {TextField as MuiTextField} from '@mui/material' import { SxProps, Theme } from '@mui/material' -import styles from './textField.scss' import { getCssVariables } from 'utils/theme.utils' +import styles from './textField.scss' type Props = { type?: 'text' | 'email' | 'password' | 'search' @@ -36,11 +36,23 @@ const TextField = ({ variant = 'outlined', sx }: Props) => { + const [theme, setTheme] = useState(getCssVariables()) + + // Needs a custom observer to force an update when the css variables change + // Custom observer will update the theme variables when the bodies classes change + useEffect(() => { + const observer = new MutationObserver(() => setTheme(getCssVariables())) + + observer.observe(document.body, { attributes: true }) + + return () => observer.disconnect() + }) + const handleChange = (e: React.ChangeEvent) => { if (onChange) onChange(e.target.value, e) } - const colors = getCssVariables(); + const { textColor } = theme return (
@@ -57,15 +69,19 @@ const TextField = ({ onChange={handleChange} sx={{ ...sx, + // input field text "& .MuiOutlinedInput-input" : { - color: colors.textColor + color: textColor }, + // label text "& .MuiInputLabel-outlined" : { - color: colors.textColor + color: textColor }, + // border "& .MuiOutlinedInput-notchedOutline" : { - color: colors.textColor + borderColor: textColor }, + }} />
From 1129838879df3474405e345198eeec39df236d88 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Mon, 16 Sep 2024 20:51:34 -0400 Subject: [PATCH 141/400] Made additions to the handledrop function in courseDetail to confirm with the user that want to drop the course or not --- devU-client/src/assets/variables.scss | 2 +- .../pages/courses/courseDetailPage.tsx | 17 +++++++++++------ .../src/components/pages/homePage/homePage.tsx | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/devU-client/src/assets/variables.scss b/devU-client/src/assets/variables.scss index faafef3e..da44a0ac 100644 --- a/devU-client/src/assets/variables.scss +++ b/devU-client/src/assets/variables.scss @@ -19,7 +19,7 @@ $secondary-darker: var(--secondary-darker); $list-item-background: var(--list-item-background); $list-item-background-hover: var(--list-item-background-hover); -$list-item-subtext: var(--list-item-subtext); +$list-item-subtext: var(--list-item-subtext); $list-simple-item-background: var(--list-simple-item-background); $list-simple-item-background-hover: var(--list-simple-item-background-hover); diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 80331b25..2f443085 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -50,17 +50,22 @@ const CourseDetailPage = () => { } - const handleDropCourse = () => { - - RequestService.delete(`/api/course/${courseId}/user-courses`).then(() => { - setAlert({autoDelete: true, type: 'success', message: 'Course Dropped'}) + //confirmation to drop course or not + var confirm = window.confirm("Are you sure you want to drop?"); + if (confirm) + { + RequestService.delete(`/api/course/${courseId}/user-courses`).then(() => { + + setAlert({autoDelete: true, type: 'success', message: 'Course Dropped'}) history.push('/courses') + + }).catch((error: Error) => { const message = error.message - setAlert({autoDelete: false, type: 'error', message}) - }) + setAlert({autoDelete: false, type: 'error', message}) }) + } } useEffect(() => { diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 255cc51b..6b3602bc 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -93,7 +93,7 @@ const HomePage = () => { ))} - {pastCourses.length === 0 &&

You do not have completed courses yet

} + {pastCourses.length === 0 &&

No upcoming courses

} From b289ed1f49c5418d177c9a11db5216f76865b822 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 17 Sep 2024 14:35:28 -0400 Subject: [PATCH 142/400] changes done in course preview page for task 1 --- devU-client/src/components/pages/courses/coursePreviewPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/devU-client/src/components/pages/courses/coursePreviewPage.tsx b/devU-client/src/components/pages/courses/coursePreviewPage.tsx index 9aa40aca..a93b6b0f 100644 --- a/devU-client/src/components/pages/courses/coursePreviewPage.tsx +++ b/devU-client/src/components/pages/courses/coursePreviewPage.tsx @@ -89,6 +89,7 @@ const CoursePreviewPage = () => {

{course?.name}

{course?.number}

{course?.semester}

+

Instructor: Jesse Hartloff

) From 06cfe621e686be640081eebbf156393e8b498639 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Tue, 17 Sep 2024 21:48:58 -0400 Subject: [PATCH 143/400] Added confirmation popup in the coursedetails to confirm the action of the course being dropped --- devU-client/src/components/pages/courses/courseDetailPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 2f443085..d5ca19d3 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -59,8 +59,6 @@ const CourseDetailPage = () => { setAlert({autoDelete: true, type: 'success', message: 'Course Dropped'}) history.push('/courses') - - }).catch((error: Error) => { const message = error.message From ee9d8f77c5717bd2e9079fb33cfcf9c6f7050da1 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Sat, 21 Sep 2024 16:58:35 -0400 Subject: [PATCH 144/400] Removed Role Toggle Removed Role Toggle component and added a new Role Dispatcher in its place that acts identical to the role toggle but stays hidden. --- .../src/components/misc/globalToolbar.tsx | 4 +- .../listPages/courses/coursesListPage.tsx | 9 ++--- .../src/components/utils/roleDispatcher.tsx | 38 +++++++++++++++++++ .../src/components/utils/roleToggle.tsx | 1 - 4 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 devU-client/src/components/utils/roleDispatcher.tsx diff --git a/devU-client/src/components/misc/globalToolbar.tsx b/devU-client/src/components/misc/globalToolbar.tsx index 9f477269..92083ac2 100644 --- a/devU-client/src/components/misc/globalToolbar.tsx +++ b/devU-client/src/components/misc/globalToolbar.tsx @@ -7,7 +7,7 @@ import UserOptionsDropdown from 'components/utils/userOptionsDropdown' import styles from './globalToolbar.scss' -import RoleToggle from '../utils/roleToggle' +import RoleDispatcher from '../utils/roleDispatcher' const GlobalToolbar = () => { @@ -24,7 +24,7 @@ const GlobalToolbar = () => { {/* Turns into a sidebar via css on mobile */}
- + Courses diff --git a/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx b/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx index 1f64db33..b1e0026c 100644 --- a/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx +++ b/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx @@ -7,7 +7,7 @@ import ErrorPage from '../../errorPage/errorPage' import RequestService from 'services/request.service' import styles from './coursesListPage.scss' import CourseListItem from "../../../listItems/courseListItem"; -import {useAppSelector} from "../../../../redux/hooks"; +// import {useAppSelector} from "../../../../redux/hooks"; import Button from "@mui/material/Button"; import {useHistory} from "react-router-dom"; @@ -25,7 +25,6 @@ const UserCoursesListPage = () => { const [error, setError] = useState(null) const [userCourses, setUserCourses] = useState(new Array()) const [filter, setFilter] = useState(false ) - const role = useAppSelector((store) => store.roleMode) const history = useHistory() //Temporary place to store state for all courses @@ -74,10 +73,10 @@ const UserCoursesListPage = () => {

All Courses

- {role.isInstructor() && - } + }}>Add Course +
{ + const dispatch = useDispatch(); + const {courseId} = useParams<{ courseId: string }>() + + useEffect(() => { + const path = window.location.pathname + const hasCoursePath = path.includes('/course/') + + if (hasCoursePath) { + RequestService.get(`/api/course/${courseId}/user-courses/users`) + .then((response) => { + const userCourses: UserCourse = response; + if (userCourses.role === 'instructor') { + dispatch(updateUserRole('Instructor')); + } else { + dispatch(updateUserRole('Student')); + } + }) + .catch(error => { + console.error(error) + }) + } + }, []); + + return ( + + ) +} + +export default RoleDispatcher \ No newline at end of file diff --git a/devU-client/src/components/utils/roleToggle.tsx b/devU-client/src/components/utils/roleToggle.tsx index f8bbc40c..d99b55c9 100644 --- a/devU-client/src/components/utils/roleToggle.tsx +++ b/devU-client/src/components/utils/roleToggle.tsx @@ -35,7 +35,6 @@ const RoleToggle = () => { console.error(error) }) } - }, []); const handleToggle = () => { From 6b3fdd29fbca45557c176baf54333fc2e4546dce Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Tue, 24 Sep 2024 13:25:54 -0400 Subject: [PATCH 145/400] Deleted the RoleToggle file Removed the old RoleToggle file as it was replaced by the RoleDispatcher. --- .../src/components/utils/roleToggle.tsx | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 devU-client/src/components/utils/roleToggle.tsx diff --git a/devU-client/src/components/utils/roleToggle.tsx b/devU-client/src/components/utils/roleToggle.tsx deleted file mode 100644 index d99b55c9..00000000 --- a/devU-client/src/components/utils/roleToggle.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import Switch from '@mui/material/Switch' -import FormControlLabel from '@mui/material/FormControlLabel' -import {updateUserRole} from '../../redux/role.redux' -import {useDispatch, useSelector} from 'react-redux' -import {RootState} from '../../redux/store' -import React, {useEffect} from 'react' -import {useParams} from "react-router-dom"; -import RequestService from 'services/request.service' -import {UserCourse} from "devu-shared-modules"; - -const RoleToggle = () => { - const dispatch = useDispatch(); - const userRole = useSelector((state: RootState) => state.roleMode.userRole); - const [hasCoursePath, setHasCoursePath] = React.useState(false) - const {courseId} = useParams<{ courseId: string }>() - - useEffect(() => { - const path = window.location.pathname - const hasCoursePath = path.includes('/course/') - setHasCoursePath(hasCoursePath) - //TODO: This is a temporary solution to get the user role for the course. The whole toggle switch should be removed in the future - if (hasCoursePath) { - RequestService.get(`/api/course/${courseId}/user-courses/users`) - .then((response) => { - const userCourses: UserCourse = response; - if (userCourses.role === 'instructor') { - handleToggle() - dispatch(updateUserRole('Instructor')); - } else { - handleToggle() - dispatch(updateUserRole('Student')); - } - }) - .catch(error => { - console.error(error) - }) - } - }, []); - - const handleToggle = () => { - const newRole = userRole === 'Student' ? 'Instructor' : 'Student'; - dispatch(updateUserRole(newRole)); - }; - - - return ( - } - /> - ) -} - -export default RoleToggle From ab9d238c81a83e4a4e212f12fed7048143641692 Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 26 Sep 2024 13:26:35 -0400 Subject: [PATCH 146/400] intial ci/cd --- .github/workflows/beta.yml | 37 +++++++++++++++++ .github/workflows/release.yml | 75 +++++++++++++++++++++++++++++++++++ .releaserc.json | 14 +++++++ 3 files changed, 126 insertions(+) create mode 100644 .github/workflows/beta.yml create mode 100644 .github/workflows/release.yml create mode 100644 .releaserc.json diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml new file mode 100644 index 00000000..66b1acb7 --- /dev/null +++ b/.github/workflows/beta.yml @@ -0,0 +1,37 @@ +# Builds DevU images on develop +# tagged as beta + +name: Build DevU beta +on: + push: + branches: + - develop + +jobs: + build-docker: + runs-on: ubuntu-latest + permissions: + contents: write # to be able to publish a GitHub release + issues: write # to be able to comment on released issues + pull-requests: write # to be able to comment on released pull requests + packages: write # to be able to publish docker image packages + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # todo run tests + + # docker image build + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: build beta tagged image + run: docker build . -t ghcr.io/${{ github.repository }}:beta + + - name: push + run: docker push ghcr.io/${{ github.repository }}:beta diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..21e6fcc9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,75 @@ +# Builds DevU images for release +# tagged as latest and version number + +name: Release +on: + push: + branches: + - release + +jobs: + tag-release: + name: tag-release + runs-on: ubuntu-latest + permissions: + contents: write # to be able to publish a GitHub release + issues: write # to be able to comment on released issues + pull-requests: write # to be able to comment on released pull requests + packages: write # to be able to publish docker image packages + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: "lts/*" + - name: install plugins + run: npm install @semantic-release/git @semantic-release/changelog -D + + - name: tag version number release based on commits + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: npx semantic-release + + build-docker: + needs: + - tag-release + runs-on: ubuntu-latest + permissions: + contents: write # to be able to publish a GitHub release + issues: write # to be able to comment on released issues + pull-requests: write # to be able to comment on released pull requests + packages: write # to be able to publish docker images + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Get version tag from git history + id: tagName + uses: "WyriHaximus/github-action-get-previous-tag@v1" + + # todo run tests + + # docker image build + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: build version tagged image + run: docker build . -t ghcr.io/${{ github.repository }}:${{ steps.tagName.outputs.tag }} + + - name: push version tagged + run: docker push ghcr.io/${{ github.repository }}:${{ steps.tagName.outputs.tag }} + + - name: build latest tagged image + run: docker build . -t ghcr.io/${{ github.repository }}:latest + + - name: push latest + run: docker push ghcr.io/${{ github.repository }}:latest diff --git a/.releaserc.json b/.releaserc.json new file mode 100644 index 00000000..d529aa4f --- /dev/null +++ b/.releaserc.json @@ -0,0 +1,14 @@ +{ + "branches": [ + { + "name": "release" + } + ], + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/changelog", + "@semantic-release/github", + "@semantic-release/git" + ] +} \ No newline at end of file From 5faa8a5980168bfbca93a51bee771bbd274a69a1 Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 26 Sep 2024 13:58:39 -0400 Subject: [PATCH 147/400] refactored api docker file --- api.Dockerfile | 30 +++++++++++++++++++----------- devU-api/src/environment.ts | 5 +++-- devU-api/src/index.ts | 2 ++ 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/api.Dockerfile b/api.Dockerfile index 4a1bc307..b69771a1 100644 --- a/api.Dockerfile +++ b/api.Dockerfile @@ -1,13 +1,4 @@ -FROM python:alpine AS config - -WORKDIR /stage -COPY devU-api/config ./config -COPY devU-api/scripts/generateConfig.sh ./generateConfig.sh -RUN apk add --no-cache bash jq openssl \ - && pip install yq -RUN ./generateConfig.sh ./default.yml - -FROM node:20 as module_builder +FROM node:20 AS module_builder WORKDIR /tmp @@ -17,6 +8,19 @@ RUN npm install && \ npm run clean-directory && \ npm run build-docker +FROM docker.io/python:alpine AS config-builder + +WORKDIR /config + +RUN apk add --no-cache bash jq openssl \ + && pip install yq + +COPY devU-api/scripts/ . + +COPY devU-api/config/ ./config + +RUN ./generateConfig.sh default.yml + FROM node:20 WORKDIR /app @@ -27,9 +31,13 @@ RUN npm install COPY ./devU-api . -COPY --from=config /stage/default.yml ./config/default.yml +COPY --from=config-builder /config/default.yml ./config/default.yml + COPY --from=module_builder /tmp/devu-shared-modules ./devu-shared-modules +# Indicate that the api is running in docker; value here is irrelevant +ENV dev=0 + ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.2.1/wait /wait RUN chmod +x /wait diff --git a/devU-api/src/environment.ts b/devU-api/src/environment.ts index d287dddc..cde24ba0 100644 --- a/devU-api/src/environment.ts +++ b/devU-api/src/environment.ts @@ -67,7 +67,8 @@ const environment = { dbUsername: (load('database.username') || 'typescript_user') as string, dbPassword: (load('database.password') || 'password') as string, database: (load('database.name') || 'typescript_api') as string, - + // environment info + isDocker: isDocker, // the below one is for local migration, due to some issues with command will not running load function nor 'localhost' // dbHost: ('localhost') as string, @@ -92,7 +93,7 @@ const environment = { refreshTokenExpirationBufferSeconds: parseInt(refreshTokenBuffer), // BE CAREFUL WITH PROVIDERS - THEY'RE NOT TOTALLY TYPE SAFE UNLESS PROPERLY CONFIGURED - providers: config.get('auth.providers') as Providers, + providers: config.get('auth.providers') as Providers } export default environment diff --git a/devU-api/src/index.ts b/devU-api/src/index.ts index bc19bc17..75056335 100644 --- a/devU-api/src/index.ts +++ b/devU-api/src/index.ts @@ -41,6 +41,8 @@ initializeMinio() app.use(morgan('combined')) app.use(passport.initialize()) + console.log(`Api: ${environment.isDocker ? '' : 'not'} running in docker`) + // Middleware; app.use('/', router) app.use(errorHandler) From 96bbd86485f066bacb94561bace611ddaea25f45 Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 26 Sep 2024 15:04:29 -0400 Subject: [PATCH 148/400] fixed actions --- .github/workflows/beta.yml | 32 ++++++++++-- .github/workflows/release.yml | 38 +++++++++++--- compose.yml | 1 - prod-docker-compose.yml | 93 +++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 prod-docker-compose.yml diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 66b1acb7..fbd35017 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -6,6 +6,7 @@ on: push: branches: - develop + - ci-cd jobs: build-docker: @@ -30,8 +31,29 @@ jobs: - name: Login to GHCR registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - name: build beta tagged image - run: docker build . -t ghcr.io/${{ github.repository }}:beta - - - name: push - run: docker push ghcr.io/${{ github.repository }}:beta + - name: Convert image name to lowercase + run: | + original_string=${{ github.repository }} + echo "repo_url=$(echo $original_string | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + # beta tags + - name: API beta image + run: | + docker build . -f api.Dockerfile -t ghcr.io/${{ env.repo_url }}-api:beta + docker push ghcr.io/${{ env.repo_url }}-api:beta + + - name: client beta image + run: | + docker build . -f client.Dockerfile -t ghcr.io/${{ env.repo_url }}-client:beta + docker push ghcr.io/${{ env.repo_url }}-client:beta + + - name: nginx beta image + run: | + docker build . -f nginx.Dockerfile -t ghcr.io/${{ env.repo_url }}-nginx:beta + docker push ghcr.io/${{ env.repo_url }}-nginx:beta + + - name: build tango image + run: | + docker build ./tango -f Dockerfile -t ghcr.io/${{ env.repo_url }}-tango:beta + docker push ghcr.io/${{ env.repo_url }}-tango:beta + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21e6fcc9..bf9e60a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,6 +54,11 @@ jobs: uses: "WyriHaximus/github-action-get-previous-tag@v1" # todo run tests + - name: Convert image name to lowercase + run: | + original_string=${{ github.repository }} + echo "repo_url=$(echo $original_string | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + # docker image build - name: Set up Docker Buildx @@ -62,14 +67,31 @@ jobs: - name: Login to GHCR registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - name: build version tagged image - run: docker build . -t ghcr.io/${{ github.repository }}:${{ steps.tagName.outputs.tag }} + - name: API latest and version image + run: | + docker build . -f api.Dockerfile -t ghcr.io/${{ env.repo_url }}-api:${{ steps.tagName.outputs.tag }} + docker push ghcr.io/${{ env.repo_url }}-api:${{ steps.tagName.outputs.tag }} + + docker build . -f api.Dockerfile -t ghcr.io/${{ env.repo_url }}-api:latest + docker push ghcr.io/${{ env.repo_url }}-api:latest - - name: push version tagged - run: docker push ghcr.io/${{ github.repository }}:${{ steps.tagName.outputs.tag }} + - name: client latest and version image + run: | + docker build . -f client.Dockerfile -t ghcr.io/${{ env.repo_url }}-client:${{ steps.tagName.outputs.tag }} + docker push ghcr.io/${{ env.repo_url }}-client:${{ steps.tagName.outputs.tag }} + + docker build . -f client.Dockerfile -t ghcr.io/${{ env.repo_url }}-client:latest + docker push ghcr.io/${{ env.repo_url }}-client:latest - - name: build latest tagged image - run: docker build . -t ghcr.io/${{ github.repository }}:latest + - name: nginx latest and version image + run: | + docker build . -f nginx.Dockerfile -t ghcr.io/${{ env.repo_url }}-nginx:${{ steps.tagName.outputs.tag }} + docker push ghcr.io/${{ env.repo_url }}-nginx:${{ steps.tagName.outputs.tag }} + + docker build . -f nginx.Dockerfile -t ghcr.io/${{ env.repo_url }}-nginx:latest + docker push ghcr.io/${{ env.repo_url }}-nginx:latest - - name: push latest - run: docker push ghcr.io/${{ github.repository }}:latest + - name: tango latest and version image + run: | + docker build ./tango -f Dockerfile -t ghcr.io/${{ env.repo_url }}-tango:latest + docker push ghcr.io/${{ env.repo_url }}-tango:latest diff --git a/compose.yml b/compose.yml index 7196d098..e8083d87 100644 --- a/compose.yml +++ b/compose.yml @@ -20,7 +20,6 @@ services: environment: TANGO_KEY: devutangokey # TODO: load in from env file. for now this is defined in tango section below WAIT_HOSTS: db:5432 - dev: 0 # value here is irrelevant; just here to make sure dev env exists ports: - '3001:3001' profiles: diff --git a/prod-docker-compose.yml b/prod-docker-compose.yml new file mode 100644 index 00000000..224e9081 --- /dev/null +++ b/prod-docker-compose.yml @@ -0,0 +1,93 @@ +# example production compose file, +# remember to copy tango.config.py to the working directory from where the compose file is ran + +name: devu +services: + api: + # Runs the API + container_name: api + image: ghcr.io/makeopensource/devu-api:beta + environment: + TANGO_KEY: devutangokey # TODO: load in from env file. for now this is defined in tango section below + WAIT_HOSTS: db:5432 + depends_on: + db: + condition: service_started +# config: +# condition: service_completed_successfully + ports: + - '3001:3001' + + client: + # Builds the front end and exports static files to ./dist + image: ghcr.io/makeopensource/devu-client:beta + volumes: + - ./dist:/out + + nginx: + # Hosts the front end static files from ./dist/local thorough a web server + image: ghcr.io/makeopensource/devu-nginx:beta + volumes: + - ./dist/local:/usr/share/nginx/html + ports: + - '9000:80' + + db: + # Runs the PostgreSQL database + image: postgres + environment: + POSTGRES_DB: typescript_api + POSTGRES_USER: typescript_user + POSTGRES_PASSWORD: password + ports: + - '5432:5432' + expose: + - '5432' + + minio: + image: minio/minio + ports: + - '9002:9000' + - '9001:9001' + expose: + - '9000' + # volumes: + # - /tmp/data:/data + environment: + MINIO_ROOT_USER: typescript_user + MINIO_ROOT_PASSWORD: changeMe + command: server /data --console-address ":9001" + + # tango stuff + redis: + container_name: redis + image: redis:latest + ports: + - '127.0.0.1:6379:6379' + deploy: + replicas: 1 + restart: unless-stopped + + tango: + container_name: tango + ports: + - '127.0.0.1:3000:3000' + image: ghcr.io/makeopensource/devu-tango:beta + environment: + - DOCKER_REDIS_HOSTNAME=redis + - RESTFUL_KEY=devutangokey +# - DOCKER_DEPLOYMENT + # Path to volumes within the Tango container. Does not need to be modified. +# - DOCKER_VOLUME_PATH + # TODO remember to modify the below to be the path to the absolute path of tango_files` on your host machine + - DOCKER_TANGO_HOST_VOLUME_PATH=/absolute/path/to/tango_files + + depends_on: + - redis + volumes: + - ./tango.config.py:/opt/TangoService/Tango/config.py + - /var/run/docker.sock:/var/run/docker.sock + - ./logs/tango/:/var/log/tango/ + - ./logs/tangonginx:/var/log/nginx + - ./tango_files:/opt/TangoService/Tango/volumes + restart: unless-stopped From a514e0330eb86d0bff871513567d05991423c649 Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 26 Sep 2024 15:23:11 -0400 Subject: [PATCH 149/400] added submodules --- .github/workflows/beta.yml | 1 + .github/workflows/release.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index fbd35017..60b1ee9e 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -20,6 +20,7 @@ jobs: steps: - uses: actions/checkout@v3 with: + submodules: recursive fetch-depth: 0 # todo run tests diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bf9e60a2..e3364773 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,6 +47,7 @@ jobs: steps: - uses: actions/checkout@v3 with: + submodules: recursive fetch-depth: 0 - name: Get version tag from git history From 2ab15e1deb4c367125dd4ac5c6e618ddab6b9389 Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 26 Sep 2024 15:59:52 -0400 Subject: [PATCH 150/400] fixed tango docker build --- .github/workflows/beta.yml | 2 +- .github/workflows/release.yml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 60b1ee9e..f19b367c 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -55,6 +55,6 @@ jobs: - name: build tango image run: | - docker build ./tango -f Dockerfile -t ghcr.io/${{ env.repo_url }}-tango:beta + docker build ./tango -f ./tango/Dockerfile -t ghcr.io/${{ env.repo_url }}-tango:beta docker push ghcr.io/${{ env.repo_url }}-tango:beta diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e3364773..2e18fc1b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -59,7 +59,6 @@ jobs: run: | original_string=${{ github.repository }} echo "repo_url=$(echo $original_string | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - # docker image build - name: Set up Docker Buildx @@ -94,5 +93,5 @@ jobs: - name: tango latest and version image run: | - docker build ./tango -f Dockerfile -t ghcr.io/${{ env.repo_url }}-tango:latest + docker build ./tango -f ./tango/Dockerfile -t ghcr.io/${{ env.repo_url }}-tango:latest docker push ghcr.io/${{ env.repo_url }}-tango:latest From d16fee2bfd18474317b662ab06708159bd6ae518 Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 26 Sep 2024 16:04:09 -0400 Subject: [PATCH 151/400] removed branch --- .github/workflows/beta.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index f19b367c..2c1f9ed9 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -6,7 +6,6 @@ on: push: branches: - develop - - ci-cd jobs: build-docker: From faef084aa6054ec12c0c20ec3acbdd637048fa1a Mon Sep 17 00:00:00 2001 From: RA341 Date: Thu, 26 Sep 2024 16:25:26 -0400 Subject: [PATCH 152/400] added watchtower to autoupdate containers --- prod-docker-compose.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/prod-docker-compose.yml b/prod-docker-compose.yml index 224e9081..926d8b13 100644 --- a/prod-docker-compose.yml +++ b/prod-docker-compose.yml @@ -91,3 +91,16 @@ services: - ./logs/tangonginx:/var/log/nginx - ./tango_files:/opt/TangoService/Tango/volumes restart: unless-stopped + + # autoupdate containers + watchtower: + container_name: watchtower + image: containrrr/watchtower + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + - TZ=${TZ} + - WATCHTOWER_POLL_INTERVAL=30 + - WATCHTOWER_CLEANUP=true + - WATCHTOWER_INCLUDE_STOPPED=true + restart: unless-stopped \ No newline at end of file From 8e92cd336fec2ae1f7376ab4f28c34093ec77e62 Mon Sep 17 00:00:00 2001 From: RA341 <39427910+RA341@users.noreply.github.com> Date: Tue, 1 Oct 2024 03:35:11 -0400 Subject: [PATCH 153/400] added nosave when installing ci plugins --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e18fc1b..3165d9b3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: with: node-version: "lts/*" - name: install plugins - run: npm install @semantic-release/git @semantic-release/changelog -D + run: npm install --no-save @semantic-release/git @semantic-release/changelog -D - name: tag version number release based on commits env: From be3d716cc2b521dd9b2f19901d934dcf87d58f18 Mon Sep 17 00:00:00 2001 From: RA341 Date: Tue, 1 Oct 2024 22:28:34 -0400 Subject: [PATCH 154/400] changed docker environment var --- api.Dockerfile | 2 +- devU-api/src/environment.ts | 2 +- compose.yml => docker-compose.yml | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename compose.yml => docker-compose.yml (100%) diff --git a/api.Dockerfile b/api.Dockerfile index b69771a1..399bf401 100644 --- a/api.Dockerfile +++ b/api.Dockerfile @@ -36,7 +36,7 @@ COPY --from=config-builder /config/default.yml ./config/default.yml COPY --from=module_builder /tmp/devu-shared-modules ./devu-shared-modules # Indicate that the api is running in docker; value here is irrelevant -ENV dev=0 +ENV IS_DOCKER=0 ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.2.1/wait /wait RUN chmod +x /wait diff --git a/devU-api/src/environment.ts b/devU-api/src/environment.ts index cde24ba0..903af00f 100644 --- a/devU-api/src/environment.ts +++ b/devU-api/src/environment.ts @@ -49,7 +49,7 @@ const refreshTokenBuffer = load('auth.jwt.refreshTokenExpirationBufferSeconds') // if the dev env exists then file is running inside docker // if it is undefined it is running on dev machine -const isDocker = !(process.env.dev === undefined) +const isDocker = !(process.env.IS_DOCKER === undefined) if (isDocker && process.env.TANGO_KEY === undefined) { throw Error( diff --git a/compose.yml b/docker-compose.yml similarity index 100% rename from compose.yml rename to docker-compose.yml From 5cc836169bf832181dcb752cf65369aadcc7f98c Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Sat, 5 Oct 2024 12:59:56 -0400 Subject: [PATCH 155/400] Added a drag and drop file component This component allows files to be dragged and dropped. This will be used in the assignment creation form for attachments in the future. --- .../forms/assignments/assignmentFormPage.tsx | 3 + .../src/components/utils/dragDropFile.scss | 53 +++++++++++++++++ .../src/components/utils/dragDropFile.tsx | 59 +++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 devU-client/src/components/utils/dragDropFile.scss create mode 100644 devU-client/src/components/utils/dragDropFile.tsx diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index b60984bf..c0ec6383 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -8,6 +8,7 @@ import RequestService from 'services/request.service' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import Button from '@mui/material/Button' +import DragDropFile from 'components/utils/dragDropFile' import {SET_ALERT} from 'redux/types/active.types' @@ -135,6 +136,8 @@ const AssignmentCreatePage = () => { onChange={handleCheckbox} className={formStyles.submitBtn}/>
+ +
diff --git a/devU-client/src/components/utils/dragDropFile.scss b/devU-client/src/components/utils/dragDropFile.scss new file mode 100644 index 00000000..4f05f6f1 --- /dev/null +++ b/devU-client/src/components/utils/dragDropFile.scss @@ -0,0 +1,53 @@ +@import "variables"; + +.formFileUpload { + height: 16rem; + width: 28rem; + max-width: 100%; + text-align: center; + position: relative; +} + +.inputFileUpload { + display: none; +} + +.labelFileUpload { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + border-width: 2px; + border-radius: 1rem; + border-style: dashed; + border-color: #cbd5e1; + background-color: $list-item-background; + + &.dragActive { + background-color: #ffffff; + } +} + +.uploadButton { + cursor: pointer; + padding: 0.25rem; + font-size: 1rem; + border: none; + font-family: 'Oswald', sans-serif; + background-color: transparent; + + &:hover { + text-decoration-line: underline; + } +} + +.dragFileElement { + position: absolute; + width: 100%; + height: 100%; + border-radius: 1rem; + top: 0px; + right: 0px; + bottom: 0px; + left: 0px; +} diff --git a/devU-client/src/components/utils/dragDropFile.tsx b/devU-client/src/components/utils/dragDropFile.tsx new file mode 100644 index 00000000..e448e721 --- /dev/null +++ b/devU-client/src/components/utils/dragDropFile.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import styles from './dragDropFile.scss'; +import Button from '@mui/material/Button' + +function DragDropFile() { + const [dragActive, setDragActive] = React.useState(false); + const inputRef = React.useRef(null); + + // handle drag events + const handleDrag = function(e : React.DragEvent) { + e.preventDefault(); + e.stopPropagation(); + if (e.type === "dragenter" || e.type === "dragover") { + setDragActive(true); + } else if (e.type === "dragleave") { + setDragActive(false); + } + }; + + // triggers when file is dropped + const handleDrop = function(e : React.DragEvent) { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + if (e.dataTransfer.files && e.dataTransfer.files[0]) { + console.log('file dropped'); + } + }; + + // triggers when file is selected with click + const handleChange = function(e : React.ChangeEvent) { + e.preventDefault(); + if (e.target.files && e.target.files[0]) { + console.log('file selected'); + } + }; + + // triggers the input when the button is clicked + const handleClick = () => { + if(inputRef.current != null ) { + inputRef.current.click(); + } + }; + + return ( +
e.preventDefault()}> + + + { dragActive &&
} +
+ ); + }; + +export default DragDropFile; \ No newline at end of file From 7e5518292572c43ba9ffa4454d73f57bb6ae21fd Mon Sep 17 00:00:00 2001 From: yessicaq Date: Sat, 5 Oct 2024 17:54:51 -0400 Subject: [PATCH 156/400] Minimal changes done on course page- still needs a lot of revisions --- .../pages/courses/courseDetailPage.scss | 60 ++++++++++++++++--- .../pages/courses/courseDetailPage.tsx | 19 +++--- 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index 70276e73..91f81398 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -1,17 +1,20 @@ @import 'variables'; + + .categoriesContainer { display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(3, 1fr); gap: 20px; /* adjust this value to set the space between the cards */ - + flex-direction: row; + justify-content: space-between; } -.header { +/*.header { display: flex; align-items: center; } - +*/ .smallLine { width: 50px; /* adjust this value to set the length of the small line */ border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ @@ -30,7 +33,50 @@ } .color{ - background-color: $list-item-background; - color: $text-color; + background-color: $purple; + color: $text-color2; max-width : 345px; -} \ No newline at end of file +} + +.header { + display: block; + justify-content: space-around; + align-items: flex-start; + background-color: $grey; + padding: 30px 50px; + border-radius: 10px; + color: $text-color +} + +.logo { + font-size: 24px; + font-weight: bold; + color: white; +} + +input { + width: 400px; + padding: 8px; + border-radius: 5px; + border: none; +} + +/*.edit-course-button { + background-color: $grey; + color: white; + padding: 10px 15px; + border-radius: 5px; +}*/ +.assignment_card { + background-color: $text-color-secondary; + padding: 20px; + text-align: center; + border-radius: 10px; + transition: transform 0.2s; + max-width : 345px; + +} + +.assignment-card:hover { + transform: scale(1.05); +} diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 80331b25..1794e863 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -20,10 +20,11 @@ import styles from './courseDetailPage.scss' import {SET_ALERT} from "../../../redux/types/active.types"; import {useActionless, useAppSelector} from "redux/hooks"; + + const CourseDetailPage = () => { const history = useHistory() const { courseId } = useParams<{courseId: string}>() - const [courseInfo, setCourseInfo] = useState(null) const [categoryMap, setCategoryMap] = useState>({}) const [setAlert] = useActionless(SET_ALERT) @@ -32,6 +33,7 @@ const CourseDetailPage = () => { RequestService.get(`/api/courses/${courseId}`) .then((course) => { setCourseInfo(course) + }) RequestService.get(`/api/course/${courseId}/assignments/released`) .then((assignments) => { @@ -63,6 +65,7 @@ const CourseDetailPage = () => { }) } + useEffect(() => { fetchCourseInfo() }, []) @@ -72,11 +75,12 @@ const CourseDetailPage = () => { {courseInfo ? (
-

{courseInfo.name}

-
- +

Section:

+ + + @@ -92,21 +96,22 @@ const CourseDetailPage = () => { }}>Edit Course} +
- +
{Object.keys(categoryMap).map((category, index) => ( -
+
{category} - + {categoryMap[category].map((assignment, index) => ( { From 158dda69474357e21eb1ccb71a74facbc09cf0ea Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:24:12 -0400 Subject: [PATCH 157/400] Updated layout for the assignment creation page. Updated the layout for the assignment creation page. It's split into 3 different parts now - assignment menu, assignment creation form, and attachments. --- .../forms/assignments/assignmentFormPage.scss | 40 +++++- .../forms/assignments/assignmentFormPage.tsx | 124 +++++++++++------- .../src/components/utils/dragDropFile.scss | 4 +- 3 files changed, 114 insertions(+), 54 deletions(-) diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index 492f0420..eea41acb 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -1,14 +1,20 @@ @import 'variables'; -h1 { +h2 { text-align: center; } + +p { + text-align: center; +} + .form { - background-color: $list-item-background; + background-color: $list-item-background; + grid-area: 1 / 2 / 2 / 3; border-radius: 10px; padding: 20px; - width: 50%; + // width: 50%; left: 0; right: 0; margin-left: auto; @@ -53,3 +59,31 @@ h1 { border: 1px solid #bbbbbb; border-radius: 4px; } + +.grid { + display: grid; + grid-template-columns: 0.5fr 1fr 0.75fr; + grid-template-rows: 1fr; + grid-column-gap: 10px; + grid-row-gap: 10px; +} + +.dragDropFile { + grid-area: 1 / 3 / 2 / 4; + padding: 20px; + background-color: $list-item-background; + border-radius: 10px; + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; +} + +.assignmentDropdown { + grid-area: 1 / 1 / 2 / 2; +} + +.formButtons { + grid-area: 2 / 1 / 3 / 4; + background-color: $list-item-background; +} \ No newline at end of file diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index c0ec6383..d3550171 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -8,6 +8,9 @@ import RequestService from 'services/request.service' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import Button from '@mui/material/Button' +import Accordion from '@mui/material/Accordion'; +import AccordionSummary from '@mui/material/AccordionSummary'; +import AccordionDetails from '@mui/material/AccordionDetails'; import DragDropFile from 'components/utils/dragDropFile' import {SET_ALERT} from 'redux/types/active.types' @@ -89,60 +92,83 @@ const AssignmentCreatePage = () => { return( -

Assignment Form

-
- - - - - - - - - - -
- -
-
- - +

Create Assignment

+
+
+

All Assignments

+ + + Dropdown 1 + + + test + + + + + Dropdown 2 + + + test + + +
+
+ + + + + + + + + +
+ +
+
+ + +
+
+ + +
+
+ + +
-
- - +
+
+ +
-
- - +
+
-
-
- - +
+

Attachments

+

Add up to 5 attachments with this assignment:

+


+
- - - -
- -
- +
diff --git a/devU-client/src/components/utils/dragDropFile.scss b/devU-client/src/components/utils/dragDropFile.scss index 4f05f6f1..ac3b93f5 100644 --- a/devU-client/src/components/utils/dragDropFile.scss +++ b/devU-client/src/components/utils/dragDropFile.scss @@ -43,8 +43,8 @@ .dragFileElement { position: absolute; - width: 100%; - height: 100%; + width: 80%; + height: 80%; border-radius: 1rem; top: 0px; right: 0px; From 0d4ca712f9f229243601f1e57f1e2b0023e8a07d Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Mon, 7 Oct 2024 23:18:11 -0400 Subject: [PATCH 158/400] I redesigned the homepage files to match the design on te figma style. I also made changes in the globaltoolbar files to match the themes in the figma. I made changes in the simpleAssignment to fix the font so when the assignment is created, it will align with course_card. I also added two buttons in userCourseListItem for gradebook and coursepage. --- .DS_Store | Bin 0 -> 8196 bytes devU-client/src/assets/global.scss | 6 +- .../listItems/simpleAssignmentListItem.scss | 11 +- .../listItems/userCourseListItem.scss | 58 ++++++++--- .../listItems/userCourseListItem.tsx | 41 ++++---- .../src/components/misc/globalToolbar.scss | 30 +++--- .../src/components/misc/globalToolbar.tsx | 9 +- devU-client/src/components/misc/navbar.scss | 2 +- .../components/pages/homePage/homePage.scss | 94 +++++++++++++++--- .../components/pages/homePage/homePage.tsx | 50 +++++++--- .../shared/layouts/pageWrapper.scss | 2 +- .../src/components/utils/darkModeToggle.scss | 6 +- .../components/utils/userOptionsDropdown.scss | 9 +- devU-client/src/index.html | 1 + 14 files changed, 231 insertions(+), 88 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1707d5e29146272db9d5aa750d835e9fd213dc9c GIT binary patch literal 8196 zcmeHMyKWOf6ulEKiLoVQAt6Pf*@6b7AVo9*nkCvmi-?dwLXbGl!;&{^;)HsYU!aT> z3JN+J{{R&~zz0z91Bfaj4QC$q-tq3n9YW@gv~yN-&&)k@CfUv15RqCt?yL|k5>XwO z##R$uNz-wC722E|xdl|1PrbdBjqNn)7nm|24u}KdfH)uyhy(wM19)faRb6Tb?>QmQK)OeXdQGvrXt$+SS^G}Q~CA?hKWjs&U^)EaI+!@E+QXaGOs?_m?emewigmev)7hTht4}BO4sfJ9^-j0nydH6dEH!oT4iybm&ok*3LW804v;zZG6%1hkeNk!?z6e!9hci< zU}r!{Pc!);e8K39}_NUVil9?dhnw)@}y#2?$C6$r`L#(P8^s?2Woa# zi@N?_um1i&lY5s ( - -
{instructor ? (course.name + " (Instructor)") : course.name}
+const UserCourseListItem = ({course, assignments, past = false, instructor = false}: Props) => { + const history = useHistory() + return( + + +
{instructor ? (course.name + " (Instructor)") : course.name.toUpperCase() + " " + course.number + " " + "(" + course.semester + ")" }
-
{course.number}
-
Semester: {prettyPrintSemester(course.semester)}
-
Start Date: {prettyPrintDate(course.startDate)}
-
End Date: {prettyPrintDate(course.endDate)}
- {assignments && assignments.length > 0 ? (assignments.map((assignment) => ( - ))) : ((past || instructor) ?
:
No Assignments Due Yet
)} - -
+ ))) : ((past || instructor) ?
:
No Assignments Due Yet
)} +
+ + +
+
- -) - +); +}; export default UserCourseListItem diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 9c3cf54a..6ee2c966 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -9,7 +9,6 @@ $font-size: 16px; display: flex; align-items: center; justify-content: center; - gap: 1.5rem; } @@ -17,11 +16,15 @@ $font-size: 16px; @extend .flex; text-decoration: none; - - color: $text-color; + color: #d9d9d9; font-size: $font-size; - - height: $bar-height; + margin:3vw; + height: 4vh; + flex-grow: .05; + border-radius: 3vw; + font-weight: 550; + font-size: 14px; + padding:0.5vw; &:hover { opacity: $hover-effect; @@ -31,14 +34,15 @@ $font-size: 16px; .header { @extend .link; - font-size: 2em; - - font-weight: bold; + font-size: 40px; + border-radius: 30px; + color: #D9D9D9; + font-weight: 550; } .bar { - height: $bar-height; - + height: 60px; + background-color: #52468A; @extend .flex; justify-content: space-between; } @@ -55,9 +59,10 @@ $font-size: 16px; // Controls turning the menu options into a sidebar // As well as whether or not that sidebar is being shown -@media (max-width: $medium) { +@media (max-width: 300px) { .flex { gap: 1rem; + } .menu { @@ -99,8 +104,9 @@ $font-size: 16px; } } -@media (max-width: $extreme) { +@media (max-width: 300px) { .flex { gap: 0.3rem; + } } diff --git a/devU-client/src/components/misc/globalToolbar.tsx b/devU-client/src/components/misc/globalToolbar.tsx index 92083ac2..7149e5ec 100644 --- a/devU-client/src/components/misc/globalToolbar.tsx +++ b/devU-client/src/components/misc/globalToolbar.tsx @@ -7,10 +7,11 @@ import UserOptionsDropdown from 'components/utils/userOptionsDropdown' import styles from './globalToolbar.scss' + import RoleDispatcher from '../utils/roleDispatcher' const GlobalToolbar = () => { - + return (
@@ -26,9 +27,11 @@ const GlobalToolbar = () => {
- - Courses + { + + My Courses + } {/**/} {/* My Courses*/} {/**/} diff --git a/devU-client/src/components/misc/navbar.scss b/devU-client/src/components/misc/navbar.scss index 1a1199ad..f490536a 100644 --- a/devU-client/src/components/misc/navbar.scss +++ b/devU-client/src/components/misc/navbar.scss @@ -2,5 +2,5 @@ .link { text-decoration: none; - color: $text-color; + color: #52468A; } \ No newline at end of file diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index 0ed3fd46..afab4e3d 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -1,26 +1,88 @@ @import 'variables'; -.coursesContainer { - display: grid; - grid-template-columns: repeat(auto-fill, 500px); - gap: 20px; /* adjust this value to set the space between the cards */ + +h2 { + align-items:left; + margin-left: 20px; + font-size: 30px; + font-weight: 550; + margin-bottom: 30px; +} + +h3 { + align-items:left; + margin-left: 20px; + font-size: 30px; + font-weight: 550; + margin-bottom: 30px; + } +.coursesContainer { + display: flex; + flex-direction: row; + gap: 20px; + margin:20px; +} +.courseCard { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: stretch; + height: 100%; + padding: 0; + background-color: #D9D9D9; + border-radius: 5px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + overflow: hidden; + margin-bottom: 20px; + } +.create_course { + border:0; /* Primary button color */ + color: #52468a; /* Button text color */ + padding: 10px 20px; /* Padding for button */ + border-radius: 5px; + text-decoration: none; + font-weight: 600; + transition: background-color 0.3s ease; +} .header { color: $text-color; - display: flex; - align-items: center; + display: block; + align-items: center; } -.smallLine { - width: 50px; /* adjust this value to set the length of the small line */ - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-right: 10px; /* adjust this value to set the space between the line and the text */ +h3::after { + content: ''; + display: block; + margin-top: 10px; + width: 100%; + height: 1px; + font-weight: 600; + background-color: $text-color; + } + +@media (max-width: 768px) { + .coursesContainer { + flex-direction: column; + align-items: center; + } + + .courseCard { + width: 100%; + max-width: none; + } } -.largeLine { - flex-grow: 1; - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-left: 10px; /* adjust this value to set the space between the line and the text */ - margin-right: 10px; /* add this line to create some space between the line and the button */ -} \ No newline at end of file +// .smallLine { +// width: 50px; /* adjust this value to set the length of the small line */ +// border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ +// margin-right: 10px; /* adjust this value to set the space between the line and the text */ +// } + +// .largeLine { +// flex-grow: 1; +// border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ +// margin-left: 10px; /* adjust this value to set the space between the line and the text */ +// margin-right: 10px; /* add this line to create some space between the line and the button */ +// } \ No newline at end of file diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 6b3602bc..381a1f76 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -9,10 +9,10 @@ import UserCourseListItem from "../../listItems/userCourseListItem"; import {useAppSelector} from 'redux/hooks' import RequestService from 'services/request.service' import {Assignment, Course} from 'devu-shared-modules' - +import {useHistory} from "react-router-dom"; const HomePage = () => { const userId = useAppSelector((store) => store.user.id) - + const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [enrollCourses, setEnrollCourses] = useState(new Array()) @@ -25,6 +25,7 @@ const HomePage = () => { }, []) const fetchData = async () => { + try { const assignmentMap = new Map>() const allCourses = await RequestService.get<{ @@ -37,7 +38,7 @@ const HomePage = () => { const pastCourses: Course[] = allCourses.pastCourses; const instructorCourses: Course[] = allCourses.instructorCourses; - + const assignmentPromises = enrolledCourses.map((course) => { const assignments = RequestService.get(`/api/course/${course.id}/assignments/released`) return Promise.all([course, assignments]) @@ -49,7 +50,7 @@ const HomePage = () => { setPastCourses(pastCourses) setEnrollCourses(enrolledCourses) setInstructorCourses(instructorCourses) - + } catch (error: any) { setError(error) } finally { @@ -60,40 +61,65 @@ const HomePage = () => { if (loading) return if (error) return - + const history = useHistory(); return(
-

My Courses

+

Courses

+ +
+
+
+
+

Current

{instructorCourses && instructorCourses.map((course) => ( +
+
))}
{enrollCourses && enrollCourses.map((course) => ( +
- +
))} - {enrollCourses.length === 0 &&

You do not have current enrollment yet

} + {enrollCourses.length === 0 &&

You do not have current enrollment yet

}
-

Completed Courses

+

Completed

+
{pastCourses && pastCourses.map((course) => ( - + +
+ +
))} - {pastCourses.length === 0 &&

No upcoming courses

} + {pastCourses.length === 0 &&

No completed courses

} +
+ +
+
+

Upcoming

+
diff --git a/devU-client/src/components/shared/layouts/pageWrapper.scss b/devU-client/src/components/shared/layouts/pageWrapper.scss index 7bfcdb74..dab83109 100644 --- a/devU-client/src/components/shared/layouts/pageWrapper.scss +++ b/devU-client/src/components/shared/layouts/pageWrapper.scss @@ -4,7 +4,7 @@ flex-direction: column; overflow-y: auto; - padding: 20px; + padding: 0vw; //navigation bar should be on top of the page each time } .content { diff --git a/devU-client/src/components/utils/darkModeToggle.scss b/devU-client/src/components/utils/darkModeToggle.scss index 17da774f..f9700e87 100644 --- a/devU-client/src/components/utils/darkModeToggle.scss +++ b/devU-client/src/components/utils/darkModeToggle.scss @@ -1,4 +1,6 @@ .toggle { - height: 50px; - width: 50px; + height: 40px; + width: 20px; + color: #F8D487; } + diff --git a/devU-client/src/components/utils/userOptionsDropdown.scss b/devU-client/src/components/utils/userOptionsDropdown.scss index 73227bd2..26d23254 100644 --- a/devU-client/src/components/utils/userOptionsDropdown.scss +++ b/devU-client/src/components/utils/userOptionsDropdown.scss @@ -29,9 +29,10 @@ .trigger { @extend .button; - color: $text-color; - - height: 50px; + color: #D9D9D9; + border-radius: 20px; + margin:5px; + height: 40px; &:hover { opacity: 0.7; @@ -43,7 +44,7 @@ } .userIcon { - font-size: 20px; + font-size: 14px; cursor: pointer; } diff --git a/devU-client/src/index.html b/devU-client/src/index.html index a21a37cb..1bc42081 100644 --- a/devU-client/src/index.html +++ b/devU-client/src/index.html @@ -11,6 +11,7 @@ +
From 8faaac28faf3026d40dc927a3031f80d35b82620 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Mon, 7 Oct 2024 23:26:49 -0400 Subject: [PATCH 159/400] added spacing for the create course button to be aligned with the page in homepage.scss file --- .../src/components/listItems/userCourseListItem.scss | 8 ++++---- devU-client/src/components/pages/homePage/homePage.scss | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/devU-client/src/components/listItems/userCourseListItem.scss b/devU-client/src/components/listItems/userCourseListItem.scss index c117f87d..12f854ea 100644 --- a/devU-client/src/components/listItems/userCourseListItem.scss +++ b/devU-client/src/components/listItems/userCourseListItem.scss @@ -3,13 +3,13 @@ .name { font-size: 1.2rem; font-weight: 600; - margin: 0; /* Remove any margin around the name */ + margin: 0; padding: 15px; /* Add padding to the text inside the name block */ background: #b5acdd; /* Background color for the name area */ width: 100%; /* Make sure it spans the entire width of the card */ - text-align: center; /* Center the text */ - color: #52468a; /* Text color */ - border-radius: 5px 5px 0 0; /* Rounded top corners */ + text-align: center; + color: #52468a; + border-radius: 5px 5px 0 0; box-sizing: border-box; } diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index afab4e3d..fbf4d332 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -39,12 +39,13 @@ h3 { } .create_course { border:0; /* Primary button color */ - color: #52468a; /* Button text color */ - padding: 10px 20px; /* Padding for button */ + color: #52468a; + padding: 10px 20px; border-radius: 5px; text-decoration: none; font-weight: 600; transition: background-color 0.3s ease; + margin:20px } .header { color: $text-color; From 0732e90a8ead7f73765c74413150b3bc0a30cc6c Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Mon, 7 Oct 2024 23:42:41 -0400 Subject: [PATCH 160/400] Updated form in the assignment creation page Updated form in the assignment creation page to better match the figma design. Also updated the datepickers used to be MUI. --- devU-client/package.json | 6 +- .../forms/assignments/assignmentFormPage.scss | 80 ++++----- .../forms/assignments/assignmentFormPage.tsx | 164 ++++++++++++------ .../components/shared/inputs/textField.scss | 2 +- .../components/shared/inputs/textField.tsx | 11 +- 5 files changed, 161 insertions(+), 102 deletions(-) diff --git a/devU-client/package.json b/devU-client/package.json index f8f029f3..af413ffc 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -10,8 +10,8 @@ "build-local": "cross-env NODE_ENV=local webpack --mode=production", "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", "pre-commit": "lint-staged", - "dev-backend": "docker compose -f ../docker-compose.yml --profile dev-client up -d", - "dev-backend-stop": "docker compose -f ../docker-compose.yml --profile dev-client stop" + "dev-backend": "docker compose -f ../compose.yml --profile dev-client up -d", + "dev-backend-stop": "docker compose -f ../compose.yml --profile dev-client stop" }, "lint-staged": { "./**/*.{js,ts,json,md}": [ @@ -30,7 +30,9 @@ "@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", "@mui/material": "^5.15.15", + "@mui/x-date-pickers": "^7.19.0", "color-hash": "^2.0.1", + "dayjs": "^1.11.13", "devu-shared-modules": "./devu-shared-modules", "history": "^4.10.1", "moment": "^2.29.1", diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index eea41acb..72dbe56d 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -11,19 +11,19 @@ p { .form { background-color: $list-item-background; - grid-area: 1 / 2 / 2 / 3; + grid-area: 1 / 1 / 2 / 2; border-radius: 10px; padding: 20px; - // width: 50%; - left: 0; - right: 0; margin-left: auto; - margin-right: auto; + margin-right: 0; } .datepickerContainer { - display: flex; - justify-content: space-between; + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: 1fr; + grid-column-gap: 10px; + grid-row-gap: 0px; width: 100%; } @@ -33,57 +33,53 @@ p { align-items: center; } -.smallLine { - width: 50px; /* adjust this value to set the length of the small line */ - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-right: 10px; /* adjust this value to set the space between the line and the text */ +.datepicker_start { + grid-area: 1 / 1 / 2 / 2; } - -.largeLine { - flex-grow: 1; - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-left: 10px; /* adjust this value to set the space between the line and the text */ - margin-right: 10px; /* add this line to create some space between the line and the button */ +.datepicker_due { + grid-area: 1 / 2 / 2 / 3; } - -.datepicker { - font: inherit; - letter-spacing: inherit; - box-sizing: content-box; - background: none; - height: 1.4375em; - -webkit-tap-highlight-color: transparent; - width: 85%; - animation-duration: 10ms; - padding: 16.5px 14px; - border: 1px solid #bbbbbb; - border-radius: 4px; +.datepicker_end { + grid-area: 1 / 3 / 2 / 4; } .grid { display: grid; - grid-template-columns: 0.5fr 1fr 0.75fr; - grid-template-rows: 1fr; + grid-template-columns: 1fr 0.75fr; + grid-template-rows: 1fr; grid-column-gap: 10px; grid-row-gap: 10px; + width: 100%; } .dragDropFile { - grid-area: 1 / 3 / 2 / 4; + grid-area: 1 / 2 / 2 / 3; padding: 20px; background-color: $list-item-background; border-radius: 10px; - left: 0; - right: 0; - margin-left: auto; + margin-left: 0; margin-right: auto; } -.assignmentDropdown { - grid-area: 1 / 1 / 2 / 2; +.textField1 { + width: 50%; + margin-right: 5%; + flex-shrink: 0; + flex-direction: none; } -.formButtons { - grid-area: 2 / 1 / 3 / 4; - background-color: $list-item-background; -} \ No newline at end of file +.textField2 { + width: 50%; + flex-shrink: 0; + flex-direction: none; +} + +.textFieldContainer { + display:flex; + width: 100%; +} + +.textArea { + width: 100%; + flex-direction: none; +} diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index d3550171..b302e53e 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -1,16 +1,16 @@ -import React, {useState} from 'react' -import DatePicker from 'react-datepicker' +import React, {useState, useEffect} from 'react' import {ExpressValidationError} from 'devu-shared-modules' import 'react-datepicker/dist/react-datepicker.css' import PageWrapper from 'components/shared/layouts/pageWrapper' +import { getCssVariables } from 'utils/theme.utils' import RequestService from 'services/request.service' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import Button from '@mui/material/Button' -import Accordion from '@mui/material/Accordion'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import AccordionDetails from '@mui/material/AccordionDetails'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import DragDropFile from 'components/utils/dragDropFile' import {SET_ALERT} from 'redux/types/active.types' @@ -19,8 +19,11 @@ import {applyMessageToErrorFields, removeClassFromField} from "../../../../utils import {useHistory, useParams} from 'react-router-dom' import formStyles from './assignmentFormPage.scss' +import { Dayjs } from 'dayjs' const AssignmentCreatePage = () => { + const [theme, setTheme] = useState(getCssVariables()) + const { textColor } = theme const [setAlert] = useActionless(SET_ALERT) const {courseId} = useParams<{ courseId: string }>() const history = useHistory() @@ -39,6 +42,14 @@ const AssignmentCreatePage = () => { const [startDate, setStartDate] = useState(new Date()) const [invalidFields, setInvalidFields] = useState(new Map()) + useEffect(() => { + const observer = new MutationObserver(() => setTheme(getCssVariables())) + + observer.observe(document.body, { attributes: true }) + + return () => observer.disconnect() + }) + const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id @@ -53,9 +64,24 @@ const AssignmentCreatePage = () => { setFormData(prevState => ({...prevState,disableHandins : e.target.checked})) } - const handleStartDateChange = (date : Date) => {setStartDate(date)} - const handleEndDateChange = (date : Date) => {setEndDate(date)} - const handleDueDateChange = (date: Date) => {setDueDate(date)} + const handleStartDateChange = (date : Dayjs | null) => { + if(date){ + const newDate = date.toDate() + setStartDate(newDate) + } + } + const handleEndDateChange = (date : Dayjs | null) => { + if(date){ + const newDate = date.toDate() + setEndDate(newDate) + } + } + const handleDueDateChange = (date: Dayjs | null) => { + if(date){ + const newDate = date.toDate() + setDueDate(newDate) + } + } const handleSubmit = () => { const finalFormData = { @@ -94,61 +120,90 @@ const AssignmentCreatePage = () => {

Create Assignment

-
-

All Assignments

- - - Dropdown 1 - - - test - - - - - Dropdown 2 - - - test - - -
- - - +

Assignment Information

+
+ + + +
- - - - - -
+ helpText={invalidFields.get("description")} + sx={{width:1}}/> + +
+ + + +
- - + + +
- - + + +
- - + + +

@@ -157,6 +212,7 @@ const AssignmentCreatePage = () => {
+
@@ -168,8 +224,6 @@ const AssignmentCreatePage = () => {


-
-
diff --git a/devU-client/src/components/shared/inputs/textField.scss b/devU-client/src/components/shared/inputs/textField.scss index 6bb76987..1aa7fc1d 100644 --- a/devU-client/src/components/shared/inputs/textField.scss +++ b/devU-client/src/components/shared/inputs/textField.scss @@ -2,7 +2,7 @@ display: flex; flex-direction: column; - margin-bottom: 10px; + margin-bottom: 30px; width: 100%; } diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 3b2d059d..d092e232 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -19,7 +19,9 @@ type Props = { invalidated?: boolean helpText?: string variant?: 'outlined' | 'standard' | 'filled' - sx?: SxProps; + sx?: SxProps + multiline?: boolean + rows?: number } const TextField = ({ @@ -34,7 +36,9 @@ const TextField = ({ invalidated, helpText, variant = 'outlined', - sx + sx, + multiline, + rows, }: Props) => { const [theme, setTheme] = useState(getCssVariables()) @@ -83,6 +87,9 @@ const TextField = ({ }, }} + {...multiline && { multiline: true }} + {...multiline && rows && { minRows: rows }} + {...multiline && rows && { maxRows: rows }} />
) From 3f29380e6e9ac7bfe547a5ffd70f4bbaf3e7612e Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Tue, 8 Oct 2024 00:03:29 -0400 Subject: [PATCH 161/400] added "no upcoming courses" info under Upcoming courses category for a better description and look. --- devU-client/src/components/pages/homePage/homePage.scss | 5 +++++ devU-client/src/components/pages/homePage/homePage.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index fbf4d332..e84eab94 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -18,6 +18,11 @@ h3 { } +h4 { + margin-left: 20px; +} + + .coursesContainer { display: flex; flex-direction: row; diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 381a1f76..cd3afaaa 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -121,7 +121,7 @@ const HomePage = () => {

Upcoming

- + {

No upcoming courses

} ) } From 6ea3c6f42ba46ca14e409a4ba9443f8f2b700c95 Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 8 Oct 2024 01:52:39 -0400 Subject: [PATCH 162/400] Displayed File Names Added a display to store and show files that were uploaded through the Drag and Drop component. --- .../forms/assignments/assignmentFormPage.scss | 12 ++++++++- .../forms/assignments/assignmentFormPage.tsx | 25 ++++++++++++++++--- .../src/components/utils/dragDropFile.tsx | 8 +++++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index 72dbe56d..d8f5b3f2 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -58,7 +58,7 @@ p { background-color: $list-item-background; border-radius: 10px; margin-left: 0; - margin-right: auto; + margin-right: auto; } .textField1 { @@ -83,3 +83,13 @@ p { width: 100%; flex-direction: none; } + +.fileNameContainer { + width: 400px; +} + +.fileName { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index b302e53e..b7945d2e 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -41,6 +41,7 @@ const AssignmentCreatePage = () => { const [dueDate, setDueDate] = useState(new Date()) const [startDate, setStartDate] = useState(new Date()) const [invalidFields, setInvalidFields] = useState(new Map()) + const [files, setFiles] = useState(new Map()) useEffect(() => { const observer = new MutationObserver(() => setTheme(getCssVariables())) @@ -116,6 +117,16 @@ const AssignmentCreatePage = () => { } + const handleFile = (file : File) => { + if(Object.keys(files).length < 5){ + setFiles(prevState => ({...prevState, [file.name] : file})) + } else { + //TODO: Add alert + console.log('Max files reached') + } + console.log(files) + } + return(

Create Assignment

@@ -147,7 +158,7 @@ const AssignmentCreatePage = () => { helpText={invalidFields.get("maxFileSize")} sx={{width:9/10}}/> - @@ -219,10 +230,18 @@ const AssignmentCreatePage = () => {
+ {/*TODO: Whenever file uploads is available on backend, store the files + create Object URLs*/}

Attachments

Add up to 5 attachments with this assignment:

-


- +

Files Uploaded:

+ {Object.keys(files).map((file) => { + return ( +
+

{file}

+
+ ); + })} +
diff --git a/devU-client/src/components/utils/dragDropFile.tsx b/devU-client/src/components/utils/dragDropFile.tsx index e448e721..2c100508 100644 --- a/devU-client/src/components/utils/dragDropFile.tsx +++ b/devU-client/src/components/utils/dragDropFile.tsx @@ -2,7 +2,11 @@ import React from "react"; import styles from './dragDropFile.scss'; import Button from '@mui/material/Button' -function DragDropFile() { +interface DragDropFileProps { + handleFile : (file : File) => void; +} + +function DragDropFile({handleFile} : DragDropFileProps) { const [dragActive, setDragActive] = React.useState(false); const inputRef = React.useRef(null); @@ -24,6 +28,7 @@ function DragDropFile() { setDragActive(false); if (e.dataTransfer.files && e.dataTransfer.files[0]) { console.log('file dropped'); + handleFile(e.dataTransfer.files[0]); } }; @@ -32,6 +37,7 @@ function DragDropFile() { e.preventDefault(); if (e.target.files && e.target.files[0]) { console.log('file selected'); + handleFile(e.target.files[0]); } }; From d5688b94a37331c7eed62e8d5b42519ffd4fd2d2 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Tue, 8 Oct 2024 02:16:23 -0400 Subject: [PATCH 163/400] Centered File Names Centered the file names that appeared within the attachments --- .../pages/forms/assignments/assignmentFormPage.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index d8f5b3f2..ce238c0b 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -85,6 +85,10 @@ p { } .fileNameContainer { + left: 0; + right: 0; + margin-left: auto; + margin-right: auto; width: 400px; } From 9303cfc9627c3e78f3deebc1928d9d4b368788c7 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:25:54 -0400 Subject: [PATCH 164/400] added add/drop students form, added button styling (globally) for primary/secondary/delete btns --- devU-client/src/assets/global.scss | 69 ++++++++-- .../pages/forms/courses/courseUpdatePage.tsx | 118 ++++++++++-------- .../pages/forms/courses/coursesFormPage.scss | 33 +++-- 3 files changed, 150 insertions(+), 70 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index c2d7b4f4..9d3f5730 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -13,6 +13,38 @@ position: relative; } + h1 { + margin: 2.25rem auto; + } + + // general button template, extends to 3 types of buttons - primary, secondary, delete + .btn { + padding: 15px 30px; + border-radius: 100px; + border: none; + font-weight: 700; + } + + button.btnPrimary { + @extend .btn; + background-color: var(--primary); + color: #fff; // primary button always white text + } + + button.btnSecondary { + @extend .btn; + background-color: var(--btn-secondary-background); + color: var(--btn-secondary-text); + border: 3px solid var(--btn-secondary-border); + } + + button.btnDelete { + @extend .btn; + background-color: var(--btn-delete-background); + color: var(--btn-delete-text); + border: 3px solid var(--btn-delete-border); + } + body { font-family: 'Open Sans', 'Helvetica', 'Arial', sans-serif; margin: 0; @@ -28,20 +60,29 @@ --focus: #38c172; --primary-lighter: #41a8f7; - --primary: #0984e3; + --primary: #52468A; --primary-darker: #054272; --secondary-lighter: #cfd1d1; --secondary: #a8abab; --secondary-darker: #686b6b; - --list-item-background: #f5f5f5; + --list-item-background: #c5c5c5; --list-item-background-hover: #e9e9e9; --list-item-subtext: #4b4b4b; --list-simple-item-background: #9f9f9f4f; --list-simple-item-background-hover: #a0a0a0; --list-simple-item-subtext: #4b4b4b; - // Non theme colors + + --btn-secondary-border: var(--primary); + --btn-secondary-background: #FFF; + --btn-secondary-text: var(--primary); + + --btn-delete-border: var(--red); + --btn-delete-background: #FFF; + --btn-delete-text: var(--red); + + // Non theme colors - will not update with light/dark toggle --grey-lightest: #dfe6e9; --grey-lighter: #b2bec3; --grey: #636e72; @@ -49,14 +90,14 @@ --blue-lighter: #74b9ff; --blue: #0984e3; - --red-lighter: #ff7675; - --red: #d63031; + --red-lighter: #FFA3A3; + --red: #8A2626; --purple-lighter: #a29bfe; --purple: #6c5ce7; - --green-lighter: #55efc4; - --green: #00b894; + --green-lighter: #B0EEA2; + --green: #306025; --yellow-lighter: #ffeaa7; --yellow: #fdcb6e; @@ -70,13 +111,13 @@ --text-color-secondary: #9b9b9b; --primary-lighter: #3796bc; - --primary: #266781; - --primary-darker: #133441; + --primary: #7257EB; + --primary-darker: #2F2363; --secondary-lighter: #636666; --secondary: #3d3f3f; --secondary-darker: #1f2020; - --list-item-background: #3b3b3b; + --list-item-background: #333333; --list-item-background-hover: #303030; --list-item-subtext: #e0e0e0; @@ -84,6 +125,14 @@ --list-simple-item-background-hover: #626262; --list-simple-item-subtext: #4b4b4b; + --btn-secondary-border: var(--primary); + --btn-secondary-background: var(--primary-darker); + --btn-secondary-text: #FFF; + + --btn-delete-border: var(--red-lighter); + --btn-delete-background: var(--red); + --btn-delete-text: #FFF; + background-color: var(--background); color: var(--text-color); } diff --git a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx index 58c01c04..ca5bee15 100644 --- a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx @@ -1,5 +1,5 @@ -import React, {useEffect, useState} from 'react' -import {useHistory, useParams} from 'react-router-dom' +import React, { useEffect, useState } from 'react' +import { useHistory, useParams } from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -7,12 +7,12 @@ import RequestService from 'services/request.service' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' -import {ExpressValidationError} from 'devu-shared-modules' +import { ExpressValidationError } from 'devu-shared-modules' -import {useActionless} from 'redux/hooks' +import { useActionless } from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -import Button from '@mui/material/Button' -import {SET_ALERT} from 'redux/types/active.types' +// import Button from '@mui/material/Button' +import { SET_ALERT } from 'redux/types/active.types' import { applyMessageToErrorFields, removeClassFromField @@ -24,10 +24,10 @@ type UrlParams = { courseId: string } -const CourseUpdatePage = ({}) => { +const CourseUpdatePage = ({ }) => { const [setAlert] = useActionless(SET_ALERT) const history = useHistory() - const [formData,setFormData] = useState({ + const [formData, setFormData] = useState({ name: '', number: '', semester: '', @@ -39,7 +39,7 @@ const CourseUpdatePage = ({}) => { const { courseId } = useParams() as UrlParams useEffect(() => { let isMounted = false; - if (!isMounted){ + if (!isMounted) { RequestService.get(`/api/courses/${courseId}`).then((res) => { setFormData({ name: res.name, @@ -52,22 +52,22 @@ const CourseUpdatePage = ({}) => { }); } }, []); - const handleChange = (value: String, e : React.ChangeEvent) => { + const handleChange = (value: String, e: React.ChangeEvent) => { const key = e.target.id const newInvalidFields = removeClassFromField(invalidFields, key) setInvalidFields(newInvalidFields) - setFormData(prevState => ({...prevState,[key] : value})) + setFormData(prevState => ({ ...prevState, [key]: value })) } - const handleStartDateChange = (date : Date) => {setStartDate(date)} - const handleEndDateChange = (date : Date) => {setEndDate(date)} + const handleStartDateChange = (date: Date) => { setStartDate(date) } + const handleEndDateChange = (date: Date) => { setEndDate(date) } const handleCourseUpdate = () => { const finalFormData = { - name : formData.name, - number : formData.number, - semester : formData.semester, - startDate : startDate.toISOString(), - endDate : endDate.toISOString(), + name: formData.name, + number: formData.number, + semester: formData.semester, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), } RequestService.put(`/api/courses/${courseId}`, finalFormData) @@ -82,48 +82,68 @@ const CourseUpdatePage = ({}) => { setInvalidFields(newFields); setAlert({ autoDelete: false, type: 'error', message }) }) - .finally(() => { - }) + .finally(() => { + }) } + const handleAddStudent = () => { + // TODO: get user id by getting email and calling /users --> search through /users --> + // RequestService.put(`/api/courses/${courseId}/users-courses/${id}:`, + + } + + const handleDropStudent = () => { + + + } - return ( - -

Course Detail Update

-
- +

Update Course Form

+
+
+

Course Details

+ - - + + - -
-
- -
- + helpText={invalidFields.get("semester")} /> +
+
+ +
+ +
+
+ +
+ +
-
- -
- +
+
+ {/* */} +
-
- -
- +
+

Add/Drop Students

+ +
+ + +
-
- + ) } diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss index 6c283fb1..46302a24 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss @@ -1,26 +1,37 @@ @import 'variables'; +.courseFormWrapper { + display: flex; + margin: 16px 50px; + gap: 30px; +} + .form { background-color: $list-item-background; - border-radius: 10px; + border-radius: 20px; padding: 30px; width: 50%; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - + margin: 0 auto; } -.datepickerContainer { - display: flex; - justify-content: space-between; +.detailsForm { + @extend .form; + // background-color: $list-item-background; + // border-radius: 20px; + // padding: 30px; + width: 70%; + margin: 0; } +.addDropForm { + @extend .form; + width: 30%; + margin: 0; +} -.submitBtn { +.datepickerContainer { display: flex; - justify-content: center; + justify-content: space-between; } .datepicker { From 65e52d5061ed09c8da6c5567dfda3a366d515bfb Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 8 Oct 2024 13:23:13 -0400 Subject: [PATCH 165/400] Added file deletion from attachments Added file deletion from attachments so that you may remove attachments when needed. --- .../forms/assignments/assignmentFormPage.scss | 21 +++++++ .../forms/assignments/assignmentFormPage.tsx | 63 ++++++++++++++----- 2 files changed, 67 insertions(+), 17 deletions(-) diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index ce238c0b..0501de86 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -61,6 +61,10 @@ p { margin-right: auto; } +.dragDropFileComponent { + margin-top: 10%; +} + .textField1 { width: 50%; margin-right: 5%; @@ -96,4 +100,21 @@ p { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + display: flex; + justify-content: center; + align-items: center; +} + +.fileRemovalButton { + background: none; + border: none; + color: $text-color; + cursor: pointer; + font-size: 0.75em; + + + &:hover { + text-decoration: dashed; + color: red; + } } diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index b7945d2e..0d238fa3 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -41,7 +41,7 @@ const AssignmentCreatePage = () => { const [dueDate, setDueDate] = useState(new Date()) const [startDate, setStartDate] = useState(new Date()) const [invalidFields, setInvalidFields] = useState(new Map()) - const [files, setFiles] = useState(new Map()) + const [files, setFiles] = useState>(new Map()) useEffect(() => { const observer = new MutationObserver(() => setTheme(getCssVariables())) @@ -117,15 +117,34 @@ const AssignmentCreatePage = () => { } - const handleFile = (file : File) => { - if(Object.keys(files).length < 5){ - setFiles(prevState => ({...prevState, [file.name] : file})) + // const handleFile = (file : File) => { + // if(Object.keys(files).length < 5){ + // setFiles(prevState => ({...prevState, [file.name] : file})) + // } else { + // //TODO: Add alert + // console.log('Max files reached') + // } + // console.log(files) + // } + + const handleFile = (file: File) => { + if (files.size < 5) { + setFiles(prevState => new Map(prevState).set(file.name, file)); } else { - //TODO: Add alert - console.log('Max files reached') + // TODO: Add alert + console.log('Max files reached'); } - console.log(files) - } + console.log(files); + }; + + const handleFileRemoval = (e: React.MouseEvent) => { + const key = e.currentTarget.id; + setFiles(prevState => { + const newFiles = new Map(prevState); + newFiles.delete(key); + return newFiles; + }); + }; return( @@ -233,15 +252,25 @@ const AssignmentCreatePage = () => { {/*TODO: Whenever file uploads is available on backend, store the files + create Object URLs*/}

Attachments

Add up to 5 attachments with this assignment:

-

Files Uploaded:

- {Object.keys(files).map((file) => { - return ( -
-

{file}

-
- ); - })} - +

Files Uploaded (click to delete) :

+
+ {Array.from(files.keys()).map((fileName) => { + return ( +
+ +
+ ); + })} +
+
+ +
From 3004dba8403968d6c8cfc77c82ea56b69b7f3f16 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:23:16 -0400 Subject: [PATCH 166/400] updated text field styling and colors, 59 --- devU-client/src/assets/global.scss | 18 +++++++++++++++--- .../pages/forms/courses/courseUpdatePage.tsx | 16 +++++++--------- .../pages/forms/courses/coursesFormPage.scss | 5 ++--- .../src/components/shared/inputs/textField.tsx | 10 ++++++---- devU-client/src/utils/theme.utils.ts | 4 ++++ 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 9d3f5730..4ea9158e 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -15,6 +15,11 @@ h1 { margin: 2.25rem auto; + text-align: center; + } + + h2 { + text-align: center; } // general button template, extends to 3 types of buttons - primary, secondary, delete @@ -82,10 +87,14 @@ --btn-delete-background: #FFF; --btn-delete-text: var(--red); + --input-field-background: var(--grey-lightest); + --input-field-label: var(--grey); + // Non theme colors - will not update with light/dark toggle - --grey-lightest: #dfe6e9; - --grey-lighter: #b2bec3; - --grey: #636e72; + --grey-lightest: #e5e5e5; + --grey-lighter: #c5c5c5; + --grey: #555555; + --grey-dark: #333333; --blue-lighter: #74b9ff; --blue: #0984e3; @@ -133,6 +142,9 @@ --btn-delete-background: var(--red); --btn-delete-text: #FFF; + --input-field-background: var(--grey); + --input-field-label: var(--grey-lightest); + background-color: var(--background); color: var(--text-color); } diff --git a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx index ca5bee15..80203263 100644 --- a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx @@ -110,34 +110,32 @@ const CourseUpdatePage = ({ }) => {
-
+
-
+ {/*
*/}
-
+
-
+ {/*
*/}
-
- {/* */}

Add/Drop Students

-
+ placeholder='e.g. hartloff' invalidated={!!invalidFields.get("ubit")} helpText={invalidFields.get("ubit")} /> +
diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss index 46302a24..7e17dcc1 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss @@ -16,9 +16,6 @@ .detailsForm { @extend .form; - // background-color: $list-item-background; - // border-radius: 20px; - // padding: 30px; width: 70%; margin: 0; } @@ -27,6 +24,8 @@ @extend .form; width: 30%; margin: 0; + display: flex; + flex-direction: column; } .datepickerContainer { diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 3b2d059d..9b602bfd 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -52,7 +52,7 @@ const TextField = ({ if (onChange) onChange(e.target.value, e) } - const { textColor } = theme + // const { textColor } = theme return (
@@ -71,15 +71,17 @@ const TextField = ({ ...sx, // input field text "& .MuiOutlinedInput-input" : { - color: textColor + color: theme.textColor, + backgroundColor: theme.inputFieldBackground, + borderRadius: '100px', }, // label text "& .MuiInputLabel-outlined" : { - color: textColor + color: theme.inputFieldLabel, }, // border "& .MuiOutlinedInput-notchedOutline" : { - borderColor: textColor + border: 'none', }, }} diff --git a/devU-client/src/utils/theme.utils.ts b/devU-client/src/utils/theme.utils.ts index 3c335179..95691c36 100644 --- a/devU-client/src/utils/theme.utils.ts +++ b/devU-client/src/utils/theme.utils.ts @@ -57,12 +57,16 @@ export function getCssVariables() { secondary: body.getPropertyValue('--secondary'), secondaryDarker: body.getPropertyValue('--secondary-darker'), + inputFieldBackground: body.getPropertyValue('--input-field-background'), + inputFieldLabel: body.getPropertyValue('--input-field-label'), + focus: body.getPropertyValue('--focus'), // Other CSS colors greyLightest: body.getPropertyValue('--grey-lightest'), greyLighter: body.getPropertyValue('--grey-lighter'), grey: body.getPropertyValue('--grey'), + greyDark: body.getPropertyValue('--grey-dark'), blueLighter: body.getPropertyValue('--blue-lighter'), blue: body.getPropertyValue('--blue'), From 8c52567cad421d734871c09c349b2a7ea717e78a Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 8 Oct 2024 13:35:28 -0400 Subject: [PATCH 167/400] Updated fontsize for filenames Updated fontsize for filenames so that it appears larger --- .../components/pages/forms/assignments/assignmentFormPage.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index 0501de86..6f41b55c 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -110,7 +110,7 @@ p { border: none; color: $text-color; cursor: pointer; - font-size: 0.75em; + font-size: 0.9em; &:hover { From aed31d26c95763d8f83a2ca38fab4559f720fcf1 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Tue, 8 Oct 2024 15:23:36 -0400 Subject: [PATCH 168/400] updated color themes for user dropdown to match the theme of styling --- devU-client/src/assets/global.scss | 4 ++-- devU-client/src/components/misc/globalToolbar.scss | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 003f1dfc..258aa886 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -28,7 +28,7 @@ --focus: #38c172; --primary-lighter: #41a8f7; - --primary: #0984e3; + --primary: #D9D9D9; --primary-darker: #054272; --secondary-lighter: #cfd1d1; --secondary: #a8abab; @@ -70,7 +70,7 @@ --text-color-secondary: #9b9b9b; --primary-lighter: #3796bc; - --primary: #266781; + --primary: #636666; --primary-darker: #133441; --secondary-lighter: #636666; --secondary: #3d3f3f; diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 6ee2c966..9a5f1c31 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -72,7 +72,7 @@ $font-size: 16px; height: 100%; width: $sidebar-width; - background: $primary; + background: $text-color; top: 0; left: -$sidebar-width; From c7b5dade74086eb2e2bcdaa856fd8b5a8113c54f Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:37:15 -0400 Subject: [PATCH 169/400] added date inputs and made page responsive, 59 --- devU-client/src/assets/variables.scss | 3 ++ .../pages/forms/courses/courseUpdatePage.tsx | 53 +++++++++---------- .../pages/forms/courses/coursesFormPage.scss | 36 +++++++++++++ .../components/shared/inputs/textField.tsx | 2 + 4 files changed, 66 insertions(+), 28 deletions(-) diff --git a/devU-client/src/assets/variables.scss b/devU-client/src/assets/variables.scss index da44a0ac..add6703a 100644 --- a/devU-client/src/assets/variables.scss +++ b/devU-client/src/assets/variables.scss @@ -25,6 +25,9 @@ $list-simple-item-background: var(--list-simple-item-background); $list-simple-item-background-hover: var(--list-simple-item-background-hover); $list-simple-item-subtext: var(--list-item-subtext); +$input-field-background: var(--input-field-background); +$input-field-label: var(--input-field-label); + $focus: var(--focus); // These variables WILL NOT update with dark vs light theme diff --git a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx index 80203263..8d6d189e 100644 --- a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx @@ -4,7 +4,7 @@ import { useHistory, useParams } from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' import RequestService from 'services/request.service' -import DatePicker from 'react-datepicker' +// import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' import { ExpressValidationError } from 'devu-shared-modules' @@ -32,8 +32,8 @@ const CourseUpdatePage = ({ }) => { number: '', semester: '', }) - const [startDate, setStartDate] = useState(new Date()) - const [endDate, setEndDate] = useState(new Date()) + const [startDate, setStartDate] = useState(new Date().toISOString().split("T")[0]) + const [endDate, setEndDate] = useState(new Date().toISOString().split("T")[0]) const [invalidFields, setInvalidFields] = useState(new Map()) const { courseId } = useParams() as UrlParams @@ -46,8 +46,8 @@ const CourseUpdatePage = ({ }) => { number: res.number, semester: res.semester, }); - setStartDate(new Date(res.startDate)); - setEndDate(new Date(res.endDate)); + setStartDate(new Date(res.startDate).toISOString().split("T")[0]); + setEndDate(new Date(res.endDate).toISOString().split("T")[0]); isMounted = true; }); } @@ -58,16 +58,16 @@ const CourseUpdatePage = ({ }) => { setInvalidFields(newInvalidFields) setFormData(prevState => ({ ...prevState, [key]: value })) } - const handleStartDateChange = (date: Date) => { setStartDate(date) } - const handleEndDateChange = (date: Date) => { setEndDate(date) } + const handleStartDateChange = (event: React.ChangeEvent) => { setStartDate(event.target.value) } + const handleEndDateChange = (event: React.ChangeEvent) => { setEndDate(event.target.value) } const handleCourseUpdate = () => { const finalFormData = { name: formData.name, number: formData.number, semester: formData.semester, - startDate: startDate.toISOString(), - endDate: endDate.toISOString(), + startDate: startDate, + endDate: endDate, } RequestService.put(`/api/courses/${courseId}`, finalFormData) @@ -104,27 +104,24 @@ const CourseUpdatePage = ({ }) => {

Course Details

- - - +
+ + + +
-
- - {/*
*/} - +
+ +
-
- - {/*
*/} - +
+ +
diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss index 7e17dcc1..05835b1e 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss @@ -28,9 +28,17 @@ flex-direction: column; } +.inputContainer { + display: flex; + flex-direction: column; + gap: 15px; +} + .datepickerContainer { display: flex; justify-content: space-between; + margin: 10px auto; + gap: 10px } .datepicker { @@ -46,4 +54,32 @@ padding: 16.5px 14px; border: 1px solid #bbbbbb; border-radius: 4px; +} + +input[type='date'] { + height: 20px; + background-color: $input-field-background; + color: $input-field-label; + padding: 0.625rem 1rem; + border: none; + border-radius: 100px; +} + +// MEDIA QUERIES + +@media (max-width: 800px) { + .courseFormWrapper { + margin: 16px; + flex-direction: column; + } + + .detailsForm, .addDropForm { + width: auto; // take up full container + } +} + +@media (max-width: 450px) { + .datepickerContainer { + flex-direction: column; + } } \ No newline at end of file diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 9b602bfd..2c643009 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -74,6 +74,8 @@ const TextField = ({ color: theme.textColor, backgroundColor: theme.inputFieldBackground, borderRadius: '100px', + // padding: '0.625rem 1rem', + marginBottom: '0px' }, // label text "& .MuiInputLabel-outlined" : { From a6c4dd21d5e805e499b98c47b124696709f34ad9 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:50:47 -0400 Subject: [PATCH 170/400] added file input button 59 --- .../components/pages/forms/courses/courseUpdatePage.tsx | 1 + .../components/pages/forms/courses/coursesFormPage.scss | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx index 8d6d189e..5d3787dc 100644 --- a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx @@ -132,6 +132,7 @@ const CourseUpdatePage = ({ }) => {

Add/Drop Students

+
diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss index 05835b1e..67877a28 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss @@ -65,8 +65,15 @@ input[type='date'] { border-radius: 100px; } -// MEDIA QUERIES +input[type='file']::file-selector-button { + background-color: $primary; + border-radius: 100px; + color: #fff; + border: none; + padding: 5px 10px; +} +// MEDIA QUERIES @media (max-width: 800px) { .courseFormWrapper { margin: 16px; From e00ae4590ccbcd10a373dde4fdfdaedf7ab2fa9c Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 8 Oct 2024 15:57:47 -0400 Subject: [PATCH 171/400] #71 : Task2 MAke changes to the frontend on the courseDetailPage.tsx(Course Page). Redid the colors, changes from columns to rows,added buttons. --- devU-client/src/assets/global.scss | 9 +- devU-client/src/assets/variables.scss | 2 +- .../components/listItems/courseListItem.scss | 10 +- .../components/listItems/courseListItem.tsx | 2 + .../src/components/misc/globalToolbar.scss | 31 +++-- .../pages/courses/courseDetailPage.scss | 124 +++++++++++++----- .../pages/courses/courseDetailPage.tsx | 123 +++++++++-------- .../pages/courses/coursePreviewPage.tsx | 1 - 8 files changed, 197 insertions(+), 105 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index c2d7b4f4..333e6f70 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -24,12 +24,13 @@ --background: white; --text-color: black; + --text-color2: white; --text-color-secondary: #363636; --focus: #38c172; --primary-lighter: #41a8f7; - --primary: #0984e3; - --primary-darker: #054272; + --primary: #ac9bd8; + --primary-darker: #5c36c3; --secondary-lighter: #cfd1d1; --secondary: #a8abab; --secondary-darker: #686b6b; @@ -70,8 +71,8 @@ --text-color-secondary: #9b9b9b; --primary-lighter: #3796bc; - --primary: #266781; - --primary-darker: #133441; + --primary: #5c36c3; + --primary-darker: rgba(95, 16, 187, 0.36); --secondary-lighter: #636666; --secondary: #3d3f3f; --secondary-darker: #1f2020; diff --git a/devU-client/src/assets/variables.scss b/devU-client/src/assets/variables.scss index faafef3e..7f621eab 100644 --- a/devU-client/src/assets/variables.scss +++ b/devU-client/src/assets/variables.scss @@ -8,7 +8,7 @@ $background: var(--background); $text-color: var(--text-color); $text-color-secondary: var(--text-color-secondary); - +$text-color2 : var(--text-color2); $primary-lighter: var(--primary-lighter); $primary: var(--primary); $primary-darker: var(--primary-darker); diff --git a/devU-client/src/components/listItems/courseListItem.scss b/devU-client/src/components/listItems/courseListItem.scss index 9e92481c..c5d73f65 100644 --- a/devU-client/src/components/listItems/courseListItem.scss +++ b/devU-client/src/components/listItems/courseListItem.scss @@ -2,8 +2,14 @@ .name { font-size: 1.2rem; - font-weight: 700; - margin-bottom: 0.4rem; + font-weight: 600; + margin-bottom: 0; + padding:15px; + width:100%; + text-align:center; + box-sizing:border-box; + + } .tag { diff --git a/devU-client/src/components/listItems/courseListItem.tsx b/devU-client/src/components/listItems/courseListItem.tsx index b6f0af1f..368ac2fd 100644 --- a/devU-client/src/components/listItems/courseListItem.tsx +++ b/devU-client/src/components/listItems/courseListItem.tsx @@ -30,6 +30,8 @@ const CourseListItem = ({course, isOpen}: Props) => { setIsOpen(isOpen); }, [isOpen]); + + return (
diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 9c3cf54a..8341f5c1 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -18,10 +18,18 @@ $font-size: 16px; text-decoration: none; - color: $text-color; + color: #d9d9d9; font-size: $font-size; - height: $bar-height; + //height: $bar-height; + + margin:3vw; + height: 4vh; + flex-grow: .05; + border-radius: 3vw; + font-weight: 550; + //font-size: 14px; + padding:0.5vw; &:hover { opacity: $hover-effect; @@ -30,14 +38,19 @@ $font-size: 16px; .header { @extend .link; + font-size: 40px; + border-radius: 30px; + color: #D9D9D9; + font-weight: 550; + //font-size: 2em; - font-size: 2em; - - font-weight: bold; + //font-weight: bold; } .bar { - height: $bar-height; + height: 80px; + background-color: $purple; + //height: $bar-height; @extend .flex; justify-content: space-between; @@ -55,7 +68,7 @@ $font-size: 16px; // Controls turning the menu options into a sidebar // As well as whether or not that sidebar is being shown -@media (max-width: $medium) { +@media (max-width: 300px) { .flex { gap: 1rem; } @@ -83,7 +96,7 @@ $font-size: 16px; background: transparent; border: none; - color: $text-color; + color: $yellow; font-size: $font-size; &:hover { @@ -99,7 +112,7 @@ $font-size: 16px; } } -@media (max-width: $extreme) { +@media (max-width: 300px) { .flex { gap: 0.3rem; } diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index 91f81398..9d7c387a 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -1,13 +1,18 @@ @import 'variables'; - +.courseFormWrapper { + display: flex; + margin: 16px 50px; + gap: 30px; +} .categoriesContainer { display: grid; grid-template-rows: repeat(3, 1fr); - gap: 20px; /* adjust this value to set the space between the cards */ - flex-direction: row; - justify-content: space-between; + gap: 200px; /* adjust this value to set the space between the cards */ + margin-bottom: 20px; + min-height: 200px; + max-height: 500px; } /*.header { @@ -15,65 +20,116 @@ align-items: center; } */ -.smallLine { +/*.smallLine { width: 50px; /* adjust this value to set the length of the small line */ - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-right: 10px; /* adjust this value to set the space between the line and the text */ -} + // border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ + // margin-right: 10px; /* adjust this value to set the space between the line and the text */ +/*} .largeLine { flex-grow: 1; border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-left: 10px; /* adjust this value to set the space between the line and the text */ - margin-right: 10px; /* add this line to create some space between the line and the button */ + // +// margin-left: 10px; /* adjust this value to set the space between the line and the text */ + // margin-right: 10px; /* add this line to create some space between the line and the button */ +//}*/ + +.actual_button{ + display: flex; + flex-direction:row; + border:0; /* Primary button color */ + /* Button text color */ + padding: 10px 20px; /* Padding for button */ + border-radius: 5px; + text-decoration: none; + font-weight: 600; + transition: background-color 0.3s ease; + //margin : 0 10px; + background-color: $purple; + color:white; } +.buttons-container { + display: flex; + flex-direction: row; + justify-content: space-around; +} + + + .buttons { margin : 0 10px; + + /* &:hover {} + color: $primary; + */ + } .color{ background-color: $purple; color: $text-color2; - max-width : 345px; + width: 100%; + padding: 20px; + text-align: center; + +} +.coursesContainer { + display: flex; + flex-direction: row; + gap: 20px; + margin:20px; +} +.courseCard { + display: flex; + flex:1; + flex-direction: column; + justify-content: space-between; + align-items: stretch; + height: 100%; + padding: 0; + background-color: #D9D9D9; + border-radius: 5px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + overflow: hidden; + margin-bottom: 20px; } .header { - display: block; - justify-content: space-around; - align-items: flex-start; - background-color: $grey; - padding: 30px 50px; + display: flex; + flex-direction: column; + justify-content: flex-start; + //grid-template-columns: 2fr 1fr; + background-color: $secondary; + padding: 30px 60px; border-radius: 10px; - color: $text-color -} + color: $text-color; + width: 70%; -.logo { - font-size: 24px; - font-weight: bold; - color: white; -} + } -input { + + + + + + + +.input { width: 400px; padding: 8px; border-radius: 5px; border: none; } -/*.edit-course-button { - background-color: $grey; - color: white; - padding: 10px 15px; - border-radius: 5px; -}*/ + .assignment_card { - background-color: $text-color-secondary; - padding: 20px; + flex: 1; + color: $text-color2; + align-items: start; text-align: center; border-radius: 10px; - transition: transform 0.2s; - max-width : 345px; + min-width : 200px; } diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 1794e863..74cc81bf 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -2,7 +2,7 @@ import React, {useEffect, useState} from 'react' import {useHistory, useParams} from 'react-router-dom' import RequestService from 'services/request.service' import {Assignment, Course} from 'devu-shared-modules' - +//import {useHistory} from "react-router-dom"; import PageWrapper from 'components/shared/layouts/pageWrapper' import Card from '@mui/material/Card' @@ -12,23 +12,25 @@ import List from '@mui/material/List' import ListItem from '@mui/material/ListItem' import ListItemButton from '@mui/material/ListItemButton' import ListItemText from '@mui/material/ListItemText' -import Button from '@mui/material/Button' -import Stack from '@mui/material/Stack' +//import Button from '@mui/material/Button' +//import Stack from '@mui/material/Stack' import styles from './courseDetailPage.scss' import {SET_ALERT} from "../../../redux/types/active.types"; -import {useActionless, useAppSelector} from "redux/hooks"; +import {useActionless} from "../../../redux/hooks"; +//import TextField from "../../shared/inputs/textField"; +//import {useActionless, useAppSelector} from "redux/hooks"; const CourseDetailPage = () => { - const history = useHistory() + //const history = useHistory() const { courseId } = useParams<{courseId: string}>() const [courseInfo, setCourseInfo] = useState(null) const [categoryMap, setCategoryMap] = useState>({}) const [setAlert] = useActionless(SET_ALERT) - const role = useAppSelector((store) => store.roleMode) + // const role = useAppSelector((store) => store.roleMode) const fetchCourseInfo = async () => { RequestService.get(`/api/courses/${courseId}`) .then((course) => { @@ -50,6 +52,7 @@ const CourseDetailPage = () => { setCategoryMap(categoryMap) }) + } @@ -69,69 +72,81 @@ const CourseDetailPage = () => { useEffect(() => { fetchCourseInfo() }, []) - + const history = useHistory() return( + {courseInfo ? (
-
-

{courseInfo.name}

+
+

{courseInfo.name}

+

Section:

+
-

Section:

+ - - - {role.isInstructor() && - } + }}>Add Assignment + + - {role.isInstructor() && - } + }}>Course WebSite + + +
+
- - -
-
- {Object.keys(categoryMap).map((category, index) => ( -
- - - - {category} - - - - {categoryMap[category].map((assignment, index) => ( - - { - history.push(`/course/${courseId}/assignment/${assignment.id}`) - }}> - - - +
+ + + {Object.keys(categoryMap).map((category, index) => ( + + + + + {category} + + + + {categoryMap[category].map((assignment, index) => ( + + { + history.push(`/course/${courseId}/assignment/${assignment.id}`) + }}> + + + + ))} + + + ))} - - +
- ))}
-
- ) : ( -

Error fetching Course Information

- )} - - ) -} - -export default CourseDetailPage \ No newline at end of file + + + + + ) : ( +

Error fetching Course Information

+ )} + + ) + } + + + export default CourseDetailPage \ No newline at end of file diff --git a/devU-client/src/components/pages/courses/coursePreviewPage.tsx b/devU-client/src/components/pages/courses/coursePreviewPage.tsx index a93b6b0f..9aa40aca 100644 --- a/devU-client/src/components/pages/courses/coursePreviewPage.tsx +++ b/devU-client/src/components/pages/courses/coursePreviewPage.tsx @@ -89,7 +89,6 @@ const CoursePreviewPage = () => {

{course?.name}

{course?.number}

{course?.semester}

-

Instructor: Jesse Hartloff

) From d7d1db77b56e7d904a88d09ee645b6a3963a8da7 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Tue, 8 Oct 2024 18:26:15 -0400 Subject: [PATCH 172/400] tested to make sure that the upcoming course feature worked --- .../components/pages/homePage/homePage.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index cd3afaaa..d52b22e6 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -16,6 +16,7 @@ const HomePage = () => { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [enrollCourses, setEnrollCourses] = useState(new Array()) + const [upcomingCourses,setupcomingCourses] = useState(new Array()) const [pastCourses, setPastCourses] = useState(new Array()) const [assignments, setAssignments] = useState(new Map>()) const [instructorCourses, setInstructorCourses] = useState(new Array()) @@ -35,7 +36,7 @@ const HomePage = () => { upcomingCourses: Course[];//TODO: Add upcoming courses feature }>(`/api/courses/user/${userId}`); const enrolledCourses: Course[] = allCourses.activeCourses; - + const upcomingCourses: Course[] = allCourses.upcomingCourses; const pastCourses: Course[] = allCourses.pastCourses; const instructorCourses: Course[] = allCourses.instructorCourses; @@ -50,6 +51,7 @@ const HomePage = () => { setPastCourses(pastCourses) setEnrollCourses(enrolledCourses) setInstructorCourses(instructorCourses) + setupcomingCourses(upcomingCourses) } catch (error: any) { setError(error) @@ -120,8 +122,19 @@ const HomePage = () => {

Upcoming

-
- {

No upcoming courses

} +
+ +
+ {upcomingCourses && upcomingCourses.map((course) => ( +
+ +
+ ))} + {upcomingCourses.length === 0 &&

No upcoming Courses

} +
+ + + ) } From 466e1f0b13ba1fc8631dafec5b5329d4caf270e3 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Thu, 10 Oct 2024 03:30:32 -0400 Subject: [PATCH 173/400] fixed global colors overwritten on edit course from other branches being merged, 59 --- devU-client/src/assets/global.scss | 29 ++++++++++--------- .../pages/forms/courses/courseUpdatePage.tsx | 7 ++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 0bfb7484..d1fc074b 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -59,16 +59,16 @@ background-color: var(--background); color: var(--text-color); - --background: white; - --text-color: #52468A; + --background: #FFF; + --text-color: #000; --text-color-secondary: #363636; --focus: #38c172; --primary-lighter: #41a8f7; -// --primary: #52468A; + --primary: var(--purple); - --primary: #D9D9D9; + // --primary: #D9D9D9; --primary-darker: #054272; --secondary-lighter: #cfd1d1; @@ -100,35 +100,36 @@ --grey: #555555; --grey-dark: #333333; - --blue-lighter: #74b9ff; - --blue: #0984e3; + --blue-lighter: #78B7FF; + --blue: #1F3D7A; --red-lighter: #FFA3A3; --red: #8A2626; - --purple-lighter: #a29bfe; - --purple: #6c5ce7; + --purple-lighter: #7257EB; + --purple: #52468A; + --purple-darker: #2F2363; --green-lighter: #B0EEA2; --green: #306025; --yellow-lighter: #ffeaa7; - --yellow: #fdcb6e; + --yellow: #F8D487; transition: background-color 150ms linear; } body.dark-mode { - --background: #1b1f20; - --text-color: #D9D9D9; + --background: #1e1e1e; + --text-color: #FFF; --text-color-secondary: #9b9b9b; --primary-lighter: #3796bc; -// --primary: #7257EB; + --primary: #7257EB; // --primary-darker: #2F2363; - --primary: #636666; + // --primary: #636666; --primary-darker: #133441; --secondary-lighter: #636666; @@ -144,7 +145,7 @@ --list-simple-item-subtext: #4b4b4b; --btn-secondary-border: var(--primary); - --btn-secondary-background: var(--primary-darker); + --btn-secondary-background: var(--purple-darker); --btn-secondary-text: #FFF; --btn-delete-border: var(--red-lighter); diff --git a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx index 5d3787dc..84367aaf 100644 --- a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx @@ -88,13 +88,12 @@ const CourseUpdatePage = ({ }) => { const handleAddStudent = () => { // TODO: get user id by getting email and calling /users --> search through /users --> - // RequestService.put(`/api/courses/${courseId}/users-courses/${id}:`, - + // RequestService.post(`/api/courses/${courseId}/users-courses/${id}:`, } const handleDropStudent = () => { - - + // get user id by getting email and calling /users --> search through /users --> + // RequestService.delete(`/api/courses/${courseId}/users-courses/${id}:`, } From abcd8d8e1ceb74c84b26c05c8e297bdb3e067500 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Thu, 10 Oct 2024 03:48:41 -0400 Subject: [PATCH 174/400] fixed focus color for text fields, 59 --- devU-client/src/assets/global.scss | 3 ++- devU-client/src/components/shared/inputs/textField.tsx | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index d1fc074b..fa61a3c7 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -62,7 +62,7 @@ --background: #FFF; --text-color: #000; --text-color-secondary: #363636; - --focus: #38c172; + --focus: var(--blue); --primary-lighter: #41a8f7; @@ -123,6 +123,7 @@ --background: #1e1e1e; --text-color: #FFF; --text-color-secondary: #9b9b9b; + --focus: var(--blue-lighter); --primary-lighter: #3796bc; diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 2c643009..e21a0f82 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -80,6 +80,9 @@ const TextField = ({ // label text "& .MuiInputLabel-outlined" : { color: theme.inputFieldLabel, + "&.Mui-focused": { + color: theme.focus, // Define this color in your theme + }, }, // border "& .MuiOutlinedInput-notchedOutline" : { From 7e436ccc8cbf8e2cf5680faac892d198da1ec017 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Sat, 12 Oct 2024 01:45:11 -0400 Subject: [PATCH 175/400] added filetype to jsx and added notes for implementing mass add/drop logic --- .../pages/forms/courses/courseUpdatePage.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx index 5d3787dc..fd40ea07 100644 --- a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx @@ -86,10 +86,15 @@ const CourseUpdatePage = ({ }) => { }) } + // const handleFileUpload = () => { + // if file uploaded parse file to grab all student emails + // note: accepted filetype (enforced by frontend) is csv + // for each email parsed from file, call handle add or drop student as needed + // } + const handleAddStudent = () => { // TODO: get user id by getting email and calling /users --> search through /users --> - // RequestService.put(`/api/courses/${courseId}/users-courses/${id}:`, - + // RequestService.post(`/api/courses/${courseId}/users-courses/${id}:`, } const handleDropStudent = () => { @@ -132,7 +137,9 @@ const CourseUpdatePage = ({ }) => {

Add/Drop Students

- + + {/* csv should be a good standard filetype */} +
From 573a423196432cad2c9c65161e75e97737c8a20c Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Sat, 12 Oct 2024 02:46:32 -0400 Subject: [PATCH 176/400] added logic for assigning different classes to odd and even table rows --- .../pages/gradebook/gradebookInstructorPage.tsx | 15 +++++++++------ .../components/pages/gradebook/gradebookPage.scss | 4 ++++ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx index 44575ab3..06736620 100644 --- a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx @@ -18,7 +18,7 @@ type TableProps = { assignmentScores: AssignmentScore[] } type RowProps = { - index: Number + index: number user: User userCourse: UserCourse assignments: Assignment[] @@ -26,8 +26,11 @@ type RowProps = { } const TableRow = ({index, user, userCourse, assignments, assignmentScores}: RowProps) => { + // TODO: style table row to alternating colors based on index odd?even + const rowClass = index % 2 === 0 ? 'even-row' : 'odd-row'; + return ( - + {index} {user.email} {user.externalId} @@ -42,7 +45,7 @@ const TableRow = ({index, user, userCourse, assignments, assignmentScores}: RowP const GradebookTable = ({users, userCourses, assignments, assignmentScores}: TableProps) => { return ( - +
@@ -106,9 +109,9 @@ const GradebookInstructorPage = () => { return ( -
-

Instructor Gradebook

-
+ {/*
*/} +

Instructor Gradebook

+ {/*
*/}
Date: Sat, 12 Oct 2024 10:05:38 -0400 Subject: [PATCH 177/400] Test for Kevin --- .../pages/courses/courseDetailPage.scss | 126 ++++++++++-------- .../pages/courses/courseDetailPage.tsx | 42 +++++- 2 files changed, 105 insertions(+), 63 deletions(-) diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index 9d7c387a..1ce8e46f 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -15,56 +15,6 @@ max-height: 500px; } -/*.header { - display: flex; - align-items: center; -} -*/ -/*.smallLine { - width: 50px; /* adjust this value to set the length of the small line */ - // border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - // margin-right: 10px; /* adjust this value to set the space between the line and the text */ -/*} - -.largeLine { - flex-grow: 1; - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - // -// margin-left: 10px; /* adjust this value to set the space between the line and the text */ - // margin-right: 10px; /* add this line to create some space between the line and the button */ -//}*/ - -.actual_button{ - display: flex; - flex-direction:row; - border:0; /* Primary button color */ - /* Button text color */ - padding: 10px 20px; /* Padding for button */ - border-radius: 5px; - text-decoration: none; - font-weight: 600; - transition: background-color 0.3s ease; - //margin : 0 10px; - background-color: $purple; - color:white; -} - -.buttons-container { - display: flex; - flex-direction: row; - justify-content: space-around; -} - - - -.buttons { - margin : 0 10px; - - /* &:hover {} - color: $primary; - */ - -} .color{ background-color: $purple; @@ -72,17 +22,20 @@ width: 100%; padding: 20px; text-align: center; + font-size: 1.2em; } .coursesContainer { display: flex; - flex-direction: row; + flex-wrap: wrap; + //flex-direction: row; gap: 20px; - margin:20px; + margin-top:20px; } .courseCard { display: flex; - flex:1; + flex:1 0 250px; + max-width: 350px; flex-direction: column; justify-content: space-between; align-items: stretch; @@ -93,18 +46,77 @@ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); overflow: hidden; margin-bottom: 20px; -} + + .MuiList-root { + padding: 20px; + + + + .MuiListItemText-primary { + text-align: center; // Center the assignment name + } + + .MuiListItemText-secondary { + text-align: center; // Center the dates + } + } + } + + + +.courseDetailPage{ + padding: 20px; + .header { display: flex; flex-direction: column; - justify-content: flex-start; + //justify-content: flex-start; //grid-template-columns: 2fr 1fr; + justify-content: space-between; + align-items: flex-start; background-color: $secondary; - padding: 30px 60px; - border-radius: 10px; + // padding: 30px 60px; + padding: 20px; + //border-radius: 10px; color: $text-color; width: 70%; + h1{ + margin: 0 0 10px 0; + font-size: 2.5rem; + } + h2{ + margin: 0 0 20px 0; + font-size: 1.5rem; + } + .buttons-container { + display: flex; + // flex-wrap: wrap; + flex-direction: row; + justify-content: space-around; + + .actual_button { + display: flex; + flex-direction: row; + border: 0; /* Primary button color */ + /* Button text color */ + padding: 10px 20px; /* Padding for button */ + border-radius: 5px; + text-decoration: none; + font-weight: 600; + transition: background-color 0.3s ease; + //margin : 0 10px; + background-color: $purple; + color: white; + border: none; + + &:hover { // Add a hover effect + background-color: darken(purple, 10%); // Slightly darken on hover + cursor: pointer; + } + } + } +} } diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 5ae26e99..dd020895 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -30,7 +30,18 @@ const CourseDetailPage = () => { const [courseInfo, setCourseInfo] = useState(null) const [categoryMap, setCategoryMap] = useState>({}) const [setAlert] = useActionless(SET_ALERT) + const[User, setUser]= useState < User ,preferredName>>({}) + // const role = useAppSelector((store) => store.roleMode) + const fetchUserinfo = async () => { + RequestService.get< typeof User>('api/users') + .then((User) =>{ + setUser(User) + + }) + + + const fetchCourseInfo = async () => { RequestService.get(`/api/courses/${courseId}`) .then((course) => { @@ -74,16 +85,19 @@ const CourseDetailPage = () => { useEffect(() => { fetchCourseInfo() + fetchUserinfo() }, []) const history = useHistory() return( +
{courseInfo ? (
+
-

{courseInfo.name}

-

Section:

+

{courseInfo.name}-{courseInfo.semester}

+

Instructor:{User.name}

@@ -112,14 +126,13 @@ const CourseDetailPage = () => {
-
- +
{Object.keys(categoryMap).map((category, index) => ( - + {category} @@ -129,7 +142,23 @@ const CourseDetailPage = () => { { history.push(`/course/${courseId}/assignment/${assignment.id}`) }}> - + + + Start Date: {new Date(assignment.startDate).toLocaleDateString()} +
{/* Add a line break */} + Due Date: {new Date(assignment.dueDate).toLocaleDateString()} +
+ + } + /> +
))} @@ -147,6 +176,7 @@ const CourseDetailPage = () => { ) : (

Error fetching Course Information

)} +
) } From 0dd5c4f4728184c62337090448b4162197143418 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Sat, 12 Oct 2024 10:53:57 -0400 Subject: [PATCH 178/400] assignments for instructors were added because they were missing before. Now they will display in the home page. I fetched the assignments in the same matter that enrolled courses were being fetched in the homepage --- .DS_Store | Bin 8196 -> 8196 bytes .../listItems/userCourseListItem.tsx | 4 ++-- .../components/pages/homePage/homePage.tsx | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.DS_Store b/.DS_Store index 1707d5e29146272db9d5aa750d835e9fd213dc9c..6693e129e9a19e665c34da57105c0ff3a6d01bca 100644 GIT binary patch delta 224 zcmZp1XmQw}DiD_^`kjG+frUYjA)O(Up(Hoo#U&{xKM5$tp>b?)^OvY&j;Qh}c;yQ+ z41<&Na|?ia7#J!)Ol}rXWKy0!d6|G5>+9rXgC%^;eDKjN7OimCKnXDnm z&-fO|5}WKN*v8}|K6#Cx)aDO@CX6hWbp`h(YY0auSWYnj8Oy?u!;lYjZgEaJl4T5^ SA}22pj$~qF*!)|VlLr9i|3PK| delta 224 zcmZp1XmQw}DiD_&bf1BNfrUYjA)O(Up(Hoo#U&{xKM5$tVVuVNV{^nYM^yO~yz&JZ zhQZ1CxdlKy3=G8%lbZz;nfPKRFB6bsbrJpDw0ZJ-0ny3#1bE;)WhMuP$q9lYlQjhS z8FPUwvB`ddZA{kElh+7JZT=u=!pOok-(~Y;4dDm{uEhyJQ-O}nVaNwMw>T#q$ub7F S$&(idM=~)4Z2m3G$pZlSc|BnO diff --git a/devU-client/src/components/listItems/userCourseListItem.tsx b/devU-client/src/components/listItems/userCourseListItem.tsx index 179c58e0..18d5e668 100644 --- a/devU-client/src/components/listItems/userCourseListItem.tsx +++ b/devU-client/src/components/listItems/userCourseListItem.tsx @@ -23,11 +23,11 @@ const UserCourseListItem = ({course, assignments, past = false, instructor = fal return( -
{instructor ? (course.name + " (Instructor)") : course.name.toUpperCase() + " " + course.number + " " + "(" + course.semester + ")" }
+
{instructor ? (course.name + " " + course.number + " (" + course.semester + ")" + " Instructor") : course.name.toUpperCase() + " " + course.number + " " + "(" + course.semester + ")" }
{assignments && assignments.length > 0 ? (assignments.map((assignment) => ( - ))) : ((past || instructor) ?
:
No Assignments Due Yet
)} + ))) : ((past) ?
:
No Assignments Due Yet
)}
- {instructorCourses && instructorCourses.map((course) => ( + {instructorCourses.map((course) => (
+ instructor = {true}/>
))}
From 86a10d82ce24d0a88bb769ed9b171cda6a244129 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sat, 12 Oct 2024 11:32:36 -0400 Subject: [PATCH 179/400] fixed broken api dev environment --- devU-api/package.json | 7 ++--- docker-compose.yml | 70 +++++++++++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/devU-api/package.json b/devU-api/package.json index 19003969..303e3eb7 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -3,8 +3,8 @@ "version": "1.0.0", "description": "DevU API", "scripts": { - "start": "ts-node-dev src/index.ts", - "update-shared": "npm update devu-shared-modules", + "start": "npm run typeorm -- migration:run -d src/database.ts && ts-node-dev src/index.ts", + "update-shared": "cd ../devU-shared && npm run build-local && npm i", "typeorm": "typeorm-ts-node-commonjs", "test": "jest --passWithNoTests", "clean": "rimraf build/*", @@ -12,10 +12,9 @@ "build": "npm-run-all clean lint", "format": "prettier --write \"./**/*.{js,ts,json,md}\"", "pre-commit": "lint-staged", - "generate-config": "docker run --pull always -v $(pwd)/config:/config --user $(id -u):$(id -g) --rm ubautograding/devtools /generateConfig.sh config/default.yml", "populate-db": "ts-node-dev ./scripts/populate-db.ts", "tango": "ts-node-dev ./src/tango/tests/tango.endpoint.test.ts", - "api-services": "docker compose -f ../docker-compose.yml --profile dev-api up -d", + "api-services": "docker compose -f ../docker-compose.yml --profile dev-api up", "api-services-stop": "docker compose -f ../docker-compose.yml --profile dev-api stop" }, "lint-staged": { diff --git a/docker-compose.yml b/docker-compose.yml index e8083d87..24c8afa4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,5 @@ name: devu services: - db: - # Runs the PostgreSQL database - image: postgres - environment: - POSTGRES_DB: typescript_api - POSTGRES_USER: typescript_user - POSTGRES_PASSWORD: password - ports: - - '5432:5432' - expose: - - '5432' api: # Runs the API container_name: api @@ -24,7 +13,8 @@ services: - '3001:3001' profiles: - '' # so it starts with normal docker compose - - 'dev-client' + - 'dev-client' # start when developing client + client: # Builds the front end and exports static files to ./dist build: @@ -35,6 +25,7 @@ services: profiles: - '' # so it starts with normal docker compose - 'dev-api' # start when developing api + nginx: # Hosts the front end static files from ./dist/local thorough a web server build: @@ -46,7 +37,22 @@ services: - '9000:80' profiles: - '' # so it starts with normal docker compose - - 'dev-api' # start when developing api not when developing client + - 'dev-api' # start when developing api + + # Database stuff + db: + # Runs the PostgreSQL database + image: postgres + environment: + POSTGRES_DB: typescript_api + POSTGRES_USER: typescript_user + POSTGRES_PASSWORD: password + ports: + - '5432:5432' + expose: + - '5432' + restart: unless-stopped + minio: image: minio/minio ports: @@ -60,15 +66,19 @@ services: MINIO_ROOT_USER: typescript_user MINIO_ROOT_PASSWORD: changeMe command: server /data --console-address ":9001" + + config: + # Generates a config file for the API + build: + context: devU-api/scripts + command: "./generateConfig.sh config/default.yml" + image: ubautograding/devtools + volumes: + - ./devU-api/config:/config + profiles: + - 'dev-api' # start when developing api, not needed in production + # tango stuff - redis: - container_name: redis - image: redis:latest - ports: - - '127.0.0.1:6379:6379' - deploy: - replicas: 1 - restart: unless-stopped tango: container_name: tango ports: @@ -78,12 +88,11 @@ services: environment: - DOCKER_REDIS_HOSTNAME=redis - RESTFUL_KEY=devutangokey -# - DOCKER_DEPLOYMENT + # - DOCKER_DEPLOYMENT # Path to volumes within the Tango container. Does not need to be modified. -# - DOCKER_VOLUME_PATH + # - DOCKER_VOLUME_PATH # Modify the below to be the path to volumes on your host machine - DOCKER_TANGO_HOST_VOLUME_PATH=/absolute/path/to/tango_files - depends_on: - redis volumes: @@ -92,11 +101,20 @@ services: - ./logs/tango/:/var/log/tango/ - ./logs/tangonginx:/var/log/nginx - ./tango_files:/opt/TangoService/Tango/volumes + restart: unless-stopped + + redis: + container_name: redis + image: redis:latest + ports: + - '127.0.0.1:6379:6379' + deploy: + replicas: 1 + restart: unless-stopped -# certbot: + # certbot: # container_name: certbot # image: certbot/certbot # volumes: # - ./ssl/certbot/conf:/etc/letsencrypt # - ./ssl/certbot/www:/var/www/certbot - restart: unless-stopped \ No newline at end of file From 44144bc254ce9682832b317e4ce2c3fd0a2c28e3 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Sat, 12 Oct 2024 12:46:32 -0500 Subject: [PATCH 180/400] added alternate colors for table rows, changes on theme toggle, styled table headers and positioning --- devU-client/src/assets/global.scss | 9 +++++++- devU-client/src/assets/variables.scss | 3 +++ .../gradebook/gradebookInstructorPage.tsx | 6 ++--- .../pages/gradebook/gradebookPage.scss | 22 +++++++++++++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index fa61a3c7..b84a027b 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -94,11 +94,15 @@ --input-field-background: var(--grey-lightest); --input-field-label: var(--grey); + --table-row-even: var(--grey-lighter); + --table-row-odd: var(--grey-lightest); + // Non theme colors - will not update with light/dark toggle --grey-lightest: #e5e5e5; --grey-lighter: #c5c5c5; --grey: #555555; - --grey-dark: #333333; + --grey-darker: #444444; + --grey-darkest: #333333; --blue-lighter: #78B7FF; --blue: #1F3D7A; @@ -156,6 +160,9 @@ --input-field-background: var(--grey); --input-field-label: var(--grey-lightest); + --table-row-even: var(--grey-darker); + --table-row-odd: var(--grey-darkest); + background-color: var(--background); color: var(--text-color); } diff --git a/devU-client/src/assets/variables.scss b/devU-client/src/assets/variables.scss index add6703a..47f058df 100644 --- a/devU-client/src/assets/variables.scss +++ b/devU-client/src/assets/variables.scss @@ -28,6 +28,9 @@ $list-simple-item-subtext: var(--list-item-subtext); $input-field-background: var(--input-field-background); $input-field-label: var(--input-field-label); +$table-row-even: var(--table-row-even); +$table-row-odd: var(--table-row-odd); + $focus: var(--focus); // These variables WILL NOT update with dark vs light theme diff --git a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx index 06736620..f5690eca 100644 --- a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx @@ -27,10 +27,10 @@ type RowProps = { const TableRow = ({index, user, userCourse, assignments, assignmentScores}: RowProps) => { // TODO: style table row to alternating colors based on index odd?even - const rowClass = index % 2 === 0 ? 'even-row' : 'odd-row'; + const rowClass = index % 2 === 0 ? 'evenRow' : 'oddRow'; return ( -
+ @@ -112,7 +112,7 @@ const GradebookInstructorPage = () => { {/*
*/}

Instructor Gradebook

{/*
*/} -
+
Date: Sat, 12 Oct 2024 13:04:06 -0500 Subject: [PATCH 181/400] added padding and center alignment to table cells and headers for readability --- .../components/pages/gradebook/gradebookInstructorPage.tsx | 2 +- .../src/components/pages/gradebook/gradebookPage.scss | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx index f5690eca..12b52b56 100644 --- a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx @@ -26,7 +26,7 @@ type RowProps = { } const TableRow = ({index, user, userCourse, assignments, assignmentScores}: RowProps) => { - // TODO: style table row to alternating colors based on index odd?even + // style table row to alternating colors based on index odd?even const rowClass = index % 2 === 0 ? 'evenRow' : 'oddRow'; return ( diff --git a/devU-client/src/components/pages/gradebook/gradebookPage.scss b/devU-client/src/components/pages/gradebook/gradebookPage.scss index b21c454b..ce0be105 100644 --- a/devU-client/src/components/pages/gradebook/gradebookPage.scss +++ b/devU-client/src/components/pages/gradebook/gradebookPage.scss @@ -51,5 +51,9 @@ th { background-color: $primary; color: #FFF; font-weight: 600; - padding: 0 10px; +} + +td, th { + padding: 10px; + text-align: center; } \ No newline at end of file From 85c49df2b1839c1900ce24b28a3598cfdff75eb0 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Sat, 12 Oct 2024 13:58:34 -0500 Subject: [PATCH 182/400] navbar changes: updated navbar colors to change on theme toggle, changed breadcrumbs color to text color for improved readability --- devU-client/src/components/misc/globalToolbar.scss | 7 +++---- devU-client/src/components/misc/navbar.scss | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 9a5f1c31..1251bde3 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -16,7 +16,7 @@ $font-size: 16px; @extend .flex; text-decoration: none; - color: #d9d9d9; + color: #FFF; font-size: $font-size; margin:3vw; height: 4vh; @@ -36,13 +36,12 @@ $font-size: 16px; font-size: 40px; border-radius: 30px; - color: #D9D9D9; font-weight: 550; } .bar { height: 60px; - background-color: #52468A; + background-color: $primary; @extend .flex; justify-content: space-between; } @@ -59,7 +58,7 @@ $font-size: 16px; // Controls turning the menu options into a sidebar // As well as whether or not that sidebar is being shown -@media (max-width: 300px) { +@media (max-width: 410px) { .flex { gap: 1rem; diff --git a/devU-client/src/components/misc/navbar.scss b/devU-client/src/components/misc/navbar.scss index f490536a..1a1199ad 100644 --- a/devU-client/src/components/misc/navbar.scss +++ b/devU-client/src/components/misc/navbar.scss @@ -2,5 +2,5 @@ .link { text-decoration: none; - color: #52468A; + color: $text-color; } \ No newline at end of file From 03911090c893e9dcf458f2bd5a448b36f24c9d6a Mon Sep 17 00:00:00 2001 From: RA341 Date: Mon, 14 Oct 2024 01:12:37 -0400 Subject: [PATCH 183/400] added assignment bucket --- devU-api/src/fileStorage.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devU-api/src/fileStorage.ts b/devU-api/src/fileStorage.ts index f0f153ca..989bdaf9 100644 --- a/devU-api/src/fileStorage.ts +++ b/devU-api/src/fileStorage.ts @@ -6,6 +6,7 @@ export enum BucketNames { GRADERS = 'graders', SUBMISSIONS = 'submissions', MAKEFILES = 'makefiles', + ASSIGNMENTSATTACHMENTS = 'assignmentattachments' } const minioConfiguration: Minio.ClientOptions = { @@ -43,7 +44,7 @@ export async function initializeMinio(inputBucketName?: string) { export async function uploadFile(bucketName: string, file: Express.Multer.File, filename: string): Promise { try { - const objInfo = await minioClient.putObject(bucketName, filename, file.buffer) + const objInfo = await minioClient.putObject(bucketName, filename, file.buffer, file.size) return objInfo.etag } catch (err: Error | any) { if (err) { From ab81520cd3838c3c63a9e6c96d2dcc09628c65a3 Mon Sep 17 00:00:00 2001 From: RA341 Date: Mon, 14 Oct 2024 01:13:12 -0400 Subject: [PATCH 184/400] added attachments to assignment endpoint --- .../assignment/assignment.controller.ts | 74 ++++++++++++++++++- .../entities/assignment/assignment.model.ts | 6 ++ .../entities/assignment/assignment.router.ts | 40 +++++++++- .../assignment/assignment.serializer.ts | 2 + .../1728881406096-assignmentAttachment.ts | 20 +++++ devU-shared/src/types/assignment.types.ts | 2 + 6 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 devU-api/src/migration/1728881406096-assignmentAttachment.ts diff --git a/devU-api/src/entities/assignment/assignment.controller.ts b/devU-api/src/entities/assignment/assignment.controller.ts index 4bc85386..521ac17a 100644 --- a/devU-api/src/entities/assignment/assignment.controller.ts +++ b/devU-api/src/entities/assignment/assignment.controller.ts @@ -5,6 +5,8 @@ import AssignmentService from './assignment.service' import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' import { serialize } from './assignment.serializer' +import { BucketNames, downloadFile, uploadFile } from '../../fileStorage' +import { generateFilename } from '../../utils/fileUpload.utils' export async function detail(req: Request, res: Response, next: NextFunction) { try { @@ -22,6 +24,40 @@ export async function detail(req: Request, res: Response, next: NextFunction) { } } +export async function handleAttachmentLink(req: Request, res: Response, next: NextFunction) { + try { + const bucketName = BucketNames.ASSIGNMENTSATTACHMENTS + const courseId = parseInt(req.params.courseId) + const fileName = req.params.filename + const assignmentId = parseInt(req.params.assignmentId) + + if (!courseId) return res.status(400).json('Bucket not found') + if (!fileName) return res.status(400).json('File name not found') + if (!assignmentId) return res.status(400).json('Assignment id not found') + + const assignment = await AssignmentService.retrieve(assignmentId, courseId) + if (!assignment) return res.status(404).json('Assignment not found') + + const ind = assignment.attachmentsHashes.findIndex(value => { + return value == fileName + }) + if (ind == -1) return res.status(404).json('File not found') + + const file = assignment.attachmentsHashes[ind] + const name = assignment.attachmentsFilenames[ind] + + const buffer = await downloadFile(bucketName, file) + + res.setHeader('Content-Disposition', `attachment; filename="${name}"`) + res.setHeader('Content-Type', 'application/octet-stream') + res.setHeader('Content-Length', buffer.length) + res.send(buffer) + } catch (error) { + console.error('Error retrieving file:', error) + res.status(500).send('Error retrieving file') + } +} + export async function getByCourse(req: Request, res: Response, next: NextFunction) { try { const courseId = parseInt(req.params.courseId) @@ -34,6 +70,7 @@ export async function getByCourse(req: Request, res: Response, next: NextFunctio next(err) } } + export async function getReleased(req: Request, res: Response, next: NextFunction) { try { const courseId = parseInt(req.params.courseId) @@ -47,21 +84,54 @@ export async function getReleased(req: Request, res: Response, next: NextFunctio } } + +async function processFiles(req: Request) { + let fileHashes: string[] = [] + let fileNames: string[] = [] + + // save files + if (req.files) { + console.log() + if (Array.isArray(req.files)) { + for (let index = 0; index < req.files.length; index++) { + const item = req.files[index] + const filename = generateFilename(item.originalname, item.size) + await uploadFile(BucketNames.ASSIGNMENTSATTACHMENTS, item, filename) + fileHashes.push(filename) + fileNames.push(item.originalname) + } + } else { + console.warn(`Files where not in array format ${req.files}`) + } + } else { + console.warn(`No files where processed`) + } + + return { fileHashes, fileNames } +} + export async function post(req: Request, res: Response, next: NextFunction) { try { + const { fileNames, fileHashes } = await processFiles(req) + + req.body['attachmentsFilenames'] = fileNames + req.body['attachmentsHashes'] = fileHashes + const assignment = await AssignmentService.create(req.body) const response = serialize(assignment) res.status(201).json(response) } catch (err) { if (err instanceof Error) { - res.status(400).json(new GenericResponse(err.message)) + res.status(400).json(new GenericResponse(err.message)) } } } export async function put(req: Request, res: Response, next: NextFunction) { try { + req.body['attachments'] = await processFiles(req) + req.body.id = parseInt(req.params.assignmentId) const results = await AssignmentService.update(req.body) @@ -86,4 +156,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { detail, post, put, _delete, getByCourse, getReleased } +export default { detail, post, put, _delete, getByCourse, getReleased, handleAttachmentLink } diff --git a/devU-api/src/entities/assignment/assignment.model.ts b/devU-api/src/entities/assignment/assignment.model.ts index 654f5403..524ada8c 100644 --- a/devU-api/src/entities/assignment/assignment.model.ts +++ b/devU-api/src/entities/assignment/assignment.model.ts @@ -95,4 +95,10 @@ export default class AssignmentModel { @DeleteDateColumn({ name: 'deleted_at' }) deletedAt?: Date + + @Column({ name: 'attachmentsHashes', array: true, default: [], type: 'text' }) + attachmentsHashes: string[] + + @Column({ name: 'attachmentsFilenames', array: true, default: [], type: 'text', nullable: false }) + attachmentsFilenames: string[] } diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index 976757ce..ddd345c9 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -1,4 +1,5 @@ // Libraries +import multer from 'multer' import express from 'express' // Middleware @@ -10,6 +11,7 @@ import AssignmentsController from './assignment.controller' import { isAuthorized, isAuthorizedByAssignmentStatus } from '../../authorization/authorization.middleware' const Router = express.Router({ mergeParams: true }) +const upload = multer() /** * @swagger @@ -51,6 +53,38 @@ Router.get('/released', isAuthorized('assignmentViewReleased'), AssignmentsContr */ Router.get('/', isAuthorized('assignmentViewAll'), AssignmentsController.getByCourse) +/** + * @swagger + * /course/:courseId/assignments/attachments/{assignmentId}/{filename}: + * get: + * summary: Retrieve attachments for a assignments + * tags: + * - Assignments + * responses: + * '200': + * description: OK + * parameters: + * - name: courseId + * in: path + * description: Enter course id + * required: true + * schema: + * type: integer + * - name: assignmentId + * in: path + * description: Enter assignment id + * required: true + * schema: + * type: integer + * - name: filename + * in: path + * description: Enter filename to retrieve + * required: true + * schema: + * type: string + */ +Router.get('/attachments/:assignmentId/:filename', isAuthorizedByAssignmentStatus, AssignmentsController.handleAttachmentLink) + /** * @swagger * /course/:courseId/assignments/{id}: @@ -100,7 +134,9 @@ Router.get('/:assignmentId', asInt('assignmentId'), isAuthorizedByAssignmentStat * schema: * $ref: '#/components/schemas/Assignment' */ -Router.post('/', isAuthorized('assignmentEditAll'), validator, AssignmentsController.post) + + +Router.post('/', isAuthorized('assignmentEditAll'), upload.array('files'), validator, AssignmentsController.post) /** * @swagger @@ -135,7 +171,7 @@ Router.put( isAuthorized('assignmentEditAll'), asInt('assignmentId'), validator, - AssignmentsController.put + AssignmentsController.put, ) /** diff --git a/devU-api/src/entities/assignment/assignment.serializer.ts b/devU-api/src/entities/assignment/assignment.serializer.ts index c0443102..a87f4322 100644 --- a/devU-api/src/entities/assignment/assignment.serializer.ts +++ b/devU-api/src/entities/assignment/assignment.serializer.ts @@ -17,5 +17,7 @@ export function serialize(assignment: AssignmentModel): Assignment { disableHandins: assignment.disableHandins, createdAt: assignment.createdAt.toISOString(), updatedAt: assignment.updatedAt.toISOString(), + attachmentsHashes: assignment.attachmentsHashes, + attachmentsFilenames: assignment.attachmentsFilenames } } diff --git a/devU-api/src/migration/1728881406096-assignmentAttachment.ts b/devU-api/src/migration/1728881406096-assignmentAttachment.ts new file mode 100644 index 00000000..9e295836 --- /dev/null +++ b/devU-api/src/migration/1728881406096-assignmentAttachment.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AssignmentAttachment1728881406096 implements MigrationInterface { + name = 'AssignmentAttachment1728881406096' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "assignments" DROP COLUMN "etags"`); + await queryRunner.query(`ALTER TABLE "assignments" DROP COLUMN "filenames"`); + await queryRunner.query(`ALTER TABLE "assignments" ADD "attachmentsHashes" text array NOT NULL DEFAULT '{}'`); + await queryRunner.query(`ALTER TABLE "assignments" ADD "attachmentsFilenames" text array NOT NULL DEFAULT '{}'`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "assignments" DROP COLUMN "attachmentsFilenames"`); + await queryRunner.query(`ALTER TABLE "assignments" DROP COLUMN "attachmentsHashes"`); + await queryRunner.query(`ALTER TABLE "assignments" ADD "filenames" text array NOT NULL DEFAULT '{}'`); + await queryRunner.query(`ALTER TABLE "assignments" ADD "etags" text array NOT NULL DEFAULT '{}'`); + } + +} diff --git a/devU-shared/src/types/assignment.types.ts b/devU-shared/src/types/assignment.types.ts index 5e81a766..24b38ded 100644 --- a/devU-shared/src/types/assignment.types.ts +++ b/devU-shared/src/types/assignment.types.ts @@ -12,5 +12,7 @@ export type Assignment = { disableHandins: boolean createdAt?: string updatedAt?: string + attachmentsHashes: string[] + attachmentsFilenames: string[] } From 0173f5aaf238a80ff564a22d97a86e610437f8cf Mon Sep 17 00:00:00 2001 From: RA341 Date: Mon, 14 Oct 2024 01:13:26 -0400 Subject: [PATCH 185/400] fixed some commands --- devU-api/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devU-api/package.json b/devU-api/package.json index 303e3eb7..e908de15 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "description": "DevU API", "scripts": { - "start": "npm run typeorm -- migration:run -d src/database.ts && ts-node-dev src/index.ts", + "start": "npm run migrate && ts-node-dev src/index.ts", + "migrate": "npm run typeorm -- migration:run -d src/database.ts", "update-shared": "cd ../devU-shared && npm run build-local && npm i", "typeorm": "typeorm-ts-node-commonjs", "test": "jest --passWithNoTests", From 9cb18d98f320d27df1120ca95e2f8ecf2e26959b Mon Sep 17 00:00:00 2001 From: RA341 Date: Mon, 14 Oct 2024 01:15:22 -0400 Subject: [PATCH 186/400] added file limit --- devU-api/src/entities/assignment/assignment.router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index ddd345c9..9724d2ad 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -11,7 +11,7 @@ import AssignmentsController from './assignment.controller' import { isAuthorized, isAuthorizedByAssignmentStatus } from '../../authorization/authorization.middleware' const Router = express.Router({ mergeParams: true }) -const upload = multer() +const upload = multer({ limits: { fileSize: 1024 * 1024 * 5 } }) // 5MB file size limit /** * @swagger From d4661469efe3bd24415b565bb6b9eee7136fda3d Mon Sep 17 00:00:00 2001 From: RA341 Date: Mon, 14 Oct 2024 01:27:35 -0400 Subject: [PATCH 187/400] fixed api docs --- devU-api/src/entities/assignment/assignment.model.ts | 8 ++++++++ .../src/entities/assignment/assignment.router.ts | 12 ++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/devU-api/src/entities/assignment/assignment.model.ts b/devU-api/src/entities/assignment/assignment.model.ts index 524ada8c..951e4e9f 100644 --- a/devU-api/src/entities/assignment/assignment.model.ts +++ b/devU-api/src/entities/assignment/assignment.model.ts @@ -50,6 +50,14 @@ export default class AssignmentModel { * type: string * format: date-time * description: Must be in ISO 8601 format + * fileHashes: + * type: string + * array: true + * description: filename hashes of stored attachments use this to retrieve and query attachments, matches the index of the fileNames, i.e. filename[i] is the name of hash[i] + * fileNames: + * type: string + * array: true + * description: filenames of stored attachments, matches the index of the fileHashes, i.e. filename[i] is the name of hash[i] */ @PrimaryGeneratedColumn() diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index 9724d2ad..9dbcbf36 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -57,12 +57,16 @@ Router.get('/', isAuthorized('assignmentViewAll'), AssignmentsController.getByCo * @swagger * /course/:courseId/assignments/attachments/{assignmentId}/{filename}: * get: - * summary: Retrieve attachments for a assignments + * summary: Retrieve an attachment for assignment * tags: * - Assignments * responses: * '200': * description: OK + * '400': + * description: Missing the 'assignment id' or 'course id' or 'filename' + * '404': + * description: file not found or not part of the assigment * parameters: * - name: courseId * in: path @@ -70,15 +74,15 @@ Router.get('/', isAuthorized('assignmentViewAll'), AssignmentsController.getByCo * required: true * schema: * type: integer - * - name: assignmentId + * - name: assignmentId * in: path * description: Enter assignment id * required: true * schema: * type: integer - * - name: filename + * - name: filename * in: path - * description: Enter filename to retrieve + * description: Enter filename hash * required: true * schema: * type: string From 1b9727858159f5bd60f5360e174cf75940e836d9 Mon Sep 17 00:00:00 2001 From: RA341 Date: Mon, 14 Oct 2024 01:45:09 -0400 Subject: [PATCH 188/400] added file limit for attachments --- devU-api/src/entities/assignment/assignment.router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index 9dbcbf36..f3b62b01 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -140,7 +140,7 @@ Router.get('/:assignmentId', asInt('assignmentId'), isAuthorizedByAssignmentStat */ -Router.post('/', isAuthorized('assignmentEditAll'), upload.array('files'), validator, AssignmentsController.post) +Router.post('/', isAuthorized('assignmentEditAll'), upload.array('files', 5), validator, AssignmentsController.post) /** * @swagger From 954d0fa987d48525e878a513328c2d48da7404fc Mon Sep 17 00:00:00 2001 From: RA341 Date: Mon, 14 Oct 2024 15:08:37 -0400 Subject: [PATCH 189/400] commented out update page for now This was causing frontend to brake --- .../assignments/assignmentUpdatePage.tsx | 330 +++++++++--------- 1 file changed, 169 insertions(+), 161 deletions(-) diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index 7370f968..baf851ce 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -1,181 +1,189 @@ -import React, {useEffect, useState} from 'react' -import {ExpressValidationError, Assignment} from 'devu-shared-modules' +import React, { useEffect, useState } from 'react' +import { ExpressValidationError, Assignment } from 'devu-shared-modules' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' -import {useHistory, useParams} from 'react-router-dom' +import { useHistory, useParams } from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' import RequestService from 'services/request.service' -import {useActionless} from 'redux/hooks' +import { useActionless } from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import Button from '@mui/material/Button' import formStyles from './assignmentFormPage.scss' -import {SET_ALERT} from 'redux/types/active.types' -import {applyMessageToErrorFields, removeClassFromField} from 'utils/textField.utils' +import { SET_ALERT } from 'redux/types/active.types' +import { applyMessageToErrorFields, removeClassFromField } from 'utils/textField.utils' type UrlParams = { - assignmentId: string + assignmentId: string } - + const AssignmentUpdatePage = () => { - const { assignmentId } = useParams() as UrlParams - const { courseId } = useParams<{courseId : string}>() - const [setAlert] = useActionless(SET_ALERT) - const [startDate, setStartDate] = useState(new Date()) - const [endDate, setEndDate] = useState(new Date()) - const [dueDate, setDueDate] = useState(new Date()) - const [invalidFields, setInvalidFields] = useState(new Map()) - const history = useHistory() - - const [formData, setFormData] = useState({ - courseId: parseInt(courseId), - name: '', - categoryName: '', - description: '', - maxFileSize: 0, - maxSubmissions: null, - disableHandins: false, - dueDate: "", - endDate: "", - startDate: "", - }) - - const handleChange = (value: String, e : React.ChangeEvent) => { - const key = e.target.id - const newInvalidFields = removeClassFromField(invalidFields, key) - setInvalidFields(newInvalidFields) - - setFormData(prevState => ({...prevState,[key] : value})) - } - const handleCheckbox = (e: React.ChangeEvent) => { - setFormData(prevState => ({...prevState,disableHandins : e.target.checked})) - } - const handleStartDateChange = (date : Date) => {setStartDate(date)} - const handleEndDateChange = (date : Date) => {setEndDate(date)} - const handleDueDateChange = (date: Date) => {setDueDate(date)} - - useEffect(() => { - RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`).then((res) => { - const assignment:Assignment =res - setFormData({ - name: assignment.name, - categoryName: assignment.categoryName, - description: assignment.description, - maxFileSize: assignment.maxFileSize, - maxSubmissions: assignment.maxSubmissions, - disableHandins: assignment.disableHandins, - startDate: assignment.startDate, - endDate: assignment.endDate, - dueDate: assignment.dueDate, - courseId: assignment.courseId - }); - setStartDate(new Date(res.startDate)); - setDueDate(new Date(res.dueDate)); - setEndDate(new Date(res.endDate)); - }); - }, []); - - const handleAssignmentUpdate = () => { - const finalFormData = { - courseId: formData.courseId, - name: formData.name, - startDate : startDate.toISOString(), - dueDate: dueDate.toISOString(), - endDate : endDate.toISOString(), - categoryName: formData.categoryName, - description: formData.description, - maxFileSize: formData.maxFileSize, - maxSubmissions: formData.maxSubmissions, - disableHandins: formData.disableHandins, - - } - - RequestService.put(`/api/course/${courseId}/assignments/${assignmentId}`, finalFormData) - .then(() => { - - setAlert({ autoDelete: true, type: 'success', message: 'Assignment Updated' }) - history.goBack() - }) - .catch((err: ExpressValidationError[] | Error) => { - const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message - const newFields = new Map() - Array.isArray(err) ? err.map((e) => applyMessageToErrorFields(newFields, e.param, e.msg)) : newFields - setInvalidFields(newFields); - setAlert({ autoDelete: false, type: 'error', message }) - }) - .finally(() => { - }) + const { assignmentId } = useParams() as UrlParams + const { courseId } = useParams<{ courseId: string }>() + const [setAlert] = useActionless(SET_ALERT) + const [startDate, setStartDate] = useState(new Date()) + const [endDate, setEndDate] = useState(new Date()) + const [dueDate, setDueDate] = useState(new Date()) + const [invalidFields, setInvalidFields] = useState(new Map()) + const history = useHistory() + + const [formData, setFormData] = useState({ + courseId: parseInt(courseId), + name: '', + categoryName: '', + description: '', + maxFileSize: 0, + maxSubmissions: null, + disableHandins: false, + dueDate: '', + endDate: '', + startDate: '', + attachmentsHashes: [], + attachmentsFilenames: [], + }) + + const handleChange = (value: String, e: React.ChangeEvent) => { + const key = e.target.id + const newInvalidFields = removeClassFromField(invalidFields, key) + setInvalidFields(newInvalidFields) + + setFormData(prevState => ({ ...prevState, [key]: value })) + } + const handleCheckbox = (e: React.ChangeEvent) => { + setFormData(prevState => ({ ...prevState, disableHandins: e.target.checked })) + } + const handleStartDateChange = (date: Date) => { + setStartDate(date) + } + const handleEndDateChange = (date: Date) => { + setEndDate(date) + } + const handleDueDateChange = (date: Date) => { + setDueDate(date) + } + + useEffect(() => { + RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`).then((res) => { + // const assignment: Assignment = res + // setFormData({ + // name: assignment.name, + // categoryName: assignment.categoryName, + // description: assignment.description, + // maxFileSize: assignment.maxFileSize, + // maxSubmissions: assignment.maxSubmissions, + // disableHandins: assignment.disableHandins, + // startDate: assignment.startDate, + // endDate: assignment.endDate, + // dueDate: assignment.dueDate, + // courseId: assignment.courseId, + // }) + setStartDate(new Date(res.startDate)) + setDueDate(new Date(res.dueDate)) + setEndDate(new Date(res.endDate)) + }) + }, []) + + const handleAssignmentUpdate = () => { + const finalFormData = { + courseId: formData.courseId, + name: formData.name, + startDate: startDate.toISOString(), + dueDate: dueDate.toISOString(), + endDate: endDate.toISOString(), + categoryName: formData.categoryName, + description: formData.description, + maxFileSize: formData.maxFileSize, + maxSubmissions: formData.maxSubmissions, + disableHandins: formData.disableHandins, + } - return ( - -
-
-

Assignment Detail Update

-
-
-
- - - - - - - - - -
- -
-
- - -
-
- - -
-
- - -
-
-
-
- - -
- -
- -
- -
-
-
- ) + RequestService.put(`/api/course/${courseId}/assignments/${assignmentId}`, finalFormData) + .then(() => { + + setAlert({ autoDelete: true, type: 'success', message: 'Assignment Updated' }) + history.goBack() + }) + .catch((err: ExpressValidationError[] | Error) => { + const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message + const newFields = new Map() + Array.isArray(err) ? err.map((e) => applyMessageToErrorFields(newFields, e.param, e.msg)) : newFields + setInvalidFields(newFields) + setAlert({ autoDelete: false, type: 'error', message }) + }) + .finally(() => { + }) + } + + return ( + +
+
+

Assignment Detail Update

+
+
+
+ + + + + + + + + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+ +
+ +
+ +
+
+
+ ) } export default AssignmentUpdatePage From d8db9aeeb517268a283d6a6eb93e0becdaaa9069 Mon Sep 17 00:00:00 2001 From: RA341 Date: Mon, 14 Oct 2024 15:10:50 -0400 Subject: [PATCH 190/400] fixed update assigment endpoint --- devU-api/src/entities/assignment/assignment.controller.ts | 5 ++++- devU-api/src/entities/assignment/assignment.router.ts | 1 + devU-api/src/entities/assignment/assignment.service.ts | 4 ++++ devU-api/src/migration/1728881406096-assignmentAttachment.ts | 4 ---- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/devU-api/src/entities/assignment/assignment.controller.ts b/devU-api/src/entities/assignment/assignment.controller.ts index 521ac17a..878df677 100644 --- a/devU-api/src/entities/assignment/assignment.controller.ts +++ b/devU-api/src/entities/assignment/assignment.controller.ts @@ -130,7 +130,10 @@ export async function post(req: Request, res: Response, next: NextFunction) { export async function put(req: Request, res: Response, next: NextFunction) { try { - req.body['attachments'] = await processFiles(req) + const { fileNames, fileHashes } = await processFiles(req) + + req.body['attachmentsFilenames'] = fileNames + req.body['attachmentsHashes'] = fileHashes req.body.id = parseInt(req.params.assignmentId) const results = await AssignmentService.update(req.body) diff --git a/devU-api/src/entities/assignment/assignment.router.ts b/devU-api/src/entities/assignment/assignment.router.ts index f3b62b01..1c75b3c0 100644 --- a/devU-api/src/entities/assignment/assignment.router.ts +++ b/devU-api/src/entities/assignment/assignment.router.ts @@ -174,6 +174,7 @@ Router.put( '/:assignmentId', isAuthorized('assignmentEditAll'), asInt('assignmentId'), + upload.array('files', 5), validator, AssignmentsController.put, ) diff --git a/devU-api/src/entities/assignment/assignment.service.ts b/devU-api/src/entities/assignment/assignment.service.ts index 707cc186..312d03a2 100644 --- a/devU-api/src/entities/assignment/assignment.service.ts +++ b/devU-api/src/entities/assignment/assignment.service.ts @@ -23,6 +23,8 @@ export async function update(assignment: Assignment) { maxFileSize, maxSubmissions, disableHandins, + attachmentsHashes, + attachmentsFilenames, } = assignment if (!id) throw new Error('Missing Id') @@ -37,6 +39,8 @@ export async function update(assignment: Assignment) { maxFileSize, maxSubmissions, disableHandins, + attachmentsHashes, + attachmentsFilenames }) } diff --git a/devU-api/src/migration/1728881406096-assignmentAttachment.ts b/devU-api/src/migration/1728881406096-assignmentAttachment.ts index 9e295836..7e352495 100644 --- a/devU-api/src/migration/1728881406096-assignmentAttachment.ts +++ b/devU-api/src/migration/1728881406096-assignmentAttachment.ts @@ -4,8 +4,6 @@ export class AssignmentAttachment1728881406096 implements MigrationInterface { name = 'AssignmentAttachment1728881406096' public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "assignments" DROP COLUMN "etags"`); - await queryRunner.query(`ALTER TABLE "assignments" DROP COLUMN "filenames"`); await queryRunner.query(`ALTER TABLE "assignments" ADD "attachmentsHashes" text array NOT NULL DEFAULT '{}'`); await queryRunner.query(`ALTER TABLE "assignments" ADD "attachmentsFilenames" text array NOT NULL DEFAULT '{}'`); } @@ -13,8 +11,6 @@ export class AssignmentAttachment1728881406096 implements MigrationInterface { public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE "assignments" DROP COLUMN "attachmentsFilenames"`); await queryRunner.query(`ALTER TABLE "assignments" DROP COLUMN "attachmentsHashes"`); - await queryRunner.query(`ALTER TABLE "assignments" ADD "filenames" text array NOT NULL DEFAULT '{}'`); - await queryRunner.query(`ALTER TABLE "assignments" ADD "etags" text array NOT NULL DEFAULT '{}'`); } } From ab93022cb29073e2c3b9d7ad603c177fa424de61 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Mon, 14 Oct 2024 23:15:17 -0400 Subject: [PATCH 191/400] added styling to the assignment details page for instructor, I added options section for the buttons to be placed next to the assignment, I added the description of the assignment as well in the assignment section. I also fixed the nav bar for the breadcrumbs, to be more uniform --- devU-client/src/components/misc/navbar.scss | 17 +- devU-client/src/components/misc/navbar.tsx | 6 +- .../assignments/assignmentDetailPage.scss | 191 ++++++++++++++---- .../assignments/assignmentDetailPage.tsx | 81 +++++--- .../components/pages/homePage/homePage.scss | 11 +- .../components/pages/homePage/homePage.tsx | 2 +- 6 files changed, 235 insertions(+), 73 deletions(-) diff --git a/devU-client/src/components/misc/navbar.scss b/devU-client/src/components/misc/navbar.scss index f490536a..0c8cf19a 100644 --- a/devU-client/src/components/misc/navbar.scss +++ b/devU-client/src/components/misc/navbar.scss @@ -1,6 +1,21 @@ @import 'variables'; +.breadcrumbContainer { + display: flex; + align-items: center; +} + .link { text-decoration: none; - color: #52468A; + color: $text-color; /* Breadcrumb link color */ + +} + +.link:hover { + text-decoration: underline; +} + +.separator { + color: #888; /* Separator color */ + margin: 0 5px; /* Add spacing between breadcrumbs */ } \ No newline at end of file diff --git a/devU-client/src/components/misc/navbar.tsx b/devU-client/src/components/misc/navbar.tsx index cbc24074..bb766f35 100644 --- a/devU-client/src/components/misc/navbar.tsx +++ b/devU-client/src/components/misc/navbar.tsx @@ -81,16 +81,16 @@ const Navbar = ({breadcrumbs}: any) => { ] return ( -
+
{breadcrumbs.map(({breadcrumb, match}: any, index: number) => { let url = match.url - if (excludedPaths.includes(match.params.path)) return
+ if (excludedPaths.includes(match.params.path)) return null if (match.params.home) url = '/' return ( {breadcrumb} - {index < (breadcrumbs.length - 1) ? ' > ' : ''} + {index < (breadcrumbs.length - 1) && ( > )} ) })} diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss index 7f46726f..e5e580c9 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss @@ -1,60 +1,121 @@ @import 'variables'; +.wrap { + display:flex; + align-items: baseline; + gap:20px; + padding:20px; + justify-content:center; +} + + .header { display: flex; - align-items: center; + flex-direction: column; + align-items: flex-start; + color: $text-color; } -.smallLine { - width: 50px; /* adjust this value to set the length of the small line */ - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-right: 10px; /* adjust this value to set the space between the line and the text */ + +.line { + border: none; + height: 2px; + background-color: #ccc; + width: 100%; + margin-top: 10px; } -.largeLine { - flex-grow: 1; - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-left: 10px; /* adjust this value to set the space between the line and the text */ - margin-right: 10px; /* add this line to create some space between the line and the button */ +.card { + background-color: #f9f9f9; + border-radius: 8px; + padding: 20px; + width: 300px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + margin: 20px; } -.buttons { - margin : 0 10px; + +.card_heading { + font-size: 20px; + font-weight: 550; + margin-bottom: 15px; + color: #52468a; + text-align: center; } -.card { - width: 60%; - height: 50%; - display: flex; - flex-direction: column; - justify-content: center; + +.options_buttons { + display: flex; + flex-direction: column; align-items: center; - padding-top: 20px; + justify-content: center; + gap: 10px; + } -.accordion { - width: 90%; +.submit_container{ display: flex; - flex-direction: column; - margin-top: 10px; - margin-left: 10px; - margin-right: 10px; - background-color: $list-item-background; - color:$list-item-background + justify-content: flex-end; + align-items: flex-end; + width: 100%; + margin-top: auto; + } -.accordionDetails { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background-color: $list-item-background; - color:$text-color; + +.buttons { + color: #52468a; + background-color: #f9f9f9; + padding: 10px 20px; + border-radius:15px; + cursor: pointer; + font-size: 15px; + border: none; + width: fit-content; + text-align: center; + margin-left: 20px; +} + + +.buttons:hover { + background-color: #888; + +} + +.assignment_card { + background-color: #f9f9f9; + border-radius: 8px; + padding: 20px; + width: 100%; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + width:800px; +} + +.accordion { + background-color: #f9f9f9; + box-shadow: none; + border: 1px solid #ddd; + margin-bottom: 10px; } +.accordionDetails { + background-color: #f9f9f9; + padding: 10px; + border-top: 1px solid #ddd; +} +.typography { + font-size: 16px; + color: #333; +} +.gridContainer { + display: flex; + justify-content: center; + align-items: center; + margin-top: 20px; +} .textField { - align-items: center; - justify-content: center; + margin-top: 10px; + margin-bottom: 20px; } .submissionCard { @@ -62,6 +123,60 @@ } .fileInput { - margin-top: 10px; - margin-bottom: 10px; + margin-top: 20px; +} + +.due_date { + font-size: 14px; + color: #888; + margin-top: 5px; + margin-bottom: 15px; +} + + +.submissionsContainer { + display: flex; + flex-direction: column; + width: 100%; + margin-top: 20px; +} + + +.submissionCard { + display: flex; + justify-content: space-between; + align-items: flex-start; + background-color: #f9f9f9; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + transition: transform 0.2s ease; +} + +.submissionCard:hover { + transform: scale(1.02); } + +.submissionHeading { + font-size: 16px; + font-weight: bold; + color: #52468a; +} + + +.submissionTime { + font-size: 14px; + color: #888; + margin-top: 5px; +} + +@media (max-width: 768px) { + .header { + flex-direction: column; + align-items: center; + } + + .buttons { + width: 100%; + max-width: none; + } +} \ No newline at end of file diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index f2420559..2eb53ada 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -11,8 +11,7 @@ import {SET_ALERT} from 'redux/types/active.types' import Card from '@mui/material/Card' import CardContent from '@mui/material/CardContent' import {Accordion, AccordionDetails, AccordionSummary, CardActionArea, TextField, Typography} from '@mui/material' -import Stack from '@mui/material/Stack' -import Button from '@mui/material/Button' + import Grid from '@mui/material/Unstable_Grid2' import styles from './assignmentDetailPage.scss' @@ -143,28 +142,50 @@ const AssignmentDetailPage = () => { return(
-
-

{assignment?.name}

-
- - {role.isInstructor() &&
+
+ + {role.isInstructor() && ( + <> +
+

Options

+
+
+ {role.isInstructor() && } - {role.isInstructor() && } +
+ {role.isInstructor() && } - {role.isInstructor() && } +
+ {role.isInstructor() && } - {role.isInstructor() && } +
+ {role.isInstructor() && } - -
+ }}>Edit Assignment} +
+
+ + )} + + + + + - - + + {assignment?.description} +
+ + {assignment?.dueDate && ( + {`Due Date: ${new Date(assignment.dueDate).toLocaleDateString()}`} + )} {nonContainerAutograders && nonContainerAutograders.length > 0 ? ( nonContainerAutograders.map((nonContainer, index) => ( @@ -182,37 +203,41 @@ const AssignmentDetailPage = () => { No Problems Exist )} - + {!(assignment?.disableHandins) && ()} - + {assignmentProblems && assignmentProblems.length > 0 ? ( - +
+ +
) : null}
- +
-

{`Submissions`}

-
+
{/**Submissions List */}
+
{submissions.map((submission, index) => ( { history.push(`/course/${courseId}/assignment/${assignmentId}/submission/${submission.id}`) }}> - - {`Submission ${submissions.length - index}`} - {`Submitted at: ${submission.createdAt && prettyPrintDateTime(submission.createdAt)}`} - + + {`Submission ${submissions.length - index}`} + {`Submitted at: ${submission.createdAt && prettyPrintDateTime(submission.createdAt)}`} + ))}
+
+ ) } diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index e84eab94..932d059b 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -1,8 +1,8 @@ @import 'variables'; -h2 { - align-items:left; +.h2 { + text-align: left; margin-left: 20px; font-size: 30px; font-weight: 550; @@ -17,6 +17,13 @@ h3 { margin-bottom: 30px; } +h1 { + align-items:left; + margin-left: 20px; + font-size: 30px; + font-weight: 550; + margin-bottom: 30px; +} h4 { margin-left: 20px; diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 3f27e2f9..4bb0a1d7 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -75,7 +75,7 @@ const HomePage = () => {
-

Courses

+

Courses

} -
+ {role.isInstructor() &&( + + )} +
-
- {categories.map((c) => ( - - ))} +
+ + {categories.map(category => ( +
+

{category}

+
# Email External ID
{index} {user.email} {user.externalId}
{/* Add table class */} + + {/* Add class for purple header */} + + + + + + {assignments.filter(a => a.categoryName === category).map((assignment, index) => ( + + + + + + ))} + +
AssignmentScore
+
+ {assignment.name}
+ +
+
+ {assignmentScores.find(aScore => aScore.assignmentId === assignment.id)?.score ?? 'N/A'} +
+
+
+ ))}
- ) -} + ); +}; -export default GradebookStudentPage +export default GradebookStudentPage; \ No newline at end of file From c394efe72c4a3c9aaab8f2ae25821bb8701b3dbd Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 15 Oct 2024 14:45:11 -0500 Subject: [PATCH 193/400] added styling for gradebook, added rounded borders and search field --- devU-client/src/assets/global.scss | 15 ++--- devU-client/src/assets/variables.scss | 9 ++- .../gradebook/gradebookInstructorPage.tsx | 61 +++++++++++++------ .../pages/gradebook/gradebookPage.scss | 30 +++++++++ devU-client/src/utils/theme.utils.ts | 2 +- 5 files changed, 86 insertions(+), 31 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index b84a027b..7b938a35 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -67,10 +67,8 @@ --primary-lighter: #41a8f7; --primary: var(--purple); - - // --primary: #D9D9D9; - --primary-darker: #054272; + --secondary-lighter: #cfd1d1; --secondary: #a8abab; --secondary-darker: #686b6b; @@ -97,6 +95,9 @@ --table-row-even: var(--grey-lighter); --table-row-odd: var(--grey-lightest); + --red-text: var(--red); + --yellow-text: var(--yellow-dark); + // Non theme colors - will not update with light/dark toggle --grey-lightest: #e5e5e5; --grey-lighter: #c5c5c5; @@ -117,7 +118,7 @@ --green-lighter: #B0EEA2; --green: #306025; - --yellow-lighter: #ffeaa7; + --yellow-dark: #664600; --yellow: #F8D487; transition: background-color 150ms linear; @@ -132,9 +133,6 @@ --primary-lighter: #3796bc; --primary: #7257EB; -// --primary-darker: #2F2363; - - // --primary: #636666; --primary-darker: #133441; --secondary-lighter: #636666; @@ -163,6 +161,9 @@ --table-row-even: var(--grey-darker); --table-row-odd: var(--grey-darkest); + --red-text: var(--red-lighter); + --yellow-text: var(--yellow); + background-color: var(--background); color: var(--text-color); } diff --git a/devU-client/src/assets/variables.scss b/devU-client/src/assets/variables.scss index 47f058df..2e699a96 100644 --- a/devU-client/src/assets/variables.scss +++ b/devU-client/src/assets/variables.scss @@ -19,7 +19,7 @@ $secondary-darker: var(--secondary-darker); $list-item-background: var(--list-item-background); $list-item-background-hover: var(--list-item-background-hover); -$list-item-subtext: var(--list-item-subtext); +$list-item-subtext: var(--list-item-subtext); $list-simple-item-background: var(--list-simple-item-background); $list-simple-item-background-hover: var(--list-simple-item-background-hover); @@ -50,9 +50,12 @@ $purple: var(--purple); $green-lighter: var(--green-lighter); $green: var(--green); -$yellow-lighter: var(--yellow-lighter); +$yellow-dark: var(--yellow-dark); $yellow: var(--yellow); +$redText: var(--red-text); +$yellowText: var(--yellow-text); + // Non color CSS variables $border-radius: 3px; @@ -69,4 +72,4 @@ $extreme: 780px; text-overflow: ellipsis; white-space: nowrap; word-wrap: normal; -} +} \ No newline at end of file diff --git a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx index 12b52b56..e1440dd0 100644 --- a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx @@ -1,15 +1,16 @@ -import React, {useEffect, useState} from 'react' +import React, { useEffect, useState } from 'react' -import {Assignment, AssignmentScore, User, UserCourse} from 'devu-shared-modules' +import { Assignment, AssignmentScore, User, UserCourse } from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' +import TextField from 'components/shared/inputs/textField' import ErrorPage from '../errorPage/errorPage' import RequestService from 'services/request.service' import styles from './gradebookPage.scss' -import {useParams} from 'react-router-dom' +import { useParams } from 'react-router-dom' type TableProps = { users: User[] @@ -25,17 +26,22 @@ type RowProps = { assignmentScores: AssignmentScore[] } -const TableRow = ({index, user, userCourse, assignments, assignmentScores}: RowProps) => { +const TableRow = ({ index, user, userCourse, assignments, assignmentScores }: RowProps) => { // style table row to alternating colors based on index odd?even const rowClass = index % 2 === 0 ? 'evenRow' : 'oddRow'; + // dont show row if dropped + if (userCourse.dropped) { + return (<>) + } + return ( {index} {user.email} - {user.externalId} + {/* {user.externalId} */} {user.preferredName} - {userCourse.dropped.toString()} + {/* {userCourse.dropped.toString()} */} {assignments.map(a => ( {assignmentScores.find(as => as.assignmentId === a.id)?.score ?? 'N/A'} ))} @@ -43,16 +49,16 @@ const TableRow = ({index, user, userCourse, assignments, assignmentScores}: RowP ) } -const GradebookTable = ({users, userCourses, assignments, assignmentScores}: TableProps) => { +const GradebookTable = ({ users, userCourses, assignments, assignmentScores }: TableProps) => { return ( - + - + {/* */} - + {/* */} {assignments.map((a) => { - return ( ) + return () })} {users.map((u, index) => ( { const [userCourses, setUserCourses] = useState(new Array()) //All user-course connections for the course const [assignments, setAssignments] = useState(new Array()) //All assignments in the course const [assignmentScores, setAssignmentScores] = useState(new Array()) //All assignment scores for assignments in the course - - const { courseId } = useParams<{courseId: string}>() - + + const { courseId } = useParams<{ courseId: string }>() + useEffect(() => { fetchData() - }, []) - + }, []) + const fetchData = async () => { try { const userCourses = await RequestService.get(`/api/course/${courseId}/user-courses/`) @@ -89,12 +95,12 @@ const GradebookInstructorPage = () => { const users = await RequestService.get(`/api/users/course/${courseId}`) setUsers(users) - - const assignments = await RequestService.get( `/api/course/${courseId}/assignments` ) + + const assignments = await RequestService.get(`/api/course/${courseId}/assignments`) assignments.sort((a, b) => (Date.parse(a.startDate) - Date.parse(b.startDate))) //Sort by assignment's start date setAssignments(assignments) - const assignmentScores = await RequestService.get( `/api/course/${courseId}/assignment-scores` ) + const assignmentScores = await RequestService.get(`/api/course/${courseId}/assignment-scores`) setAssignmentScores(assignmentScores) } catch (error: any) { @@ -107,13 +113,28 @@ const GradebookInstructorPage = () => { if (loading) return if (error) return + const handleStudentSearch = () => { + + } + return ( {/*
*/}

Instructor Gradebook

{/*
*/}
- +

Key: ! = late, - = no submission

+
+ +
+
+ Date: Tue, 15 Oct 2024 18:33:37 -0400 Subject: [PATCH 194/400] fixed adding the problem button, to add the problem immediately to the assignment for the instructor and not only through a noncontainer autograder --- .../assignments/assignmentDetailPage.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 2eb53ada..8e853251 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -1,7 +1,7 @@ import React, {useEffect, useState} from 'react' import {useHistory, useParams} from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' -import {Assignment, AssignmentProblem, Submission, NonContainerAutoGrader, /*ContainerAutoGrader*/} from 'devu-shared-modules' +import {Assignment, AssignmentProblem, Submission, /*NonContainerAutoGrader, /*ContainerAutoGrader*/} from 'devu-shared-modules' import RequestService from 'services/request.service' import ErrorPage from '../errorPage/errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' @@ -36,7 +36,7 @@ const AssignmentDetailPage = () => { // const [containerAutograder, setContainerAutograder] = useState() // const containerAutograder = false; //TODO: Use the above commented out code to get the container autograder - const [nonContainerAutograders, setNonContainerAutograders] = useState(new Array ()) + // const [ setNonContainerAutograders] = useState(new Array ()) useEffect(() => { fetchData() @@ -70,8 +70,8 @@ const AssignmentDetailPage = () => { // const containerAutograder = (await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`)).pop() ?? null // setContainerAutograder(containerAutograder) - const nonContainers = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/non-container-auto-graders`) - setNonContainerAutograders(nonContainers) + // const nonContainers = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/non-container-auto-graders`) + // setNonContainerAutograders(nonContainers) } catch (err:any) { @@ -186,18 +186,19 @@ const AssignmentDetailPage = () => { {assignment?.dueDate && ( {`Due Date: ${new Date(assignment.dueDate).toLocaleDateString()}`} )} - {nonContainerAutograders && nonContainerAutograders.length > 0 ? ( - nonContainerAutograders.map((nonContainer, index) => ( + {assignmentProblems && assignmentProblems.length > 0 ? ( + assignmentProblems.map((assignment, index) => ( {`Assignment Problem ${index + 1}`} - {nonContainer.question} - + {assignment.problemName} + )) + ) : ( No Problems Exist @@ -242,4 +243,4 @@ const AssignmentDetailPage = () => { ) } -export default AssignmentDetailPage +export default AssignmentDetailPage \ No newline at end of file From 1559cf62ec0b3336c3b7e675320e3e7cd627ef2c Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 15 Oct 2024 21:58:30 -0400 Subject: [PATCH 195/400] For task cards #58 and #71 I updated the format of the coursepage. I made sure the buttons we wanted visible were visible, I changed the styling coordinating colors properly on the page. I updated the courDetailPage.scss and the courseDetailPage.tsx --- .../pages/courses/courseDetailPage.scss | 130 ++++++++---------- .../pages/courses/courseDetailPage.tsx | 43 ++++-- 2 files changed, 88 insertions(+), 85 deletions(-) diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index 1ce8e46f..78b039bf 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -34,91 +34,94 @@ } .courseCard { display: flex; - flex:1 0 250px; + flex: 1 0 250px; max-width: 350px; flex-direction: column; justify-content: space-between; align-items: stretch; height: 100%; padding: 0; - background-color: #D9D9D9; + background-color: rebeccapurple; border-radius: 5px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); overflow: hidden; margin-bottom: 20px; - .MuiList-root { - padding: 20px; + .MuiList-root { + padding: 20px; + .MuiListItemButton-root { + display: flex; + padding-top: 0; + padding-left: 0; + justify-content: center; /* Center the content horizontally */ + align-items: center; + } + .MuiListItemText-primary { + text-align: center; + } - .MuiListItemText-primary { - text-align: center; // Center the assignment name - } - - .MuiListItemText-secondary { - text-align: center; // Center the dates - } + .MuiListItemText-secondary { + text-align: center; // Center the dates } } + } -.courseDetailPage{ - padding: 20px; +.courseDetailPage { + padding: 40px; -.header { - display: flex; - flex-direction: column; - //justify-content: flex-start; - //grid-template-columns: 2fr 1fr; - justify-content: space-between; - align-items: flex-start; - background-color: $secondary; - // padding: 30px 60px; - padding: 20px; - //border-radius: 10px; - color: $text-color; - width: 70%; - h1{ + .header { + display: flex; + flex-direction: column; + + justify-content: space-between; + align-items: flex-start; + background-color: $secondary; + // padding: 30px 60px; + padding: 20px; + //border-radius: 10px; + color: $text-color; + width: 70% + } + + h1 { margin: 0 0 10px 0; font-size: 2.5rem; } - h2{ + + h2 { + padding-top: 20px; margin: 0 0 20px 0; - font-size: 1.5rem; + font-size: 1.0rem; + padding-bottom: 10px; } - .buttons-container { - display: flex; - // flex-wrap: wrap; - flex-direction: row; - justify-content: space-around; +} - .actual_button { + .buttons-container { display: flex; - flex-direction: row; - border: 0; /* Primary button color */ - /* Button text color */ - padding: 10px 20px; /* Padding for button */ - border-radius: 5px; - text-decoration: none; - font-weight: 600; - transition: background-color 0.3s ease; - //margin : 0 10px; - background-color: $purple; - color: white; - border: none; - - &:hover { // Add a hover effect - background-color: darken(purple, 10%); // Slightly darken on hover - cursor: pointer; - } + flex-wrap: wrap; + //flex-direction: row; + justify-content: space-around; + width:100%; } - } -} - } + .actual_button { + display: flex; + flex-direction: row; + border: 0; /* Primary button color */ + /* Button text color */ + padding: 10px 20px; /* Padding for button */ + border-radius: 5px; + font-weight: 600; + transition: background-color 0.3s ease; + margin : 0 10px; + background-color: $purple !important; + color: white !important; + } @@ -127,24 +130,7 @@ -.input { - width: 400px; - padding: 8px; - border-radius: 5px; - border: none; -} -.assignment_card { - flex: 1; - color: $text-color2; - align-items: start; - text-align: center; - border-radius: 10px; - min-width : 200px; -} -.assignment-card:hover { - transform: scale(1.05); -} diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index dd020895..4388698d 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -30,16 +30,16 @@ const CourseDetailPage = () => { const [courseInfo, setCourseInfo] = useState(null) const [categoryMap, setCategoryMap] = useState>({}) const [setAlert] = useActionless(SET_ALERT) - const[User, setUser]= useState < User ,preferredName>>({}) + // const[User, setUser]= useState < User ,preferredName>>({}) // const role = useAppSelector((store) => store.roleMode) - const fetchUserinfo = async () => { + /* const fetchUserinfo = async () => { RequestService.get< typeof User>('api/users') .then((User) =>{ setUser(User) }) - +*/ const fetchCourseInfo = async () => { @@ -85,28 +85,33 @@ const CourseDetailPage = () => { useEffect(() => { fetchCourseInfo() - fetchUserinfo() + //fetchUserinfo() }, []) const history = useHistory() return(
+ {courseInfo ? ( -
+
-

{courseInfo.name}-{courseInfo.semester}

-

Instructor:{User.name}

+

{courseInfo.name}({courseInfo.semester})

+

Instructor:

+ +
- + -
-
+ +
+ + +
+ + +

Assignments

@@ -142,7 +153,12 @@ const CourseDetailPage = () => { { history.push(`/course/${courseId}/assignment/${assignment.id}`) }}> - + {assignment.name} + + } secondary={ { ))} - +
- @@ -176,7 +191,9 @@ const CourseDetailPage = () => { ) : (

Error fetching Course Information

)} + +
) } From 0f9542515fe6829c0cf92390ddbd1a4f3ca52241 Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Wed, 16 Oct 2024 00:10:48 -0400 Subject: [PATCH 196/400] Fixed merging typo --- devU-client/src/components/misc/globalToolbar.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index a371454d..60ed99ea 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -29,7 +29,6 @@ $font-size: 16px; font-weight: 550; //font-size: 14px; - font-size: 14px; padding:0.5vw; &:hover { @@ -56,7 +55,7 @@ $font-size: 16px; border-radius: 30px; color: #D9D9D9; font-weight: 550; -} + @extend .flex; justify-content: space-between; From 92fdde88174fbe19e0b116ab6642a326e9150ba9 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Sat, 19 Oct 2024 17:58:18 -0400 Subject: [PATCH 197/400] updated styling of datepicker and submit button --- .../pages/forms/courses/coursesFormPage.tsx | 76 +++++++++++-------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx index 3fd06420..1cbe0e15 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx @@ -1,53 +1,57 @@ -import React, {useState} from 'react' -import DatePicker from 'react-datepicker' -import 'react-datepicker/dist/react-datepicker.css' -import {useHistory} from 'react-router-dom' -import {ExpressValidationError} from 'devu-shared-modules' +import React, { useState } from 'react' +// import DatePicker from 'react-datepicker' +// import 'react-datepicker/dist/react-datepicker.css' +import { useHistory } from 'react-router-dom' +import { ExpressValidationError } from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' import RequestService from 'services/request.service' -import {useActionless} from 'redux/hooks' +import { useActionless } from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -import {SET_ALERT} from 'redux/types/active.types' +import { SET_ALERT } from 'redux/types/active.types' import formStyles from './coursesFormPage.scss' -import {applyMessageToErrorFields, removeClassFromField} from "../../../../utils/textField.utils"; - -import Button from '@mui/material/Button' +import { applyMessageToErrorFields, removeClassFromField } from "../../../../utils/textField.utils"; const EditCourseFormPage = () => { const [setAlert] = useActionless(SET_ALERT) const history = useHistory(); - const [formData,setFormData] = useState({ + const [formData, setFormData] = useState({ name: '', number: '', semester: '', }) - const [startDate, setStartDate] = useState(new Date()) - const [endDate, setEndDate] = useState(new Date()) + // const [startDate, setStartDate] = useState(new Date()) + // const [endDate, setEndDate] = useState(new Date()) + const [startDate, setStartDate] = useState(new Date().toISOString().split("T")[0]) + const [endDate, setEndDate] = useState(new Date().toISOString().split("T")[0]) const [invalidFields, setInvalidFields] = useState(new Map()) - const handleChange = (value: String, e : React.ChangeEvent) => { + const handleChange = (value: String, e: React.ChangeEvent) => { const key = e.target.id - setFormData(prevState => ({...prevState,[key] : value})) + setFormData(prevState => ({ ...prevState, [key]: value })) const newInvalidFields = removeClassFromField(invalidFields, key) setInvalidFields(newInvalidFields) } - const handleStartDateChange = (date : Date) => {setStartDate(date)} - const handleEndDateChange = (date : Date) => {setEndDate(date)} + // const handleStartDateChange = (date : Date) => {setStartDate(date)} + // const handleEndDateChange = (date : Date) => {setEndDate(date)} + const handleStartDateChange = (event: React.ChangeEvent) => { setStartDate(event.target.value) } + const handleEndDateChange = (event: React.ChangeEvent) => { setEndDate(event.target.value) } const handleSubmit = () => { const finalFormData = { - name : formData.name, - number : formData.number, - semester : formData.semester, - startDate : startDate.toISOString(), - endDate : endDate.toISOString(), + name: formData.name, + number: formData.number, + semester: formData.semester, + // startDate: startDate.toISOString(), + // endDate: endDate.toISOString(), + startDate: startDate, + endDate: endDate, } RequestService.post('/api/courses/instructor', finalFormData) @@ -63,8 +67,8 @@ const EditCourseFormPage = () => { setInvalidFields(newFields); setAlert({ autoDelete: false, type: 'error', message }) }) - .finally(() => { - }) + .finally(() => { + }) } return ( @@ -72,14 +76,14 @@ const EditCourseFormPage = () => {

Course Form

+ invalidated={!!invalidFields.get("name")} helpText={invalidFields.get("name")} /> + invalidated={!!invalidFields.get("number")} helpText={invalidFields.get("number")} /> + placeholder='Ex. f2022, w2023, s2024' invalidated={!!invalidFields.get("semester")} + helpText={invalidFields.get("semester")} /> -
+ {/*

@@ -92,11 +96,21 @@ const EditCourseFormPage = () => {
+
*/} +
+
+ + +
+
+ + +
-
+
- +
From 7fb8654f4bd25256ed43cdb1312f5440db6fae7d Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Sat, 19 Oct 2024 18:30:58 -0400 Subject: [PATCH 198/400] updated styling to be mobile responsive --- .../pages/forms/courses/coursesFormPage.tsx | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx index 1cbe0e15..64e61cfe 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx @@ -74,45 +74,45 @@ const EditCourseFormPage = () => { return (

Course Form

-
- - - - - {/*
-
- + +
+
+ + + + {/*
+
+ +
+ +
+
+
- -
-
- -
- -
-
*/} -
-
- - + +
+
*/} +
+
+ + +
+
+ + +
-
- - + {/*
*/} +
+
-
- -
- -
-
) From d00af01a924d2d717ddaf501cdbd7717c8ac6d9c Mon Sep 17 00:00:00 2001 From: RA341 Date: Sat, 19 Oct 2024 22:38:06 -0400 Subject: [PATCH 199/400] refactored process file to service --- .../assignment/assignment.controller.ts | 32 ++----------------- .../entities/assignment/assignment.service.ts | 32 ++++++++++++++++++- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/devU-api/src/entities/assignment/assignment.controller.ts b/devU-api/src/entities/assignment/assignment.controller.ts index 878df677..ab4e20a5 100644 --- a/devU-api/src/entities/assignment/assignment.controller.ts +++ b/devU-api/src/entities/assignment/assignment.controller.ts @@ -5,8 +5,7 @@ import AssignmentService from './assignment.service' import { GenericResponse, NotFound, Updated } from '../../utils/apiResponse.utils' import { serialize } from './assignment.serializer' -import { BucketNames, downloadFile, uploadFile } from '../../fileStorage' -import { generateFilename } from '../../utils/fileUpload.utils' +import { BucketNames, downloadFile } from '../../fileStorage' export async function detail(req: Request, res: Response, next: NextFunction) { try { @@ -85,34 +84,9 @@ export async function getReleased(req: Request, res: Response, next: NextFunctio } -async function processFiles(req: Request) { - let fileHashes: string[] = [] - let fileNames: string[] = [] - - // save files - if (req.files) { - console.log() - if (Array.isArray(req.files)) { - for (let index = 0; index < req.files.length; index++) { - const item = req.files[index] - const filename = generateFilename(item.originalname, item.size) - await uploadFile(BucketNames.ASSIGNMENTSATTACHMENTS, item, filename) - fileHashes.push(filename) - fileNames.push(item.originalname) - } - } else { - console.warn(`Files where not in array format ${req.files}`) - } - } else { - console.warn(`No files where processed`) - } - - return { fileHashes, fileNames } -} - export async function post(req: Request, res: Response, next: NextFunction) { try { - const { fileNames, fileHashes } = await processFiles(req) + const { fileNames, fileHashes } = await AssignmentService.processFiles(req) req.body['attachmentsFilenames'] = fileNames req.body['attachmentsHashes'] = fileHashes @@ -130,7 +104,7 @@ export async function post(req: Request, res: Response, next: NextFunction) { export async function put(req: Request, res: Response, next: NextFunction) { try { - const { fileNames, fileHashes } = await processFiles(req) + const { fileNames, fileHashes } = await AssignmentService.processFiles(req) req.body['attachmentsFilenames'] = fileNames req.body['attachmentsHashes'] = fileHashes diff --git a/devU-api/src/entities/assignment/assignment.service.ts b/devU-api/src/entities/assignment/assignment.service.ts index 312d03a2..551f9902 100644 --- a/devU-api/src/entities/assignment/assignment.service.ts +++ b/devU-api/src/entities/assignment/assignment.service.ts @@ -4,6 +4,9 @@ import { dataSource } from '../../database' import AssignmentModel from './assignment.model' import { Assignment } from 'devu-shared-modules' +import { Request } from 'express' +import { generateFilename } from '../../utils/fileUpload.utils' +import { BucketNames, uploadFile } from '../../fileStorage' const connect = () => dataSource.getRepository(AssignmentModel) @@ -40,7 +43,7 @@ export async function update(assignment: Assignment) { maxSubmissions, disableHandins, attachmentsHashes, - attachmentsFilenames + attachmentsFilenames, }) } @@ -78,6 +81,32 @@ export async function isReleased(id: number) { return startDate && startDate < currentDate } +async function processFiles(req: Request) { + let fileHashes: string[] = [] + let fileNames: string[] = [] + + // save files + if (req.files) { + console.log() + if (Array.isArray(req.files)) { + for (let index = 0; index < req.files.length; index++) { + const item = req.files[index] + const filename = generateFilename(item.originalname, item.size) + await uploadFile(BucketNames.ASSIGNMENTSATTACHMENTS, item, filename) + fileHashes.push(filename) + fileNames.push(item.originalname) + } + } else { + console.warn(`Files where not in array format ${req.files}`) + } + } else { + console.warn(`No files where processed`) + } + + return { fileHashes, fileNames } +} + + export default { create, retrieve, @@ -87,4 +116,5 @@ export default { listByCourse, listByCourseReleased, isReleased, + processFiles } From 1082097c0a76e504bf64c848dd71b8f047002595 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sat, 19 Oct 2024 22:39:11 -0400 Subject: [PATCH 200/400] changed return type to octet stream instead of JSON --- devU-api/src/fileUpload/fileUpload.controller.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devU-api/src/fileUpload/fileUpload.controller.ts b/devU-api/src/fileUpload/fileUpload.controller.ts index 344ae486..0c30911e 100644 --- a/devU-api/src/fileUpload/fileUpload.controller.ts +++ b/devU-api/src/fileUpload/fileUpload.controller.ts @@ -26,7 +26,9 @@ export async function detail(req: Request, res: Response, next: NextFunction) { if (!file) return res.status(404).json(NotFound) - res.status(200).json(file) + res.setHeader('Content-Type', 'application/octet-stream') + res.setHeader('Content-Length', file.length) + res.send(file) } catch (err) { next(err) } From 44c8797e4d9aa281c232b9c2e4ee4c30449fc59b Mon Sep 17 00:00:00 2001 From: RA341 Date: Sat, 19 Oct 2024 22:39:34 -0400 Subject: [PATCH 201/400] added bucket name to file path, to retrieve file --- devU-api/src/entities/submission/submission.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-api/src/entities/submission/submission.service.ts b/devU-api/src/entities/submission/submission.service.ts index e7a9ba09..4e21c8ab 100644 --- a/devU-api/src/entities/submission/submission.service.ts +++ b/devU-api/src/entities/submission/submission.service.ts @@ -38,7 +38,7 @@ export async function create(submission: Submission, file?: Express.Multer.File if (!content.filepaths) { content.filepaths = [] } - content.filepaths.push(filename) + content.filepaths.push(`${bucket}/${filename}`) submission.content = JSON.stringify(content) await fileConn().save(fileModel) From e52ccd7c00ce37df25e15552eccbe0db20f2d2ee Mon Sep 17 00:00:00 2001 From: RA341 Date: Sat, 19 Oct 2024 23:40:20 -0400 Subject: [PATCH 202/400] added a method for getting max submissions --- devU-api/src/entities/assignment/assignment.service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/devU-api/src/entities/assignment/assignment.service.ts b/devU-api/src/entities/assignment/assignment.service.ts index 551f9902..7d7f713d 100644 --- a/devU-api/src/entities/assignment/assignment.service.ts +++ b/devU-api/src/entities/assignment/assignment.service.ts @@ -81,6 +81,10 @@ export async function isReleased(id: number) { return startDate && startDate < currentDate } +async function getMaxSubmissionsForAssignment(id: number) { + return await connect().findOne({ where: { id: id }, select: ['maxSubmissions', 'maxFileSize'] }) +} + async function processFiles(req: Request) { let fileHashes: string[] = [] let fileNames: string[] = [] @@ -116,5 +120,6 @@ export default { listByCourse, listByCourseReleased, isReleased, - processFiles + getMaxSubmissionsForAssignment, + processFiles, } From c598cafef622a05548a29b05f86f4ed0fd65cb1c Mon Sep 17 00:00:00 2001 From: RA341 Date: Sat, 19 Oct 2024 23:54:56 -0400 Subject: [PATCH 203/400] added checks for max file size and max submissions --- .../submission/submission.middleware.ts | 61 +++++++++++++++++++ .../entities/submission/submission.router.ts | 3 +- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 devU-api/src/entities/submission/submission.middleware.ts diff --git a/devU-api/src/entities/submission/submission.middleware.ts b/devU-api/src/entities/submission/submission.middleware.ts new file mode 100644 index 00000000..959c68dc --- /dev/null +++ b/devU-api/src/entities/submission/submission.middleware.ts @@ -0,0 +1,61 @@ +// middleware to enforce mac submissions + +import { Request, Response, NextFunction } from 'express' +import SubmissionService from './submission.service' +import AssignmentService from '../assignment/assignment.service' + +// TODO discuss how to bypass this when an instructor wants to eg bypass for a specific student +async function checkSubmissions(req: Request, res: Response, next: NextFunction) { + const body = req.body + const userID = req.currentUser?.userId + const assignmentId = body.assignmentId + + if (!userID) { + return res.status(403).send('userid is missing') + } + + try { + const assignmentInfo = await AssignmentService.getMaxSubmissionsForAssignment(assignmentId) + + if (assignmentInfo == null) return res.status(403).send('could not retrieve assignment info') + + if (assignmentInfo!.maxSubmissions == null) { + console.debug('Max submissions are not specified, skipping check') + // check file size + if (req.file && req.file.size > assignmentInfo!.maxFileSize!) { + return res.status(403).json({ + 'error': 'file is bigger than allowed max file size', + 'allowed size': assignmentInfo!.maxFileSize!, + 'file size': req.file.size, + }) + } + + return next() + } + + const submissions = await SubmissionService.listByAssignment(assignmentId, userID) + // check submissions + if (submissions.length >= assignmentInfo!.maxSubmissions!) { + return res.status(403).json({ + 'error': 'max submissions reached.', + 'Max submissions': assignmentInfo!.maxSubmissions!, + 'Current submissions': submissions.length, + }) + } + + // check file size + if (req.file && req.file.size > assignmentInfo!.maxFileSize!) { + return res.status(403).json({ + 'error': 'file is bigger than allowed max file size', + 'allowed size': assignmentInfo!.maxFileSize!, + 'file size': req.file.size, + }) + } + + next() + } catch (e) { + return res.status(500).send(e) + } +} + +export { checkSubmissions } \ No newline at end of file diff --git a/devU-api/src/entities/submission/submission.router.ts b/devU-api/src/entities/submission/submission.router.ts index c8b4517c..a364626a 100644 --- a/devU-api/src/entities/submission/submission.router.ts +++ b/devU-api/src/entities/submission/submission.router.ts @@ -9,6 +9,7 @@ import { asInt } from '../../middleware/validator/generic.validator' // Controller import SubmissionController from '../submission/submission.controller' +import { checkSubmissions } from './submission.middleware' const Router = express.Router({ mergeParams: true }) const upload = Multer() @@ -97,7 +98,7 @@ Router.get('/user/:userId', isAuthorized('enrolled'), asInt('userId'), Submissio * schema: * $ref: '#/components/schemas/Submission' */ -Router.post('/', isAuthorized('enrolled'), upload.single('files'), validator, SubmissionController.post) +Router.post('/', isAuthorized('enrolled'), upload.single('files'), validator, checkSubmissions, SubmissionController.post) // TODO: submissionCreateSelf or submissionCreateAll /** From cc84cf2c0a4c9775756e57e768b6386fc4537be8 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 20 Oct 2024 01:15:04 -0400 Subject: [PATCH 204/400] removed userid check in submission validator --- devU-api/src/entities/submission/submission.middleware.ts | 3 +-- devU-api/src/entities/submission/submission.validator.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/devU-api/src/entities/submission/submission.middleware.ts b/devU-api/src/entities/submission/submission.middleware.ts index 959c68dc..0b663af9 100644 --- a/devU-api/src/entities/submission/submission.middleware.ts +++ b/devU-api/src/entities/submission/submission.middleware.ts @@ -6,9 +6,8 @@ import AssignmentService from '../assignment/assignment.service' // TODO discuss how to bypass this when an instructor wants to eg bypass for a specific student async function checkSubmissions(req: Request, res: Response, next: NextFunction) { - const body = req.body const userID = req.currentUser?.userId - const assignmentId = body.assignmentId + const assignmentId = req.body.assignmentId if (!userID) { return res.status(403).send('userid is missing') diff --git a/devU-api/src/entities/submission/submission.validator.ts b/devU-api/src/entities/submission/submission.validator.ts index 4930606f..66c67532 100644 --- a/devU-api/src/entities/submission/submission.validator.ts +++ b/devU-api/src/entities/submission/submission.validator.ts @@ -2,7 +2,6 @@ import { check } from 'express-validator' import validate from '../../middleware/validator/generic.validator' -const userId = check('userId').isNumeric() const assignmentId = check('assignmentId').isNumeric() const courseId = check('courseId').isNumeric() const content = check('content').isString() @@ -17,6 +16,6 @@ const file = check('file') } }) -const validator = [courseId, assignmentId, userId, content, file, validate] +const validator = [courseId, assignmentId, content, file, validate] export default validator From 374c390d0429ad5d8432a7985f7985edeec4d2c5 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Sun, 20 Oct 2024 15:14:49 -0400 Subject: [PATCH 205/400] fixed mobile responsiveness for update and create course --- .../pages/forms/courses/courseUpdatePage.tsx | 2 +- .../pages/forms/courses/coursesFormPage.scss | 14 ++++++++++---- .../pages/forms/courses/coursesFormPage.tsx | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx index 84367aaf..e8fd3945 100644 --- a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx @@ -101,7 +101,7 @@ const CourseUpdatePage = ({ }) => {

Update Course Form

-
+

Course Details

{

Course Form

-
+
Date: Sun, 20 Oct 2024 16:57:52 -0400 Subject: [PATCH 206/400] updated the page header for create course form --- .../pages/forms/courses/coursesFormPage.tsx | 27 +++---------------- 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx index eeb7dbe5..963898da 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx @@ -1,6 +1,4 @@ import React, { useState } from 'react' -// import DatePicker from 'react-datepicker' -// import 'react-datepicker/dist/react-datepicker.css' import { useHistory } from 'react-router-dom' import { ExpressValidationError } from 'devu-shared-modules' @@ -24,8 +22,7 @@ const EditCourseFormPage = () => { number: '', semester: '', }) - // const [startDate, setStartDate] = useState(new Date()) - // const [endDate, setEndDate] = useState(new Date()) + const [startDate, setStartDate] = useState(new Date().toISOString().split("T")[0]) const [endDate, setEndDate] = useState(new Date().toISOString().split("T")[0]) const [invalidFields, setInvalidFields] = useState(new Map()) @@ -37,8 +34,7 @@ const EditCourseFormPage = () => { const newInvalidFields = removeClassFromField(invalidFields, key) setInvalidFields(newInvalidFields) } - // const handleStartDateChange = (date : Date) => {setStartDate(date)} - // const handleEndDateChange = (date : Date) => {setEndDate(date)} + const handleStartDateChange = (event: React.ChangeEvent) => { setStartDate(event.target.value) } const handleEndDateChange = (event: React.ChangeEvent) => { setEndDate(event.target.value) } @@ -48,8 +44,6 @@ const EditCourseFormPage = () => { name: formData.name, number: formData.number, semester: formData.semester, - // startDate: startDate.toISOString(), - // endDate: endDate.toISOString(), startDate: startDate, endDate: endDate, } @@ -73,7 +67,7 @@ const EditCourseFormPage = () => { return ( -

Course Form

+

Create Course

@@ -84,20 +78,6 @@ const EditCourseFormPage = () => { - {/*
-
- -
- -
-
- -
- -
-
*/}
@@ -108,7 +88,6 @@ const EditCourseFormPage = () => {
- {/*
*/}
From 9c29319fe01fc4f2d20e823c7ff729283a23f7c5 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Tue, 22 Oct 2024 03:17:42 -0400 Subject: [PATCH 207/400] Updated styling of the submissions details page for students, added a submission summary and card that presents the feedback and content of the submission --- .../src/components/misc/globalToolbar.scss | 3 +- devU-client/src/components/misc/navbar.scss | 5 +- .../components/pages/homePage/homePage.scss | 22 +-- .../components/pages/homePage/homePage.tsx | 14 +- .../submissions/submissionDetailPage.scss | 124 ++++++++++++++++ .../submissions/submissionDetailPage.tsx | 137 ++++++++++++++---- 6 files changed, 255 insertions(+), 50 deletions(-) create mode 100644 devU-client/src/components/pages/submissions/submissionDetailPage.scss diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 60ed99ea..6dc6e06c 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -50,9 +50,8 @@ $font-size: 16px; height: 80px; background-color: $purple; //height: $bar-height; - font-size: 40px; - border-radius: 30px; + //border-radius: 30px removed so the edges are not round color: #D9D9D9; font-weight: 550; diff --git a/devU-client/src/components/misc/navbar.scss b/devU-client/src/components/misc/navbar.scss index 0c8cf19a..9ee712c2 100644 --- a/devU-client/src/components/misc/navbar.scss +++ b/devU-client/src/components/misc/navbar.scss @@ -3,14 +3,17 @@ .breadcrumbContainer { display: flex; align-items: center; + margin-left: 3.5rem; + margin-top:10px; + } .link { text-decoration: none; color: $text-color; /* Breadcrumb link color */ - } + .link:hover { text-decoration: underline; } diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index 932d059b..5e3b9513 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -1,7 +1,7 @@ @import 'variables'; -.h2 { +.courses_title { text-align: left; margin-left: 20px; font-size: 30px; @@ -9,7 +9,7 @@ margin-bottom: 30px; } -h3 { +.courses_heading{ align-items:left; margin-left: 20px; font-size: 30px; @@ -17,15 +17,15 @@ h3 { margin-bottom: 30px; } -h1 { - align-items:left; - margin-left: 20px; - font-size: 30px; - font-weight: 550; - margin-bottom: 30px; -} +// h1 { +// align-items:left; +// margin-left: 20px; +// font-size: 30px; +// font-weight: 550; +// margin-bottom: 30px; +// } -h4 { +.no_courses { margin-left: 20px; } @@ -65,7 +65,7 @@ h4 { align-items: center; } -h3::after { +.courses_heading::after { content: ''; display: block; margin-top: 10px; diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 4bb0a1d7..44218b29 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -75,7 +75,7 @@ const HomePage = () => {
-

Courses

+

Courses

-

Current

+

Current

@@ -101,12 +101,12 @@ const HomePage = () => {
))} - {enrollCourses.length === 0 &&

You do not have current enrollment yet

} + {enrollCourses.length === 0 &&

You do not have current enrollment yet

}
-

Completed

+

Completed

@@ -122,12 +122,12 @@ const HomePage = () => { />
))} - {pastCourses.length === 0 &&

No completed courses

} + {pastCourses.length === 0 &&

No completed courses

}
-

Upcoming

+

Upcoming

@@ -137,7 +137,7 @@ const HomePage = () => {
))} - {upcomingCourses.length === 0 &&

No upcoming Courses

} + {upcomingCourses.length === 0 &&

No upcoming Courses

}
diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.scss b/devU-client/src/components/pages/submissions/submissionDetailPage.scss new file mode 100644 index 00000000..8dc7786f --- /dev/null +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.scss @@ -0,0 +1,124 @@ +@import 'variables'; + +.scores { + padding: 20px; + } + + .submissionsLayout { + display: flex; + gap: 20px; + } + + .submissionsContainer { + flex: 1; + max-width: 250px; + border-right: 1px solid lightgray; + padding-right: 10px; + overflow-y: auto; + } + + .submissionCard { + margin-bottom: 15px; + cursor: pointer; + } + + .submissionContent { + flex: 2; + max-height: 800px; + overflow-y: auto; + padding: 0 20px; + background-color: $background; + border: 1px solid #ddd; + color: $text-color; + border-radius: 30px; + padding:30px; + } + + pre { + color: $text-color; + white-space: pre-wrap; + word-wrap: break-word; + font-size: 12px; + } + + .feedbackContainer{ + background-color: $background; + + } + + .scoreDisplay { + + color:$text-color; + + } + +.content_title{ + display: inline-block; + padding: 5px 10px; + background-color: $secondary; + color: $text-color; + border-radius: 5px; + font-size: 1rem; + font-weight: bold; + text-align: left; + margin-bottom: 10px; + margin-top:30px; +} + +.scrollableContent { + max-height: 350px; + overflow-y: auto; + border: 1px solid $background; + padding: 10px; + background-color: $background; + border-radius: 5px; + border: 2px solid $text-color; + padding:0px; +} + p{ + text-align: left; + margin-left: 10px; + } + + .scrollableContent::-webkit-scrollbar { + width: 12px; /* Width of the scrollbar */ + } + + .scrollableContent::-webkit-scrollbar-thumb { + background-color: $purple; + border-radius: 10px; + } + + .scrollableContent::-webkit-scrollbar-track { + background-color: #f0f0f0; + } + + .problemAnswerContainer { + margin-top: 10px; + max-height: 400px; + overflow-y: auto; + padding: 10px; + background-color: $background; + border-radius: 5px; + } + + .assignmentTable { + width: 100%; + border-collapse: collapse; + } + + .assignmentTable th, .assignmentTable td { + + padding: 8px; + text-align: center; + } + + .assignmentTable th { + background-color: $background; + font-weight: bold; + } + + .assignmentTable tr:nth-child(even) { + background-color: $background; + } + \ No newline at end of file diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx index c2bff140..e3dcb070 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx @@ -1,15 +1,20 @@ import React, {useEffect, useState} from 'react' - import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import ErrorPage from '../errorPage/errorPage' import RequestService from 'services/request.service' -import {Assignment, AssignmentProblem, Submission, SubmissionProblemScore, SubmissionScore} from 'devu-shared-modules' -import {Link, useParams} from 'react-router-dom' +import {Assignment, AssignmentProblem, Submission, SubmissionProblemScore,SubmissionScore} from 'devu-shared-modules' +import { useParams,/*useHistory*/} from 'react-router-dom' import Button from '../../shared/inputs/button' import TextField from '../../shared/inputs/textField' import {useActionless} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' +import styles from './submissionDetailPage.scss' +import 'react-datepicker/dist/react-datepicker.css' +import Card from '@mui/material/Card' +import CardContent from '@mui/material/CardContent' +import {CardActionArea, Typography} from '@mui/material' +import {prettyPrintDateTime} from "../../../utils/date.utils"; const SubmissionDetailPage = () => { @@ -21,10 +26,11 @@ const SubmissionDetailPage = () => { const { submissionId, assignmentId, courseId } = useParams<{submissionId: string, assignmentId: string, courseId: string}>() const [submissionScore, setSubmissionScore] = useState(null) const [submissionProblemScores, setSubmissionProblemScores] = useState(new Array()) - const [submission, setSubmission] = useState() + const [selectedSubmission, setSelectedSubmission] = useState() + //const [submission, setSubmission] = useState() const [assignmentProblems, setAssignmentProblems] = useState(new Array()) const [assignment, setAssignment] = useState() - + const [submissions, setSubmissions] = useState(new Array()) const [showManualGrade, setToggleManualGrade] = useState(false) const [formData, setFormData] = useState({ submissionId: submissionId, @@ -32,24 +38,34 @@ const SubmissionDetailPage = () => { feedback: '', releasedAt: "2024-10-05T14:48:00.00Z" }) + const fetchData = async () => { try { - const submission = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submissions/${submissionId}`) - setSubmission(submission) + + //const submission = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submissions/${submissionId}`) + //setSubmission(submission) const submissionScore = (await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submission-scores?submission=${submissionId}`)).pop() ?? null setSubmissionScore(submissionScore) - + + const selectedSubmission = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submissions/${submissionId}`); + setSelectedSubmission(selectedSubmission); + const submissionProblemScores = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submission-problem-scores/submission/${submissionId}`) - setSubmissionProblemScores(submissionProblemScores) + setSubmissionProblemScores(submissionProblemScores) - const assignment = await RequestService.get(`/api/course/${courseId}/assignments/${submission.assignmentId}`) + const assignment = await RequestService.get(`/api/course/${courseId}/assignments/${selectedSubmission.assignmentId}`) setAssignment(assignment) const assignmentProblems = await RequestService.get(`/api/course/${courseId}/assignment/${assignment.id}/assignment-problems`) setAssignmentProblems(assignmentProblems) + const submissionsReq = await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submissions/`) + submissionsReq.sort((a, b) => (Date.parse(b.createdAt ?? '') - Date.parse(a.createdAt ?? ''))) + setSubmissions(submissionsReq) + + } catch (error: any) { setError(error) } finally { @@ -79,6 +95,7 @@ const SubmissionDetailPage = () => { setAlert({ autoDelete: true, type: 'success', message: 'Submission Score Updated' }) }) + } else { // Create a new submission score @@ -90,9 +107,13 @@ const SubmissionDetailPage = () => { } } + + + if (loading) return if (error) return - + //const history = useHistory() + //var submission_form = JSON.parse(submission?.content); return( @@ -104,27 +125,85 @@ const SubmissionDetailPage = () => {
)} +
+

Submissions For Assignment {assignment?.name}

+ + +
+ +
+

Submission List:

+ {submissions.map((submission, index) => ( + + + setSelectedSubmission(submission)}> + + {`Submission ${submissions.length - index}`} + {`Submitted at: ${submission.createdAt && prettyPrintDateTime(submission.createdAt)}`} + + + + ))} +
+
+ + + {selectedSubmission ? ( + <> + +
+

{submissionScore ? `Score: ${submissionScore.score}` : "Score: N/A"}

+
-

Submission Detail for {assignment?.name}

-

Submission Grades:

-
## EmailExternal IDExternal IDPreferred NameDroppedDropped{a.name}{a.name}
- {assignmentProblems.map(ap => ( - - ))} - - - {assignmentProblems.map(ap => ( - - ))} - - +
+

Feedback:

+
+
{ap.problemName} ({ap.maxScore})Total Score
{submissionProblemScores.find(sps => sps.assignmentProblemId === ap.id)?.score ?? "N/A"}{submissionScore?.score ?? "N/A"}
+ + + {assignmentProblems.map(ap => ( + + ))} + + + + + + + {assignmentProblems.map(ap => ( + + ))} + + +
{ap.problemName}Total Score
+ {submissionProblemScores.find(sps => sps.assignmentProblemId === ap.id)?.score ?? "N/A"} + {submissionScore?.score ?? "N/A"}
- View - Feedback -
+
+ {submissionScore?.feedback ? ( +

{submissionScore.feedback}

+ ) : ( +

No feedback provided for this submission.

+ )} + {submissionProblemScores.map(sps => ( +
+

Feedback for {assignmentProblems.find(ap => ap.id === sps.assignmentProblemId)?.problemName}:

+
{sps.feedback}
+
+ ))} -

Submission Content:

-
{submission?.content}
+
+

Content

+
+
{selectedSubmission.content}
+
+ + ) : ( +

Select a submission to view its content.

+ )} +
+
+
) } From 64b6c462fcd14ca123d73b30877ddd0ad6c963df Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:07:12 -0400 Subject: [PATCH 208/400] removed isAuthorized('admin') from user.router.ts --- devU-api/src/entities/user/user.router.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devU-api/src/entities/user/user.router.ts b/devU-api/src/entities/user/user.router.ts index 67ab2309..c0dbcfe2 100644 --- a/devU-api/src/entities/user/user.router.ts +++ b/devU-api/src/entities/user/user.router.ts @@ -19,7 +19,8 @@ const Router = express.Router() * '200': * description: OK */ -Router.get('/', isAuthorized('admin'), UserController.get) +Router.get('/', /*isAuthorized('admin'),*/ UserController.get) +// Router.get('/', isAuthorized('admin'), UserController.get) /** * @swagger From c256f05ae6ffefc1050594892cd6dceb27e595b1 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Tue, 22 Oct 2024 10:39:58 -0400 Subject: [PATCH 209/400] fixed spacing and margins between container --- .../submissions/submissionDetailPage.scss | 25 ++++++++++++++++--- .../submissions/submissionDetailPage.tsx | 4 +-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.scss b/devU-client/src/components/pages/submissions/submissionDetailPage.scss index 8dc7786f..3bdc792f 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.scss +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.scss @@ -1,18 +1,22 @@ @import 'variables'; +.heading { + text-align: center; + margin-left:250px; + +} .scores { padding: 20px; } .submissionsLayout { display: flex; - gap: 20px; + gap: 50px; } .submissionsContainer { flex: 1; max-width: 250px; - border-right: 1px solid lightgray; padding-right: 10px; overflow-y: auto; } @@ -110,7 +114,7 @@ .assignmentTable th, .assignmentTable td { padding: 8px; - text-align: center; + text-align: left; } .assignmentTable th { @@ -121,4 +125,17 @@ .assignmentTable tr:nth-child(even) { background-color: $background; } - \ No newline at end of file + + .sub_list{ + text-align: center; + + } + @media (max-width: 768px) { + .submissionsContainer { + display:none; + } + .heading { + margin:10px; + } + +} \ No newline at end of file diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx index e3dcb070..734f513c 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx @@ -126,13 +126,13 @@ const SubmissionDetailPage = () => {
)}
-

Submissions For Assignment {assignment?.name}

+

Submissions For Assignment {assignment?.name}

-

Submission List:

+

Submission List:

{submissions.map((submission, index) => ( From 1a4dd0a64595ab0b46f8e505d16fd29ce3a6c88e Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 22 Oct 2024 11:24:59 -0400 Subject: [PATCH 210/400] Updated the Assignment Edit page Updated this page to add functionality for : - Updating current assignment info - Creating Problems - Editing Problems - Editing attachments --- .../components/misc/editAssignmentModal.tsx | 0 .../forms/assignments/assignmentFormPage.tsx | 35 +- .../assignments/assignmentUpdatePage.scss | 87 ++++ .../assignments/assignmentUpdatePage.tsx | 380 +++++++++++++----- .../components/pages/homePage/homePage.scss | 52 --- .../components/shared/inputs/textField.tsx | 2 +- .../src/components/utils/dragDropFile.scss | 10 + .../src/components/utils/dragDropFile.tsx | 10 +- devU-client/src/utils/theme.utils.ts | 1 + devU-shared/src/types/assignment.types.ts | 4 +- 10 files changed, 404 insertions(+), 177 deletions(-) create mode 100644 devU-client/src/components/misc/editAssignmentModal.tsx create mode 100644 devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss diff --git a/devU-client/src/components/misc/editAssignmentModal.tsx b/devU-client/src/components/misc/editAssignmentModal.tsx new file mode 100644 index 00000000..e69de29b diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index 0d238fa3..a83cc2ed 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -31,10 +31,10 @@ const AssignmentCreatePage = () => { const [formData, setFormData] = useState({ courseId: courseId, name: '', - categoryName: null, - description: null, + categoryName: '', + description: '', maxFileSize: 0, - maxSubmissions: null, + maxSubmissions: 0, disableHandins: false, }) const [endDate, setEndDate] = useState(new Date()) @@ -98,7 +98,23 @@ const AssignmentCreatePage = () => { disableHandins: formData.disableHandins, } - RequestService.post(`/api/course/${courseId}/assignments/`, finalFormData) + const multipart = new FormData + multipart.append('courseId', finalFormData.courseId) + multipart.append('name', finalFormData.name) + multipart.append('startDate', finalFormData.startDate) + multipart.append('dueDate', finalFormData.dueDate) + multipart.append('endDate', finalFormData.endDate) + multipart.append('categoryName', finalFormData.categoryName) + if(finalFormData.description !== null) { multipart.append('description', finalFormData.description) } + multipart.append('maxFileSize', finalFormData.maxFileSize.toString()) + if(finalFormData.maxSubmissions !== null) { multipart.append('maxSubmissions', finalFormData.maxSubmissions.toString()) } + multipart.append('disableHandins', finalFormData.disableHandins.toString()) + for(const file of files.values()){ + multipart.append('files', file) + } + + + RequestService.postMultipart(`/api/course/${courseId}/assignments/`, multipart) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Assignment Added' }) history.goBack() @@ -117,16 +133,6 @@ const AssignmentCreatePage = () => { } - // const handleFile = (file : File) => { - // if(Object.keys(files).length < 5){ - // setFiles(prevState => ({...prevState, [file.name] : file})) - // } else { - // //TODO: Add alert - // console.log('Max files reached') - // } - // console.log(files) - // } - const handleFile = (file: File) => { if (files.size < 5) { setFiles(prevState => new Map(prevState).set(file.name, file)); @@ -134,7 +140,6 @@ const AssignmentCreatePage = () => { // TODO: Add alert console.log('Max files reached'); } - console.log(files); }; const handleFileRemoval = (e: React.MouseEvent) => { diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss new file mode 100644 index 00000000..ce26af6e --- /dev/null +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -0,0 +1,87 @@ +@import 'variables'; + +.grid { + display: grid; + grid-template-columns: 0.3fr 1fr 0.5fr; + grid-template-rows: 1fr 0.75fr; + grid-column-gap: 10px; + grid-row-gap: 10px; + width: 100%; + + .assignmentsList, .form, .problemsList, .attachments {background-color: $list-item-background;} + .assignmentsList {grid-area: 1 / 1 / 3 / 2;} + .form {grid-area: 1 / 2 / 2 / 3;} + .problemsList {grid-area: 1 / 3 / 2 / 4;} + .attachments {grid-area: 2 / 2 / 3 / 4;} +} + + +.textFieldContainer { + display:flex; + width: 100%; + justify-content: center; + align-items: center; +} + +.textField { + align-items: center; +} + +.datepickerContainer { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: 1fr; + grid-column-gap: 10px; + grid-row-gap: 0px; + width: 100%; + justify-content: center; + align-items: center; + text-align: center; + + .datepicker_start {grid-area: 1 / 1 / 2 / 2;} + .datepicker_due {grid-area: 1 / 2 / 2 / 3;} + .datepicker_end {grid-area: 1 / 3 / 2 / 4;} +} + + +input[type='date'] { + height: 20px; + background-color: $input-field-background; + color: $input-field-label; + padding: 0.625rem 1rem; + border: none; + border-radius: 100px; + } + +.problemsList, .assignmentsList { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; +} + +.assignment { + padding: 10px; + font-weight: bold; + cursor: pointer; + :hover { + padding: 10px; + background-color: $list-item-background-hover; + border-radius: 10px; + } +} + +.problem, .fileList{ + display: flex; + width: 100%; + align-items: center; + justify-content: center; +} + +.header { + text-align: center; +} + +.editProblem { + margin-right: 10px; +} \ No newline at end of file diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index baf851ce..75057c3c 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -1,33 +1,33 @@ import React, { useEffect, useState } from 'react' -import { ExpressValidationError, Assignment } from 'devu-shared-modules' -import DatePicker from 'react-datepicker' +import { ExpressValidationError, Assignment, AssignmentProblem } from 'devu-shared-modules' import 'react-datepicker/dist/react-datepicker.css' import { useHistory, useParams } from 'react-router-dom' - import PageWrapper from 'components/shared/layouts/pageWrapper' - import RequestService from 'services/request.service' - import { useActionless } from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -import Button from '@mui/material/Button' -import formStyles from './assignmentFormPage.scss' - +import Button from '../../../shared/inputs/button' +import styles from './assignmentUpdatePage.scss' +import DragDropFile from 'components/utils/dragDropFile' import { SET_ALERT } from 'redux/types/active.types' import { applyMessageToErrorFields, removeClassFromField } from 'utils/textField.utils' +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; -type UrlParams = { - assignmentId: string -} +type UrlParams = { assignmentId: string } const AssignmentUpdatePage = () => { const { assignmentId } = useParams() as UrlParams const { courseId } = useParams<{ courseId: string }>() const [setAlert] = useActionless(SET_ALERT) - const [startDate, setStartDate] = useState(new Date()) - const [endDate, setEndDate] = useState(new Date()) - const [dueDate, setDueDate] = useState(new Date()) + const [currentAssignmentId, setCurrentAssignmentId] = useState(parseInt(assignmentId)) + const [assignmentsList, setAssignmentsList] = useState([]) + const [assignmentProblems, setAssignmentProblems] = useState([]) + const [allAssignmentProblems, setAllAssignmentProblems] = useState>(new Map()) const [invalidFields, setInvalidFields] = useState(new Map()) + const [openModal, setOpenModal] = useState(false) + const [files, setFiles] = useState([]) const history = useHistory() const [formData, setFormData] = useState({ @@ -36,15 +36,31 @@ const AssignmentUpdatePage = () => { categoryName: '', description: '', maxFileSize: 0, - maxSubmissions: null, + maxSubmissions: 0, disableHandins: false, dueDate: '', endDate: '', startDate: '', - attachmentsHashes: [], - attachmentsFilenames: [], }) + const [assignmentProblemData, setAssignmentProblemData] = useState({ + assignmentId: currentAssignmentId, + problemName: '', + maxScore: -1, + }) + + const handleOpenModal = (problem : AssignmentProblem) => { + if(problem === assignmentProblemData) { + setOpenModal(true) + } else { + setAssignmentProblemData(problem) + } + } + const handleCloseModal = () => {setOpenModal(false)} + useEffect(() => { + if (assignmentProblemData.maxScore !== -1) { setOpenModal(true) } + }, [assignmentProblemData]) + const handleChange = (value: String, e: React.ChangeEvent) => { const key = e.target.id const newInvalidFields = removeClassFromField(invalidFields, key) @@ -52,58 +68,79 @@ const AssignmentUpdatePage = () => { setFormData(prevState => ({ ...prevState, [key]: value })) } - const handleCheckbox = (e: React.ChangeEvent) => { - setFormData(prevState => ({ ...prevState, disableHandins: e.target.checked })) - } - const handleStartDateChange = (date: Date) => { - setStartDate(date) + const handleProblemChange = (value: String, e: React.ChangeEvent) => { + const key = e.target.id + setAssignmentProblemData(prevState => ({ ...prevState, [key]: value })) } - const handleEndDateChange = (date: Date) => { - setEndDate(date) + + const handleCheckbox = (e: React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, disableHandins: e.target.checked }))} + const handleStartDateChange = (e : React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, startDate: e.target.value }))} + const handleEndDateChange = (e : React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, endDate: e.target.value }))} + const handleDueDateChange = (e : React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, dueDate: e.target.value }))} + + const handleFile = (file: File) => { + if(files.length < 5) { + setFiles([...files, file]) + } } - const handleDueDateChange = (date: Date) => { - setDueDate(date) + + const fetchAssignmentProblems = () => { + RequestService.get(`/api/course/${courseId}/assignment/${currentAssignmentId}/assignment-problems`) + .then((res) => { setAssignmentProblems(res) }) } + useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`).then((res) => { setFormData(res) })}, []) + useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems`).then((res) => { setAssignmentProblems(res) })}, []) + useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments`).then((res) => { setAssignmentsList(res) })}, []) useEffect(() => { - RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`).then((res) => { - // const assignment: Assignment = res - // setFormData({ - // name: assignment.name, - // categoryName: assignment.categoryName, - // description: assignment.description, - // maxFileSize: assignment.maxFileSize, - // maxSubmissions: assignment.maxSubmissions, - // disableHandins: assignment.disableHandins, - // startDate: assignment.startDate, - // endDate: assignment.endDate, - // dueDate: assignment.dueDate, - // courseId: assignment.courseId, - // }) - setStartDate(new Date(res.startDate)) - setDueDate(new Date(res.dueDate)) - setEndDate(new Date(res.endDate)) - }) - }, []) + for(let i : number = 0; i < assignmentsList.length; i++) { + RequestService.get(`/api/course/${courseId}/assignment/${assignmentsList[i].id}/assignment-problems`) + .then((res) => { + setAllAssignmentProblems(prevState => { + const newMap = new Map(prevState); + newMap.set(Number(assignmentsList[i].id), res); + return newMap; + }); + }) + } + },[assignmentsList]) const handleAssignmentUpdate = () => { const finalFormData = { courseId: formData.courseId, name: formData.name, - startDate: startDate.toISOString(), - dueDate: dueDate.toISOString(), - endDate: endDate.toISOString(), + startDate: formData.startDate, + dueDate: formData.dueDate, + endDate: formData.endDate, categoryName: formData.categoryName, description: formData.description, maxFileSize: formData.maxFileSize, maxSubmissions: formData.maxSubmissions, disableHandins: formData.disableHandins, + } + const multipart = new FormData() + multipart.append('courseId', finalFormData.courseId.toString()) + multipart.append('name', finalFormData.name) + multipart.append('startDate', finalFormData.startDate) + multipart.append('dueDate', finalFormData.dueDate) + multipart.append('endDate', finalFormData.endDate) + multipart.append('categoryName', finalFormData.categoryName) + if(finalFormData.description !== null) { + multipart.append('description', finalFormData.description) + } + multipart.append('maxFileSize', finalFormData.maxFileSize.toString()) + if(finalFormData.maxSubmissions !== null) { + multipart.append('maxSubmissions', finalFormData.maxSubmissions.toString()) + } + multipart.append('disableHandins', finalFormData.disableHandins.toString()) + + for(let i = 0; i < files.length; i++) { + multipart.append('files', files[i]) } - RequestService.put(`/api/course/${courseId}/assignments/${assignmentId}`, finalFormData) + RequestService.putMultipart(`/api/course/${courseId}/assignments/${currentAssignmentId}`, multipart) .then(() => { - setAlert({ autoDelete: true, type: 'success', message: 'Assignment Updated' }) history.goBack() }) @@ -118,68 +155,201 @@ const AssignmentUpdatePage = () => { }) } + const handleProblemUpdate = () => { + const finalFormData = { + assignmentId: assignmentProblemData.assignmentId, + problemName: assignmentProblemData.problemName, + maxScore: assignmentProblemData.maxScore, + } + + RequestService.put(`/api/course/${courseId}/assignment/${currentAssignmentId}/assignment-problems/${assignmentProblemData.id}`, finalFormData) + .then(() => { + setAlert({ autoDelete: true, type: 'success', message: 'Problem Updated' }) + setOpenModal(false) + fetchAssignmentProblems() + }) + } + + const handleAssignmentChange = (e: React.MouseEvent) => { + const assignmentDetails = assignmentsList.find((assignment) => assignment.id === parseInt(e.currentTarget.id)) + if (assignmentDetails !== undefined && assignmentDetails.id !== undefined) { + setAssignmentProblems(allAssignmentProblems.get(assignmentDetails.id) || []) + setCurrentAssignmentId(assignmentDetails.id) + } + if (assignmentDetails !== undefined) { + setFormData(assignmentDetails) + } + } + + const [addProblemModal, setAddProblemModal] = useState(false) + const [addProblemForm, setAddProblemForm] = useState({ + assignmentId: currentAssignmentId, + problemName: '', + maxScore: 0, + }) + const openAddProblemModal = () => {setAddProblemModal(true)} + const handleCloseAddProblemModal = () => {setAddProblemModal(false)} + + const handleAddProblemChange = (value: String, e: React.ChangeEvent) => { + const key = e.target.id + setAddProblemForm(prevState => ({ ...prevState, [key]: value })) + } + const handleAddProblem = () => { + RequestService.post(`/api/course/${courseId}/assignment/${currentAssignmentId}/assignment-problems`, addProblemForm) + .then(() => { + setAlert({ autoDelete: true, type: 'success', message: 'Problem Added' }) + setAddProblemModal(false) + setAddProblemForm({ + assignmentId: currentAssignmentId, + problemName: '', + maxScore: 0, + }) + fetchAssignmentProblems() + }) + } + + const handleDeleteProblem = (problemId: number) => { + RequestService.delete(`/api/course/${courseId}/assignment/${currentAssignmentId}/assignment-problems/${problemId}`) + .then(() => { + setAlert({ autoDelete: true, type: 'success', message: 'Problem Deleted' }) + fetchAssignmentProblems() + }) + } + return ( + -
-
-

Assignment Detail Update

-
-
-
- - - - - - - - - -
- -
-
- - + + +

Edit Problem

+ + + + + + + + +
+ + +

Add Problem

+ + + + + + + + +
+ +

Edit Assignment

+
+
+

Assignments

+ {assignmentsList.map((assignment) => ( +
+

{assignment.name}

+
+ ))} +
+
+

Assignment Information

+
+
+ + +
-
- - + + + +
+ + + +
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+ +
-
- - +
+
+
+
-
-
- - +
+

Problems

+ {assignmentProblems.map((problem, index) => ( +
+

{`Problem ${index + 1}`}

+ + +
+ ))} +
- -
- -
- +
+

Attachments

+ +
+
+

Files:

+ {files.map((file, index) => ( +
+

{`${file.name}, `}

+
+ ))} +
diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index 932d059b..25839090 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -1,35 +1,6 @@ @import 'variables'; -.h2 { - text-align: left; - margin-left: 20px; - font-size: 30px; - font-weight: 550; - margin-bottom: 30px; -} - -h3 { - align-items:left; - margin-left: 20px; - font-size: 30px; - font-weight: 550; - margin-bottom: 30px; - -} -h1 { - align-items:left; - margin-left: 20px; - font-size: 30px; - font-weight: 550; - margin-bottom: 30px; -} - -h4 { - margin-left: 20px; -} - - .coursesContainer { display: flex; flex-direction: row; @@ -65,16 +36,6 @@ h4 { align-items: center; } -h3::after { - content: ''; - display: block; - margin-top: 10px; - width: 100%; - height: 1px; - font-weight: 600; - background-color: $text-color; - } - @media (max-width: 768px) { .coursesContainer { flex-direction: column; @@ -86,16 +47,3 @@ h3::after { max-width: none; } } - -// .smallLine { -// width: 50px; /* adjust this value to set the length of the small line */ -// border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ -// margin-right: 10px; /* adjust this value to set the space between the line and the text */ -// } - -// .largeLine { -// flex-grow: 1; -// border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ -// margin-left: 10px; /* adjust this value to set the space between the line and the text */ -// margin-right: 10px; /* add this line to create some space between the line and the button */ -// } \ No newline at end of file diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 839e891f..8cdda4e0 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -77,7 +77,7 @@ const TextField = ({ "& .MuiOutlinedInput-input" : { color: theme.textColor, backgroundColor: theme.inputFieldBackground, - borderRadius: '100px', + borderRadius: '10px', // padding: '0.625rem 1rem', marginBottom: '0px' }, diff --git a/devU-client/src/components/utils/dragDropFile.scss b/devU-client/src/components/utils/dragDropFile.scss index ac3b93f5..4587605b 100644 --- a/devU-client/src/components/utils/dragDropFile.scss +++ b/devU-client/src/components/utils/dragDropFile.scss @@ -8,6 +8,15 @@ position: relative; } +.formFileUploadWide { + height: 16rem; + width: 80%; + margin-left: 10%; + margin-right: 10%; + text-align: center; + position: relative; +} + .inputFileUpload { display: none; } @@ -51,3 +60,4 @@ bottom: 0px; left: 0px; } + diff --git a/devU-client/src/components/utils/dragDropFile.tsx b/devU-client/src/components/utils/dragDropFile.tsx index 2c100508..b32d1940 100644 --- a/devU-client/src/components/utils/dragDropFile.tsx +++ b/devU-client/src/components/utils/dragDropFile.tsx @@ -4,11 +4,17 @@ import Button from '@mui/material/Button' interface DragDropFileProps { handleFile : (file : File) => void; + className ?: string; } -function DragDropFile({handleFile} : DragDropFileProps) { +function DragDropFile({handleFile, className} : DragDropFileProps) { const [dragActive, setDragActive] = React.useState(false); const inputRef = React.useRef(null); + + // set default class name + if (className === undefined) { + className = "formFileUpload" + } // handle drag events const handleDrag = function(e : React.DragEvent) { @@ -49,7 +55,7 @@ function DragDropFile({handleFile} : DragDropFileProps) { }; return ( -
e.preventDefault()}> + e.preventDefault()}>
)} - +

Submission Detail for {assignment?.name}

Submission Grades:

@@ -119,11 +123,16 @@ const SubmissionDetailPage = () => {
{submissionScore?.score ?? "N/A"}
+ + View Feedback
- +

Submission Content:

+

All Submissions:

+ +
{submission?.content}
) diff --git a/devU-client/src/components/shared/layouts/pageWrapper.scss b/devU-client/src/components/shared/layouts/pageWrapper.scss index dab83109..99523111 100644 --- a/devU-client/src/components/shared/layouts/pageWrapper.scss +++ b/devU-client/src/components/shared/layouts/pageWrapper.scss @@ -10,4 +10,5 @@ .content { margin-top: 20px; flex-grow: 1; + padding: 10px 50px; } From f231aeb13eea6a73e35b90bc5e8f26b069e3ebc6 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 29 Oct 2024 13:23:18 -0400 Subject: [PATCH 242/400] Updated the styling for the coursedetil page and assignment page. I added a button to the assignment page. renamed a button on the course, fixed the course name format, padding in page wrapper --- devU-client/src/components/pages/courses/courseDetailPage.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index 4e9c9708..010aad52 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -72,6 +72,7 @@ .courseDetailPage { + padding: 40px; From f49789d0426f32a3394730b935f2e1faea3bcf31 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 29 Oct 2024 13:35:42 -0400 Subject: [PATCH 243/400] Updated the styling for the coursedetil page and assignment page. I added a button to the assignment page. renamed a button on the course, fixed the course name format, padding in page wrapper --- .../src/components/pages/courses/courseDetailPage.scss | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index ee78ff87..851688e6 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -92,18 +92,24 @@ h1 { margin: 0 0 10px 0; - font-size: 2.5rem; + font-size: 1.5rem; text-align: left; font-weight: bold; //font-size: 1.0rem; } - h2 { + .h2 { padding-top: 20px; margin: 0 0 20px 0; font-size: 1.0rem; padding-bottom: 10px; } + + h3{ + font-size: 1.5rem; + border-bottom: 1px solid; + color: $text-color; + } } .buttons-container { From 5f5b8488fd1cef96ccf15ec6701e6cae6b84a3f4 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 29 Oct 2024 13:40:51 -0400 Subject: [PATCH 244/400] Updated the styling for the coursedetil page and assignment page. I added a button to the assignment page. renamed a button on the course, fixed the course name format, padding in page wrapper --- .../src/components/pages/assignments/assignmentDetailPage.tsx | 2 +- devU-client/src/components/pages/courses/courseDetailPage.scss | 2 +- devU-client/src/components/pages/courses/courseDetailPage.tsx | 2 +- .../components/pages/submissions/InstructorSubmissionspage.tsx | 3 +-- devU-client/src/components/shared/layouts/pageWrapper.scss | 3 ++- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 222e9dbf..fa94bafc 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -171,7 +171,7 @@ const AssignmentDetailPage = () => {
{role.isInstructor() && } diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index 851688e6..9f647cfb 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -95,7 +95,7 @@ font-size: 1.5rem; text-align: left; font-weight: bold; - //font-size: 1.0rem; + } .h2 { diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index a6972882..a7072c72 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -87,7 +87,7 @@ const CourseDetailPage = () => { useEffect(() => { fetchCourseInfo() - //fetchUserinfo() + }, []) const history = useHistory() return( diff --git a/devU-client/src/components/pages/submissions/InstructorSubmissionspage.tsx b/devU-client/src/components/pages/submissions/InstructorSubmissionspage.tsx index a5ca5052..e8bfc209 100644 --- a/devU-client/src/components/pages/submissions/InstructorSubmissionspage.tsx +++ b/devU-client/src/components/pages/submissions/InstructorSubmissionspage.tsx @@ -139,8 +139,7 @@ const InstructorSubmissionsPage: React.FC = () => { {assignmentId: string, courseId: string}>() - // const [originalUsers, setOriginalUsers] = useState([]); // Store original users - //const [filteredUsers, setFilteredUsers] = useState([]); + diff --git a/devU-client/src/components/shared/layouts/pageWrapper.scss b/devU-client/src/components/shared/layouts/pageWrapper.scss index 99523111..5daad34a 100644 --- a/devU-client/src/components/shared/layouts/pageWrapper.scss +++ b/devU-client/src/components/shared/layouts/pageWrapper.scss @@ -3,8 +3,9 @@ display: flex; flex-direction: column; + //navigation bar should be on top of the page each time overflow-y: auto; - padding: 0vw; //navigation bar should be on top of the page each time + padding: 0vw; } .content { From a04a9f20c457c46717c33967ffcd1b8ecfceefab Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 29 Oct 2024 14:21:16 -0400 Subject: [PATCH 245/400] Updated Assignment Detail Page, Assignment Edit Page, and Assignment Creation Page Updated styling of the pages mentioned in the title to look like other pages as well as fixing mobile compatibility. On the Assignment Detail Page, it now shows the filenames although it is not clickable yet. --- .../assignments/assignmentDetailPage.tsx | 1 + .../forms/assignments/assignmentFormPage.scss | 67 ++++++++- .../forms/assignments/assignmentFormPage.tsx | 134 +++++------------- .../assignments/assignmentUpdatePage.scss | 34 +++-- .../assignments/assignmentUpdatePage.tsx | 34 +++-- .../pages/forms/courses/coursesFormPage.scss | 1 - 6 files changed, 153 insertions(+), 118 deletions(-) diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 8e853251..7be3a825 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -181,6 +181,7 @@ const AssignmentDetailPage = () => { {assignment?.description} + Attachments : {assignment?.attachmentsFilenames}
{assignment?.dueDate && ( diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index 6f41b55c..4fbeda34 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -9,12 +9,17 @@ p { text-align: center; } +.flex { + display: flex; + justify-content: center; + align-items: center; +} + .form { background-color: $list-item-background; grid-area: 1 / 1 / 2 / 2; border-radius: 10px; padding: 20px; - margin-left: auto; margin-right: 0; } @@ -25,6 +30,9 @@ p { grid-column-gap: 10px; grid-row-gap: 0px; width: 100%; + justify-content: center; + align-items: center; + text-align: center; } .header { @@ -50,6 +58,8 @@ p { grid-column-gap: 10px; grid-row-gap: 10px; width: 100%; + padding-left: 10px; + padding-right: 10px; } .dragDropFile { @@ -58,7 +68,6 @@ p { background-color: $list-item-background; border-radius: 10px; margin-left: 0; - margin-right: auto; } .dragDropFileComponent { @@ -118,3 +127,57 @@ p { color: red; } } + +@media (max-width: 1000px) { + .grid { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: repeat(2, 1fr); + grid-column-gap: 0px; + grid-row-gap: 10px; + margin-left: 20px; + margin-right: 20px; + width: 100%; + } + + .form { + grid-area: 1 / 1 / 2 / 2; + margin-left: 10px; + margin-right: 10px; + } + .dragDropFile { + grid-area: 2 / 1 / 3 / 2; + align-content: center; + text-align: center; + margin-left: 10px; + margin-right: 10px; + } + .dragDropFileInput { + display: flex; + justify-content: center; + } +} + +@media (max-width: 550px) { + .datepickerContainer{ + display: grid; + grid-template-columns: 1fr; + grid-template-rows: repeat(3, 1fr); + grid-column-gap: 0px; + grid-row-gap: 10px; + width: 100%; + justify-content: center; + align-items: center; + text-align: center; + } + + .datepicker_start { + grid-area: 1 / 1 / 2 / 2; + } + .datepicker_due { + grid-area: 2 / 1 / 3 / 2; + } + .datepicker_end { + grid-area: 3 / 1 / 4 / 2; + } +} \ No newline at end of file diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index a83cc2ed..dbd0a3f7 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -1,16 +1,13 @@ -import React, {useState, useEffect} from 'react' +import React, {useState} from 'react' import {ExpressValidationError} from 'devu-shared-modules' import 'react-datepicker/dist/react-datepicker.css' import PageWrapper from 'components/shared/layouts/pageWrapper' -import { getCssVariables } from 'utils/theme.utils' import RequestService from 'services/request.service' import {useActionless} from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -import Button from '@mui/material/Button' -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +// import Button from '@mui/material/Button' +import Button from '../../../shared/inputs/button' import DragDropFile from 'components/utils/dragDropFile' import {SET_ALERT} from 'redux/types/active.types' @@ -19,11 +16,8 @@ import {applyMessageToErrorFields, removeClassFromField} from "../../../../utils import {useHistory, useParams} from 'react-router-dom' import formStyles from './assignmentFormPage.scss' -import { Dayjs } from 'dayjs' const AssignmentCreatePage = () => { - const [theme, setTheme] = useState(getCssVariables()) - const { textColor } = theme const [setAlert] = useActionless(SET_ALERT) const {courseId} = useParams<{ courseId: string }>() const history = useHistory() @@ -37,21 +31,12 @@ const AssignmentCreatePage = () => { maxSubmissions: 0, disableHandins: false, }) - const [endDate, setEndDate] = useState(new Date()) - const [dueDate, setDueDate] = useState(new Date()) - const [startDate, setStartDate] = useState(new Date()) + const [endDate, setEndDate] = useState('') + const [dueDate, setDueDate] = useState('') + const [startDate, setStartDate] = useState('') const [invalidFields, setInvalidFields] = useState(new Map()) const [files, setFiles] = useState>(new Map()) - useEffect(() => { - const observer = new MutationObserver(() => setTheme(getCssVariables())) - - observer.observe(document.body, { attributes: true }) - - return () => observer.disconnect() - }) - - const handleChange = (value: String, e : React.ChangeEvent) => { const key = e.target.id @@ -65,32 +50,17 @@ const AssignmentCreatePage = () => { setFormData(prevState => ({...prevState,disableHandins : e.target.checked})) } - const handleStartDateChange = (date : Dayjs | null) => { - if(date){ - const newDate = date.toDate() - setStartDate(newDate) - } - } - const handleEndDateChange = (date : Dayjs | null) => { - if(date){ - const newDate = date.toDate() - setEndDate(newDate) - } - } - const handleDueDateChange = (date: Dayjs | null) => { - if(date){ - const newDate = date.toDate() - setDueDate(newDate) - } - } + const handleStartDateChange = (e : React.ChangeEvent) => {setStartDate(e.target.value)} + const handleEndDateChange = (e : React.ChangeEvent) => {setEndDate(e.target.value)} + const handleDueDateChange = (e : React.ChangeEvent) => {setDueDate(e.target.value)} const handleSubmit = () => { const finalFormData = { courseId: courseId, name: formData.name, - startDate : startDate.toISOString(), - dueDate: dueDate.toISOString(), - endDate : endDate.toISOString(), + startDate : startDate, + dueDate: dueDate, + endDate : endDate, categoryName: formData.categoryName, description: formData.description, maxFileSize: formData.maxFileSize, @@ -154,6 +124,7 @@ const AssignmentCreatePage = () => { return(

Create Assignment

+

Assignment Information

@@ -174,7 +145,10 @@ const AssignmentCreatePage = () => { + sx={{ + "& .MuiInputBase-input.MuiOutlinedInput-input.MuiInputBase-inputMultiline.css-1sqnrkk-MuiInputBase-input-MuiOutlinedInput-input": {padding : "15px"}, + "& .MuiInputBase-root.MuiOutlinedInput-root.MuiInputBase-colorPrimary.MuiInputBase-formControl.MuiInputBase-multiline.css-dpjnhs-MuiInputBase-root-MuiOutlinedInput-root" : {padding : "0px"}, + }}/>
{
- - - -
-
- - - -
-
- - - + +
+ +
+
+ +
+ +
+
+ +
+

- +

-
{/*TODO: Whenever file uploads is available on backend, store the files + create Object URLs*/}

Attachments

-

Add up to 5 attachments with this assignment:

+

Add up to 5 attachments with this assignment:

Files Uploaded (click to delete) :

{Array.from(files.keys()).map((fileName) => { @@ -274,11 +212,13 @@ const AssignmentCreatePage = () => { })}
- +
+ +
- +
) } diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss index d1aa3c95..1c947de6 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -56,19 +56,34 @@ input[type='date'] { .problemsList, .assignmentsList { display: flex; flex-direction: column; + gap: 30px; align-items: center; width: 100%; } -.assignment { - padding: 10px; - font-weight: bold; +// .assignment { +// padding: 10px; +// font-weight: bold; +// cursor: pointer; +// :hover { +// padding: 10px; +// background-color: $list-item-background-hover; +// border-radius: 10px; +// } +// } + +.assignmentBtn { + background-color: transparent; + background-repeat: no-repeat; + border: none; cursor: pointer; - :hover { - padding: 10px; - background-color: $list-item-background-hover; - border-radius: 10px; - } + overflow: hidden; + outline: none; +} + +.assignmentBtn:hover { + background-color: $list-item-background-hover; + border-radius: 10px; } .problem, .fileList{ @@ -80,6 +95,7 @@ input[type='date'] { .header { text-align: center; + color: $text-color; } .editProblem { @@ -116,7 +132,7 @@ input[type='date'] { grid-template-columns: 1fr; grid-template-rows: repeat(3, 1fr); grid-column-gap: 0px; - grid-row-gap: 30px; + grid-row-gap: 10px; width: 100%; justify-content: center; align-items: center; diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index f53a5f83..fd97ba9c 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -14,6 +14,7 @@ import { applyMessageToErrorFields, removeClassFromField } from 'utils/textField import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; +import { getCssVariables } from 'utils/theme.utils' type UrlParams = { assignmentId: string } @@ -30,6 +31,18 @@ const AssignmentUpdatePage = () => { const [files, setFiles] = useState([]) const history = useHistory() + const [theme, setTheme] = useState(getCssVariables()) + + // Needs a custom observer to force an update when the css variables change + // Custom observer will update the theme variables when the bodies classes change + useEffect(() => { + const observer = new MutationObserver(() => setTheme(getCssVariables())) + + observer.observe(document.body, { attributes: true }) + + return () => observer.disconnect() + }) + const [formData, setFormData] = useState({ courseId: parseInt(courseId), name: '', @@ -221,8 +234,8 @@ const AssignmentUpdatePage = () => { +

Edit Problem

- @@ -233,15 +246,15 @@ const AssignmentUpdatePage = () => {
-

Add Problem

- + +

Add Problem

-
- - - + + + +

Edit Assignment

@@ -250,7 +263,7 @@ const AssignmentUpdatePage = () => {

Assignments

{assignmentsList.map((assignment) => (
-

{assignment.name}

+
))}
@@ -277,7 +290,10 @@ const AssignmentUpdatePage = () => { className={styles.textField} helpText={invalidFields.get('description')} value={formData.description ? formData.description : ''} - sx={{width : 9/10, padding : "10px"}}/> + sx={{width : 9/10, + "& .MuiInputBase-input.MuiOutlinedInput-input.MuiInputBase-inputMultiline.css-1sqnrkk-MuiInputBase-input-MuiOutlinedInput-input": {padding : "15px"}, + "& .MuiInputBase-root.MuiOutlinedInput-root.MuiInputBase-colorPrimary.MuiInputBase-formControl.MuiInputBase-multiline.css-dpjnhs-MuiInputBase-root-MuiOutlinedInput-root" : {padding : "0px"}, + }}/>
Date: Tue, 29 Oct 2024 14:38:18 -0400 Subject: [PATCH 246/400] fixed styling for homepage, assignment instructors view, and submission details for page for student --- .../listItems/simpleAssignmentListItem.scss | 9 +--- .../listItems/userCourseListItem.scss | 30 ++++++------- .../assignments/assignmentDetailPage.scss | 35 ++++++++------- .../assignments/assignmentDetailPage.tsx | 44 +++++++++---------- .../components/pages/homePage/homePage.scss | 22 ++-------- .../components/pages/homePage/homePage.tsx | 8 ++-- .../submissions/submissionDetailPage.scss | 27 +++++++++--- .../submissions/submissionDetailPage.tsx | 18 ++++---- .../shared/layouts/pageWrapper.scss | 1 + 9 files changed, 97 insertions(+), 97 deletions(-) diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.scss b/devU-client/src/components/listItems/simpleAssignmentListItem.scss index 78631090..d4cf36a2 100644 --- a/devU-client/src/components/listItems/simpleAssignmentListItem.scss +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.scss @@ -4,14 +4,10 @@ padding: 10px; display: flex; flex-direction: row; - border-radius: 0.6rem; - justify-content: space-between; - transition: background-color 0.2s linear; - - + color:$primary; } .meta { @@ -46,9 +42,6 @@ border-radius: 0.6rem; - - - transition: background-color 0.2s linear; color: #52468A; diff --git a/devU-client/src/components/listItems/userCourseListItem.scss b/devU-client/src/components/listItems/userCourseListItem.scss index 12f854ea..2ebb8dea 100644 --- a/devU-client/src/components/listItems/userCourseListItem.scss +++ b/devU-client/src/components/listItems/userCourseListItem.scss @@ -4,13 +4,15 @@ font-size: 1.2rem; font-weight: 600; margin: 0; - padding: 15px; /* Add padding to the text inside the name block */ - background: #b5acdd; /* Background color for the name area */ - width: 100%; /* Make sure it spans the entire width of the card */ + padding: 30px; /* Add padding to the text inside the name block */ + background: $primary; + width: 100%; text-align: center; - color: #52468a; + color: $text-color; border-radius: 5px 5px 0 0; box-sizing: border-box; + text-overflow: ellipsis; + overflow-wrap: break-word; } .No_assignments @@ -23,23 +25,24 @@ font-size: 12px; font-weight: 600; border-radius:5px; - padding: 10px; + padding: 30px; flex-wrap: wrap; - color:#52468A; + color:$primary; + text-overflow: ellipsis; + overflow-wrap: break-word; } .Buttons{ display: flex; - justify-content: space-between; - padding: 15px; + justify-content: space-around; margin-top: auto; + padding:10px; } .gradebook_button ,.coursepage_button { border: 0; - color: #52468A; + color: $primary; background: none; font-weight: 600; margin-left: 15px; - padding: 10px; cursor: pointer; } @@ -51,7 +54,7 @@ .subText { font-size: 1rem; - display: grid; + display: inline-block; justify-content: space-between; gap: 1.5rem; width: 100%; @@ -81,12 +84,7 @@ text-decoration: none; border-radius: 0.6rem; - - padding: 5px; - margin-bottom: 1rem; - - color: $text-color; } diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss index e5e580c9..30e754b2 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss @@ -20,13 +20,13 @@ .line { border: none; height: 2px; - background-color: #ccc; + background-color: $list-item-background; width: 100%; margin-top: 10px; } .card { - background-color: #f9f9f9; + background-color: $list-item-background; border-radius: 8px; padding: 20px; width: 300px; @@ -39,7 +39,7 @@ font-size: 20px; font-weight: 550; margin-bottom: 15px; - color: #52468a; + color: $text-color; text-align: center; } @@ -64,8 +64,8 @@ .buttons { - color: #52468a; - background-color: #f9f9f9; + color: $primary; + background-color: $list-item-background; padding: 10px 20px; border-radius:15px; cursor: pointer; @@ -78,12 +78,12 @@ .buttons:hover { - background-color: #888; + background-color: $list-item-background-hover; } .assignment_card { - background-color: #f9f9f9; + background-color: $list-item-background; border-radius: 8px; padding: 20px; width: 100%; @@ -92,14 +92,14 @@ } .accordion { - background-color: #f9f9f9; + background-color: $list-item-background; box-shadow: none; border: 1px solid #ddd; margin-bottom: 10px; } .accordionDetails { - background-color: #f9f9f9; + background-color:$list-item-background; padding: 10px; border-top: 1px solid #ddd; } @@ -112,7 +112,9 @@ justify-content: center; align-items: center; margin-top: 20px; + background-color: $list-item-background; } + .textField { margin-top: 10px; margin-bottom: 20px; @@ -120,6 +122,7 @@ .submissionCard { margin-bottom : 15px; + background-color: $list-item-background; } .fileInput { @@ -128,7 +131,7 @@ .due_date { font-size: 14px; - color: #888; + color: $text-color; margin-top: 5px; margin-bottom: 15px; } @@ -137,6 +140,7 @@ .submissionsContainer { display: flex; flex-direction: column; + padding: 30px; width: 100%; margin-top: 20px; } @@ -146,10 +150,11 @@ display: flex; justify-content: space-between; align-items: flex-start; - background-color: #f9f9f9; + background-color: $list-item-background; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; + padding: 10px; } .submissionCard:hover { @@ -157,16 +162,16 @@ } .submissionHeading { - font-size: 16px; - font-weight: bold; - color: #52468a; + color: $primary; + padding: 10px; } .submissionTime { font-size: 14px; - color: #888; + color: $text-color; margin-top: 5px; + padding: 10px; } @media (max-width: 768px) { diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 8e853251..47586ce7 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -8,9 +8,9 @@ import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import {useActionless, useAppSelector} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' -import Card from '@mui/material/Card' -import CardContent from '@mui/material/CardContent' -import {Accordion, AccordionDetails, AccordionSummary, CardActionArea, TextField, Typography} from '@mui/material' +//import Card from '@mui/material/Card' +//import CardContent from '@mui/material/CardContent' +import {/*Accordion AccordionDetails, AccordionSummary*/ TextField, Typography} from '@mui/material' import Grid from '@mui/material/Unstable_Grid2' @@ -179,7 +179,7 @@ const AssignmentDetailPage = () => { - +
{assignment?.description}
@@ -188,21 +188,21 @@ const AssignmentDetailPage = () => { )} {assignmentProblems && assignmentProblems.length > 0 ? ( assignmentProblems.map((assignment, index) => ( - - - {`Assignment Problem ${index + 1}`} - - +
+
+
{`Assignment Problem ${index + 1}`}
+
+
{assignment.problemName} - - +
+
)) ) : ( - +
No Problems Exist - +
)} {!(assignment?.disableHandins) && ()} @@ -212,7 +212,7 @@ const AssignmentDetailPage = () => {
) : null} -
+
@@ -225,16 +225,16 @@ const AssignmentDetailPage = () => {
{submissions.map((submission, index) => ( - - { +
+
{ history.push(`/course/${courseId}/assignment/${assignmentId}/submission/${submission.id}`) }}> - - {`Submission ${submissions.length - index}`} - {`Submitted at: ${submission.createdAt && prettyPrintDateTime(submission.createdAt)}`} - - - +
+
{`Submission ${submissions.length - index}`}
+
{`Submitted at: ${submission.createdAt && prettyPrintDateTime(submission.createdAt)}`}
+
+
+
))}
diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index 665af415..fbbae7e7 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -2,29 +2,15 @@ -.courses_title { - text-align: left; - margin-left: 20px; - font-size: 30px; - font-weight: 550; - margin-bottom: 30px; -} - .courses_heading{ - align-items:left; + text-align: left; margin-left: 20px; font-size: 30px; font-weight: 550; margin-bottom: 30px; } -// h1 { -// align-items:left; -// margin-left: 20px; -// font-size: 30px; -// font-weight: 550; -// margin-bottom: 30px; -// } + .no_courses { margin-left: 20px; @@ -43,7 +29,7 @@ flex-direction: column; justify-content: space-between; align-items: stretch; - height: 100%; + width:400px; padding: 0; background-color: #D9D9D9; border-radius: 5px; @@ -53,7 +39,7 @@ } .create_course { border:0; /* Primary button color */ - color: #52468a; + color: $primary; padding: 10px 20px; border-radius: 5px; text-decoration: none; diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 44218b29..0fe0eab4 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -75,7 +75,7 @@ const HomePage = () => {
-

Courses

+

Courses

-

Current

+

Current

@@ -101,7 +101,7 @@ const HomePage = () => {
))} - {enrollCourses.length === 0 &&

You do not have current enrollment yet

} + {enrollCourses.length === 0 && instructorCourses.length == 0 &&

You do not have current enrollment yet

}
@@ -127,7 +127,7 @@ const HomePage = () => {
-

Upcoming

+

Upcoming

diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.scss b/devU-client/src/components/pages/submissions/submissionDetailPage.scss index 3bdc792f..7d5e4a55 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.scss +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.scss @@ -19,11 +19,27 @@ max-width: 250px; padding-right: 10px; overflow-y: auto; + background-color: $list-item-background; + border-radius: 30px; } .submissionCard { + margin-bottom: 15px; margin-bottom: 15px; cursor: pointer; + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + background-color: $list-item-background; + transition: box-shadow 0.3s ease; + padding: 10px; + margin: 10px; + + + &:hover { + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + } + } .submissionContent { @@ -31,11 +47,11 @@ max-height: 800px; overflow-y: auto; padding: 0 20px; - background-color: $background; border: 1px solid #ddd; color: $text-color; border-radius: 30px; padding:30px; + background-color: $list-item-background; } pre { @@ -46,7 +62,7 @@ } .feedbackContainer{ - background-color: $background; + background-color: $list-item-background; } @@ -102,7 +118,7 @@ max-height: 400px; overflow-y: auto; padding: 10px; - background-color: $background; + background-color: $list-item-background; border-radius: 5px; } @@ -118,12 +134,13 @@ } .assignmentTable th { - background-color: $background; + background-color: $list-item-background; font-weight: bold; + color:$text-color; } .assignmentTable tr:nth-child(even) { - background-color: $background; + background-color: $list-item-background; } .sub_list{ diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx index 734f513c..1bd9c87d 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx @@ -11,9 +11,9 @@ import {useActionless} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' import styles from './submissionDetailPage.scss' import 'react-datepicker/dist/react-datepicker.css' -import Card from '@mui/material/Card' -import CardContent from '@mui/material/CardContent' -import {CardActionArea, Typography} from '@mui/material' +//import Card from '@mui/material/Card' +//import CardContent from '@mui/material/CardContent' +import {CardActionArea} from '@mui/material' import {prettyPrintDateTime} from "../../../utils/date.utils"; @@ -134,15 +134,15 @@ const SubmissionDetailPage = () => {

Submission List:

{submissions.map((submission, index) => ( - +
setSelectedSubmission(submission)}> - - {`Submission ${submissions.length - index}`} - {`Submitted at: ${submission.createdAt && prettyPrintDateTime(submission.createdAt)}`} - +
+
{`Submission ${submissions.length - index}`}
+
{`Submitted at: ${submission.createdAt && prettyPrintDateTime(submission.createdAt)}`}
+
- +
))}
diff --git a/devU-client/src/components/shared/layouts/pageWrapper.scss b/devU-client/src/components/shared/layouts/pageWrapper.scss index dab83109..a7d4b322 100644 --- a/devU-client/src/components/shared/layouts/pageWrapper.scss +++ b/devU-client/src/components/shared/layouts/pageWrapper.scss @@ -10,4 +10,5 @@ .content { margin-top: 20px; flex-grow: 1; + padding:50px; } From dfe969fe509a0995845d35aae2a17854fcbdda27 Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 29 Oct 2024 15:16:20 -0400 Subject: [PATCH 247/400] Updated padding on Assignment creation/update Padding on pages to remove the padding change that occurred on PageWrapper --- .../pages/forms/assignments/assignmentFormPage.scss | 4 ++++ .../components/pages/forms/assignments/assignmentFormPage.tsx | 2 +- .../pages/forms/assignments/assignmentUpdatePage.scss | 4 ++++ .../pages/forms/assignments/assignmentUpdatePage.tsx | 2 +- devU-client/src/components/shared/layouts/pageWrapper.scss | 1 - 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index 4fbeda34..4ee90fa6 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -9,6 +9,10 @@ p { text-align: center; } +.pageWrapper { + padding:0px; +} + .flex { display: flex; justify-content: center; diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index dbd0a3f7..9881fb51 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -122,7 +122,7 @@ const AssignmentCreatePage = () => { }; return( - +

Create Assignment

diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss index 1c947de6..bf048d61 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -72,6 +72,10 @@ input[type='date'] { // } // } +.pageWrapper { + padding:0px; + } + .assignmentBtn { background-color: transparent; background-repeat: no-repeat; diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index fd97ba9c..62a78db6 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -231,7 +231,7 @@ const AssignmentUpdatePage = () => { return ( - + diff --git a/devU-client/src/components/shared/layouts/pageWrapper.scss b/devU-client/src/components/shared/layouts/pageWrapper.scss index a7d4b322..dab83109 100644 --- a/devU-client/src/components/shared/layouts/pageWrapper.scss +++ b/devU-client/src/components/shared/layouts/pageWrapper.scss @@ -10,5 +10,4 @@ .content { margin-top: 20px; flex-grow: 1; - padding:50px; } From 85ff2d104d01e99e6c7729618144240fad467550 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Sun, 3 Nov 2024 16:13:14 -0500 Subject: [PATCH 248/400] added all options and defaults for session based on season, 110 --- .../pages/forms/courses/automateDates.tsx | 101 ++++++++++++++++++ .../pages/forms/courses/coursesFormPage.tsx | 6 +- 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 devU-client/src/components/pages/forms/courses/automateDates.tsx diff --git a/devU-client/src/components/pages/forms/courses/automateDates.tsx b/devU-client/src/components/pages/forms/courses/automateDates.tsx new file mode 100644 index 00000000..ab855ea1 --- /dev/null +++ b/devU-client/src/components/pages/forms/courses/automateDates.tsx @@ -0,0 +1,101 @@ +import React, { useState } from 'react'; + +import formStyles from './coursesFormPage.scss' + +function AutomateDates() { + const [season, setSeason] = useState(''); + const [year, setYear] = useState(''); + const [session, setSession] = useState(''); + const currentYear = new Date().getFullYear(); + + // Define the session options based on the selected season + const getSessionOptions = (season: string) => { + switch (season) { + case 'Fall': + case 'Spring': + return [ + { value: '15 week', label: '15 week' }, + { value: '7 week', label: '7 week' }, + { value: 'Custom', label: 'Custom' } + ]; + case 'Winter': + return [ + { value: '15 days', label: '15 days' }, + { value: '14 days', label: '14 days' }, + { value: '10 days', label: '10 days' }, + { value: 'Custom', label: 'Custom' } + ]; + case 'Summer': + return [ + { value: 'Summer Session I (J)', label: 'Summer Session I (J)' }, + { value: 'Summer Session II (K)', label: 'Summer Session II (K)' }, + { value: 'Summer Session III (M)', label: 'Summer Session III (M)' }, + { value: '9 Weeks (L)', label: '9 Weeks (L)' }, + { value: '10 Weeks (A)', label: '10 Weeks (A)' }, + { value: '12 Weeks (I)', label: '12 Weeks (I)' }, + { value: 'Custom', label: 'Custom' } + ]; + default: + return []; + } + }; + + // Define default values based on season + const getDefaultSession = (season: string) => { + switch (season) { + case 'Fall': + case 'Spring': + return '15 week'; + case 'Winter': + return '15 days'; + case 'Summer': + return 'Summer Session I (J)'; + default: + return ''; + } + }; + + // Handle changes to season and update session options + const handleSeasonChange = (event: React.ChangeEvent) => { + const selectedSeason = event.target.value; + setSeason(selectedSeason); + setSession(getDefaultSession(selectedSeason)); + }; + + return ( +
+ {/* Season Dropdown */} + + + + {/* Year Dropdown */} + + + + {/* Session Dropdown */} + + +
+ ); +} + +export default AutomateDates; diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx index e5814ce2..fc9db8c7 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx @@ -10,6 +10,7 @@ import { useActionless } from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import { SET_ALERT } from 'redux/types/active.types' import formStyles from './coursesFormPage.scss' +import AutomateDates from './automateDates' import { applyMessageToErrorFields, removeClassFromField } from "../../../../utils/textField.utils"; @@ -75,9 +76,10 @@ const EditCourseFormPage = () => { invalidated={!!invalidFields.get("name")} helpText={invalidFields.get("name")} /> - + helpText={invalidFields.get("semester")} /> */} +
From 26a942e8fb18287c5bf343ba73caf7e539c1ab3c Mon Sep 17 00:00:00 2001 From: RA341 Date: Mon, 4 Nov 2024 02:19:40 -0500 Subject: [PATCH 249/400] added webhooks --- devU-api/package.json | 2 +- .../entities/webhooks/webhooks.controller.ts | 71 +++++++++++++++++++ .../entities/webhooks/webhooks.middleware.ts | 67 +++++++++++++++++ .../src/entities/webhooks/webhooks.model.ts | 38 ++++++++++ .../src/entities/webhooks/webhooks.router.ts | 54 ++++++++++++++ .../entities/webhooks/webhooks.serializer.ts | 13 ++++ .../src/entities/webhooks/webhooks.service.ts | 36 ++++++++++ .../entities/webhooks/webhooks.validator.ts | 16 +++++ devU-api/src/index.ts | 3 + .../src/migration/1730698767895-webhooks.ts | 16 +++++ devU-api/src/router/courseData.router.ts | 4 ++ devU-shared/src/index.ts | 1 + devU-shared/src/types/webhooks.types.ts | 8 +++ 13 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 devU-api/src/entities/webhooks/webhooks.controller.ts create mode 100644 devU-api/src/entities/webhooks/webhooks.middleware.ts create mode 100644 devU-api/src/entities/webhooks/webhooks.model.ts create mode 100644 devU-api/src/entities/webhooks/webhooks.router.ts create mode 100644 devU-api/src/entities/webhooks/webhooks.serializer.ts create mode 100644 devU-api/src/entities/webhooks/webhooks.service.ts create mode 100644 devU-api/src/entities/webhooks/webhooks.validator.ts create mode 100644 devU-api/src/migration/1730698767895-webhooks.ts create mode 100644 devU-shared/src/types/webhooks.types.ts diff --git a/devU-api/package.json b/devU-api/package.json index e908de15..1c45a402 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -5,7 +5,7 @@ "scripts": { "start": "npm run migrate && ts-node-dev src/index.ts", "migrate": "npm run typeorm -- migration:run -d src/database.ts", - "update-shared": "cd ../devU-shared && npm run build-local && npm i", + "update-shared": "cd ../devU-shared && npm run build-local && cd ../devU-api && npm i", "typeorm": "typeorm-ts-node-commonjs", "test": "jest --passWithNoTests", "clean": "rimraf build/*", diff --git a/devU-api/src/entities/webhooks/webhooks.controller.ts b/devU-api/src/entities/webhooks/webhooks.controller.ts new file mode 100644 index 00000000..8bc455b5 --- /dev/null +++ b/devU-api/src/entities/webhooks/webhooks.controller.ts @@ -0,0 +1,71 @@ +import { NextFunction, Request, Response } from 'express' + +import { Updated } from '../../utils/apiResponse.utils' +import { serialize } from './webhooks.serializer' +import WebhooksService from './webhooks.service' + +export async function get(req: Request, res: Response, next: NextFunction) { + try { + const hooksModel = await WebhooksService.list() + const hooks = hooksModel.map(serialize) + res.status(200).json(hooks) + } catch (err) { + next(err) + } +} + +export async function getById(req: Request, res: Response, next: NextFunction) { + try { + const hooksModel = await WebhooksService.retrieveByUserId(req.currentUser!.userId!) + if (!hooksModel) { + return res.status(404).json({ 'error': 'Webhook for user not found not found' }) + } + const hooks = hooksModel.map(serialize) + + res.status(200).json(hooks) + } catch (err) { + next(err) + } +} + + +export async function post(req: Request, res: Response, next: NextFunction) { + try { + req.body['userId'] = req.currentUser!.userId! + + const created = await WebhooksService.create(req.body) + res.status(201).json(serialize(created)) + } catch (err: any) { + next(err) + } +} + +export async function put(req: Request, res: Response, next: NextFunction) { + try { + const webhookId = parseInt(req.params.id) + await WebhooksService.update(webhookId, req.body) + + res.status(201).json(Updated) + } catch (err: any) { + next(err) + } +} + +export async function _delete(req: Request, res: Response, next: NextFunction) { + try { + const webhookId = parseInt(req.params.id) + await WebhooksService._delete(webhookId) + + res.status(204).json('Deleted') + } catch (err: any) { + next(err) + } +} + +export default { + get, + put, + post, + _delete, + getById, +} diff --git a/devU-api/src/entities/webhooks/webhooks.middleware.ts b/devU-api/src/entities/webhooks/webhooks.middleware.ts new file mode 100644 index 00000000..97bf9fa5 --- /dev/null +++ b/devU-api/src/entities/webhooks/webhooks.middleware.ts @@ -0,0 +1,67 @@ +import { NextFunction, Request, Response } from 'express' +import WebhooksService from './webhooks.service' +import fetch from 'node-fetch' + + +export async function processWebhook(statusCode: number, userId: number, path: string, body: any) { + return new Promise(async (resolve, reject) => { + try { + // process path and check for match + const hooks = await WebhooksService.retrieveByUserId(userId) + if (hooks.length !== 0) { + for (let hook of hooks) { + // todo make matcher more generic that checks url patterns + if (hook.matcherUrl == path) { + try { + // todo add multiple ways to format a webhook message currently only formated for discord + const re = await fetch(hook.destinationUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ 'content': body }), + }) + + if (re.status >= 300) { + reject(`Failed to send webhook ${re.statusText}`) + } + } catch (e) { + reject(e) + } + } + } + // prepare request and send + } else { + reject('Url could not be matched') + } + + resolve('Goodbye and thanks for all the fish') + } catch (e) { + reject(e) + } + }) +} + +// adds a +export function responseInterceptor(req: Request, res: Response, next: NextFunction) { + const originalSend = res.send + + // Override function + // @ts-ignore + res.send = function(body) { + // send response to client + originalSend.call(this, body) + + // send body for processing in webhook + if (req.currentUser?.userId) { + processWebhook(res.statusCode, req.currentUser?.userId, req.originalUrl, body).then( + value => { + console.log('Sent webhook successfully') + }, + ).catch(err => { + console.error('Error sending webhook', err) + }) + } + } + next() +} diff --git a/devU-api/src/entities/webhooks/webhooks.model.ts b/devU-api/src/entities/webhooks/webhooks.model.ts new file mode 100644 index 00000000..bdeabb00 --- /dev/null +++ b/devU-api/src/entities/webhooks/webhooks.model.ts @@ -0,0 +1,38 @@ +import { + JoinColumn, + ManyToOne, + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + DeleteDateColumn, +} from 'typeorm' + +import UserModel from '../user/user.model' + +@Entity('Webhooks') +export default class WebhooksModel { + @PrimaryGeneratedColumn() + id: number + + @Column({name: 'destination_url'}) + destinationUrl: string + + @Column({name: 'matcher_url'}) + matcherUrl: string + + @Column({ name: 'user_id' }) + @JoinColumn({ name: 'user_id' }) + @ManyToOne(() => UserModel) + userId: number + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date + + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date +} diff --git a/devU-api/src/entities/webhooks/webhooks.router.ts b/devU-api/src/entities/webhooks/webhooks.router.ts new file mode 100644 index 00000000..371c7fa4 --- /dev/null +++ b/devU-api/src/entities/webhooks/webhooks.router.ts @@ -0,0 +1,54 @@ +import express from 'express' + +import validator from './webhooks.validator' + +import WebhooksController from './webhooks.controller' +import { isAuthorized } from '../../authorization/authorization.middleware' + +const Router = express.Router({ mergeParams: true }) + + +/** + * @swagger + * /webhooks: + * get: + * summary: get all webhooks created by a user + */ +Router.get('/', isAuthorized('courseViewAll'), WebhooksController.getById) + +/** + * @swagger + * /webhooks: + * get: + * summary: get all webhooks, only for admins + */ +Router.get('/all', isAuthorized('admin'), WebhooksController.get) + + +/** + * @swagger + * /webhooks: + * get: + * summary: create a webhook + */ +Router.post('/', isAuthorized('courseViewAll'), validator, WebhooksController.post) + + +/** + * @swagger + * /webhooks: + * put: + * summary: Edit webhook urls + */ +Router.put('/:id', isAuthorized('courseViewAll'), validator, WebhooksController.put) + + +/** + * @swagger + * /webhooks: + * delete: + * summary: delete a webhook + */ +Router.delete('/:id', isAuthorized('courseViewAll'), WebhooksController._delete) + +export default Router diff --git a/devU-api/src/entities/webhooks/webhooks.serializer.ts b/devU-api/src/entities/webhooks/webhooks.serializer.ts new file mode 100644 index 00000000..96b61253 --- /dev/null +++ b/devU-api/src/entities/webhooks/webhooks.serializer.ts @@ -0,0 +1,13 @@ +import WebhooksModel from './webhooks.model' +import { Webhooks } from 'devu-shared-modules' + +export function serialize(webhooks: WebhooksModel): Webhooks { + return { + id: webhooks.id, + userId: webhooks.userId, + destinationUrl: webhooks.destinationUrl, + matcherUrl: webhooks.matcherUrl, + updatedAt: webhooks.updatedAt.toISOString(), + createdAt: webhooks.createdAt.toISOString(), + } +} diff --git a/devU-api/src/entities/webhooks/webhooks.service.ts b/devU-api/src/entities/webhooks/webhooks.service.ts new file mode 100644 index 00000000..7d8e041e --- /dev/null +++ b/devU-api/src/entities/webhooks/webhooks.service.ts @@ -0,0 +1,36 @@ +import { dataSource } from '../../database' +import WebhooksModel from './webhooks.model' +import { Webhooks } from 'devu-shared-modules' +import { IsNull } from 'typeorm' + +const connect = () => dataSource.getRepository(WebhooksModel) + +async function create(input: Webhooks) { + return await connect().save(input) +} + + + +async function retrieveByUserId(userId: number) { + return await connect().findBy({ userId: userId, deletedAt: IsNull() }) +} + +async function update(id: number, input: Webhooks) { + return await connect().update(id, { matcherUrl: input.matcherUrl, destinationUrl: input.destinationUrl }) +} + +async function list() { + return await connect().findBy({ deletedAt: IsNull() }) +} + +async function _delete(id: number) { + return await connect().softDelete({ id, deletedAt: IsNull() }) +} + +export default { + create, + list, + update, + retrieveByUserId, + _delete, +} diff --git a/devU-api/src/entities/webhooks/webhooks.validator.ts b/devU-api/src/entities/webhooks/webhooks.validator.ts new file mode 100644 index 00000000..146c79bd --- /dev/null +++ b/devU-api/src/entities/webhooks/webhooks.validator.ts @@ -0,0 +1,16 @@ +import { check } from 'express-validator' + +import validate from '../../middleware/validator/generic.validator' + + +const destinationUrl = check('destinationUrl').isString().trim() +const matcherUrl = check('matcherUrl').isString().trim() + + +const validator = [ + destinationUrl, + matcherUrl, + validate, +] + +export default validator diff --git a/devU-api/src/index.ts b/devU-api/src/index.ts index 75056335..92730683 100644 --- a/devU-api/src/index.ts +++ b/devU-api/src/index.ts @@ -19,6 +19,7 @@ import errorHandler from './middleware/errorHandler.middleware' // Authentication Handlers import './utils/passport.utils' +import { responseInterceptor } from './entities/webhooks/webhooks.middleware' const app = express() @@ -43,6 +44,8 @@ initializeMinio() console.log(`Api: ${environment.isDocker ? '' : 'not'} running in docker`) + app.use(responseInterceptor) + // Middleware; app.use('/', router) app.use(errorHandler) diff --git a/devU-api/src/migration/1730698767895-webhooks.ts b/devU-api/src/migration/1730698767895-webhooks.ts new file mode 100644 index 00000000..51e06be1 --- /dev/null +++ b/devU-api/src/migration/1730698767895-webhooks.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Webhooks1730698767895 implements MigrationInterface { + name = 'Webhooks1730698767895' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "Webhooks" ("id" SERIAL NOT NULL, "destination_url" character varying NOT NULL, "matcher_url" character varying NOT NULL, "user_id" integer NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, CONSTRAINT "PK_ef540eaf209b4e5cb871ea34910" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "Webhooks" ADD CONSTRAINT "FK_0831572f37f912eed2756aef1af" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "Webhooks" DROP CONSTRAINT "FK_0831572f37f912eed2756aef1af"`); + await queryRunner.query(`DROP TABLE "Webhooks"`); + } + +} diff --git a/devU-api/src/router/courseData.router.ts b/devU-api/src/router/courseData.router.ts index 037e8005..174d0d70 100644 --- a/devU-api/src/router/courseData.router.ts +++ b/devU-api/src/router/courseData.router.ts @@ -18,6 +18,7 @@ import role from '../entities/role/role.router' import nonContainerAutoGraderRouter from '../entities/nonContainerAutoGrader/nonContainerAutoGrader.router' import { asInt } from '../middleware/validator/generic.validator' +import webhooksRouter from '../entities/webhooks/webhooks.router' const assignmentRouter = express.Router({ mergeParams: true }) assignmentRouter.use('/assignment-problems', assignmentProblem) @@ -41,4 +42,7 @@ Router.use('/file-upload', fileUpload) Router.use('/roles', role) Router.use('/user-courses', userCourse) +Router.use('/webhooks', webhooksRouter) + + export default Router diff --git a/devU-shared/src/index.ts b/devU-shared/src/index.ts index d83081ea..464b8ba2 100644 --- a/devU-shared/src/index.ts +++ b/devU-shared/src/index.ts @@ -21,6 +21,7 @@ export * from './types/nonContainerAutoGrader.types' export * from './types/deadlineExtensions.types' export * from './types/grader.types' export * from './types/role.types' +export * from './types/webhooks.types' export * from './utils/object.utils' export * from './utils/string.utils' diff --git a/devU-shared/src/types/webhooks.types.ts b/devU-shared/src/types/webhooks.types.ts new file mode 100644 index 00000000..185c07e8 --- /dev/null +++ b/devU-shared/src/types/webhooks.types.ts @@ -0,0 +1,8 @@ +export type Webhooks = { + id?: number + userId: number + destinationUrl: string + matcherUrl: string + createdAt?: string + updatedAt?: string +} From 47b7e6a189c2c656a0469ab8c89edc2e7a298033 Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Mon, 4 Nov 2024 13:29:42 -0500 Subject: [PATCH 250/400] Added base webhookURLForm Added the base of the webhookURLForm that will eventually connect to the backend. --- .../src/components/authenticatedRouter.tsx | 3 ++ .../src/components/pages/webhookURLForm.scss | 5 +++ .../src/components/pages/webhookURLForm.tsx | 38 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 devU-client/src/components/pages/webhookURLForm.scss create mode 100644 devU-client/src/components/pages/webhookURLForm.tsx diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index 50c9217f..a4d09cf7 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -21,6 +21,8 @@ import CoursesListPage from "./pages/listPages/courses/coursesListPage"; import AssignmentProblemFormPage from './pages/forms/assignments/assignmentProblemFormPage' import InstructorSubmissionspage from "./pages/submissions/InstructorSubmissionspage"; +import webhookURLForm from './pages/webhookURLForm' + const AuthenticatedRouter = () => ( @@ -50,6 +52,7 @@ const AuthenticatedRouter = () => ( component={InstructorSubmissionspage}/> + diff --git a/devU-client/src/components/pages/webhookURLForm.scss b/devU-client/src/components/pages/webhookURLForm.scss new file mode 100644 index 00000000..25c022e5 --- /dev/null +++ b/devU-client/src/components/pages/webhookURLForm.scss @@ -0,0 +1,5 @@ +@import 'variables'; + +.textField { + align-items: center; +} \ No newline at end of file diff --git a/devU-client/src/components/pages/webhookURLForm.tsx b/devU-client/src/components/pages/webhookURLForm.tsx new file mode 100644 index 00000000..4f82c1f3 --- /dev/null +++ b/devU-client/src/components/pages/webhookURLForm.tsx @@ -0,0 +1,38 @@ +import React, {useState} from 'react' +import TextField from 'components/shared/inputs/textField' +import Button from 'components/shared/inputs/button' +import PageWrapper from 'components/shared/layouts/pageWrapper' +import styles from './webhookURLForm.scss' + +const webhookURLForm = () => { + const [webhookURL, setWebhookURL] = useState() + const [webhookUrls, setWebhookUrls] = useState([]) + + const handleChange = (value: string) => { + setWebhookURL(value); + }; + + const handleAddURL = () => { + if (webhookURL) { + setWebhookUrls([...webhookUrls, webhookURL]) + setWebhookURL('') + } + } + + return ( + +
+ + +
+ +
    + {webhookUrls.map((url, index) => ( +
  • {url}
  • + ))} +
+
+ ) +} + +export default webhookURLForm \ No newline at end of file From 76157c09e016d95dea82bec9a2836e6cad59bc80 Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Mon, 4 Nov 2024 13:32:36 -0500 Subject: [PATCH 251/400] Added comment to aid backend Added a comment of where to start writing the connection between frontend and backend --- devU-client/src/components/pages/webhookURLForm.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/devU-client/src/components/pages/webhookURLForm.tsx b/devU-client/src/components/pages/webhookURLForm.tsx index 4f82c1f3..8c3442bd 100644 --- a/devU-client/src/components/pages/webhookURLForm.tsx +++ b/devU-client/src/components/pages/webhookURLForm.tsx @@ -15,6 +15,8 @@ const webhookURLForm = () => { const handleAddURL = () => { if (webhookURL) { setWebhookUrls([...webhookUrls, webhookURL]) + //Handle adding webhook URL to backend here + setWebhookURL('') } } From 1345584e84ff203d6f41c3124d41d29ea936457f Mon Sep 17 00:00:00 2001 From: RA341 Date: Mon, 4 Nov 2024 20:36:22 -0500 Subject: [PATCH 252/400] added matcher url --- .../src/components/authenticatedRouter.tsx | 6 +- .../src/components/pages/webhookURLForm.tsx | 60 ++++++++++++++----- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index a4d09cf7..a8016a49 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -21,7 +21,7 @@ import CoursesListPage from "./pages/listPages/courses/coursesListPage"; import AssignmentProblemFormPage from './pages/forms/assignments/assignmentProblemFormPage' import InstructorSubmissionspage from "./pages/submissions/InstructorSubmissionspage"; -import webhookURLForm from './pages/webhookURLForm' +import WebhookURLForm from './pages/webhookURLForm' const AuthenticatedRouter = () => ( @@ -45,6 +45,7 @@ const AuthenticatedRouter = () => ( component={NonContainerAutoGraderForm}/> + @@ -52,7 +53,8 @@ const AuthenticatedRouter = () => ( component={InstructorSubmissionspage}/> - + // TBD, undecided where webhooks should be placed + {/**/} diff --git a/devU-client/src/components/pages/webhookURLForm.tsx b/devU-client/src/components/pages/webhookURLForm.tsx index 8c3442bd..8b979cc1 100644 --- a/devU-client/src/components/pages/webhookURLForm.tsx +++ b/devU-client/src/components/pages/webhookURLForm.tsx @@ -3,37 +3,69 @@ import TextField from 'components/shared/inputs/textField' import Button from 'components/shared/inputs/button' import PageWrapper from 'components/shared/layouts/pageWrapper' import styles from './webhookURLForm.scss' +import RequestService from '../../services/request.service' +import { useParams } from 'react-router-dom' +import { SET_ALERT } from 'redux/types/active.types' +import { useActionless } from '../../redux/hooks' const webhookURLForm = () => { const [webhookURL, setWebhookURL] = useState() const [webhookUrls, setWebhookUrls] = useState([]) + const [watcherUrl, setWatcherUrl] = useState() + const { courseId } = useParams<{ courseId: string}>() + const [setAlert] = useActionless(SET_ALERT) - const handleChange = (value: string) => { + const handleUrlChange = (value: string) => { setWebhookURL(value); }; + const handleWatcherUrl = (value: string) => { + setWatcherUrl(value); + } + const handleAddURL = () => { if (webhookURL) { setWebhookUrls([...webhookUrls, webhookURL]) //Handle adding webhook URL to backend here - + RequestService.post(`/course/${courseId}/webhooks`, { + "destinationUrl": webhookURL, + "matcherUrl": watcherUrl + }).then( + _ => { + setAlert({ autoDelete: true, type: 'success', message: 'Added webhook' }) + } + ).catch(reason => { + setAlert({ autoDelete: true, type: 'error', message: `Failed to add webhook ${reason.toString()}` }) + }) + + // clear field setWebhookURL('') + setWatcherUrl('') } } return ( - -
- - -
- -
    - {webhookUrls.map((url, index) => ( -
  • {url}
  • - ))} -
-
+ +
+ + + +
+ +
    + {webhookUrls.map((url, index) => ( +
  • {url}
  • + ))} +
+
) } From 5de5dab61cfe381b0ac6f5f6c6f1fe41cd0adef5 Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 5 Nov 2024 03:51:29 -0500 Subject: [PATCH 253/400] Updated the styling of the webhooks form Updated the styling of the webhooks form --- .../pages/courses/courseDetailPage.tsx | 8 +++++ .../src/components/pages/webhookURLForm.scss | 36 +++++++++++++++++++ .../src/components/pages/webhookURLForm.tsx | 20 ++++++----- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index a7072c72..3802b674 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -126,6 +126,14 @@ const CourseDetailPage = () => { }}>Update Course Form )} + + {role.isInstructor() &&( + + )}
-
    +
    {webhookUrls.map((url, index) => ( -
  • {url}
  • + //
  • {url}
  • +
    +
    {url}
    +
    ))} -
+
) } From c2d9cfed206060418998f46d61be3a8032dbad5d Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Tue, 5 Nov 2024 09:48:28 -0500 Subject: [PATCH 254/400] completed enrolled/not-enrolled feature --- .../components/listItems/courseListItem.scss | 18 ++- .../src/components/misc/globalToolbar.tsx | 2 +- .../listPages/courses/coursesListPage.tsx | 120 ++++++++++-------- package-lock.json | 34 +++++ package.json | 5 + 5 files changed, 121 insertions(+), 58 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/devU-client/src/components/listItems/courseListItem.scss b/devU-client/src/components/listItems/courseListItem.scss index c5d73f65..e8449c52 100644 --- a/devU-client/src/components/listItems/courseListItem.scss +++ b/devU-client/src/components/listItems/courseListItem.scss @@ -60,13 +60,29 @@ color: $text-color; - + &.enrolled { + background-color: #dff0d8; // Light green background for enrolled courses + border-color: #3c763d; // Darker green border for enrolled courses + color: #3c763d; // Change text color to darker green + } &:hover, &:focus { background: $list-item-background-hover; } } +.enrollmentStatus { + margin-left: 10px; + font-weight: bold; // Make it bold for emphasis +} + +.enrolled { + color: green; // Change color for enrolled courses +} + +.notEnrolled { + color: red; // Change color for not enrolled courses +} @media (max-width: $medium) { .subText { diff --git a/devU-client/src/components/misc/globalToolbar.tsx b/devU-client/src/components/misc/globalToolbar.tsx index 7149e5ec..29a894ef 100644 --- a/devU-client/src/components/misc/globalToolbar.tsx +++ b/devU-client/src/components/misc/globalToolbar.tsx @@ -29,7 +29,7 @@ const GlobalToolbar = () => { { - My Courses + Join a Course } {/**/} diff --git a/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx b/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx index b1e0026c..2184be95 100644 --- a/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx +++ b/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx @@ -1,70 +1,81 @@ -import React, {useEffect, useState} from 'react' -import {Course, UserCourse} from 'devu-shared-modules' -import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import PageWrapper from 'components/shared/layouts/pageWrapper' -import Dropdown, {Option} from 'components/shared/inputs/dropdown' -import ErrorPage from '../../errorPage/errorPage' -import RequestService from 'services/request.service' -import styles from './coursesListPage.scss' +import React, { useEffect, useState } from 'react'; +import { Course } from 'devu-shared-modules'; +import LoadingOverlay from 'components/shared/loaders/loadingOverlay'; +import PageWrapper from 'components/shared/layouts/pageWrapper'; +import Dropdown, { Option } from 'components/shared/inputs/dropdown'; +import ErrorPage from '../../errorPage/errorPage'; +import RequestService from 'services/request.service'; +import styles from './coursesListPage.scss'; import CourseListItem from "../../../listItems/courseListItem"; -// import {useAppSelector} from "../../../../redux/hooks"; import Button from "@mui/material/Button"; -import {useHistory} from "react-router-dom"; +import { useHistory } from "react-router-dom"; +import { useAppSelector } from "../../../../redux/hooks"; -type Filter = true | false +type Filter = true | false; const filterOptions: Option[] = [ - {label: 'Expand All', value: true}, - {label: 'Collapse All', value: false}, -] + { label: 'Expand All', value: true }, + { label: 'Collapse All', value: false }, +]; const UserCoursesListPage = () => { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [allCourses, setAllCourses] = useState([]); + const [filter, setFilter] = useState(false); + const history = useHistory(); - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - const [userCourses, setUserCourses] = useState(new Array()) - const [filter, setFilter] = useState(false ) - const history = useHistory() - - //Temporary place to store state for all courses - const [allCourses, setAllCourses] = useState(new Array()) + // Get userId from Redux store + const userId = useAppSelector((store) => store.user.id); useEffect(() => { - fetchData() - }, []) + fetchData(); + }, []); const fetchData = async () => { try { - // const userCourses = await RequestService.get(`/api/user-courses?filterBy=${filter}`) - const courseRequests = userCourses.map((u) => RequestService.get(`/api/courses/${u.courseId}`)) - const courses = await Promise.all(courseRequests) - - // Mapify course ids so we can look them up more easilly via their id - const courseMap: Record = {} - for (const course of courses) courseMap[course.id || ''] = course - - // Temporary place to grab and display all courses - const allCourses = await RequestService.get('/api/courses') - setAllCourses(allCourses) - - setUserCourses(userCourses) + // Fetch user-specific courses + const userCourseData = await RequestService.get<{ + instructorCourses: Course[]; + activeCourses: Course[]; + pastCourses: Course[]; + upcomingCourses: Course[]; + }>(`/api/courses/user/${userId}`); + + // Flatten and combine user course data into a single array + const userCoursesList = [ + ...userCourseData.instructorCourses, + ...userCourseData.activeCourses, + ...userCourseData.pastCourses, + ...userCourseData.upcomingCourses, + ]; + + // Fetch all courses + const allCourseData = await RequestService.get(`/api/courses`); + + // Filter to get courses the user is not enrolled in + const unenrolledCourses = allCourseData.filter( + (course) => !userCoursesList.some((userCourse) => userCourse.id === course.id) + ); + + + setAllCourses(unenrolledCourses); } catch (error: any) { - setError(error) + setError(error); } finally { - setLoading(false) + setLoading(false); } - } + }; const handleFilterChange = (updatedFilter: Filter) => { - setFilter(updatedFilter) - } + setFilter(updatedFilter); + }; - if (loading) return - if (error) return - - const defaultOption = filterOptions.find((o) => o.value === filter) + if (loading) return ; + if (error) return ; + const defaultOption = filterOptions.find((o) => o.value === filter); return ( @@ -73,9 +84,8 @@ const UserCoursesListPage = () => {

All Courses

-
{ />
- {allCourses.map(course => ( - + {allCourses.map((course) => ( + ))} - ) - - -} + ); +}; -export default UserCoursesListPage +export default UserCoursesListPage; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..84cd429b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,34 @@ +{ + "name": "devU", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@types/react": "^18.3.12" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..ab744a68 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@types/react": "^18.3.12" + } +} From 810988e953c25914b69f35990b47fa5e39cba901 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 5 Nov 2024 09:51:15 -0500 Subject: [PATCH 255/400] added some styling for new dropdowns --- .../pages/forms/courses/automateDates.tsx | 57 +++++++++++-------- .../pages/forms/courses/coursesFormPage.scss | 22 +++++++ .../pages/forms/courses/coursesFormPage.tsx | 4 +- 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/devU-client/src/components/pages/forms/courses/automateDates.tsx b/devU-client/src/components/pages/forms/courses/automateDates.tsx index ab855ea1..792d488e 100644 --- a/devU-client/src/components/pages/forms/courses/automateDates.tsx +++ b/devU-client/src/components/pages/forms/courses/automateDates.tsx @@ -65,35 +65,42 @@ function AutomateDates() { return (
{/* Season Dropdown */} - - +
+ + +
{/* Year Dropdown */} - - +
+ + +
{/* Session Dropdown */} - - +
+ + +
+
); } diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss index 3dc2c20d..bd5d3e27 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss @@ -70,6 +70,28 @@ input[type='date'] { border-radius: 100px; } +.semesterOptions { + display: flex; + justify-content: space-between; +} + +.fieldContainer { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + gap: 5px; +} + +.fieldContainer>select { + // background-color: $input-field-background; + // color: $input-field-label; + // border-radius: 20px; + padding: 0.625rem 1rem; + border: none; + border-radius: 100px; +} + input[type='file']::file-selector-button { background-color: $primary; border-radius: 100px; diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx index fc9db8c7..50101209 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx @@ -81,11 +81,11 @@ const EditCourseFormPage = () => { helpText={invalidFields.get("semester")} /> */}
-
+
-
+
From ff6153b4a87729efd66f4233700421653ebbe102 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:44:14 -0500 Subject: [PATCH 256/400] date selection automated on frontend --- .../pages/forms/courses/automateDates.tsx | 278 +++++++++++++++--- .../pages/forms/courses/coursesFormPage.tsx | 12 +- 2 files changed, 250 insertions(+), 40 deletions(-) diff --git a/devU-client/src/components/pages/forms/courses/automateDates.tsx b/devU-client/src/components/pages/forms/courses/automateDates.tsx index 792d488e..c81d9234 100644 --- a/devU-client/src/components/pages/forms/courses/automateDates.tsx +++ b/devU-client/src/components/pages/forms/courses/automateDates.tsx @@ -1,12 +1,202 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; +import formStyles from './coursesFormPage.scss'; -import formStyles from './coursesFormPage.scss' +interface AutomateDatesProps { + onDatesChange: (dates: { startDate: string; endDate: string }) => void; +} -function AutomateDates() { +const AutomateDates = ({ onDatesChange }: AutomateDatesProps) => { const [season, setSeason] = useState(''); - const [year, setYear] = useState(''); + const [year, setYear] = useState(new Date().getFullYear().toString()); const [session, setSession] = useState(''); - const currentYear = new Date().getFullYear(); + const [startDate, setStartDate] = useState(''); + const [endDate, setEndDate] = useState(''); + + // Hardcoded dates for each year, season, and session + const dateMapping: { [key: string]: { [key: string]: { [key: string]: { start: string; end: string } } } } = { + 2024: { + Fall: { + '15 week': { start: '2024-09-01', end: '2024-12-15' }, + '7 week': { start: '2024-09-01', end: '2024-10-20' }, + Custom: { start: '', end: '' }, + }, + Spring: { + '15 week': { start: '2024-09-01', end: '2024-12-15' }, + '7 week': { start: '2024-09-01', end: '2024-10-20' }, + Custom: { start: '', end: '' }, + }, + Winter: { + '15 days': { start: '2024-01-02', end: '2024-01-17' }, + '14 days': { start: '2024-01-02', end: '2024-01-16' }, + Custom: { start: '', end: '' }, + }, + Summer: { + 'Summer Session I (J)': { start: '2024-06-01', end: '2024-06-30' }, + 'Summer Session II (K)': { start: '2024-07-01', end: '2024-07-31' }, + 'Summer Session III (M)': { start: '2024-08-01', end: '2024-08-30' }, + '9 Weeks (L)': { start: '2024-06-01', end: '2024-08-01' }, + '10 Weeks (A)': { start: '2024-06-01', end: '2024-08-10' }, + '12 Weeks (I)': { start: '2024-05-20', end: '2024-08-10' }, + Custom: { start: '', end: '' }, + }, + }, + 2025: { + Fall: { + '15 week': { start: '2025-09-01', end: '2025-12-15' }, + '7 week': { start: '2025-09-01', end: '2025-10-20' }, + Custom: { start: '', end: '' }, + }, + Spring: { + '15 week': { start: '2025-09-01', end: '2025-12-15' }, + '7 week': { start: '2025-09-01', end: '2025-10-20' }, + Custom: { start: '', end: '' }, + }, + Winter: { + '15 days': { start: '2025-01-02', end: '2025-01-17' }, + '14 days': { start: '2025-01-02', end: '2025-01-16' }, + Custom: { start: '', end: '' }, + }, + Summer: { + 'Summer Session I (J)': { start: '2025-06-01', end: '2025-06-30' }, + 'Summer Session II (K)': { start: '2025-07-01', end: '2025-07-31' }, + 'Summer Session III (M)': { start: '2025-08-01', end: '2025-08-30' }, + '9 Weeks (L)': { start: '2025-06-01', end: '2025-08-01' }, + '10 Weeks (A)': { start: '2025-06-01', end: '2025-08-10' }, + '12 Weeks (I)': { start: '2025-05-20', end: '2025-08-10' }, + Custom: { start: '', end: '' }, + }, + }, + 2026: { + Fall: { + '15 week': { start: '2026-09-01', end: '2026-12-15' }, + '7 week': { start: '2026-09-01', end: '2026-10-20' }, + Custom: { start: '', end: '' }, + }, + Spring: { + '15 week': { start: '2026-09-01', end: '2026-12-15' }, + '7 week': { start: '2026-09-01', end: '2026-10-20' }, + Custom: { start: '', end: '' }, + }, + Winter: { + '15 days': { start: '2026-01-02', end: '2026-01-17' }, + '14 days': { start: '2026-01-02', end: '2026-01-16' }, + Custom: { start: '', end: '' }, + }, + Summer: { + 'Summer Session I (J)': { start: '2026-06-01', end: '2026-06-30' }, + 'Summer Session II (K)': { start: '2026-07-01', end: '2026-07-31' }, + 'Summer Session III (M)': { start: '2026-08-01', end: '2026-08-30' }, + '9 Weeks (L)': { start: '2026-06-01', end: '2026-08-01' }, + '10 Weeks (A)': { start: '2026-06-01', end: '2026-08-10' }, + '12 Weeks (I)': { start: '2026-05-20', end: '2026-08-10' }, + Custom: { start: '', end: '' }, + }, + }, + 2027: { + Fall: { + '15 week': { start: '2027-09-01', end: '2027-12-15' }, + '7 week': { start: '2027-09-01', end: '2027-10-20' }, + Custom: { start: '', end: '' }, + }, + Spring: { + '15 week': { start: '2027-09-01', end: '2027-12-15' }, + '7 week': { start: '2027-09-01', end: '2027-10-20' }, + Custom: { start: '', end: '' }, + }, + Winter: { + '15 days': { start: '2027-01-02', end: '2027-01-17' }, + '14 days': { start: '2027-01-02', end: '2027-01-16' }, + Custom: { start: '', end: '' }, + }, + Summer: { + 'Summer Session I (J)': { start: '2027-06-01', end: '2027-06-30' }, + 'Summer Session II (K)': { start: '2027-07-01', end: '2027-07-31' }, + 'Summer Session III (M)': { start: '2027-08-01', end: '2027-08-30' }, + '9 Weeks (L)': { start: '2027-06-01', end: '2027-08-01' }, + '10 Weeks (A)': { start: '2027-06-01', end: '2027-08-10' }, + '12 Weeks (I)': { start: '2027-05-20', end: '2027-08-10' }, + Custom: { start: '', end: '' }, + }, + }, + 2028: { + Fall: { + '15 week': { start: '2028-09-01', end: '2028-12-15' }, + '7 week': { start: '2028-09-01', end: '2028-10-20' }, + Custom: { start: '', end: '' }, + }, + Spring: { + '15 week': { start: '2028-09-01', end: '2028-12-15' }, + '7 week': { start: '2028-09-01', end: '2028-10-20' }, + Custom: { start: '', end: '' }, + }, + Winter: { + '15 days': { start: '2028-01-02', end: '2028-01-17' }, + '14 days': { start: '2028-01-02', end: '2028-01-16' }, + Custom: { start: '', end: '' }, + }, + Summer: { + 'Summer Session I (J)': { start: '2028-06-01', end: '2028-06-30' }, + 'Summer Session II (K)': { start: '2028-07-01', end: '2028-07-31' }, + 'Summer Session III (M)': { start: '2028-08-01', end: '2028-08-30' }, + '9 Weeks (L)': { start: '2028-06-01', end: '2028-08-01' }, + '10 Weeks (A)': { start: '2028-06-01', end: '2028-08-10' }, + '12 Weeks (I)': { start: '2028-05-20', end: '2028-08-10' }, + Custom: { start: '', end: '' }, + }, + }, + 2029: { + Fall: { + '15 week': { start: '2029-09-01', end: '2029-12-15' }, + '7 week': { start: '2029-09-01', end: '2029-10-20' }, + Custom: { start: '', end: '' }, + }, + Spring: { + '15 week': { start: '2029-09-01', end: '2029-12-15' }, + '7 week': { start: '2029-09-01', end: '2029-10-20' }, + Custom: { start: '', end: '' }, + }, + Winter: { + '15 days': { start: '2029-01-02', end: '2029-01-17' }, + '14 days': { start: '2029-01-02', end: '2029-01-16' }, + Custom: { start: '', end: '' }, + }, + Summer: { + 'Summer Session I (J)': { start: '2029-06-01', end: '2029-06-30' }, + 'Summer Session II (K)': { start: '2029-07-01', end: '2029-07-31' }, + 'Summer Session III (M)': { start: '2029-08-01', end: '2029-08-30' }, + '9 Weeks (L)': { start: '2029-06-01', end: '2029-08-01' }, + '10 Weeks (A)': { start: '2029-06-01', end: '2029-08-10' }, + '12 Weeks (I)': { start: '2029-05-20', end: '2029-08-10' }, + Custom: { start: '', end: '' }, + }, + }, + 2030: { + Fall: { + '15 week': { start: '2030-09-01', end: '2030-12-15' }, + '7 week': { start: '2030-09-01', end: '2030-10-20' }, + Custom: { start: '', end: '' }, + }, + Spring: { + '15 week': { start: '2030-09-01', end: '2030-12-15' }, + '7 week': { start: '2030-09-01', end: '2030-10-20' }, + Custom: { start: '', end: '' }, + }, + Winter: { + '15 days': { start: '2030-01-02', end: '2030-01-17' }, + '14 days': { start: '2030-01-02', end: '2030-01-16' }, + Custom: { start: '', end: '' }, + }, + Summer: { + 'Summer Session I (J)': { start: '2030-06-01', end: '2030-06-30' }, + 'Summer Session II (K)': { start: '2030-07-01', end: '2030-07-31' }, + 'Summer Session III (M)': { start: '2030-08-01', end: '2030-08-30' }, + '9 Weeks (L)': { start: '2030-06-01', end: '2030-08-01' }, + '10 Weeks (A)': { start: '2030-06-01', end: '2030-08-10' }, + '12 Weeks (I)': { start: '2030-05-20', end: '2030-08-10' }, + Custom: { start: '', end: '' }, + }, + }, + }; // Define the session options based on the selected season const getSessionOptions = (season: string) => { @@ -16,14 +206,14 @@ function AutomateDates() { return [ { value: '15 week', label: '15 week' }, { value: '7 week', label: '7 week' }, - { value: 'Custom', label: 'Custom' } + { value: 'Custom', label: 'Custom' }, ]; case 'Winter': return [ { value: '15 days', label: '15 days' }, { value: '14 days', label: '14 days' }, { value: '10 days', label: '10 days' }, - { value: 'Custom', label: 'Custom' } + { value: 'Custom', label: 'Custom' }, ]; case 'Summer': return [ @@ -33,41 +223,59 @@ function AutomateDates() { { value: '9 Weeks (L)', label: '9 Weeks (L)' }, { value: '10 Weeks (A)', label: '10 Weeks (A)' }, { value: '12 Weeks (I)', label: '12 Weeks (I)' }, - { value: 'Custom', label: 'Custom' } + { value: 'Custom', label: 'Custom' }, ]; default: return []; } }; - // Define default values based on season - const getDefaultSession = (season: string) => { - switch (season) { - case 'Fall': - case 'Spring': - return '15 week'; - case 'Winter': - return '15 days'; - case 'Summer': - return 'Summer Session I (J)'; - default: - return ''; + // Update start and end dates based on selected year, season, and session + const updateDates = () => { + console.log("year, season, session: ", year, season, session); + if (dateMapping[year]?.[season]?.[session]) { + const { start, end } = dateMapping[year][season][session]; + setStartDate(start); + setEndDate(end); + } else { + setStartDate(''); + setEndDate(''); } }; - // Handle changes to season and update session options - const handleSeasonChange = (event: React.ChangeEvent) => { - const selectedSeason = event.target.value; - setSeason(selectedSeason); - setSession(getDefaultSession(selectedSeason)); - }; + useEffect(() => { + updateDates(); + }, [season, session, year]); + + // set default session + useEffect(() => { + if (season) { + switch (season) { + case 'Fall': + case 'Spring': + setSession('15 week'); + break; + case 'Winter': + setSession('15 days'); + break; + case 'Summer': + setSession('Summer Session I (J)'); + break; + default: + setSession(''); // Clear session + } + } + }, [season]); + + useEffect(() => { + onDatesChange({ startDate, endDate }); + }, [startDate, endDate, onDatesChange]); return (
- {/* Season Dropdown */}
- setSeason(e.target.value)}> @@ -76,31 +284,23 @@ function AutomateDates() {
- {/* Year Dropdown */}
- {/* Session Dropdown */}
-
); } diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx index 50101209..24244e99 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx @@ -36,6 +36,16 @@ const EditCourseFormPage = () => { setInvalidFields(newInvalidFields) } + interface Dates { + startDate: string; + endDate: string; + } + + const handleDatesChange = ({ startDate, endDate }: Dates) => { + setStartDate(startDate); + setEndDate(endDate); + }; + const handleStartDateChange = (event: React.ChangeEvent) => { setStartDate(event.target.value) } const handleEndDateChange = (event: React.ChangeEvent) => { setEndDate(event.target.value) } @@ -79,7 +89,7 @@ const EditCourseFormPage = () => { {/* */} - +
From 7c4b7c0105cb14cb4ac13eb75f22d11597deb809 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:56:10 -0500 Subject: [PATCH 257/400] set default semester for request to API --- .../pages/forms/courses/automateDates.tsx | 32 +++++++++---------- .../pages/forms/courses/coursesFormPage.tsx | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/devU-client/src/components/pages/forms/courses/automateDates.tsx b/devU-client/src/components/pages/forms/courses/automateDates.tsx index c81d9234..ac30f8d1 100644 --- a/devU-client/src/components/pages/forms/courses/automateDates.tsx +++ b/devU-client/src/components/pages/forms/courses/automateDates.tsx @@ -21,8 +21,8 @@ const AutomateDates = ({ onDatesChange }: AutomateDatesProps) => { Custom: { start: '', end: '' }, }, Spring: { - '15 week': { start: '2024-09-01', end: '2024-12-15' }, - '7 week': { start: '2024-09-01', end: '2024-10-20' }, + '15 week': { start: '2024-02-01', end: '2024-05-15' }, + '7 week': { start: '2024-02-01', end: '2024-03-20' }, Custom: { start: '', end: '' }, }, Winter: { @@ -47,8 +47,8 @@ const AutomateDates = ({ onDatesChange }: AutomateDatesProps) => { Custom: { start: '', end: '' }, }, Spring: { - '15 week': { start: '2025-09-01', end: '2025-12-15' }, - '7 week': { start: '2025-09-01', end: '2025-10-20' }, + '15 week': { start: '2025-02-01', end: '2025-05-15' }, + '7 week': { start: '2025-02-01', end: '2025-03-20' }, Custom: { start: '', end: '' }, }, Winter: { @@ -73,8 +73,8 @@ const AutomateDates = ({ onDatesChange }: AutomateDatesProps) => { Custom: { start: '', end: '' }, }, Spring: { - '15 week': { start: '2026-09-01', end: '2026-12-15' }, - '7 week': { start: '2026-09-01', end: '2026-10-20' }, + '15 week': { start: '2026-02-01', end: '2026-05-15' }, + '7 week': { start: '2026-02-01', end: '2026-03-20' }, Custom: { start: '', end: '' }, }, Winter: { @@ -99,8 +99,8 @@ const AutomateDates = ({ onDatesChange }: AutomateDatesProps) => { Custom: { start: '', end: '' }, }, Spring: { - '15 week': { start: '2027-09-01', end: '2027-12-15' }, - '7 week': { start: '2027-09-01', end: '2027-10-20' }, + '15 week': { start: '2027-02-01', end: '2027-05-15' }, + '7 week': { start: '2027-02-01', end: '2027-03-20' }, Custom: { start: '', end: '' }, }, Winter: { @@ -125,8 +125,8 @@ const AutomateDates = ({ onDatesChange }: AutomateDatesProps) => { Custom: { start: '', end: '' }, }, Spring: { - '15 week': { start: '2028-09-01', end: '2028-12-15' }, - '7 week': { start: '2028-09-01', end: '2028-10-20' }, + '15 week': { start: '2028-02-01', end: '2028-05-15' }, + '7 week': { start: '2028-02-01', end: '2028-03-20' }, Custom: { start: '', end: '' }, }, Winter: { @@ -151,8 +151,8 @@ const AutomateDates = ({ onDatesChange }: AutomateDatesProps) => { Custom: { start: '', end: '' }, }, Spring: { - '15 week': { start: '2029-09-01', end: '2029-12-15' }, - '7 week': { start: '2029-09-01', end: '2029-10-20' }, + '15 week': { start: '2029-02-01', end: '2029-05-15' }, + '7 week': { start: '2029-02-01', end: '2029-03-20' }, Custom: { start: '', end: '' }, }, Winter: { @@ -172,13 +172,13 @@ const AutomateDates = ({ onDatesChange }: AutomateDatesProps) => { }, 2030: { Fall: { - '15 week': { start: '2030-09-01', end: '2030-12-15' }, - '7 week': { start: '2030-09-01', end: '2030-10-20' }, + '15 week': { start: '2030-02-01', end: '2030-05-15' }, + '7 week': { start: '2030-02-01', end: '2030-10-20' }, Custom: { start: '', end: '' }, }, Spring: { '15 week': { start: '2030-09-01', end: '2030-12-15' }, - '7 week': { start: '2030-09-01', end: '2030-10-20' }, + '7 week': { start: '2030-09-01', end: '2030-03-20' }, Custom: { start: '', end: '' }, }, Winter: { @@ -212,7 +212,7 @@ const AutomateDates = ({ onDatesChange }: AutomateDatesProps) => { return [ { value: '15 days', label: '15 days' }, { value: '14 days', label: '14 days' }, - { value: '10 days', label: '10 days' }, + // { value: '10 days', label: '10 days' }, { value: 'Custom', label: 'Custom' }, ]; case 'Summer': diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx index 24244e99..fb447a72 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx @@ -21,7 +21,7 @@ const EditCourseFormPage = () => { const [formData, setFormData] = useState({ name: '', number: '', - semester: '', + semester: 'f0000', }) const [startDate, setStartDate] = useState(new Date().toISOString().split("T")[0]) From 4552cfa05c55397d21d6258dd4e392528ad5c732 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:22:05 -0500 Subject: [PATCH 258/400] semester options mobile friendly and works with theme toggle --- .../pages/forms/courses/courseUpdatePage.tsx | 24 ++++++++++++++----- .../pages/forms/courses/coursesFormPage.scss | 17 +++++++++---- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx index 065df760..de78631a 100644 --- a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx @@ -4,12 +4,13 @@ import { useHistory, useParams } from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' import RequestService from 'services/request.service' -import 'react-datepicker/dist/react-datepicker.css' +// import 'react-datepicker/dist/react-datepicker.css' import { ExpressValidationError } from 'devu-shared-modules' import { useActionless } from 'redux/hooks' import TextField from 'components/shared/inputs/textField' +import AutomateDates from './automateDates' import { SET_ALERT } from 'redux/types/active.types' import { applyMessageToErrorFields, @@ -43,7 +44,7 @@ const CourseUpdatePage = ({ }) => { const [formData, setFormData] = useState({ name: '', number: '', - semester: '', + semester: 'f0000', }) const [startDate, setStartDate] = useState(new Date().toISOString()) const [endDate, setEndDate] = useState(new Date().toISOString()) @@ -68,6 +69,16 @@ const CourseUpdatePage = ({ }) => { } }, []); + interface Dates { + startDate: string; + endDate: string; + } + + const handleDatesChange = ({ startDate, endDate }: Dates) => { + setStartDate(startDate); + setEndDate(endDate); + }; + const handleChange = (value: string, e: React.ChangeEvent) => { const key = e.target.id const newInvalidFields = removeClassFromField(invalidFields, key) @@ -254,16 +265,17 @@ const CourseUpdatePage = ({ }) => { defaultValue={formData.name} /> - + helpText={invalidFields.get("semester")} /> */}
+
-
+
-
+
diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss index bd5d3e27..750743d2 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss @@ -83,10 +83,9 @@ input[type='date'] { gap: 5px; } -.fieldContainer>select { - // background-color: $input-field-background; - // color: $input-field-label; - // border-radius: 20px; +select { + background: $input-field-background; + color: $input-field-label; padding: 0.625rem 1rem; border: none; border-radius: 100px; @@ -110,9 +109,17 @@ input[type='file']::file-selector-button { .updateDetailsForm, .addDropForm { width: auto; // take up full container } + + .semesterOptions { + flex-direction: column; + } + + select { + width: 100%; + } } -@media (max-width: 450px) { +@media (max-width: 575px) { .datepickerContainer { flex-direction: column; } From 74a59a2835638ca06e1b7d00f81c60fb7d7c0abc Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Tue, 5 Nov 2024 13:12:12 -0500 Subject: [PATCH 259/400] added ability to check if course is public --- devU-api/package.json | 2 +- devU-api/scripts/populate-db.ts | 29 ++-- devU-api/src/entities/course/course.model.ts | 3 + .../src/entities/course/course.serializer.ts | 1 + .../1626719306608-addAssignmentsAndCourses.ts | 1 + ...2-addAssignmentsAndCourses1626719306608.ts | 14 ++ .../components/listItems/courseListItem.tsx | 10 ++ .../pages/forms/courses/coursesFormPage.tsx | 137 ++++++++++-------- devU-shared/src/types/course.types.ts | 4 + 9 files changed, 130 insertions(+), 71 deletions(-) create mode 100644 devU-api/src/migration/1730826999182-addAssignmentsAndCourses1626719306608.ts diff --git a/devU-api/package.json b/devU-api/package.json index e908de15..9fbe875d 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -53,7 +53,7 @@ "prettier": "^2.3.0", "rimraf": "^3.0.2", "ts-jest": "^27.0.2", - "ts-node": "^10.0.0", + "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", "typescript": "^4.3.2" }, diff --git a/devU-api/scripts/populate-db.ts b/devU-api/scripts/populate-db.ts index cdb1b7a4..6ed62dfb 100644 --- a/devU-api/scripts/populate-db.ts +++ b/devU-api/scripts/populate-db.ts @@ -55,16 +55,23 @@ async function SendPOST(path: string, requestBody: string | FormData, requesterE return responseBody } -async function CreateCourse(name: string, number: string, semester: string) { +async function CreateCourse( + name: string, + number: string, + semester: string, + isPublic: boolean +) { const courseData = { - name: name, - semester: semester, - number: number, - startDate: '2024-01-24T00:00:00-0500', - endDate: '2024-05-10T23:59:59-0500', - } - console.log('Creating course: ', courseData.name) - return await SendPOST('/courses/instructor', JSON.stringify(courseData), 'admin') + name: name, + semester: semester, + number: number, + startDate: '2024-01-24T00:00:00-0500', + endDate: '2024-05-10T23:59:59-0500', + is_public: isPublic // Include the public property + }; + + console.log('Creating course: ', courseData.name); + return await SendPOST('/courses/instructor', JSON.stringify(courseData), 'admin'); } async function joinCourse(courseId: number, userId: number, role: string) { @@ -208,8 +215,8 @@ async function runCourseAndSubmission() { const jones = await fetchToken('jones@buffalo.edu', 'jones') //Create courses - const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024')).id - const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id + const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024',true)).id + const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024',true)).id //Enroll students await joinCourse(courseId1, billy, 'student') diff --git a/devU-api/src/entities/course/course.model.ts b/devU-api/src/entities/course/course.model.ts index 3ab6c5a7..2c2e6698 100644 --- a/devU-api/src/entities/course/course.model.ts +++ b/devU-api/src/entities/course/course.model.ts @@ -58,4 +58,7 @@ export default class CourseModel { @DeleteDateColumn({ name: 'deleted_at' }) deletedAt?: Date + + @Column({ type: 'boolean', name: 'is_public', default: false }) + isPublic: boolean; } diff --git a/devU-api/src/entities/course/course.serializer.ts b/devU-api/src/entities/course/course.serializer.ts index 8462cffe..2f9e8790 100644 --- a/devU-api/src/entities/course/course.serializer.ts +++ b/devU-api/src/entities/course/course.serializer.ts @@ -12,5 +12,6 @@ export function serialize(course: CourseModel): Course { endDate: course.endDate.toISOString(), createdAt: course.createdAt.toISOString(), updatedAt: course.updatedAt.toISOString(), + isPublic: course.isPublic } } diff --git a/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts b/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts index f761ab3b..bc37d3fd 100644 --- a/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts +++ b/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts @@ -15,6 +15,7 @@ export class addAssignmentsAndCourses1626719306608 implements MigrationInterface "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, + "is_public" boolean NOT NULL DEFAULT false, CONSTRAINT "courses_primary_key_constraint" PRIMARY KEY ("id") )` ) diff --git a/devU-api/src/migration/1730826999182-addAssignmentsAndCourses1626719306608.ts b/devU-api/src/migration/1730826999182-addAssignmentsAndCourses1626719306608.ts new file mode 100644 index 00000000..b5526812 --- /dev/null +++ b/devU-api/src/migration/1730826999182-addAssignmentsAndCourses1626719306608.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddAssignmentsAndCourses16267193066081730826999182 implements MigrationInterface { + name = 'AddAssignmentsAndCourses16267193066081730826999182' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "courses" ADD "is_public" boolean NOT NULL DEFAULT false`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "courses" DROP COLUMN "is_public"`); + } + +} diff --git a/devU-client/src/components/listItems/courseListItem.tsx b/devU-client/src/components/listItems/courseListItem.tsx index 368ac2fd..db096d66 100644 --- a/devU-client/src/components/listItems/courseListItem.tsx +++ b/devU-client/src/components/listItems/courseListItem.tsx @@ -44,7 +44,17 @@ const CourseListItem = ({course, isOpen}: Props) => { {infoSection("Course Number", course.number)} {infoSection("Semester", prettyPrintSemester(course.semester))} {infoSection("Start/End Date", prettyPrintDate(course.startDate), prettyPrintDate(course.endDate))} +
+ {course && ( + course.isPublic ? ( + Public Course + ) : ( + Private Course + ) + )} +
+ }
) diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx index e5814ce2..0088d931 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx @@ -1,102 +1,121 @@ -import React, { useState } from 'react' -import { useHistory } from 'react-router-dom' -import { ExpressValidationError } from 'devu-shared-modules' - -import PageWrapper from 'components/shared/layouts/pageWrapper' - -import RequestService from 'services/request.service' - -import { useActionless } from 'redux/hooks' -import TextField from 'components/shared/inputs/textField' -import { SET_ALERT } from 'redux/types/active.types' -import formStyles from './coursesFormPage.scss' -import { applyMessageToErrorFields, removeClassFromField } from "../../../../utils/textField.utils"; - +import React, { useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import RequestService from 'services/request.service'; +import { useActionless } from 'redux/hooks'; +import TextField from 'components/shared/inputs/textField'; +import { SET_ALERT } from 'redux/types/active.types'; +import formStyles from './coursesFormPage.scss'; +import PageWrapper from 'components/shared/layouts/pageWrapper'; const EditCourseFormPage = () => { - const [setAlert] = useActionless(SET_ALERT) + const [setAlert] = useActionless(SET_ALERT); const history = useHistory(); const [formData, setFormData] = useState({ name: '', number: '', semester: '', - }) + isPublic: false + }); - const [startDate, setStartDate] = useState(new Date().toISOString().split("T")[0]) - const [endDate, setEndDate] = useState(new Date().toISOString().split("T")[0]) - const [invalidFields, setInvalidFields] = useState(new Map()) + const [startDate, setStartDate] = useState(new Date().toISOString().split("T")[0]); + const [endDate, setEndDate] = useState(new Date().toISOString().split("T")[0]); - const handleChange = (value: String, e: React.ChangeEvent) => { - const key = e.target.id - setFormData(prevState => ({ ...prevState, [key]: value })) + const handleChange = (value: string, e: React.ChangeEvent) => { + const key = e.target.id; + setFormData(prevState => ({ ...prevState, [key]: value })); + }; - const newInvalidFields = removeClassFromField(invalidFields, key) - setInvalidFields(newInvalidFields) - } + const handleCheckboxChange = (e: React.ChangeEvent) => { + setFormData(prevState => ({ ...prevState, isPublic: e.target.checked })); // Change to isPublic + }; - const handleStartDateChange = (event: React.ChangeEvent) => { setStartDate(event.target.value) } - const handleEndDateChange = (event: React.ChangeEvent) => { setEndDate(event.target.value) } + const handleStartDateChange = (event: React.ChangeEvent) => { + setStartDate(event.target.value); + }; + const handleEndDateChange = (event: React.ChangeEvent) => { + setEndDate(event.target.value); + }; + + const formatDateForSubmission = (date: string) => { + return new Date(date).toISOString(); + }; + + const isFormValid = () => { + return formData.name && formData.number && formData.semester && startDate && endDate; + }; const handleSubmit = () => { const finalFormData = { name: formData.name, number: formData.number, semester: formData.semester, - startDate: startDate + "T16:02:41.849Z", - endDate: endDate + "T16:02:41.849Z", - } + startDate: formatDateForSubmission(startDate), + endDate: formatDateForSubmission(endDate), + isPublic: formData.isPublic + }; RequestService.post('/api/courses/instructor', finalFormData) .then(() => { - setAlert({ autoDelete: true, type: 'success', message: 'Course Added' }) - history.goBack() + setAlert({ autoDelete: true, type: 'success', message: 'Course Added' }); + history.goBack(); }) - .catch((err: ExpressValidationError[] | Error) => { - const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message - - const newFields = new Map() - Array.isArray(err) ? err.map((e) => applyMessageToErrorFields(newFields, e.param, e.msg)) : newFields - setInvalidFields(newFields); - setAlert({ autoDelete: false, type: 'error', message }) - }) - .finally(() => { - }) - } + .catch((err) => { + setAlert({ autoDelete: false, type: 'error', message: err.message }); + }); + }; return (

Create Course

-
- - - + + +
-
+
-
+
+
+ +
- +
- ) - -} - + ); +}; -export default EditCourseFormPage \ No newline at end of file +export default EditCourseFormPage; \ No newline at end of file diff --git a/devU-shared/src/types/course.types.ts b/devU-shared/src/types/course.types.ts index 0e03d98e..ffdf70e9 100644 --- a/devU-shared/src/types/course.types.ts +++ b/devU-shared/src/types/course.types.ts @@ -7,4 +7,8 @@ export type Course = { endDate: string createdAt?: string updatedAt?: string + isPublic?: boolean; + makePrivateDate?: string; + allowlist?: string[]; + blocklist?: string[]; } From 38853eb4eb41603208e015e5a4cde2ef9bc98d26 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Tue, 5 Nov 2024 15:11:57 -0500 Subject: [PATCH 260/400] added public/private checkbox and private date --- devU-api/src/entities/course/course.model.ts | 4 +++ .../src/entities/course/course.serializer.ts | 3 ++- .../1626719306608-addAssignmentsAndCourses.ts | 1 + ...2-addAssignmentsAndCourses1626719306608.ts | 14 ---------- .../pages/forms/courses/courseUpdatePage.tsx | 27 ++++++++++++++++++- .../pages/forms/courses/coursesFormPage.tsx | 16 ++++++++--- devU-shared/src/types/course.types.ts | 2 +- 7 files changed, 47 insertions(+), 20 deletions(-) delete mode 100644 devU-api/src/migration/1730826999182-addAssignmentsAndCourses1626719306608.ts diff --git a/devU-api/src/entities/course/course.model.ts b/devU-api/src/entities/course/course.model.ts index 2c2e6698..dd657d2d 100644 --- a/devU-api/src/entities/course/course.model.ts +++ b/devU-api/src/entities/course/course.model.ts @@ -61,4 +61,8 @@ export default class CourseModel { @Column({ type: 'boolean', name: 'is_public', default: false }) isPublic: boolean; + + @Column({ name: 'private_data', type: 'timestamp', default: () => 'now()' }) + private_data?: Date; + } diff --git a/devU-api/src/entities/course/course.serializer.ts b/devU-api/src/entities/course/course.serializer.ts index 2f9e8790..7094c1ce 100644 --- a/devU-api/src/entities/course/course.serializer.ts +++ b/devU-api/src/entities/course/course.serializer.ts @@ -12,6 +12,7 @@ export function serialize(course: CourseModel): Course { endDate: course.endDate.toISOString(), createdAt: course.createdAt.toISOString(), updatedAt: course.updatedAt.toISOString(), - isPublic: course.isPublic + isPublic: course.isPublic, + private_data: course.private_data ? course.private_data.toISOString() : undefined } } diff --git a/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts b/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts index bc37d3fd..332ac2a8 100644 --- a/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts +++ b/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts @@ -16,6 +16,7 @@ export class addAssignmentsAndCourses1626719306608 implements MigrationInterface "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "is_public" boolean NOT NULL DEFAULT false, + "private_data" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "courses_primary_key_constraint" PRIMARY KEY ("id") )` ) diff --git a/devU-api/src/migration/1730826999182-addAssignmentsAndCourses1626719306608.ts b/devU-api/src/migration/1730826999182-addAssignmentsAndCourses1626719306608.ts deleted file mode 100644 index b5526812..00000000 --- a/devU-api/src/migration/1730826999182-addAssignmentsAndCourses1626719306608.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class AddAssignmentsAndCourses16267193066081730826999182 implements MigrationInterface { - name = 'AddAssignmentsAndCourses16267193066081730826999182' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "courses" ADD "is_public" boolean NOT NULL DEFAULT false`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "courses" DROP COLUMN "is_public"`); - } - -} diff --git a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx index 065df760..5b5ff338 100644 --- a/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/courses/courseUpdatePage.tsx @@ -44,12 +44,14 @@ const CourseUpdatePage = ({ }) => { name: '', number: '', semester: '', + isPublic: false }) const [startDate, setStartDate] = useState(new Date().toISOString()) const [endDate, setEndDate] = useState(new Date().toISOString()) const [studentEmail, setStudentEmail] = useState("") const [emails, setEmails] = useState([]) const [invalidFields, setInvalidFields] = useState(new Map()) + const [privateDate, setPrivateDate] = useState(new Date().toISOString().split("T")[0]); const { courseId } = useParams() as UrlParams useEffect(() => { @@ -60,9 +62,11 @@ const CourseUpdatePage = ({ }) => { name: res.name, number: res.number, semester: res.semester, + isPublic: res.isPublic }); setStartDate(new Date(res.startDate).toISOString().split("T")[0]); setEndDate(new Date(res.endDate).toISOString().split("T")[0]); + setPrivateDate(new Date(res.privateDate).toISOString().split("T")[0]); isMounted = true; }); } @@ -80,10 +84,15 @@ const CourseUpdatePage = ({ }) => { setFormData(prevState => ({ ...prevState, [key]: value })) } } - + const handleCheckboxChange = (e: React.ChangeEvent) => { + setFormData(prevState => ({ ...prevState, isPublic: e.target.checked })); + }; const handleStartDateChange = (event: React.ChangeEvent) => { setStartDate(event.target.value) } const handleEndDateChange = (event: React.ChangeEvent) => { setEndDate(event.target.value) } + const handlePrivateDateChange = (event: React.ChangeEvent) => { + setPrivateDate(event.target.value); + }; const handleCourseUpdate = () => { const finalFormData = { name: formData.name, @@ -91,6 +100,8 @@ const CourseUpdatePage = ({ }) => { semester: formData.semester, startDate: startDate + "T16:02:41.849Z", endDate: endDate + "T16:02:41.849Z", + isPublic: formData.isPublic, + privateDate: privateDate + "T16:02:41.849Z", } RequestService.put(`/api/courses/${courseId}`, finalFormData) @@ -267,6 +278,20 @@ const CourseUpdatePage = ({ }) => {
+
+ + +
+
+ +
diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx index 0088d931..ca101c8b 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx @@ -20,6 +20,7 @@ const EditCourseFormPage = () => { const [startDate, setStartDate] = useState(new Date().toISOString().split("T")[0]); const [endDate, setEndDate] = useState(new Date().toISOString().split("T")[0]); + const [privateDate, setPrivateDate] = useState(new Date().toISOString().split("T")[0]); const handleChange = (value: string, e: React.ChangeEvent) => { const key = e.target.id; @@ -27,7 +28,7 @@ const EditCourseFormPage = () => { }; const handleCheckboxChange = (e: React.ChangeEvent) => { - setFormData(prevState => ({ ...prevState, isPublic: e.target.checked })); // Change to isPublic + setFormData(prevState => ({ ...prevState, isPublic: e.target.checked })); }; const handleStartDateChange = (event: React.ChangeEvent) => { @@ -38,6 +39,10 @@ const EditCourseFormPage = () => { setEndDate(event.target.value); }; + const handlePrivateDateChange = (event: React.ChangeEvent) => { + setPrivateDate(event.target.value); + }; + const formatDateForSubmission = (date: string) => { return new Date(date).toISOString(); }; @@ -53,7 +58,8 @@ const EditCourseFormPage = () => { semester: formData.semester, startDate: formatDateForSubmission(startDate), endDate: formatDateForSubmission(endDate), - isPublic: formData.isPublic + isPublic: formData.isPublic, + privateDate: formatDateForSubmission(privateDate) }; RequestService.post('/api/courses/instructor', finalFormData) @@ -98,12 +104,16 @@ const EditCourseFormPage = () => {
+
+ + +
- + ) } diff --git a/devU-client/src/components/pages/assignments/scoreboard.scss b/devU-client/src/components/pages/assignments/scoreboard.scss new file mode 100644 index 00000000..2de1f3ef --- /dev/null +++ b/devU-client/src/components/pages/assignments/scoreboard.scss @@ -0,0 +1,24 @@ +@import 'variables'; + +.scoreboardTable { + width: 100%; + border-collapse: collapse; + margin-top: 20px; + + th, td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; + } + + th { + background-color: rebeccapurple; + + + } + .scoreboardContainer { + margin-top: 20px; + border: 1px solid #ddd; + padding: 5px; + } +} \ No newline at end of file diff --git a/devU-client/src/components/pages/assignments/scoreboard.tsx b/devU-client/src/components/pages/assignments/scoreboard.tsx new file mode 100644 index 00000000..3c794cc1 --- /dev/null +++ b/devU-client/src/components/pages/assignments/scoreboard.tsx @@ -0,0 +1,46 @@ + +import React from 'react'; +import styles from './scoreboard.scss'; + +interface ScoreboardProps { + courseId: string; + assignmentId: string; +} + +const Scoreboard: React.FC = ({ courseId, assignmentId }) => { + // Dummy scoreboard data + const scoreboardData = [ + { UBit: 'ashwa', score: 95, runtime: '1.2s' }, + { UBit: 'yessicaq', score: 88, runtime: '1.5s' }, + { UBit: 'neemo', score: 76, runtime: '2.1s' }, + { UBit: 'alex', score: 29, runtime: '17.2s' }, + { UBit: 'jesse', score: 56, runtime: '9.5s' }, + { UBit: 'kevin', score: 7, runtime: '100s' }, + + ]; + + return ( +
+

{`Scoreboard for Assignment ${assignmentId} in Course ${courseId}`}

+ + + + + + + + + + {scoreboardData.map((entry) => ( + + + + + + ))} + +
UBitScoreRuntime
{entry.UBit}{entry.score}{entry.runtime}
+
+ ); +}; +export default Scoreboard; From cd44ce0ece8d376b0d3431a2390a6dbef2d79f95 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 5 Nov 2024 15:35:46 -0500 Subject: [PATCH 263/400] I allowed the user to be able to navigate to the course page when clicking anywhere on the conatiner of the course --- .../components/pages/homePage/homePage.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 44218b29..aac29170 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -71,6 +71,9 @@ const HomePage = () => { if (loading) return if (error) return const history = useHistory(); + const handleCourseClick = (courseId: any) => { + history.push(`/course/${courseId}`); // Assuming your course page route is '/course/:courseId' + } return(
@@ -89,15 +92,19 @@ const HomePage = () => {
{instructorCourses.map((course) => ( -
- handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> +
))}
{enrollCourses && enrollCourses.map((course) => ( -
+
handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> +
))} @@ -114,7 +121,9 @@ const HomePage = () => {
{pastCourses && pastCourses.map((course) => ( -
+
handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> {
{upcomingCourses && upcomingCourses.map((course) => ( -
+
handleCourseClick(course.id)} style={{ cursor: 'pointer' }}>
))} From 753678b8ab4877d48f3497370e845379d64a9e38 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 5 Nov 2024 15:38:23 -0500 Subject: [PATCH 264/400] I updated the styling, buttons format for a user to be able to view their course,name number, assignments, and styled it according to the figma --- .../components/pages/courses/courseDetailPage.scss | 10 ++++++++++ .../components/pages/courses/courseDetailPage.tsx | 13 +++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index 9f647cfb..709a7b73 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -134,6 +134,16 @@ color: white !important; } +.assignmentName { + &::after { + content: ''; + display: block; + width: 100%; + margin: 5px auto; + border-bottom: 1px solid #ccc; + } +} + diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index a7072c72..088d9e51 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -103,7 +103,7 @@ const CourseDetailPage = () => {

Instructor:

-
+
@@ -160,6 +160,7 @@ const CourseDetailPage = () => { history.push(`/course/${courseId}/assignment/${assignment.id}`) }}> {assignment.name} @@ -168,14 +169,14 @@ const CourseDetailPage = () => { secondary={ - Start Date: {new Date(assignment.startDate).toLocaleDateString()} -
{/* Add a line break */} - Due Date: {new Date(assignment.dueDate).toLocaleDateString()} + Start: {new Date(assignment.startDate).toLocaleDateString()} + | + Due: {new Date(assignment.dueDate).toLocaleDateString()}
} From e9e0dbbdc052bd2556f1ddac0cbb5fff963322ac Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 5 Nov 2024 15:40:25 -0500 Subject: [PATCH 265/400] The instructor can now view a list of submissions as well as a search bar to search through those submissions --- .../gradebook/gradebookInstructorPage.tsx | 78 +++++++------------ 1 file changed, 27 insertions(+), 51 deletions(-) diff --git a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx index e1440dd0..44575ab3 100644 --- a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx @@ -1,16 +1,15 @@ -import React, { useEffect, useState } from 'react' +import React, {useEffect, useState} from 'react' -import { Assignment, AssignmentScore, User, UserCourse } from 'devu-shared-modules' +import {Assignment, AssignmentScore, User, UserCourse} from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' -import TextField from 'components/shared/inputs/textField' import ErrorPage from '../errorPage/errorPage' import RequestService from 'services/request.service' import styles from './gradebookPage.scss' -import { useParams } from 'react-router-dom' +import {useParams} from 'react-router-dom' type TableProps = { users: User[] @@ -19,29 +18,21 @@ type TableProps = { assignmentScores: AssignmentScore[] } type RowProps = { - index: number + index: Number user: User userCourse: UserCourse assignments: Assignment[] assignmentScores: AssignmentScore[] } -const TableRow = ({ index, user, userCourse, assignments, assignmentScores }: RowProps) => { - // style table row to alternating colors based on index odd?even - const rowClass = index % 2 === 0 ? 'evenRow' : 'oddRow'; - - // dont show row if dropped - if (userCourse.dropped) { - return (<>) - } - +const TableRow = ({index, user, userCourse, assignments, assignmentScores}: RowProps) => { return ( - + {index} {user.email} - {/* {user.externalId} */} + {user.externalId} {user.preferredName} - {/* {userCourse.dropped.toString()} */} + {userCourse.dropped.toString()} {assignments.map(a => ( {assignmentScores.find(as => as.assignmentId === a.id)?.score ?? 'N/A'} ))} @@ -49,16 +40,16 @@ const TableRow = ({ index, user, userCourse, assignments, assignmentScores }: Ro ) } -const GradebookTable = ({ users, userCourses, assignments, assignmentScores }: TableProps) => { +const GradebookTable = ({users, userCourses, assignments, assignmentScores}: TableProps) => { return ( - - +
#
+ - {/* */} + - {/* */} + {assignments.map((a) => { - return () + return ( ) })} {users.map((u, index) => ( { const [userCourses, setUserCourses] = useState(new Array()) //All user-course connections for the course const [assignments, setAssignments] = useState(new Array()) //All assignments in the course const [assignmentScores, setAssignmentScores] = useState(new Array()) //All assignment scores for assignments in the course - - const { courseId } = useParams<{ courseId: string }>() - + + const { courseId } = useParams<{courseId: string}>() + useEffect(() => { fetchData() - }, []) - + }, []) + const fetchData = async () => { try { const userCourses = await RequestService.get(`/api/course/${courseId}/user-courses/`) @@ -95,12 +86,12 @@ const GradebookInstructorPage = () => { const users = await RequestService.get(`/api/users/course/${courseId}`) setUsers(users) - - const assignments = await RequestService.get(`/api/course/${courseId}/assignments`) + + const assignments = await RequestService.get( `/api/course/${courseId}/assignments` ) assignments.sort((a, b) => (Date.parse(a.startDate) - Date.parse(b.startDate))) //Sort by assignment's start date setAssignments(assignments) - const assignmentScores = await RequestService.get(`/api/course/${courseId}/assignment-scores`) + const assignmentScores = await RequestService.get( `/api/course/${courseId}/assignment-scores` ) setAssignmentScores(assignmentScores) } catch (error: any) { @@ -113,28 +104,13 @@ const GradebookInstructorPage = () => { if (loading) return if (error) return - const handleStudentSearch = () => { - - } - return ( - {/*
*/} -

Instructor Gradebook

- {/*
*/} -
-
-

Key: ! = late, - = no submission

-
- -
-
- +

Instructor Gradebook

+
+
+ Date: Tue, 5 Nov 2024 15:46:36 -0500 Subject: [PATCH 266/400] I have removed the unnecessary drop down for question problems. --- .../containers/nonContainerAutoGraderForm.tsx | 10 ++++++---- .../pages/submissions/submissionDetailPage.tsx | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.tsx b/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.tsx index cd6d5e68..72471264 100644 --- a/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.tsx +++ b/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.tsx @@ -46,7 +46,7 @@ const NonContainerAutoGraderForm = () => { const toggleRegex = (e: React.ChangeEvent) => { setFormData(prevState => ({...prevState,isRegex : e.target.checked})) } - + useEffect(() => { RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems/`) .then((res) => { @@ -74,8 +74,10 @@ const NonContainerAutoGraderForm = () => { RequestService.post(`/api/course/${courseId}/assignment/${assignmentId}/non-container-auto-graders/`, finalFormData) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Non-Container Auto-Grader Added' }) - history.goBack() + // history.goBack() + history.push(`/course/${courseId}/assignment/${assignmentId}?refreshProblems=true`); }) + .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message const newFields = applyStylesToErrorFields(err, formData, textStyles.errorField) @@ -110,13 +112,13 @@ const NonContainerAutoGraderForm = () => { - +

- +
diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx index 734f513c..55bf10c8 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx @@ -15,10 +15,13 @@ import Card from '@mui/material/Card' import CardContent from '@mui/material/CardContent' import {CardActionArea, Typography} from '@mui/material' import {prettyPrintDateTime} from "../../../utils/date.utils"; +//import React, { useState } from 'react'; +//import { Document, Page } from 'react-pdf'; +//import StickyNote from 'react-sticky-notes'; const SubmissionDetailPage = () => { - + // const [notes, setNotes] = useState([]); const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [setAlert] = useActionless(SET_ALERT) @@ -107,6 +110,7 @@ const SubmissionDetailPage = () => { } } + @@ -114,6 +118,16 @@ const SubmissionDetailPage = () => { if (error) return //const history = useHistory() //var submission_form = JSON.parse(submission?.content); + + /* const handleAddNote = () => { + setNotes([ + + { + id: notes.length + 1, + content: '', + position: { x: 100, y: 100 }, // Initial position + }, + ]);*/ return( From 28c828401f6db1fb94443176e4e2b8d038d27dc7 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 5 Nov 2024 16:40:43 -0500 Subject: [PATCH 267/400] I have removed the unnecessary drop down for question problems. --- .../src/components/pages/assignments/assignmentDetailPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index ab346155..758eb799 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -242,7 +242,7 @@ const AssignmentDetailPage = () => {
- {/**Submissions List */} +
{submissions.map((submission, index) => ( From f5cb1a284453d5447ff1e2e04ff88092167eb151 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 5 Nov 2024 18:46:47 -0500 Subject: [PATCH 268/400] Updating the styling on the courseDetailPage.tsx. Updating the buttons on the pages. Fixing the numbers, course, and instructor info --- devU-client/src/components/pages/courses/courseDetailPage.scss | 2 +- devU-client/src/components/pages/courses/courseDetailPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index 709a7b73..fa1ebff2 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -115,7 +115,7 @@ .buttons-container { display: flex; flex-wrap: wrap; - //flex-direction: row; + justify-content: space-around; width:100%; } diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 088d9e51..7610f9d9 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -13,7 +13,7 @@ import ListItem from '@mui/material/ListItem' import ListItemButton from '@mui/material/ListItemButton' import ListItemText from '@mui/material/ListItemText' //import Button from '@mui/material/Button' -//import Stack from '@mui/material/Stack' + import styles from './courseDetailPage.scss' From d5f9db50b6058739a344a1d102c4882497d25803 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 5 Nov 2024 18:49:05 -0500 Subject: [PATCH 269/400] Updating the styling for the student gradebookInstructorPage.tsx. Adjusting styling, making purple, alternating grey rows. --- .../src/components/pages/gradebook/gradebookInstructorPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx index 44575ab3..c2c8efce 100644 --- a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx @@ -24,7 +24,7 @@ type RowProps = { assignments: Assignment[] assignmentScores: AssignmentScore[] } - +//table for style const TableRow = ({index, user, userCourse, assignments, assignmentScores}: RowProps) => { return (
From 60373a8fdd09500c22a07b074ab04f073d68c805 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 5 Nov 2024 18:56:48 -0500 Subject: [PATCH 270/400] Removing the drop down for labeling assignment questions and allowing the title of the actual question to show instead. --- .../src/components/pages/assignments/assignmentDetailPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 758eb799..08511170 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -68,7 +68,7 @@ const AssignmentDetailPage = () => { // const submissionProblemScoresPromises = submissionsReq.map(s => { // return RequestService.get(`/api/submission-problem-scores/${s.id}`) - // }) + // // const submissionProblemScoresReq = (await Promise.all(submissionProblemScoresPromises)).reduce((a, b) => a.concat(b), []) // setSubmissionProblemScores(submissionProblemScoresReq) From 93c4736e344d2e0858379ee054e1c27fd98066af Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 5 Nov 2024 19:03:07 -0500 Subject: [PATCH 271/400] Now a user can click anywhere on a course container on the home page and be guided to the course page --- devU-client/src/components/pages/homePage/homePage.scss | 2 +- devU-client/src/components/pages/homePage/homePage.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index 665af415..b2d2e882 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -20,7 +20,7 @@ } // h1 { // align-items:left; -// margin-left: 20px; +// margin-left 20px; // font-size: 30px; // font-weight: 550; // margin-bottom: 30px; diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index aac29170..98f7cfb1 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -53,7 +53,7 @@ const HomePage = () => { }) const assignmentResults_instructor = await Promise.all(assignmentPromises_instructor) assignmentResults_instructor.forEach(([course, assignments]) => assignmentMap.set(course, assignments)) - + //set setAssignments(assignmentMap) setPastCourses(pastCourses) setEnrollCourses(enrolledCourses) @@ -147,6 +147,7 @@ const HomePage = () => { ))} + {upcomingCourses.length === 0 &&

No upcoming Courses

} From 41fa0b87f5f3123343567e65845a25c469a7a754 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 5 Nov 2024 19:21:54 -0500 Subject: [PATCH 272/400] create the InstructorSubmissionspage.tsx. add a table of users. add a searchbar. retrieve scores from submissions. --- .../pages/submissions/InstructorSubmissionspage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devU-client/src/components/pages/submissions/InstructorSubmissionspage.tsx b/devU-client/src/components/pages/submissions/InstructorSubmissionspage.tsx index e8bfc209..98d6668c 100644 --- a/devU-client/src/components/pages/submissions/InstructorSubmissionspage.tsx +++ b/devU-client/src/components/pages/submissions/InstructorSubmissionspage.tsx @@ -24,7 +24,7 @@ import styles from './Submissionspage.scss'; import TextField from "../../shared/inputs/textField"; - +//tableprops interface TableProps { users: User[]; @@ -37,7 +37,7 @@ interface TableProps { } - +//row interface RowProps { From abb8a3ce1adcad51941040c1e31f74f6eb2af874 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 5 Nov 2024 19:30:12 -0500 Subject: [PATCH 273/400] Updating the submission page by implementing a scoreboard to the bottom of the page. Using dummy data for an approximate runtime, score, ubit. --- .../components/pages/assignments/assignmentDetailPage.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 08511170..8f9a6d2e 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -7,7 +7,6 @@ import ErrorPage from '../errorPage/errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import {useActionless, useAppSelector} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' - import Card from '@mui/material/Card' import CardContent from '@mui/material/CardContent' import {Accordion, AccordionDetails, CardActionArea, TextField, Typography} from '@mui/material' @@ -191,10 +190,6 @@ const AssignmentDetailPage = () => { }>Scoreboard } - - - - From e3ce3869c37a634c900f787f56c41581f05ed029 Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 5 Nov 2024 19:51:37 -0500 Subject: [PATCH 274/400] Updating the submission page by implementing a scoreboard to the bottom of the page. Using dummy data for an approximate runtime, score, ubit. --- .../src/components/pages/assignments/assignmentDetailPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 8f9a6d2e..3a6cd54b 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -37,7 +37,7 @@ const AssignmentDetailPage = () => { const [assignment, setAssignment] = useState() // const [containerAutograder, setContainerAutograder] = useState() - // const containerAutograder = false; //TODO: Use the above commented out code to get the container autograder + // const contaierAutograder = false; //TODO: Use the above commented out code to get the container autograder // const [ setNonContainerAutograders] = useState(new Array ()) const [showScoreboard, setShowScoreboard] = useState(false); const location = useLocation(); From 06dfd1a6f755e74267982505ff5c0aed1a04a555 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Tue, 5 Nov 2024 23:05:39 -0500 Subject: [PATCH 275/400] fixed compiling errors --- .../assignments/assignmentDetailPage.scss | 4 +++- .../assignments/assignmentDetailPage.tsx | 6 +++--- .../pages/forms/courses/coursesFormPage.tsx | 20 +++++++++++++------ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss index 30e754b2..182c4933 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss @@ -174,10 +174,12 @@ padding: 10px; } -@media (max-width: 768px) { +@media (max-width: 370px) { .header { flex-direction: column; align-items: center; + width:400px; + } .buttons { diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 156fc4f8..1391a8ea 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -7,9 +7,9 @@ import ErrorPage from '../errorPage/errorPage' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' import {useActionless, useAppSelector} from 'redux/hooks' import {SET_ALERT} from 'redux/types/active.types' -import Card from '@mui/material/Card' -import CardContent from '@mui/material/CardContent' -import {Accordion, AccordionDetails, CardActionArea, TextField, Typography} from '@mui/material' +//import Card from '@mui/material/Card' +//import CardContent from '@mui/material/CardContent' +import {Accordion, AccordionDetails, TextField, Typography} from '@mui/material' import Grid from '@mui/material/Unstable_Grid2' diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx index ed74323b..0891e313 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' import { useHistory } from 'react-router-dom' -import { ExpressValidationError } from 'devu-shared-modules' +//import { ExpressValidationError } from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -11,7 +11,7 @@ import TextField from 'components/shared/inputs/textField' import { SET_ALERT } from 'redux/types/active.types' import formStyles from './coursesFormPage.scss' import AutomateDates from './automateDates' -import { applyMessageToErrorFields, removeClassFromField } from "../../../../utils/textField.utils"; +import { removeClassFromField } from "../../../../utils/textField.utils"; const EditCourseFormPage = () => { const [setAlert] = useActionless(SET_ALERT); @@ -27,11 +27,16 @@ const EditCourseFormPage = () => { const [startDate, setStartDate] = useState(new Date().toISOString().split("T")[0]); const [endDate, setEndDate] = useState(new Date().toISOString().split("T")[0]); const [privateDate, setPrivateDate] = useState(new Date().toISOString().split("T")[0]); + const [invalidFields,setInvalidFields] = useState(new Map()); - const handleChange = (value: string, e: React.ChangeEvent) => { - const key = e.target.id; - setFormData(prevState => ({ ...prevState, [key]: value })); - }; + const handleChange = (value: String, e : React.ChangeEvent) => { + const key = e.target.id + setFormData(prevState => ({...prevState,[key] : value})) + + const newInvalidFields = removeClassFromField(invalidFields, key) + setInvalidFields(newInvalidFields) + } + const handleCheckboxChange = (e: React.ChangeEvent) => { setFormData(prevState => ({ ...prevState, isPublic: e.target.checked })); @@ -42,6 +47,7 @@ const EditCourseFormPage = () => { endDate: string; } + const handleDatesChange = ({ startDate, endDate }: Dates) => { setStartDate(startDate); setEndDate(endDate); @@ -57,10 +63,12 @@ const EditCourseFormPage = () => { const formatDateForSubmission = (date: string) => { return new Date(date).toISOString(); }; + const isFormValid = () => { return formData.name && formData.number && formData.semester && startDate && endDate; }; + const handleSubmit = () => { const finalFormData = { From 4b30f482973bde4af6c0d84066b674724b756e07 Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Tue, 5 Nov 2024 23:34:22 -0500 Subject: [PATCH 276/400] update migrations --- .../1626719306608-addAssignmentsAndCourses.ts | 2 -- .../src/migration/1730867565396-publicCourses.ts | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 devU-api/src/migration/1730867565396-publicCourses.ts diff --git a/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts b/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts index 332ac2a8..f761ab3b 100644 --- a/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts +++ b/devU-api/src/migration/1626719306608-addAssignmentsAndCourses.ts @@ -15,8 +15,6 @@ export class addAssignmentsAndCourses1626719306608 implements MigrationInterface "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, - "is_public" boolean NOT NULL DEFAULT false, - "private_data" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "courses_primary_key_constraint" PRIMARY KEY ("id") )` ) diff --git a/devU-api/src/migration/1730867565396-publicCourses.ts b/devU-api/src/migration/1730867565396-publicCourses.ts new file mode 100644 index 00000000..029ff8c8 --- /dev/null +++ b/devU-api/src/migration/1730867565396-publicCourses.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Migration1730867565396 implements MigrationInterface { + name = 'Migration1730867565396' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "courses" ADD "is_public" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "courses" ADD "private_data" TIMESTAMP NOT NULL DEFAULT now()`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "courses" DROP COLUMN "private_data"`); + await queryRunner.query(`ALTER TABLE "courses" DROP COLUMN "is_public"`); + } + +} From de17ad52c25a2845aa9428a287e2a4c058d284f8 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 5 Nov 2024 23:55:20 -0500 Subject: [PATCH 277/400] removed assignment filter by start date from backend --- .../entities/assignment/assignment.service.ts | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/devU-api/src/entities/assignment/assignment.service.ts b/devU-api/src/entities/assignment/assignment.service.ts index b6fc1531..57efc2cc 100644 --- a/devU-api/src/entities/assignment/assignment.service.ts +++ b/devU-api/src/entities/assignment/assignment.service.ts @@ -1,4 +1,4 @@ -import { IsNull, MoreThanOrEqual } from 'typeorm' +import { IsNull } from 'typeorm' import { dataSource } from '../../database' import AssignmentModel from './assignment.model' @@ -64,22 +64,11 @@ export async function listByCourse(courseId: number) { } export async function listByCourseReleased(courseId: number) { - // filter by start date after current time - const now = new Date(Date.now()) - const allAssignments = await connect().findBy({ courseId: courseId, startDate: MoreThanOrEqual(now), deletedAt: IsNull() }) - - console.log("ASSIGNMENTS WITH FILTER: ", allAssignments) - - // for each assignment in allAssignments (a list), if the startDate is more than 3 days from now then add it to list releasedAssignments - // const now = new Date(); - // const threeDaysFromNow = new Date(); - // threeDaysFromNow.setDate(now.getDate() + 3); - - // // Filter assignments where the startDate is within the next 3 days - // const releasedAssignments = allAssignments.filter(assignment => { - // const startDate = new Date(assignment.startDate); - // return startDate >= now && startDate <= threeDaysFromNow; - // }); + // TODO: filter by start date after current time + // const now = new Date(Date.now()) + const allAssignments = await connect().findBy({ courseId: courseId, /*startDate: MoreThanOrEqual(now),*/ deletedAt: IsNull() }) + + // console.log("ASSIGNMENTS WITH FILTER: ", allAssignments) return allAssignments; } From 74287999f85a5f3395ad50e91c49e86da82efd18 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Wed, 6 Nov 2024 00:22:52 -0500 Subject: [PATCH 278/400] fixed private/public course bug --- .DS_Store | Bin 8196 -> 6148 bytes .../components/listItems/courseListItem.tsx | 6 ++++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.DS_Store b/.DS_Store index 6693e129e9a19e665c34da57105c0ff3a6d01bca..4dcf6bbbef6078d240b70cd86cd79f533f5bd663 100644 GIT binary patch delta 107 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{Mvv5sJ6q~50$SAQfU^g?P#AY4=X6DKNg&QWO tRxD=c;1Fa6DgpulZXn?bQok|rJM(0I8BdUI1}2C}Aj26p$Mei#1_0pC5t9G_ delta 579 zcmZoMXmOBWU|?W$DortDU;r^WfEYvza8FDWo2aMAsIoC&H}hr%jz7$c**Q2S7O*g? zZ02EMX0B&pNMXoiC}t?hNjD5m&d)6X>SD00E4T-w;BvY7E-pzq`AI-A4ynt_Ex6o| zIikv^;FT}PKr(xZ0Z=Q@>>P%ChV*)dVq}XM7(PWJBpHg4E&k1IVF|YwtQjPauo#;> zvc(LHAbS~D8A=!u8S)s?8S;TPPnKmbs^>-sF~l>Z0mCDQAr;+WV9hvGB0JOSu>j1O zI8`GY4N|QJ^%V}~Fh6Wee9R=s40Z;C1UHa&1;y`XL5}atlletFC;RhoZ~(msih730 P@jO%22{;2`?gJ(ORquaI diff --git a/devU-client/src/components/listItems/courseListItem.tsx b/devU-client/src/components/listItems/courseListItem.tsx index db096d66..3f9b1977 100644 --- a/devU-client/src/components/listItems/courseListItem.tsx +++ b/devU-client/src/components/listItems/courseListItem.tsx @@ -30,7 +30,9 @@ const CourseListItem = ({course, isOpen}: Props) => { setIsOpen(isOpen); }, [isOpen]); - + if (!course || !course.isPublic) { + return null; + } return (
@@ -70,4 +72,4 @@ const infoSection = (display: string, firstValue: string, secondValue?: string) ) -export default CourseListItem +export default CourseListItem \ No newline at end of file From 1fc8f86c29d5bd5e93366289be2caec3b055e05d Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 8 Nov 2024 01:56:09 -0500 Subject: [PATCH 279/400] fixed request --- devU-client/src/components/pages/webhookURLForm.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devU-client/src/components/pages/webhookURLForm.tsx b/devU-client/src/components/pages/webhookURLForm.tsx index b8f0fb2e..df337aa7 100644 --- a/devU-client/src/components/pages/webhookURLForm.tsx +++ b/devU-client/src/components/pages/webhookURLForm.tsx @@ -7,6 +7,7 @@ import RequestService from '../../services/request.service' import { useParams } from 'react-router-dom' import { SET_ALERT } from 'redux/types/active.types' import { useActionless } from '../../redux/hooks' +import { apiUrl } from '../../config' const webhookURLForm = () => { const [webhookURL, setWebhookURL] = useState() @@ -26,7 +27,7 @@ const webhookURLForm = () => { const handleAddURL = () => { if (webhookURL) { //Handle adding webhook URL to backend here - RequestService.post(`/course/${courseId}/webhooks`, { + RequestService.post(`${apiUrl}/course/${courseId}/webhooks`, { "destinationUrl": webhookURL, "matcherUrl": watcherUrl }).then( From d8c5eb322699f725942504e5feb9ec49260052cb Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 8 Nov 2024 03:07:13 -0500 Subject: [PATCH 280/400] added admin user CRUD, and middleware --- devU-api/package.json | 1 + .../login.developer.controller.ts | 2 +- devU-api/src/entities/user/user.controller.ts | 47 +++++++++++++- devU-api/src/entities/user/user.middlware.ts | 16 +++++ devU-api/src/entities/user/user.model.ts | 3 + devU-api/src/entities/user/user.router.ts | 64 +++++++++++++++++++ devU-api/src/entities/user/user.serializer.ts | 1 + devU-api/src/entities/user/user.service.ts | 44 ++++++++++++- .../entities/webhooks/webhooks.middleware.ts | 2 +- .../src/migration/1731053786646-user-admin.ts | 14 ++++ devU-shared/src/types/user.types.ts | 1 + 11 files changed, 189 insertions(+), 6 deletions(-) create mode 100644 devU-api/src/entities/user/user.middlware.ts create mode 100644 devU-api/src/migration/1731053786646-user-admin.ts diff --git a/devU-api/package.json b/devU-api/package.json index 1471b189..27951f8e 100644 --- a/devU-api/package.json +++ b/devU-api/package.json @@ -5,6 +5,7 @@ "scripts": { "start": "npm run migrate && ts-node-dev src/index.ts", "migrate": "npm run typeorm -- migration:run -d src/database.ts", + "create-migrate": "npx typeorm-ts-node-commonjs migration:generate -d src/database.ts", "update-shared": "cd ../devU-shared && npm run build-local && cd ../devU-api && npm i", "typeorm": "typeorm-ts-node-commonjs", "test": "jest --passWithNoTests", diff --git a/devU-api/src/authentication/login.developer/login.developer.controller.ts b/devU-api/src/authentication/login.developer/login.developer.controller.ts index 8593f10b..ede9f56e 100644 --- a/devU-api/src/authentication/login.developer/login.developer.controller.ts +++ b/devU-api/src/authentication/login.developer/login.developer.controller.ts @@ -10,7 +10,7 @@ export async function callback(req: Request, res: Response, next: NextFunction) try { const { email = '', externalId = '' } = req.body - const { user } = await UserService.ensure({ email, externalId }) + const { user } = await UserService.ensure({ email, externalId, isAdmin: false }) const refreshToken = AuthService.createRefreshToken(user) res.cookie('refreshToken', refreshToken, refreshCookieOptions) diff --git a/devU-api/src/entities/user/user.controller.ts b/devU-api/src/entities/user/user.controller.ts index b36a2eaa..053e0de0 100644 --- a/devU-api/src/entities/user/user.controller.ts +++ b/devU-api/src/entities/user/user.controller.ts @@ -31,6 +31,7 @@ export async function detail(req: Request, res: Response, next: NextFunction) { next(err) } } + //USE THIS export async function getByCourse(req: Request, res: Response, next: NextFunction) { try { @@ -48,6 +49,48 @@ export async function getByCourse(req: Request, res: Response, next: NextFunctio } } +// create an admin, only an admin can create a new admin +export async function createNewAdmin(req: Request, res: Response, next: NextFunction) { + try { + let newAdminUserId = req.body.newAdminUserId + if (!newAdminUserId) { + return res.status(404).send('Not found') + } + + await UserService.createAdmin(newAdminUserId!) + res.status(201).send('Created new admin') + } catch (e) { + next(e) + } +} + + +// delete an admin, only an admin can delete an admin +export async function deleteAdmin(req: Request, res: Response, next: NextFunction) { + try { + let newAdminUserId = req.body.newAdminUserId + if (!newAdminUserId) { + return res.status(404).send('Not found') + } + await UserService.softDeleteAdmin(newAdminUserId) + res.status(204).send('User is no longer admin') + } catch (e) { + next(e) + } +} + +// list admins +export async function listAdmins(req: Request, res: Response, next: NextFunction) { + try { + let users = await UserService.listAdmin() + const response = users.map(serialize) + res.status(200).json(response) + } catch (e) { + next(e) + } +} + + export async function post(req: Request, res: Response, next: NextFunction) { try { const user = await UserService.create(req.body) @@ -56,7 +99,7 @@ export async function post(req: Request, res: Response, next: NextFunction) { res.status(201).json(response) } catch (err) { if (err instanceof Error) { - res.status(400).json(new GenericResponse(err.message)) + res.status(400).json(new GenericResponse(err.message)) } } } @@ -87,4 +130,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { } } -export default { get, detail, post, put, _delete, getByCourse } \ No newline at end of file +export default { get, detail, post, put, _delete, getByCourse, deleteAdmin, createNewAdmin, listAdmins } \ No newline at end of file diff --git a/devU-api/src/entities/user/user.middlware.ts b/devU-api/src/entities/user/user.middlware.ts new file mode 100644 index 00000000..a51c2df4 --- /dev/null +++ b/devU-api/src/entities/user/user.middlware.ts @@ -0,0 +1,16 @@ +import { NextFunction, Request, Response } from 'express' +import UserService from './user.service' + +// is admin middleware, use this when marking an endpoint as only accessible by admin +// different from userCourse permissions. this is attached to a user instead of a course level permission +export async function isAdmin(req: Request, res: Response, next: NextFunction) { + const userId = req.currentUser?.userId + if (!userId) { + return res.status(403).send('Unauthorized') + } + + const isAdmin = await UserService.isAdmin(userId) + if (!isAdmin) return res.status(403).send('unauthorized') + + next() +} diff --git a/devU-api/src/entities/user/user.model.ts b/devU-api/src/entities/user/user.model.ts index 59d0c04a..70a5ad07 100644 --- a/devU-api/src/entities/user/user.model.ts +++ b/devU-api/src/entities/user/user.model.ts @@ -40,4 +40,7 @@ export default class UserModel { @Column({ name: 'preferred_name', length: 128, nullable: true }) preferredName: string + + @Column({ name: 'is_admin', default: false }) + isAdmin: boolean } diff --git a/devU-api/src/entities/user/user.router.ts b/devU-api/src/entities/user/user.router.ts index d2557d4b..0bb0be4a 100644 --- a/devU-api/src/entities/user/user.router.ts +++ b/devU-api/src/entities/user/user.router.ts @@ -5,6 +5,7 @@ import { asInt } from '../../middleware/validator/generic.validator' import { isAuthorized } from '../../authorization/authorization.middleware' import UserController from './user.controller' +import { isAdmin } from './user.middlware' const Router = express.Router() @@ -70,6 +71,69 @@ Router.get('/:id', asInt(), UserController.detail) Router.get('/course/:id', /* isAuthorized('courseViewAll'), */ asInt(), UserController.getByCourse) // TODO: Removed authorization for now, fix later +const adminRouter = express.Router() + +Router.use('/admin', isAdmin, adminRouter) + +/** + * @swagger + * /users/admin/: + * get: + * summary: list admin users + * tags: + * - Users + * responses: + * '200': + * description: OK + */ +adminRouter.get('/list', UserController.listAdmins) + +/** + * @swagger + * /users/admin/: + * post: + * summary: Make a user admin + * tags: + * - Users + * responses: + * '200': + * description: OK + * requestBody: + * application/json: + * schema: + * type: object + * required: + * - userId + * properties: + * newAdminUserId: + * description: "User id to make admin" + * type: number + */ +adminRouter.post('/', UserController.createNewAdmin) + +/** + * @swagger + * /users/admin/: + * delete: + * summary: delete a user admin + * tags: + * - Users + * responses: + * '200': + * description: OK + * requestBody: + * application/json: + * schema: + * type: object + * required: + * - userId + * properties: + * newAdminUserId: + * description: "User id to make admin" + * type: number + */ +adminRouter.delete('/', UserController.deleteAdmin) + /** * @swagger * /users: diff --git a/devU-api/src/entities/user/user.serializer.ts b/devU-api/src/entities/user/user.serializer.ts index 4109f31c..68dad1b0 100644 --- a/devU-api/src/entities/user/user.serializer.ts +++ b/devU-api/src/entities/user/user.serializer.ts @@ -10,5 +10,6 @@ export function serialize(user: UserModel): User { createdAt: user.createdAt.toISOString(), updatedAt: user.updatedAt.toISOString(), preferredName: user.preferredName, + isAdmin: user.isAdmin, } } diff --git a/devU-api/src/entities/user/user.service.ts b/devU-api/src/entities/user/user.service.ts index b25bb9a7..b0e62ec4 100644 --- a/devU-api/src/entities/user/user.service.ts +++ b/devU-api/src/entities/user/user.service.ts @@ -10,6 +10,13 @@ import UserCourseService from '../userCourse/userCourse.service' const connect = () => dataSource.getRepository(UserModel) export async function create(user: User) { + // check if the first account + const users = await connect().count({ take: 1 }) + if (users == 0) { + // make first created account admin + user.isAdmin = true + } + return await connect().save(user) } @@ -29,6 +36,35 @@ export async function retrieve(id: number) { return await connect().findOneBy({ id, deletedAt: IsNull() }) } +export async function isAdmin(id: number) { + return await connect().findOne({ + where: { id, deletedAt: IsNull() }, + select: ['isAdmin'], + }) +} + +export async function createAdmin(id: number) { + return await connect().update(id, { isAdmin: true }) +} + +// soft deletes an admin +export async function softDeleteAdmin(id: number) { + let res = await connect().count({ take: 2, where: { isAdmin: true } }) + // check if this deletes the last admin + // there must always be at least 1 admin + if (res == 1) { + throw Error('Unable to delete, only a single admin remains') + } + + return await connect().update(id, { isAdmin: false }) +} + +// list all admins +export async function listAdmin() { + return await connect().findBy({ isAdmin: true, deletedAt: IsNull() }) +} + + export async function retrieveByEmail(email: string) { return await connect().findOneBy({ email: email, deletedAt: IsNull() }) } @@ -46,13 +82,13 @@ export async function listByCourse(courseId: number, userRole?: string) { } export async function ensure(userInfo: User) { - const { externalId, email } = userInfo + const { externalId } = userInfo const user = await connect().findOneBy({ externalId }) if (user) return { user, isNewUser: false } - const newUser = await create({ email, externalId }) + const newUser = await create(userInfo) return { user: newUser, isNewUser: true } } @@ -64,6 +100,10 @@ export default { update, _delete, list, + isAdmin, + createAdmin, + softDeleteAdmin, + listAdmin, ensure, listByCourse, } diff --git a/devU-api/src/entities/webhooks/webhooks.middleware.ts b/devU-api/src/entities/webhooks/webhooks.middleware.ts index 97bf9fa5..6c40389f 100644 --- a/devU-api/src/entities/webhooks/webhooks.middleware.ts +++ b/devU-api/src/entities/webhooks/webhooks.middleware.ts @@ -59,7 +59,7 @@ export function responseInterceptor(req: Request, res: Response, next: NextFunct console.log('Sent webhook successfully') }, ).catch(err => { - console.error('Error sending webhook', err) + console.warn('Error sending webhook', err) }) } } diff --git a/devU-api/src/migration/1731053786646-user-admin.ts b/devU-api/src/migration/1731053786646-user-admin.ts new file mode 100644 index 00000000..07505e51 --- /dev/null +++ b/devU-api/src/migration/1731053786646-user-admin.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UserAdmin1731053786646 implements MigrationInterface { + name = 'UserAdmin1731053786646' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "users" ADD "is_admin" boolean NOT NULL DEFAULT false`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "is_admin"`); + } + +} diff --git a/devU-shared/src/types/user.types.ts b/devU-shared/src/types/user.types.ts index b19cf9fa..8421b1d1 100644 --- a/devU-shared/src/types/user.types.ts +++ b/devU-shared/src/types/user.types.ts @@ -5,4 +5,5 @@ export type User = { createdAt?: string updatedAt?: string preferredName?: string + isAdmin: boolean } From b61aa14a9509879517681a4c4bfb39e0a401f213 Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 8 Nov 2024 15:12:54 -0500 Subject: [PATCH 281/400] fixed minor issues --- devU-api/src/entities/user/user.middlware.ts | 2 +- devU-client/package.json | 2 +- devU-client/src/redux/initialState/user.initialState.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/devU-api/src/entities/user/user.middlware.ts b/devU-api/src/entities/user/user.middlware.ts index a51c2df4..3f323c93 100644 --- a/devU-api/src/entities/user/user.middlware.ts +++ b/devU-api/src/entities/user/user.middlware.ts @@ -10,7 +10,7 @@ export async function isAdmin(req: Request, res: Response, next: NextFunction) { } const isAdmin = await UserService.isAdmin(userId) - if (!isAdmin) return res.status(403).send('unauthorized') + if (!isAdmin!.isAdmin!) return res.status(403).send('unauthorized') next() } diff --git a/devU-client/package.json b/devU-client/package.json index af413ffc..290c8056 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -1,6 +1,6 @@ { "scripts": { - "update-shared": "npm update devu-shared-modules", + "update-shared": "cd ../devU-shared && npm run build-local && cd ../devU-client && npm i", "local": "cross-env NODE_ENV=local webpack-dev-server --mode development", "start": "cross-env NODE_ENV=development webpack-dev-server -d --open --mode development", "prod": "cross-env NODE_ENV=production webpack-dev-server -d --open --mode development", diff --git a/devU-client/src/redux/initialState/user.initialState.ts b/devU-client/src/redux/initialState/user.initialState.ts index 17d26f44..4284c0d8 100644 --- a/devU-client/src/redux/initialState/user.initialState.ts +++ b/devU-client/src/redux/initialState/user.initialState.ts @@ -8,6 +8,7 @@ const defaultState: UserState = { createdAt: '', updatedAt: '', preferredName: '', + isAdmin: false, } export default defaultState From 0dab95cb40017c7835c4f10058bbc59985978601 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sat, 9 Nov 2024 13:48:19 -0500 Subject: [PATCH 282/400] fix bug in submission --- devU-api/src/entities/submission/submission.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-api/src/entities/submission/submission.controller.ts b/devU-api/src/entities/submission/submission.controller.ts index a12a3e1d..a79474d3 100644 --- a/devU-api/src/entities/submission/submission.controller.ts +++ b/devU-api/src/entities/submission/submission.controller.ts @@ -58,7 +58,7 @@ export async function post(req: Request, res: Response, next: NextFunction) { if (!req.currentUser?.userId) return res.status(400).json(new GenericResponse('Request requires auth')) const reqSubmission = req.body - + reqSubmission.userId = req.currentUser?.userId reqSubmission.submitterIp = req.header('x-forwarded-for') || req.socket.remoteAddress reqSubmission.submittedBy = req.currentUser?.userId From 0f0db5b24db6932585b24741625573fa92d16c62 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Sun, 10 Nov 2024 09:29:01 -0500 Subject: [PATCH 283/400] Added standard endpoints for sticky notes Added standard endpoints for basic CRUD operations on the sticky market --- .../stickyNote/stickyNote.controller.ts | 78 +++++++++++++++++++ .../entities/stickyNote/stickyNote.model.ts | 24 ++++++ .../entities/stickyNote/stickyNote.router.ts | 26 +++++++ .../stickyNote/stickyNote.serializer.ts | 11 +++ .../entities/stickyNote/stickyNote.service.ts | 40 ++++++++++ .../stickyNote/stickyNote.validator.ts | 10 +++ .../1731177885475-add-sticky-notes.ts | 16 ++++ devU-api/src/router/courseData.router.ts | 6 ++ devU-shared/src/index.ts | 1 + devU-shared/src/types/stickyNote.types.ts | 5 ++ 10 files changed, 217 insertions(+) create mode 100644 devU-api/src/entities/stickyNote/stickyNote.controller.ts create mode 100644 devU-api/src/entities/stickyNote/stickyNote.model.ts create mode 100644 devU-api/src/entities/stickyNote/stickyNote.router.ts create mode 100644 devU-api/src/entities/stickyNote/stickyNote.serializer.ts create mode 100644 devU-api/src/entities/stickyNote/stickyNote.service.ts create mode 100644 devU-api/src/entities/stickyNote/stickyNote.validator.ts create mode 100644 devU-api/src/migration/1731177885475-add-sticky-notes.ts create mode 100644 devU-shared/src/types/stickyNote.types.ts diff --git a/devU-api/src/entities/stickyNote/stickyNote.controller.ts b/devU-api/src/entities/stickyNote/stickyNote.controller.ts new file mode 100644 index 00000000..ca6e3dd3 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.controller.ts @@ -0,0 +1,78 @@ +import {NextFunction, Request, Response} from 'express' + +import StickyNoteService from './stickyNote.service' + +import {NotFound, Updated} from '../../utils/apiResponse.utils' + +import {serialize} from './stickyNote.serializer' + +export async function retrieve(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(req.params.id) + const stickyNote = await StickyNoteService.retrieve(id) + + if (!stickyNote) return res.status(404).json(NotFound) + + const response = serialize(stickyNote) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} + +export async function post(req: Request, res: Response, next: NextFunction) { + try { + const reqStickyNote = req.body + const stickyNote = await StickyNoteService.create(reqStickyNote) + const response = serialize(stickyNote) + + res.status(201).json(response) + } catch (err) { + next(err) + } +} + +export async function put(req: Request, res: Response, next: NextFunction) { + try { + const reqStickyNote = req.body + const stickyNote = await StickyNoteService.update(reqStickyNote) + + if (!stickyNote.affected) return res.status(404).json(NotFound) + + res.status(200).json(Updated) + } catch (err) { + next(err) + } +} + +export async function remove(req: Request, res: Response, next: NextFunction) { + try { + const id = parseInt(req.params.id) + await StickyNoteService._delete(id) + + res.status(204).send() + } catch (err) { + next(err) + } +} + +export async function listBySubmission(req: Request, res: Response, next: NextFunction) { + try { + const submissionId = parseInt(req.params.submissionId) + const stickyNotes = await StickyNoteService.listBySubmission(submissionId) + const response = stickyNotes.map(serialize) + + res.status(200).json(response) + } catch (err) { + next(err) + } +} + +export default { + retrieve, + post, + put, + remove, + listBySubmission, +} \ No newline at end of file diff --git a/devU-api/src/entities/stickyNote/stickyNote.model.ts b/devU-api/src/entities/stickyNote/stickyNote.model.ts new file mode 100644 index 00000000..7eae4551 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.model.ts @@ -0,0 +1,24 @@ +import { + JoinColumn, + ManyToOne, + Entity, + Column, + PrimaryGeneratedColumn, +} from 'typeorm' + +import SubmissionModel from '../submission/submission.model' + +@Entity('sticky_notes') +export default class StickyNotesModel { + + @PrimaryGeneratedColumn() + id: number + + @Column({ name: 'submissionId' }) + @JoinColumn({ name: 'submissionId' }) + @ManyToOne(() => SubmissionModel) + submissionId: number + + @Column({ name: 'content' }) + content: string +} \ No newline at end of file diff --git a/devU-api/src/entities/stickyNote/stickyNote.router.ts b/devU-api/src/entities/stickyNote/stickyNote.router.ts new file mode 100644 index 00000000..eff12bf4 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.router.ts @@ -0,0 +1,26 @@ +import express from 'express' + +// Middleware +import validator from './stickyNote.validator' +// import { isAuthorized } from '../../authorization/authorization.middleware' +import { asInt } from '../../middleware/validator/generic.validator' + +// Controller +import StickyNoteController from './stickyNote.controller' + +const Router = express.Router({ mergeParams: true }) + +Router.get('/:id' , asInt(), validator , StickyNoteController.retrieve) + +Router.post('/', validator, StickyNoteController.post) + +Router.put('/', validator, StickyNoteController.put) + +Router.delete('/:id', asInt(), validator, StickyNoteController.remove) + +Router.get('/all',validator , StickyNoteController.listBySubmission) + + +export default Router + + diff --git a/devU-api/src/entities/stickyNote/stickyNote.serializer.ts b/devU-api/src/entities/stickyNote/stickyNote.serializer.ts new file mode 100644 index 00000000..72b0cd77 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.serializer.ts @@ -0,0 +1,11 @@ +import {StickyNote} from 'devu-shared-modules' + +import StickyNoteModel from './stickyNote.model' + +export function serialize(stickyNote: StickyNoteModel): StickyNote { + return { + id: stickyNote.id, + submissionId: stickyNote.submissionId, + content: stickyNote.content, + } +} \ No newline at end of file diff --git a/devU-api/src/entities/stickyNote/stickyNote.service.ts b/devU-api/src/entities/stickyNote/stickyNote.service.ts new file mode 100644 index 00000000..6afe40b3 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.service.ts @@ -0,0 +1,40 @@ +// import {IsNull} from 'typeorm' +import {dataSource} from '../../database' + +import StickyNotesModel from './stickyNote.model' +import {StickyNote} from 'devu-shared-modules' + +const StickyNoteConn = () => dataSource.getRepository(StickyNotesModel) + +export async function create(stickyNote: StickyNote) { + return await StickyNoteConn().save(stickyNote) +} + +export async function update(stickyNote: StickyNote) { + const {id, submissionId, content} = stickyNote + if (!id) throw new Error('Missing Id') + return await StickyNoteConn().update(id, {submissionId, content}) +} + +export async function _delete(id: number) { +// return await StickyNoteConn().softDelete({id, deletedAt: IsNull()}) + return await StickyNoteConn().softDelete({id}) +} + +export async function retrieve(id: number) { +// return await StickyNoteConn().findOneBy({id, deletedAt: IsNull()}) + return await StickyNoteConn().findOneBy({id}) +} + +export async function listBySubmission(submissionId: number) { +// return await StickyNoteConn().findBy({submissionId :submissionId, deletedAt: IsNull()}) + return await StickyNoteConn().findBy({submissionId: submissionId}) +} + +export default { + create, + update, + _delete, + retrieve, + listBySubmission, +} \ No newline at end of file diff --git a/devU-api/src/entities/stickyNote/stickyNote.validator.ts b/devU-api/src/entities/stickyNote/stickyNote.validator.ts new file mode 100644 index 00000000..74902a48 --- /dev/null +++ b/devU-api/src/entities/stickyNote/stickyNote.validator.ts @@ -0,0 +1,10 @@ +import {check} from 'express-validator' + +import validate from '../../middleware/validator/generic.validator' + +const submissionId = check('submissionId').isNumeric() +const content = check('content').isString() + +const validator = [submissionId, content, validate] + +export default validator \ No newline at end of file diff --git a/devU-api/src/migration/1731177885475-add-sticky-notes.ts b/devU-api/src/migration/1731177885475-add-sticky-notes.ts new file mode 100644 index 00000000..a8f8e291 --- /dev/null +++ b/devU-api/src/migration/1731177885475-add-sticky-notes.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddStickyNotes1731177885475 implements MigrationInterface { + name = 'AddStickyNotes1731177885475' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "sticky_notes" ("id" SERIAL NOT NULL, "submissionId" integer NOT NULL, "content" character varying NOT NULL, CONSTRAINT "PK_615fc1d0c6b75c46aa2a7dd5f0a" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "sticky_notes" ADD CONSTRAINT "FK_a92bd7a1dc5c606db210b176a43" FOREIGN KEY ("submissionId") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sticky_notes" DROP CONSTRAINT "FK_a92bd7a1dc5c606db210b176a43"`); + await queryRunner.query(`DROP TABLE "sticky_notes"`); + } + +} diff --git a/devU-api/src/router/courseData.router.ts b/devU-api/src/router/courseData.router.ts index 174d0d70..c388ee8a 100644 --- a/devU-api/src/router/courseData.router.ts +++ b/devU-api/src/router/courseData.router.ts @@ -14,13 +14,19 @@ import categoryScores from '../entities/categoryScore/categoryScore.router' import courseScores from '../entities/courseScore/courseScore.router' import assignmentScore from '../entities/assignmentScore/assignmentScore.router' import role from '../entities/role/role.router' +import stickyNotes from '../entities/stickyNote/stickyNote.router' import nonContainerAutoGraderRouter from '../entities/nonContainerAutoGrader/nonContainerAutoGrader.router' import { asInt } from '../middleware/validator/generic.validator' import webhooksRouter from '../entities/webhooks/webhooks.router' +const submissionRouter = express.Router({ mergeParams: true }) +submissionRouter.use('/sticky-notes', stickyNotes) + const assignmentRouter = express.Router({ mergeParams: true }) +assignmentRouter.use('/submission/:submissionId', asInt('submissionId'), submissionRouter) + assignmentRouter.use('/assignment-problems', assignmentProblem) assignmentRouter.use('/container-auto-graders', containerAutoGrader) assignmentRouter.use('/deadline-extensions', deadlineExtensions) diff --git a/devU-shared/src/index.ts b/devU-shared/src/index.ts index 464b8ba2..20cc1a83 100644 --- a/devU-shared/src/index.ts +++ b/devU-shared/src/index.ts @@ -22,6 +22,7 @@ export * from './types/deadlineExtensions.types' export * from './types/grader.types' export * from './types/role.types' export * from './types/webhooks.types' +export * from './types/stickyNote.types' export * from './utils/object.utils' export * from './utils/string.utils' diff --git a/devU-shared/src/types/stickyNote.types.ts b/devU-shared/src/types/stickyNote.types.ts new file mode 100644 index 00000000..bf7cc764 --- /dev/null +++ b/devU-shared/src/types/stickyNote.types.ts @@ -0,0 +1,5 @@ +export type StickyNote = { + id?: number + submissionId: number + content: string +} \ No newline at end of file From a2cbecd9deb6e52b8b174e224da7b1f34f432950 Mon Sep 17 00:00:00 2001 From: ashwaqaljanahi2021 Date: Sun, 10 Nov 2024 10:01:52 -0500 Subject: [PATCH 284/400] fixed mobile viewing for assignment pages for student and instructors --- .../migration/1730867565396-publicCourses.ts | 27 +++++++-- .../assignments/assignmentDetailPage.scss | 57 +++++++++++++++++-- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/devU-api/src/migration/1730867565396-publicCourses.ts b/devU-api/src/migration/1730867565396-publicCourses.ts index 029ff8c8..79a7b464 100644 --- a/devU-api/src/migration/1730867565396-publicCourses.ts +++ b/devU-api/src/migration/1730867565396-publicCourses.ts @@ -4,13 +4,30 @@ export class Migration1730867565396 implements MigrationInterface { name = 'Migration1730867565396' public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "courses" ADD "is_public" boolean NOT NULL DEFAULT false`); - await queryRunner.query(`ALTER TABLE "courses" ADD "private_data" TIMESTAMP NOT NULL DEFAULT now()`); + //have to use raw SQL to fix queryfailederror for columns that already exist + const isPublicExists = await queryRunner.query(` + SELECT column_name + FROM information_schema.columns + WHERE table_name = 'courses' AND column_name = 'is_public'; + `); + + if (isPublicExists.length === 0) { + await queryRunner.query(`ALTER TABLE "courses" ADD "is_public" boolean NOT NULL DEFAULT false`); + } + + const privateDataExists = await queryRunner.query(` + SELECT column_name + FROM information_schema.columns + WHERE table_name = 'courses' AND column_name = 'private_data'; + `); + + if (privateDataExists.length === 0) { + await queryRunner.query(`ALTER TABLE "courses" ADD "private_data" TIMESTAMP NOT NULL DEFAULT now()`); + } } public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "courses" DROP COLUMN "private_data"`); - await queryRunner.query(`ALTER TABLE "courses" DROP COLUMN "is_public"`); + await queryRunner.query(`ALTER TABLE "courses" DROP COLUMN IF EXISTS "private_data"`); + await queryRunner.query(`ALTER TABLE "courses" DROP COLUMN IF EXISTS "is_public"`); } - } diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss index 182c4933..aa3e8e49 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss @@ -174,16 +174,61 @@ padding: 10px; } +@media (max-width: 768px) { + .wrap { + flex-direction: column; + align-items: center; + padding: 15px; + } + + .assignment_card { + width: 90%; + max-width: 500px; + margin: 0 auto; + padding: 20px; + } + + .options_buttons { + flex-direction: column; + justify-content: center; + width: 100%; + gap: 10px; + } + + .buttons { + width: auto; + padding: 10px 20px; + font-size: 15px; + margin: 0; + } + .card { + background-color: $list-item-background; + border-radius: 8px; + padding: 20px; + width: 200px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + margin: 20px; + } +} + @media (max-width: 370px) { .header { - flex-direction: column; - align-items: center; - width:400px; - + align-items: center; + padding: 10px; + } + + .card, .assignment_card { + width: 100%; + max-width: 100%; + padding: 15px; } .buttons { - width: 100%; - max-width: none; + padding: 10px; + font-size: 14px; + } + + .submissionsContainer { + padding: 10px; } } \ No newline at end of file From 499637b68afa880e4fb4f2032fb445f5f22ef1dd Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Sun, 10 Nov 2024 16:32:36 -0500 Subject: [PATCH 285/400] fixed alignment and colors on assignment creation page --- .../forms/assignments/assignmentFormPage.scss | 167 +++++------------- .../forms/assignments/assignmentFormPage.tsx | 135 +++++++------- .../shared/layouts/pageWrapper.scss | 4 +- .../src/components/utils/dragDropFile.scss | 12 +- 4 files changed, 118 insertions(+), 200 deletions(-) diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index 4ee90fa6..5d971aaa 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -7,106 +7,49 @@ h2 { p { text-align: center; + margin-left: 0; } .pageWrapper { - padding:0px; + padding: 0 100px; } .flex { display: flex; - justify-content: center; - align-items: center; + justify-content: space-between; + gap: 30px; + margin-bottom: 30px; } .form { - background-color: $list-item-background; - grid-area: 1 / 1 / 2 / 2; - border-radius: 10px; - padding: 20px; - margin-right: 0; - } - - .datepickerContainer { - display: grid; - grid-template-columns: repeat(3, 1fr); - grid-template-rows: 1fr; - grid-column-gap: 10px; - grid-row-gap: 0px; - width: 100%; - justify-content: center; - align-items: center; - text-align: center; - } - -.header { - color: $text-color; - display: flex; - align-items: center; -} - -.datepicker_start { - grid-area: 1 / 1 / 2 / 2; -} -.datepicker_due { - grid-area: 1 / 2 / 2 / 3; -} -.datepicker_end { - grid-area: 1 / 3 / 2 / 4; -} - -.grid { - display: grid; - grid-template-columns: 1fr 0.75fr; - grid-template-rows: 1fr; - grid-column-gap: 10px; - grid-row-gap: 10px; - width: 100%; - padding-left: 10px; - padding-right: 10px; -} - -.dragDropFile { - grid-area: 1 / 2 / 2 / 3; - padding: 20px; background-color: $list-item-background; - border-radius: 10px; - margin-left: 0; -} - -.dragDropFileComponent { - margin-top: 10%; -} - -.textField1 { + border-radius: 20px; + padding: 30px; width: 50%; - margin-right: 5%; - flex-shrink: 0; - flex-direction: none; } -.textField2 { - width: 50%; - flex-shrink: 0; - flex-direction: none; +.form>h2 { + margin-top: 0; } -.textFieldContainer { - display:flex; +.datepickerContainer { width: 100%; + text-align: center; + display: flex; + justify-content: space-between; + gap: 5px; } -.textArea { - width: 100%; - flex-direction: none; +.header { + color: $text-color; + display: flex; + align-items: center; } -.fileNameContainer { - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - width: 400px; +.textFieldContainer { + display: flex; + justify-content: space-between; + gap: 15px; } .fileName { @@ -125,63 +68,43 @@ p { cursor: pointer; font-size: 0.9em; - + &:hover { text-decoration: dashed; color: red; } } -@media (max-width: 1000px) { - .grid { - display: grid; - grid-template-columns: 1fr; - grid-template-rows: repeat(2, 1fr); - grid-column-gap: 0px; - grid-row-gap: 10px; - margin-left: 20px; - margin-right: 20px; - width: 100%; +@media (max-width: 1115px) { + .pageWrapper { + padding: 0 50px; } +} - .form { - grid-area: 1 / 1 / 2 / 2; - margin-left: 10px; - margin-right: 10px; - } - .dragDropFile { - grid-area: 2 / 1 / 3 / 2; - align-content: center; - text-align: center; - margin-left: 10px; - margin-right: 10px; - } - .dragDropFileInput { - display: flex; - justify-content: center; +@media (max-width: 1015px) { + .pageWrapper { + padding: 0 100px; } -} -@media (max-width: 550px) { - .datepickerContainer{ - display: grid; - grid-template-columns: 1fr; - grid-template-rows: repeat(3, 1fr); - grid-column-gap: 0px; - grid-row-gap: 10px; - width: 100%; - justify-content: center; + .flex { + flex-direction: column; align-items: center; - text-align: center; } - .datepicker_start { - grid-area: 1 / 1 / 2 / 2; + .form { + width: 100%; } - .datepicker_due { - grid-area: 2 / 1 / 3 / 2; +} + +@media (max-width: 670px) { + .pageWrapper { + padding: 0 50px; } - .datepicker_end { - grid-area: 3 / 1 / 4 / 2; +} + +@media (max-width: 560px) { + .datepickerContainer { + flex-direction: column; + gap: 15px; } } \ No newline at end of file diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index 9881fb51..3e4c935d 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -1,25 +1,24 @@ -import React, {useState} from 'react' -import {ExpressValidationError} from 'devu-shared-modules' +import React, { useState } from 'react' +import { ExpressValidationError } from 'devu-shared-modules' import 'react-datepicker/dist/react-datepicker.css' import PageWrapper from 'components/shared/layouts/pageWrapper' import RequestService from 'services/request.service' -import {useActionless} from 'redux/hooks' +import { useActionless } from 'redux/hooks' import TextField from 'components/shared/inputs/textField' -// import Button from '@mui/material/Button' -import Button from '../../../shared/inputs/button' +// import Button from '../../../shared/inputs/button' import DragDropFile from 'components/utils/dragDropFile' -import {SET_ALERT} from 'redux/types/active.types' +import { SET_ALERT } from 'redux/types/active.types' -import {applyMessageToErrorFields, removeClassFromField} from "../../../../utils/textField.utils"; -import {useHistory, useParams} from 'react-router-dom' +import { applyMessageToErrorFields, removeClassFromField } from "../../../../utils/textField.utils"; +import { useHistory, useParams } from 'react-router-dom' import formStyles from './assignmentFormPage.scss' const AssignmentCreatePage = () => { const [setAlert] = useActionless(SET_ALERT) - const {courseId} = useParams<{ courseId: string }>() + const { courseId } = useParams<{ courseId: string }>() const history = useHistory() const [formData, setFormData] = useState({ @@ -35,32 +34,32 @@ const AssignmentCreatePage = () => { const [dueDate, setDueDate] = useState('') const [startDate, setStartDate] = useState('') const [invalidFields, setInvalidFields] = useState(new Map()) - const [files, setFiles] = useState>(new Map()) + const [files, setFiles] = useState>(new Map()) - const handleChange = (value: String, e : React.ChangeEvent) => { + const handleChange = (value: String, e: React.ChangeEvent) => { const key = e.target.id const newInvalidFields = removeClassFromField(invalidFields, key) setInvalidFields(newInvalidFields) - setFormData(prevState => ({...prevState,[key] : value})) + setFormData(prevState => ({ ...prevState, [key]: value })) } const handleCheckbox = (e: React.ChangeEvent) => { - setFormData(prevState => ({...prevState,disableHandins : e.target.checked})) + setFormData(prevState => ({ ...prevState, disableHandins: e.target.checked })) } - const handleStartDateChange = (e : React.ChangeEvent) => {setStartDate(e.target.value)} - const handleEndDateChange = (e : React.ChangeEvent) => {setEndDate(e.target.value)} - const handleDueDateChange = (e : React.ChangeEvent) => {setDueDate(e.target.value)} + const handleStartDateChange = (e: React.ChangeEvent) => { setStartDate(e.target.value) } + const handleEndDateChange = (e: React.ChangeEvent) => { setEndDate(e.target.value) } + const handleDueDateChange = (e: React.ChangeEvent) => { setDueDate(e.target.value) } const handleSubmit = () => { const finalFormData = { courseId: courseId, name: formData.name, - startDate : startDate, + startDate: startDate, dueDate: dueDate, - endDate : endDate, + endDate: endDate, categoryName: formData.categoryName, description: formData.description, maxFileSize: formData.maxFileSize, @@ -75,11 +74,11 @@ const AssignmentCreatePage = () => { multipart.append('dueDate', finalFormData.dueDate) multipart.append('endDate', finalFormData.endDate) multipart.append('categoryName', finalFormData.categoryName) - if(finalFormData.description !== null) { multipart.append('description', finalFormData.description) } + if (finalFormData.description !== null) { multipart.append('description', finalFormData.description) } multipart.append('maxFileSize', finalFormData.maxFileSize.toString()) - if(finalFormData.maxSubmissions !== null) { multipart.append('maxSubmissions', finalFormData.maxSubmissions.toString()) } + if (finalFormData.maxSubmissions !== null) { multipart.append('maxSubmissions', finalFormData.maxSubmissions.toString()) } multipart.append('disableHandins', finalFormData.disableHandins.toString()) - for(const file of files.values()){ + for (const file of files.values()) { multipart.append('files', file) } @@ -97,9 +96,9 @@ const AssignmentCreatePage = () => { setAlert({ autoDelete: false, type: 'error', message }) }) - .finally(() => { + .finally(() => { - }) + }) } @@ -121,80 +120,77 @@ const AssignmentCreatePage = () => { }); }; - return( + return (

Create Assignment

-

Assignment Information

+ invalidated={!!invalidFields.get("name")} + helpText={invalidFields.get("name")} + /> + invalidated={!!invalidFields.get("categoryName")} + helpText={invalidFields.get("categoryName")} + />
- +
+ invalidated={!!invalidFields.get("maxFileSize")} + helpText={invalidFields.get("maxFileSize")} + /> + invalidated={!!invalidFields.get("maxSubmission")} + helpText={invalidFields.get("maxSubmission")} + />
-
- -
-
+
+ +
+
-
- -
-
+
+ +
+
-
- +
+
-
-
+
+
+ onChange={handleCheckbox} className={formStyles.submitBtn} />
-
-
- +
+
+
-
+
{/*TODO: Whenever file uploads is available on backend, store the files + create Object URLs*/}

Attachments

-

Add up to 5 attachments with this assignment:

+

Add up to 5 attachments with this assignment:

Files Uploaded (click to delete) :

{Array.from(files.keys()).map((fileName) => { @@ -211,14 +207,13 @@ const AssignmentCreatePage = () => { ); })}
-
-
- -
+ {/*
*/} +
+
+ {/*
*/}
-
) } diff --git a/devU-client/src/components/shared/layouts/pageWrapper.scss b/devU-client/src/components/shared/layouts/pageWrapper.scss index 5daad34a..8c0cf1d1 100644 --- a/devU-client/src/components/shared/layouts/pageWrapper.scss +++ b/devU-client/src/components/shared/layouts/pageWrapper.scss @@ -9,7 +9,7 @@ } .content { - margin-top: 20px; + // margin-top: 20px; flex-grow: 1; - padding: 10px 50px; + padding: 0px 50px; } diff --git a/devU-client/src/components/utils/dragDropFile.scss b/devU-client/src/components/utils/dragDropFile.scss index 4587605b..b6b981b9 100644 --- a/devU-client/src/components/utils/dragDropFile.scss +++ b/devU-client/src/components/utils/dragDropFile.scss @@ -10,11 +10,11 @@ .formFileUploadWide { height: 16rem; - width: 80%; - margin-left: 10%; - margin-right: 10%; + width: 100%; + // margin-left: 10%; + // margin-right: 10%; text-align: center; - position: relative; + // position: relative; } .inputFileUpload { @@ -29,8 +29,8 @@ border-width: 2px; border-radius: 1rem; border-style: dashed; - border-color: #cbd5e1; - background-color: $list-item-background; + border-color: $input-field-label; + background-color: $input-field-background; &.dragActive { background-color: #ffffff; From 968cbdedbe3980fb178cdfe06e2b4097e4053c45 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Sun, 10 Nov 2024 17:25:31 -0500 Subject: [PATCH 286/400] fixed colors and padding on instructor gradebook --- .../gradebook/gradebookInstructorPage.tsx | 58 +++++++++++-------- .../pages/gradebook/gradebookPage.scss | 13 ++++- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx index c2c8efce..6d0c721c 100644 --- a/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookInstructorPage.tsx @@ -1,6 +1,6 @@ -import React, {useEffect, useState} from 'react' +import React, { useEffect, useState } from 'react' -import {Assignment, AssignmentScore, User, UserCourse} from 'devu-shared-modules' +import { Assignment, AssignmentScore, User, UserCourse } from 'devu-shared-modules' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' @@ -9,7 +9,7 @@ import ErrorPage from '../errorPage/errorPage' import RequestService from 'services/request.service' import styles from './gradebookPage.scss' -import {useParams} from 'react-router-dom' +import { useParams } from 'react-router-dom' type TableProps = { users: User[] @@ -18,19 +18,27 @@ type TableProps = { assignmentScores: AssignmentScore[] } type RowProps = { - index: Number + index: number user: User userCourse: UserCourse assignments: Assignment[] assignmentScores: AssignmentScore[] } //table for style -const TableRow = ({index, user, userCourse, assignments, assignmentScores}: RowProps) => { +const TableRow = ({ index, user, userCourse, assignments, assignmentScores }: RowProps) => { + // style table row to alternating colors based on index odd?even + const rowClass = index % 2 === 0 ? 'evenRow' : 'oddRow'; + + // dont show row if dropped + // if (userCourse.dropped) { + // return (<>) + // } + return ( -
+ - + {/* */} {assignments.map(a => ( @@ -40,16 +48,16 @@ const TableRow = ({index, user, userCourse, assignments, assignmentScores}: RowP ) } -const GradebookTable = ({users, userCourses, assignments, assignmentScores}: TableProps) => { +const GradebookTable = ({ users, userCourses, assignments, assignmentScores }: TableProps) => { return (
# EmailExternal IDExternal ID Preferred NameDroppedDropped{a.name}{a.name}
{index} {user.email}{user.externalId}{user.externalId}{user.preferredName} {userCourse.dropped.toString()}
- + - + {/* */} {assignments.map((a) => { - return ( ) + return () })} {users.map((u, index) => ( { const [userCourses, setUserCourses] = useState(new Array()) //All user-course connections for the course const [assignments, setAssignments] = useState(new Array()) //All assignments in the course const [assignmentScores, setAssignmentScores] = useState(new Array()) //All assignment scores for assignments in the course - - const { courseId } = useParams<{courseId: string}>() - + + const { courseId } = useParams<{ courseId: string }>() + useEffect(() => { fetchData() - }, []) - + }, []) + const fetchData = async () => { try { const userCourses = await RequestService.get(`/api/course/${courseId}/user-courses/`) @@ -86,12 +94,12 @@ const GradebookInstructorPage = () => { const users = await RequestService.get(`/api/users/course/${courseId}`) setUsers(users) - - const assignments = await RequestService.get( `/api/course/${courseId}/assignments` ) + + const assignments = await RequestService.get(`/api/course/${courseId}/assignments`) assignments.sort((a, b) => (Date.parse(a.startDate) - Date.parse(b.startDate))) //Sort by assignment's start date setAssignments(assignments) - const assignmentScores = await RequestService.get( `/api/course/${courseId}/assignment-scores` ) + const assignmentScores = await RequestService.get(`/api/course/${courseId}/assignment-scores`) setAssignmentScores(assignmentScores) } catch (error: any) { @@ -105,12 +113,12 @@ const GradebookInstructorPage = () => { if (error) return return ( - -
-

Instructor Gradebook

-
-
- + {/*
*/} +

Instructor Gradebook

+ {/*
*/} +
+ Date: Sun, 10 Nov 2024 17:57:40 -0500 Subject: [PATCH 287/400] added consistent padding to navbar and breadcrumbs --- devU-client/src/assets/variables.scss | 2 ++ .../src/components/misc/globalToolbar.scss | 20 ++++++++----------- devU-client/src/components/misc/navbar.scss | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/devU-client/src/assets/variables.scss b/devU-client/src/assets/variables.scss index 1056e108..b503f783 100644 --- a/devU-client/src/assets/variables.scss +++ b/devU-client/src/assets/variables.scss @@ -66,6 +66,8 @@ $medium: 1000px; $small: 600px; $extreme: 780px; +$pagePadding: 50px; + @mixin ellipsis { overflow: hidden; text-overflow: ellipsis; diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 0ddc42da..6fc1a45f 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -1,6 +1,6 @@ @import 'variables'; -$bar-height: 50px; +$bar-height: 60px; $sidebar-width: 260px; $hover-effect: 0.7; $font-size: 16px; @@ -37,23 +37,19 @@ $font-size: 16px; } .header { - @extend .link; - font-size: 40px; - border-radius: 30px; - font-weight: 550; - //font-size: 2em; - //font-weight: bold; + font-size: 32px; + text-decoration: none; + color: #FFF; + font-weight: 600; } .bar { - - height: 80px; + height: $bar-height; background-color: $primary; font-size: 40px; color: #D9D9D9; font-weight: 550; - - + padding: 0 $pagePadding; @extend .flex; justify-content: space-between; @@ -71,7 +67,7 @@ $font-size: 16px; // Controls turning the menu options into a sidebar // As well as whether or not that sidebar is being shown -@media (max-width: 410px) { +@media (max-width: 450px) { .flex { gap: 1rem; diff --git a/devU-client/src/components/misc/navbar.scss b/devU-client/src/components/misc/navbar.scss index 9ee712c2..2fe47743 100644 --- a/devU-client/src/components/misc/navbar.scss +++ b/devU-client/src/components/misc/navbar.scss @@ -3,7 +3,7 @@ .breadcrumbContainer { display: flex; align-items: center; - margin-left: 3.5rem; + margin-left: $pagePadding; margin-top:10px; } From 2476314eef77501cae087fb7f4f7ff7bdc775995 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 10 Nov 2024 18:01:03 -0500 Subject: [PATCH 288/400] added isadmin check in isAuthorized --- devU-api/src/authorization/authorization.middleware.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/devU-api/src/authorization/authorization.middleware.ts b/devU-api/src/authorization/authorization.middleware.ts index 567554a7..4fdf73bd 100644 --- a/devU-api/src/authorization/authorization.middleware.ts +++ b/devU-api/src/authorization/authorization.middleware.ts @@ -5,6 +5,7 @@ import UserCourseService from '../entities/userCourse/userCourse.service' import RoleService from '../entities/role/role.service' import { serialize } from '../entities/role/role.serializer' import { Role } from '../../devu-shared-modules' +import UserService from '../entities/user/user.service' /** * Are you authorized to access this endpoint? @@ -23,6 +24,14 @@ export function isAuthorized(permission: string, permissionIfSelf?: string) { return res.status(404).json(NotFound) } + // check if admin + const user = await UserService.isAdmin(userId!) + if (user && user.isAdmin!) { + // no role checks needed + // user is admin ! + return next() + } + // Pull userCourse const userCourse = await UserCourseService.retrieveByCourseAndUser(courseId, userId) From de02646dd2863071652b0cb816e3e004520a046f Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 12 Nov 2024 10:49:50 -0500 Subject: [PATCH 289/400] Added a page to view pdf submissions Added a page to view pdf submissions for students --- .../src/fileUpload/fileUpload.controller.ts | 1 + devU-client/package.json | 1 + .../src/components/authenticatedRouter.tsx | 2 + .../submissions/submissionDetailPage.tsx | 3 + .../pages/submissions/submissionFileView.tsx | 113 ++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 devU-client/src/components/pages/submissions/submissionFileView.tsx diff --git a/devU-api/src/fileUpload/fileUpload.controller.ts b/devU-api/src/fileUpload/fileUpload.controller.ts index 0c30911e..89a6734d 100644 --- a/devU-api/src/fileUpload/fileUpload.controller.ts +++ b/devU-api/src/fileUpload/fileUpload.controller.ts @@ -23,6 +23,7 @@ export async function detail(req: Request, res: Response, next: NextFunction) { const bucketName = req.params.bucketName const fileName = req.params.fileName const file: Buffer = await FileUploadService.retrieve(bucketName, fileName) + console.log(file) if (!file) return res.status(404).json(NotFound) diff --git a/devU-client/package.json b/devU-client/package.json index af413ffc..df476526 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -41,6 +41,7 @@ "react": "^17.0.2", "react-datepicker": "^4.1.1", "react-dom": "^17.0.1", + "react-pdf": "^9.1.1", "react-redux": "^7.2.4", "react-router-breadcrumbs-hoc": "^4.1.0", "react-router-dom": "^5.2.0", diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index a8016a49..65e87ede 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -20,6 +20,7 @@ import CoursePreviewPage from './pages/courses/coursePreviewPage' import CoursesListPage from "./pages/listPages/courses/coursesListPage"; import AssignmentProblemFormPage from './pages/forms/assignments/assignmentProblemFormPage' import InstructorSubmissionspage from "./pages/submissions/InstructorSubmissionspage"; +import SubmissionFileView from './pages/submissions/submissionFileView' import WebhookURLForm from './pages/webhookURLForm' @@ -53,6 +54,7 @@ const AuthenticatedRouter = () => ( component={InstructorSubmissionspage}/> + // TBD, undecided where webhooks should be placed {/**/} diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx index 3e805589..52f6b9df 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx @@ -18,6 +18,7 @@ import {prettyPrintDateTime} from "../../../utils/date.utils"; //import React, { useState } from 'react'; //import { Document, Page } from 'react-pdf'; //import StickyNote from 'react-sticky-notes'; +import { useHistory } from 'react-router-dom' const SubmissionDetailPage = () => { @@ -25,6 +26,7 @@ const SubmissionDetailPage = () => { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [setAlert] = useActionless(SET_ALERT) + const history = useHistory() const { submissionId, assignmentId, courseId } = useParams<{submissionId: string, assignmentId: string, courseId: string}>() const [submissionScore, setSubmissionScore] = useState(null) @@ -208,6 +210,7 @@ const SubmissionDetailPage = () => {

Content

+
{selectedSubmission.content}
diff --git a/devU-client/src/components/pages/submissions/submissionFileView.tsx b/devU-client/src/components/pages/submissions/submissionFileView.tsx new file mode 100644 index 00000000..cc322573 --- /dev/null +++ b/devU-client/src/components/pages/submissions/submissionFileView.tsx @@ -0,0 +1,113 @@ +import React, { useEffect, useState } from 'react' +import { useParams } from 'react-router-dom' +import RequestService from 'services/request.service' +import { Submission } from 'devu-shared-modules' +import {Document, Page} from 'react-pdf' +import { pdfjs } from 'react-pdf' +import PageWrapper from 'components/shared/layouts/pageWrapper' +import { getToken } from 'utils/authentication.utils' + +import config from '../../../config' +const proxiedUrls = { + '/api': `${config.apiUrl}`, +} + +function _replaceUrl(userUrl: string) { + const proxy: string | undefined = Object.keys(proxiedUrls).find((key) => { + if (userUrl.startsWith(key)) return true + return false + }) + + if (!proxy) return userUrl + + return userUrl.replace(proxy, proxiedUrls[proxy as keyof typeof proxiedUrls]) + } + + +pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`; + +const SubmissionFileView = () => { + const { courseId, assignmentId, submissionId } = useParams<{ courseId: string, assignmentId: string, submissionId: string }>() + const [bucket, setBucket] = useState('') + const [filename, setFilename] = useState('') + const authToken = getToken() + + const [file, setFile] = useState(null) + const [numPages, setNumPages] = useState(0) + + useEffect(() => { + fetchData() + }, []) + + useEffect(() => { + console.log(filename) + if (bucket && filename) { + fetchFile() + } + }, [bucket, filename]) + + const fetchData = async () => { + try { + await RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/submissions/${submissionId}`) + .then((data) => { + const submissionFiles = JSON.parse(data.content) + const [tempbucket,tempfilename] = submissionFiles.filepaths[0].split('/') + setBucket(tempbucket) + setFilename(tempfilename) + + + }) + } catch (e) { + console.error(e) + } + } + + const fetchFile = async () => { + try { + const url = _replaceUrl(`/api/course/${courseId}/file-upload/${bucket}/${filename}`) + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/pdf', + 'Authorization': `Bearer ${authToken}`, + }, + }) + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const arrayBuffer = await response.arrayBuffer(); + const blob = new Blob([arrayBuffer], { type: 'application/pdf' }); + const file = new File([blob], filename, { type: 'application/pdf' }); + + setFile(file); + } catch (e) { + console.error(e) + } + } + + const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => { + setNumPages(numPages) + } + + return ( + +
+ {file && + + {[...Array(numPages)].map((_, index) => ( +
+ +
+ ))} +
+ } +
+
+ +
+ ) + +} + +export default SubmissionFileView \ No newline at end of file From a674275f79f1f92d6d497756d411d01285d22bb2 Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 12 Nov 2024 11:10:51 -0500 Subject: [PATCH 290/400] Fixed up CRUD endpoints for sticky notes Fixed up CRUD endpoints for sticky notes so that they function as intended. --- .../entities/stickyNote/stickyNote.controller.ts | 4 +++- .../src/entities/stickyNote/stickyNote.model.ts | 4 ++++ .../src/entities/stickyNote/stickyNote.router.ts | 7 +++---- .../entities/stickyNote/stickyNote.service.ts | 8 ++++---- .../migration/1731177885475-add-sticky-notes.ts | 16 ---------------- .../1731427638811-add-sticky-note-endpoints.ts | 14 ++++++++++++++ 6 files changed, 28 insertions(+), 25 deletions(-) delete mode 100644 devU-api/src/migration/1731177885475-add-sticky-notes.ts create mode 100644 devU-api/src/migration/1731427638811-add-sticky-note-endpoints.ts diff --git a/devU-api/src/entities/stickyNote/stickyNote.controller.ts b/devU-api/src/entities/stickyNote/stickyNote.controller.ts index ca6e3dd3..0fb707e0 100644 --- a/devU-api/src/entities/stickyNote/stickyNote.controller.ts +++ b/devU-api/src/entities/stickyNote/stickyNote.controller.ts @@ -35,8 +35,10 @@ export async function post(req: Request, res: Response, next: NextFunction) { export async function put(req: Request, res: Response, next: NextFunction) { try { + const id = parseInt(req.params.id) const reqStickyNote = req.body - const stickyNote = await StickyNoteService.update(reqStickyNote) + // const stickyNote = await StickyNoteService.update(reqStickyNote) + const stickyNote = await StickyNoteService.update(id, reqStickyNote) if (!stickyNote.affected) return res.status(404).json(NotFound) diff --git a/devU-api/src/entities/stickyNote/stickyNote.model.ts b/devU-api/src/entities/stickyNote/stickyNote.model.ts index 7eae4551..f6ce4c9d 100644 --- a/devU-api/src/entities/stickyNote/stickyNote.model.ts +++ b/devU-api/src/entities/stickyNote/stickyNote.model.ts @@ -3,6 +3,7 @@ import { ManyToOne, Entity, Column, + DeleteDateColumn, PrimaryGeneratedColumn, } from 'typeorm' @@ -21,4 +22,7 @@ export default class StickyNotesModel { @Column({ name: 'content' }) content: string + + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt?: Date } \ No newline at end of file diff --git a/devU-api/src/entities/stickyNote/stickyNote.router.ts b/devU-api/src/entities/stickyNote/stickyNote.router.ts index eff12bf4..35c016f4 100644 --- a/devU-api/src/entities/stickyNote/stickyNote.router.ts +++ b/devU-api/src/entities/stickyNote/stickyNote.router.ts @@ -10,17 +10,16 @@ import StickyNoteController from './stickyNote.controller' const Router = express.Router({ mergeParams: true }) +Router.get('/all',validator , StickyNoteController.listBySubmission) + Router.get('/:id' , asInt(), validator , StickyNoteController.retrieve) Router.post('/', validator, StickyNoteController.post) -Router.put('/', validator, StickyNoteController.put) +Router.put('/:id', validator, StickyNoteController.put) Router.delete('/:id', asInt(), validator, StickyNoteController.remove) -Router.get('/all',validator , StickyNoteController.listBySubmission) - - export default Router diff --git a/devU-api/src/entities/stickyNote/stickyNote.service.ts b/devU-api/src/entities/stickyNote/stickyNote.service.ts index 6afe40b3..e411fc34 100644 --- a/devU-api/src/entities/stickyNote/stickyNote.service.ts +++ b/devU-api/src/entities/stickyNote/stickyNote.service.ts @@ -1,4 +1,4 @@ -// import {IsNull} from 'typeorm' +import {IsNull} from 'typeorm' import {dataSource} from '../../database' import StickyNotesModel from './stickyNote.model' @@ -10,15 +10,15 @@ export async function create(stickyNote: StickyNote) { return await StickyNoteConn().save(stickyNote) } -export async function update(stickyNote: StickyNote) { - const {id, submissionId, content} = stickyNote +export async function update(id : number,stickyNote: StickyNote) { + const {submissionId, content} = stickyNote if (!id) throw new Error('Missing Id') return await StickyNoteConn().update(id, {submissionId, content}) } export async function _delete(id: number) { // return await StickyNoteConn().softDelete({id, deletedAt: IsNull()}) - return await StickyNoteConn().softDelete({id}) + return await StickyNoteConn().softDelete({id, deletedAt: IsNull()}) } export async function retrieve(id: number) { diff --git a/devU-api/src/migration/1731177885475-add-sticky-notes.ts b/devU-api/src/migration/1731177885475-add-sticky-notes.ts deleted file mode 100644 index a8f8e291..00000000 --- a/devU-api/src/migration/1731177885475-add-sticky-notes.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; - -export class AddStickyNotes1731177885475 implements MigrationInterface { - name = 'AddStickyNotes1731177885475' - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query(`CREATE TABLE "sticky_notes" ("id" SERIAL NOT NULL, "submissionId" integer NOT NULL, "content" character varying NOT NULL, CONSTRAINT "PK_615fc1d0c6b75c46aa2a7dd5f0a" PRIMARY KEY ("id"))`); - await queryRunner.query(`ALTER TABLE "sticky_notes" ADD CONSTRAINT "FK_a92bd7a1dc5c606db210b176a43" FOREIGN KEY ("submissionId") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`ALTER TABLE "sticky_notes" DROP CONSTRAINT "FK_a92bd7a1dc5c606db210b176a43"`); - await queryRunner.query(`DROP TABLE "sticky_notes"`); - } - -} diff --git a/devU-api/src/migration/1731427638811-add-sticky-note-endpoints.ts b/devU-api/src/migration/1731427638811-add-sticky-note-endpoints.ts new file mode 100644 index 00000000..0b0100f9 --- /dev/null +++ b/devU-api/src/migration/1731427638811-add-sticky-note-endpoints.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddStickyNoteEndpoints1731427638811 implements MigrationInterface { + name = 'AddStickyNoteEndpoints1731427638811' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sticky_notes" ADD "deleted_at" TIMESTAMP`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sticky_notes" DROP COLUMN "deleted_at"`); + } + +} From e8371d013f8587288cf15e868842bbeedca1a2c2 Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 12 Nov 2024 12:44:03 -0500 Subject: [PATCH 291/400] Added Authorization to the Sticky Notes router and updated Roles Added Authorization to the Sticky Notes router and updated Roles. --- devU-api/src/entities/role/role.defaults.ts | 4 ++++ devU-api/src/entities/role/role.model.ts | 6 ++++++ devU-api/src/entities/role/role.serializer.ts | 2 ++ .../src/entities/stickyNote/stickyNote.router.ts | 12 ++++++------ .../1731433278166-updatedRolesWithStickyNotes.ts | 16 ++++++++++++++++ devU-shared/src/types/role.types.ts | 2 ++ 6 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 devU-api/src/migration/1731433278166-updatedRolesWithStickyNotes.ts diff --git a/devU-api/src/entities/role/role.defaults.ts b/devU-api/src/entities/role/role.defaults.ts index dbf90ea0..ffe5f7f8 100644 --- a/devU-api/src/entities/role/role.defaults.ts +++ b/devU-api/src/entities/role/role.defaults.ts @@ -18,6 +18,8 @@ const student: Role = { submissionCreateAll: false, submissionCreateSelf: true, submissionViewAll: false, + stickyNoteViewAll: false, + stickyNoteEditAll: false, userCourseEditAll: false, } @@ -39,6 +41,8 @@ const instructor: Role = { submissionCreateAll: true, submissionCreateSelf: true, submissionViewAll: true, + stickyNoteViewAll: true, + stickyNoteEditAll: true, userCourseEditAll: true, } diff --git a/devU-api/src/entities/role/role.model.ts b/devU-api/src/entities/role/role.model.ts index 05a13410..76920b42 100644 --- a/devU-api/src/entities/role/role.model.ts +++ b/devU-api/src/entities/role/role.model.ts @@ -107,4 +107,10 @@ export default class RoleModel { @Column({ name: 'user_course_edit_all' }) // TODO: Don't let the last instructor change their role userCourseEditAll: boolean + + @Column({ name: 'sticky_note_view_all' }) + stickyNoteViewAll: boolean + + @Column({ name: 'sticky_note_edit_all' }) + stickyNoteEditAll: boolean } diff --git a/devU-api/src/entities/role/role.serializer.ts b/devU-api/src/entities/role/role.serializer.ts index ab6ead33..6b999ef8 100644 --- a/devU-api/src/entities/role/role.serializer.ts +++ b/devU-api/src/entities/role/role.serializer.ts @@ -26,5 +26,7 @@ export function serialize(role: RoleModel): Role { submissionCreateSelf: role.submissionCreateSelf, submissionViewAll: role.submissionViewAll, userCourseEditAll: role.userCourseEditAll, + stickyNoteViewAll: role.stickyNoteViewAll, + stickyNoteEditAll: role.stickyNoteEditAll, } } diff --git a/devU-api/src/entities/stickyNote/stickyNote.router.ts b/devU-api/src/entities/stickyNote/stickyNote.router.ts index 35c016f4..34dbf494 100644 --- a/devU-api/src/entities/stickyNote/stickyNote.router.ts +++ b/devU-api/src/entities/stickyNote/stickyNote.router.ts @@ -2,7 +2,7 @@ import express from 'express' // Middleware import validator from './stickyNote.validator' -// import { isAuthorized } from '../../authorization/authorization.middleware' +import { isAuthorized } from '../../authorization/authorization.middleware' import { asInt } from '../../middleware/validator/generic.validator' // Controller @@ -10,15 +10,15 @@ import StickyNoteController from './stickyNote.controller' const Router = express.Router({ mergeParams: true }) -Router.get('/all',validator , StickyNoteController.listBySubmission) +Router.get('/all', isAuthorized("stickyNoteViewAll"), validator , StickyNoteController.listBySubmission) -Router.get('/:id' , asInt(), validator , StickyNoteController.retrieve) +Router.get('/:id' ,isAuthorized("stickyNoteViewAll") , asInt("id"), validator , StickyNoteController.retrieve) -Router.post('/', validator, StickyNoteController.post) +Router.post('/',isAuthorized("stickyNoteEditAll") ,validator, StickyNoteController.post) -Router.put('/:id', validator, StickyNoteController.put) +Router.put('/:id',isAuthorized("stickyNoteEditAll") , validator, StickyNoteController.put) -Router.delete('/:id', asInt(), validator, StickyNoteController.remove) +Router.delete('/:id',isAuthorized("stickyNoteEditAll") , asInt("id"), validator, StickyNoteController.remove) export default Router diff --git a/devU-api/src/migration/1731433278166-updatedRolesWithStickyNotes.ts b/devU-api/src/migration/1731433278166-updatedRolesWithStickyNotes.ts new file mode 100644 index 00000000..c8e6636b --- /dev/null +++ b/devU-api/src/migration/1731433278166-updatedRolesWithStickyNotes.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class UpdatedRolesWithStickyNotes1731433278166 implements MigrationInterface { + name = 'UpdatedRolesWithStickyNotes1731433278166' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "role" ADD "sticky_note_view_all" boolean NOT NULL`); + await queryRunner.query(`ALTER TABLE "role" ADD "sticky_note_edit_all" boolean NOT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "sticky_note_edit_all"`); + await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "sticky_note_view_all"`); + } + +} diff --git a/devU-shared/src/types/role.types.ts b/devU-shared/src/types/role.types.ts index 38c3e539..48775989 100644 --- a/devU-shared/src/types/role.types.ts +++ b/devU-shared/src/types/role.types.ts @@ -20,4 +20,6 @@ export type Role = { submissionCreateSelf: boolean submissionViewAll: boolean userCourseEditAll: boolean + stickyNoteViewAll: boolean + stickyNoteEditAll: boolean } From e5d7cd84be9b9fde47e7051555d400018b3587fc Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 12 Nov 2024 12:48:59 -0500 Subject: [PATCH 292/400] Deleted comments and added deletedAt filtering Deleted Comments and Added deletedAt filtering --- devU-api/src/entities/stickyNote/stickyNote.controller.ts | 1 - devU-api/src/entities/stickyNote/stickyNote.service.ts | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/devU-api/src/entities/stickyNote/stickyNote.controller.ts b/devU-api/src/entities/stickyNote/stickyNote.controller.ts index 0fb707e0..29aab129 100644 --- a/devU-api/src/entities/stickyNote/stickyNote.controller.ts +++ b/devU-api/src/entities/stickyNote/stickyNote.controller.ts @@ -37,7 +37,6 @@ export async function put(req: Request, res: Response, next: NextFunction) { try { const id = parseInt(req.params.id) const reqStickyNote = req.body - // const stickyNote = await StickyNoteService.update(reqStickyNote) const stickyNote = await StickyNoteService.update(id, reqStickyNote) if (!stickyNote.affected) return res.status(404).json(NotFound) diff --git a/devU-api/src/entities/stickyNote/stickyNote.service.ts b/devU-api/src/entities/stickyNote/stickyNote.service.ts index e411fc34..eca8b17c 100644 --- a/devU-api/src/entities/stickyNote/stickyNote.service.ts +++ b/devU-api/src/entities/stickyNote/stickyNote.service.ts @@ -17,18 +17,15 @@ export async function update(id : number,stickyNote: StickyNote) { } export async function _delete(id: number) { -// return await StickyNoteConn().softDelete({id, deletedAt: IsNull()}) return await StickyNoteConn().softDelete({id, deletedAt: IsNull()}) } export async function retrieve(id: number) { -// return await StickyNoteConn().findOneBy({id, deletedAt: IsNull()}) - return await StickyNoteConn().findOneBy({id}) + return await StickyNoteConn().findOneBy({id, deletedAt: IsNull()}) } export async function listBySubmission(submissionId: number) { -// return await StickyNoteConn().findBy({submissionId :submissionId, deletedAt: IsNull()}) - return await StickyNoteConn().findBy({submissionId: submissionId}) + return await StickyNoteConn().findBy({submissionId: submissionId , deletedAt: IsNull()}) } export default { From 8dc951ec35edb57f8cbbcebba03aec3833a036ff Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 12 Nov 2024 13:00:32 -0500 Subject: [PATCH 293/400] Updated button text Updated button text on submission detail page for redirect to viewing uploaded file. --- .../src/components/pages/submissions/submissionDetailPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx index 52f6b9df..fe979ed5 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.tsx +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.tsx @@ -210,7 +210,7 @@ const SubmissionDetailPage = () => {

Content

- +
{selectedSubmission.content}
From 1f67e6466a78d5d6d2763d35f34f82473abbcd7f Mon Sep 17 00:00:00 2001 From: Kevin Zhong Date: Tue, 12 Nov 2024 13:07:28 -0500 Subject: [PATCH 294/400] Removed extra console.log() Removed extra console.log() from files for cleanup. --- devU-api/src/fileUpload/fileUpload.controller.ts | 1 - .../src/components/pages/submissions/submissionFileView.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/devU-api/src/fileUpload/fileUpload.controller.ts b/devU-api/src/fileUpload/fileUpload.controller.ts index 89a6734d..0c30911e 100644 --- a/devU-api/src/fileUpload/fileUpload.controller.ts +++ b/devU-api/src/fileUpload/fileUpload.controller.ts @@ -23,7 +23,6 @@ export async function detail(req: Request, res: Response, next: NextFunction) { const bucketName = req.params.bucketName const fileName = req.params.fileName const file: Buffer = await FileUploadService.retrieve(bucketName, fileName) - console.log(file) if (!file) return res.status(404).json(NotFound) diff --git a/devU-client/src/components/pages/submissions/submissionFileView.tsx b/devU-client/src/components/pages/submissions/submissionFileView.tsx index cc322573..8a31cd7f 100644 --- a/devU-client/src/components/pages/submissions/submissionFileView.tsx +++ b/devU-client/src/components/pages/submissions/submissionFileView.tsx @@ -40,7 +40,6 @@ const SubmissionFileView = () => { }, []) useEffect(() => { - console.log(filename) if (bucket && filename) { fetchFile() } From 14099b3fe56dec0a485088ef9477a42691f2b867 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:13:21 -0500 Subject: [PATCH 295/400] fixed styling for gradebook pages --- devU-client/src/assets/global.scss | 4 +- .../src/components/misc/globalToolbar.scss | 17 +-- .../pages/gradebook/gradebookPage.scss | 133 +++++------------- .../pages/gradebook/gradebookStudentPage.tsx | 50 ++++--- 4 files changed, 63 insertions(+), 141 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 4458b368..af771fb3 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -14,7 +14,7 @@ } h1 { - margin: 2.25rem auto; + margin: 1.75rem auto; text-align: center; } @@ -24,7 +24,7 @@ // general button template, extends to 3 types of buttons - primary, secondary, delete .btn { - padding: 15px 30px; + padding: 10px 15px; border-radius: 100px; border: none; font-weight: 700; diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 6fc1a45f..314d0a8c 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -17,19 +17,8 @@ $font-size: 16px; text-decoration: none; color: #FFF; - font-size: $font-size; - - //height: $bar-height; - - margin:3vw; - height: 4vh; - flex-grow: .05; - border-radius: 3vw; - font-weight: 550; - //font-size: 14px; - - padding:0.5vw; + font-weight: 500; &:hover { opacity: $hover-effect; @@ -40,7 +29,7 @@ $font-size: 16px; font-size: 32px; text-decoration: none; color: #FFF; - font-weight: 600; + font-weight: 500; } .bar { @@ -67,7 +56,7 @@ $font-size: 16px; // Controls turning the menu options into a sidebar // As well as whether or not that sidebar is being shown -@media (max-width: 450px) { +@media (max-width: 500px) { .flex { gap: 1rem; diff --git a/devU-client/src/components/pages/gradebook/gradebookPage.scss b/devU-client/src/components/pages/gradebook/gradebookPage.scss index 48cdb4ac..1c63eb30 100644 --- a/devU-client/src/components/pages/gradebook/gradebookPage.scss +++ b/devU-client/src/components/pages/gradebook/gradebookPage.scss @@ -1,25 +1,8 @@ @import 'variables'; .categoryName { - color: $text-color; - font-size: 1.25rem; -} - -.assignmentName { - color: $focus; -} - -.smallLine { - width: 50px; /* adjust this value to set the length of the small line */ - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-right: 10px; /* adjust this value to set the space between the line and the text */ -} - -.largeLine { - flex-grow: 1; - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-left: 10px; /* adjust this value to set the space between the line and the text */ - margin-right: 10px; /* add this line to create some space between the line and the button */ + color: $text-color; + font-size: 1.25rem; } .tableWrapper { @@ -33,8 +16,9 @@ table { border-radius: 20px; - margin: 15px auto; + // margin: 15px auto; border-collapse: collapse; + width: 100%; } tr.evenRow { @@ -51,7 +35,8 @@ th { font-weight: 600; } -td, th { +td, +th { padding: 10px; text-align: center; } @@ -78,102 +63,52 @@ tr:last-of-type td:last-of-type { align-items: center; } -.tableOptions span:first-child, span.yellow { +.tableOptions span:first-child, +span.yellow { color: $yellowText; } -.tableOptions span:last-child, span.red { - color: $redText; +.tableOptions span:last-child, +span.red { + color: $redText; } // STUDENT GRADEBOOK STYLING -.header { - color: $text-color; - display: flex; - align-items: center; - justify-content: center; - // margin-bottom: 20px; -} +// .header { +// color: $text-color; +// display: flex; +// align-items: center; +// justify-content: center; +// // margin-bottom: 20px; +// } -.gradebook-container { - /* Add a container for the gradebook tables */ - display: flex; - flex: 1 0 300px; - gap: 20px; /* Space between the tables */ - overflow: hidden; -} -.gradebook-container .table { - border-radius: 20px !important; - transform: translateZ(0); -} - -.table { - color: $text-color; - width: 100%; - overflow: hidden; - border-collapse: collapse; - border-radius: 20px; - background-color: $secondary-lighter; - table-layout: fixed; /* Key for even distribution */ - - - th, td { - - border: none; - border-radius: 0; - padding: 15px; - text-align: left; - width: 50%; /* Distribute columns evenly */ - overflow: hidden; /* Prevent content from overflowing */ - text-overflow: ellipsis; /* Add ellipsis for long names */ - } - - - th { - background-color: #f2f2f2; - font-weight: bold; - } - - .evenRow { - // background-color: if(type-of($secondary-lighter) == 'color', lighten($secondary-lighter, 5%), #e0e0e0); // Fallback to #e0e0e0 - background-color: $table-row-even; - } - - .oddRow { - // background-color: if(type-of($secondary-lighter) == 'color', darken($secondary-lighter, 5%), #d0d0d0); // Fallback to #d0d0d0 - background-color: $table-row-odd; - } - -} -.headerRow th { /* Style for the purple header row */ - background-color: $primary; /* Purple background */ - color: white; -} -.content{ - padding: 15px; +.assignmentName { text-align: left; } -.actual_button { - color: white; +.gradebook-container { display: flex; - align-items: flex-end; - border: 0; /* Primary button color */ - /* Button text color */ - padding: 10px 20px; /* Padding for button */ - border-radius: 5px; - font-weight: 600; - transition: background-color 0.3s ease; - margin : 0 10px; - background-color: $purple !important; + gap: 20px; + flex-wrap: wrap; +} +.category { + width: calc(100%/3 - 14px); } @media (max-width: 650px) { .pageWrapper { padding: 0 20px; } -} + .category { + width: calc(100%/2 - 14px); + } +} +@media (max-width: 450px) { + .category { + width: 100%; + } +} \ No newline at end of file diff --git a/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx b/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx index 3a4448fd..c7c4c2e6 100644 --- a/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx @@ -48,49 +48,47 @@ const GradebookStudentPage = () => { const categories = [...new Set(assignments.map(a => a.categoryName))]; return ( - +

Student Gradebook

- {role.isInstructor() &&( - )} -
- - {categories.map(category => ( -
-

{category}

-
## EmailExternal IDExternal IDPreferred Name Dropped{a.name}{a.name}
{/* Add table class */} - - {/* Add class for purple header */} + {categories.map(category => ( +
+

{category}

+ {/*
Add table class */} +
+ + {/* Add class for purple header */} - - - + {/* */} + + {assignments.filter(a => a.categoryName === category).map((assignment, index) => ( - - + ))} - -
Assignment Score
-
- {assignment.name}
- -
-
- {assignmentScores.find(aScore => aScore.assignmentId === assignment.id)?.score ?? 'N/A'} +
+ {assignment.name}
+ {/*
*/} + {assignmentScores.find(aScore => aScore.assignmentId === assignment.id)?.score ?? 'N/A'} + {/*
*/} +
-
- ))} + + +
+ ))}
); From ad02c8d11cd3dcc8341b43d71a3babdb2111193d Mon Sep 17 00:00:00 2001 From: yessicaq Date: Tue, 12 Nov 2024 15:11:54 -0500 Subject: [PATCH 296/400] removing the submit button from assignments that are past the due date --- .../assignments/assignmentDetailPage.tsx | 23 +++++++++++++++---- .../pages/courses/courseDetailPage.tsx | 4 ++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx index 08511170..2655a2c2 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.tsx @@ -81,7 +81,7 @@ const AssignmentDetailPage = () => { } catch (err:any) { setError(err) - const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message + const message = "Submission past due date"//Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message setAlert({autoDelete: false, type: 'error', message}) } finally { setLoading(false) @@ -146,6 +146,14 @@ const AssignmentDetailPage = () => { await fetchData() } } + const isSubmissionDisabled = () => { + if (assignment?.dueDate) { + const dueDate = new Date(assignment.dueDate); + const now = new Date(); + return now > dueDate; + } + return false; + }; return( @@ -172,7 +180,7 @@ const AssignmentDetailPage = () => {
{role.isInstructor() && } + }}>Add Assignment Question}
{role.isInstructor() && +
) : null} diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 7610f9d9..9d88a37d 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -99,8 +99,8 @@ const CourseDetailPage = () => {
-

CSE{courseInfo.number}: {courseInfo.name} ({courseInfo.semester})

-

Instructor:

+

{courseInfo.number}: {courseInfo.name} ({courseInfo.semester})

+

Section:

From e1776527081aa45169cef9fbf8f166fe99627fc4 Mon Sep 17 00:00:00 2001 From: RA341 Date: Tue, 12 Nov 2024 15:40:09 -0500 Subject: [PATCH 297/400] changed var name --- devU-api/src/entities/user/user.controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devU-api/src/entities/user/user.controller.ts b/devU-api/src/entities/user/user.controller.ts index 053e0de0..6b5cefce 100644 --- a/devU-api/src/entities/user/user.controller.ts +++ b/devU-api/src/entities/user/user.controller.ts @@ -68,12 +68,12 @@ export async function createNewAdmin(req: Request, res: Response, next: NextFunc // delete an admin, only an admin can delete an admin export async function deleteAdmin(req: Request, res: Response, next: NextFunction) { try { - let newAdminUserId = req.body.newAdminUserId - if (!newAdminUserId) { + let deleteAdminUserId = req.body.newAdminUserId + if (!deleteAdminUserId) { return res.status(404).send('Not found') } - await UserService.softDeleteAdmin(newAdminUserId) - res.status(204).send('User is no longer admin') + await UserService.softDeleteAdmin(deleteAdminUserId) + res.status(204) } catch (e) { next(e) } From e56eca8bf71130da558340f2dddcd308d4b7d120 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Tue, 12 Nov 2024 15:41:03 -0500 Subject: [PATCH 298/400] updated styling of homepage --- .../listItems/simpleAssignmentListItem.scss | 14 ++- .../listItems/userCourseListItem.scss | 43 ++++--- .../listItems/userCourseListItem.tsx | 46 +++---- .../forms/assignments/assignmentFormPage.scss | 1 + .../components/pages/homePage/homePage.scss | 88 +++++++------- .../components/pages/homePage/homePage.tsx | 112 +++++++++--------- 6 files changed, 157 insertions(+), 147 deletions(-) diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.scss b/devU-client/src/components/listItems/simpleAssignmentListItem.scss index d4cf36a2..1cd22c05 100644 --- a/devU-client/src/components/listItems/simpleAssignmentListItem.scss +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.scss @@ -7,7 +7,8 @@ border-radius: 0.6rem; justify-content: space-between; transition: background-color 0.2s linear; - color:$primary; + // color:$primary; + color: $text-color; } .meta { @@ -44,10 +45,11 @@ border-radius: 0.6rem; transition: background-color 0.2s linear; - color: #52468A; + // color: $primary; - &:hover, - &:focus { - background: $list-simple-item-background-hover; - } + color: $text-color; + // &:hover, + // &:focus { + // background: $list-simple-item-background-hover; + // } } \ No newline at end of file diff --git a/devU-client/src/components/listItems/userCourseListItem.scss b/devU-client/src/components/listItems/userCourseListItem.scss index 2ebb8dea..a2e5d25a 100644 --- a/devU-client/src/components/listItems/userCourseListItem.scss +++ b/devU-client/src/components/listItems/userCourseListItem.scss @@ -3,20 +3,20 @@ .name { font-size: 1.2rem; font-weight: 600; - margin: 0; - padding: 30px; /* Add padding to the text inside the name block */ - background: $primary; - width: 100%; - text-align: center; - color: $text-color; - border-radius: 5px 5px 0 0; + margin: 0; + padding: 30px; + /* Add padding to the text inside the name block */ + background: $primary; + width: 100%; + text-align: center; + color: #FFF; + border-radius: 20px 20px 0 0; box-sizing: border-box; text-overflow: ellipsis; overflow-wrap: break-word; } -.No_assignments -{ +.No_assignments { display: flex; justify-content: center; align-items: center; @@ -24,22 +24,26 @@ flex-grow: 1; font-size: 12px; font-weight: 600; - border-radius:5px; + border-radius: 5px; padding: 30px; flex-wrap: wrap; - color:$primary; + // color: $primary; + color: $text-color; text-overflow: ellipsis; overflow-wrap: break-word; } -.Buttons{ + +.Buttons { display: flex; justify-content: space-around; margin-top: auto; - padding:10px; + padding: 10px; } -.gradebook_button ,.coursepage_button { + +.gradebook_button, +.coursepage_button { border: 0; - color: $primary; + color: $text-color; background: none; font-weight: 600; margin-left: 15px; @@ -93,10 +97,11 @@ .subText { display: block; } + .Buttons { - flex-direction: row; - justify-content: space-between; + flex-direction: row; + justify-content: space-between; padding: 10px; - margin-top: auto; -} + margin-top: auto; + } } \ No newline at end of file diff --git a/devU-client/src/components/listItems/userCourseListItem.tsx b/devU-client/src/components/listItems/userCourseListItem.tsx index 18d5e668..a9090fde 100644 --- a/devU-client/src/components/listItems/userCourseListItem.tsx +++ b/devU-client/src/components/listItems/userCourseListItem.tsx @@ -1,6 +1,6 @@ import React from 'react' -import {Assignment, Course} from 'devu-shared-modules' -import {useHistory} from "react-router-dom"; +import { Assignment, Course } from 'devu-shared-modules' +import { useHistory } from "react-router-dom"; import ListItemWrapper from 'components/shared/layouts/listItemWrapper' //import {prettyPrintDate} from 'utils/date.utils' @@ -18,30 +18,30 @@ type Props = { } -const UserCourseListItem = ({course, assignments, past = false, instructor = false}: Props) => { +const UserCourseListItem = ({ course, assignments, past = false, instructor = false }: Props) => { const history = useHistory() - return( - - -
{instructor ? (course.name + " " + course.number + " (" + course.semester + ")" + " Instructor") : course.name.toUpperCase() + " " + course.number + " " + "(" + course.semester + ")" }
-
- {assignments && assignments.length > 0 ? (assignments.map((assignment) => ( - - ))) : ((past) ?
:
No Assignments Due Yet
)} -
- - -
+ return ( + + +
{instructor ? (course.name + " " + course.number + " (" + course.semester + ")" + " Instructor") : course.name.toUpperCase() + " " + course.number + " " + "(" + course.semester + ")"}
+
+ {assignments && assignments.length > 0 ? (assignments.map((assignment) => ( + + ))) : ((past) ?
:
No Assignments Due Yet
)} +
+ + +
-
+ -); + ); }; export default UserCourseListItem diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index 5d971aaa..e3ca442e 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -26,6 +26,7 @@ p { border-radius: 20px; padding: 30px; width: 50%; + height: fit-content; } .form>h2 { diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index da8e93f1..5ca2b808 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -2,14 +2,14 @@ -.courses_heading{ +.courses_heading { text-align: left; - margin-left: 20px; - font-size: 30px; - font-weight: 550; + // margin-left: 20px; + // font-size: 30px; + // font-weight: 550; margin-bottom: 30px; - } + // h1 { // align-items:left; // margin-left 20px; @@ -19,67 +19,73 @@ // } -.no_courses { - margin-left: 20px; -} +// .no_courses { +// margin-left: 20px; +// } .coursesContainer { display: flex; flex-direction: row; - gap: 20px; - margin:20px; + gap: 20px; + // margin: 20px; } + .courseCard { display: flex; flex-direction: column; justify-content: space-between; align-items: stretch; - width:400px; - padding: 0; - background-color: #D9D9D9; - border-radius: 5px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - overflow: hidden; + width: 400px; + padding: 0; + background-color: $list-item-background; + border-radius: 20px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + overflow: hidden; margin-bottom: 20px; - } -.create_course { - border:0; /* Primary button color */ - color: $primary; - padding: 10px 20px; - border-radius: 5px; - text-decoration: none; - font-weight: 600; - transition: background-color 0.3s ease; - margin:20px } + +// .create_course { +// border: 0; +// color: $primary; +// padding: 10px 20px; +// border-radius: 5px; +// text-decoration: none; +// font-weight: 600; +// transition: background-color 0.3s ease; +// margin: 20px +// } + .header { color: $text-color; display: block; - align-items: center; + align-items: center; } - -.courses_heading::after { - content: ''; +#createCourseBtn { display: block; - margin-top: 10px; - width: 100%; - height: 1px; - font-weight: 600; - background-color: $text-color; - } + margin-left: auto; +} +// .courses_heading::after { +// content: ''; +// display: block; +// margin-top: 10px; +// width: 100%; +// height: 1px; +// font-weight: 600; +// background-color: $text-color; +// } @media (max-width: 768px) { .coursesContainer { - flex-direction: column; - align-items: center; + flex-direction: column; + align-items: center; } .courseCard { - width: 100%; - max-width: none; + width: 100%; + max-width: none; } -} +} \ No newline at end of file diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 6d1a5614..69a97ee1 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -1,4 +1,4 @@ -import React, {useEffect, useState} from 'react' +import React, { useEffect, useState } from 'react' import PageWrapper from 'components/shared/layouts/pageWrapper' import LoadingOverlay from 'components/shared/loaders/loadingOverlay' @@ -6,17 +6,17 @@ import ErrorPage from '../errorPage/errorPage' import styles from './homePage.scss' import UserCourseListItem from "../../listItems/userCourseListItem"; -import {useAppSelector} from 'redux/hooks' +import { useAppSelector } from 'redux/hooks' import RequestService from 'services/request.service' -import {Assignment, Course} from 'devu-shared-modules' -import {useHistory} from "react-router-dom"; +import { Assignment, Course } from 'devu-shared-modules' +import { useHistory } from "react-router-dom"; const HomePage = () => { const userId = useAppSelector((store) => store.user.id) - + const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [enrollCourses, setEnrollCourses] = useState(new Array()) - const [upcomingCourses,setupcomingCourses] = useState(new Array()) + const [upcomingCourses, setupcomingCourses] = useState(new Array()) const [pastCourses, setPastCourses] = useState(new Array()) const [assignments, setAssignments] = useState(new Map>()) const [instructorCourses, setInstructorCourses] = useState(new Array()) @@ -26,7 +26,7 @@ const HomePage = () => { }, []) const fetchData = async () => { - + try { const assignmentMap = new Map>() const allCourses = await RequestService.get<{ @@ -39,14 +39,14 @@ const HomePage = () => { const upcomingCourses: Course[] = allCourses.upcomingCourses; const pastCourses: Course[] = allCourses.pastCourses; const instructorCourses: Course[] = allCourses.instructorCourses; - + const assignmentPromises = enrolledCourses.map((course) => { const assignments = RequestService.get(`/api/course/${course.id}/assignments/released`) return Promise.all([course, assignments]) }) const assignmentResults = await Promise.all(assignmentPromises) assignmentResults.forEach(([course, assignments]) => assignmentMap.set(course, assignments)) - + const assignmentPromises_instructor = instructorCourses.map((course) => { const assignments = RequestService.get(`/api/course/${course.id}/assignments/released`) return Promise.all([course, assignments]) @@ -59,7 +59,7 @@ const HomePage = () => { setEnrollCourses(enrolledCourses) setInstructorCourses(instructorCourses) setupcomingCourses(upcomingCourses) - + } catch (error: any) { setError(error) } finally { @@ -72,87 +72,83 @@ const HomePage = () => { if (error) return const history = useHistory(); const handleCourseClick = (courseId: any) => { - history.push(`/course/${courseId}`); // Assuming your course page route is '/course/:courseId' - } - return( + history.push(`/course/${courseId}`); // Assuming your course page route is '/course/:courseId' + } + return (
-
+ {/*
*/}

Courses

- -
-
-
-
-

Current

-
+ {/*
*/}
+ {/*
*/} + {/*
*/} +

Current

+ {/*
*/} + {/*
*/}
{instructorCourses.map((course) => ( -
handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> - -
+
handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> + +
))}
{enrollCourses && enrollCourses.map((course) => (
handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> + key={course.id} + onClick={() => handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> - +
))} {enrollCourses.length === 0 && instructorCourses.length == 0 &&

You do not have current enrollment yet

}
- -
-
-

Completed

-
-
- - + {/*
*/} + {/*
*/} +

Completed

+ {/*
*/} + {/*
*/}
{pastCourses && pastCourses.map((course) => ( -
handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> - handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> + -
+
))} {pastCourses.length === 0 &&

No completed courses

}
-
-
-

Upcoming

-
-
+ {/*
*/} + {/*
*/} +

Upcoming

+ {/*
*/} + {/*
*/} -
+
{upcomingCourses && upcomingCourses.map((course) => (
handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> - + onClick={() => handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> +
))} {upcomingCourses.length === 0 &&

No upcoming Courses

} -
- - - +
+ + + ) } From da9d955b5ceca289d5b56d4811e99b461ae66403 Mon Sep 17 00:00:00 2001 From: RA341 Date: Tue, 12 Nov 2024 15:46:31 -0500 Subject: [PATCH 299/400] changed admin unauthorized response --- devU-api/src/entities/user/user.middlware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devU-api/src/entities/user/user.middlware.ts b/devU-api/src/entities/user/user.middlware.ts index 3f323c93..5e046a5f 100644 --- a/devU-api/src/entities/user/user.middlware.ts +++ b/devU-api/src/entities/user/user.middlware.ts @@ -6,11 +6,11 @@ import UserService from './user.service' export async function isAdmin(req: Request, res: Response, next: NextFunction) { const userId = req.currentUser?.userId if (!userId) { - return res.status(403).send('Unauthorized') + return res.status(403).json({ 'error': 'Unauthorized' }) } const isAdmin = await UserService.isAdmin(userId) - if (!isAdmin!.isAdmin!) return res.status(403).send('unauthorized') + if (!isAdmin!.isAdmin!) return res.status(403).json({ "error": 'Unauthorized' }) next() } From 8db0978bd2f8ba71427686519d002953f6537016 Mon Sep 17 00:00:00 2001 From: Kevin Zhong <112502339+kevinzhong930@users.noreply.github.com> Date: Wed, 13 Nov 2024 10:32:08 -0500 Subject: [PATCH 300/400] Added file to create the sticky-notes table Added file to create the sticky-notes table. Fixed the issue with other migration file trying to add columns to a table that did not exist. --- .../migration/1731177885475-add-sticky-notes.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 devU-api/src/migration/1731177885475-add-sticky-notes.ts diff --git a/devU-api/src/migration/1731177885475-add-sticky-notes.ts b/devU-api/src/migration/1731177885475-add-sticky-notes.ts new file mode 100644 index 00000000..a8f8e291 --- /dev/null +++ b/devU-api/src/migration/1731177885475-add-sticky-notes.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddStickyNotes1731177885475 implements MigrationInterface { + name = 'AddStickyNotes1731177885475' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "sticky_notes" ("id" SERIAL NOT NULL, "submissionId" integer NOT NULL, "content" character varying NOT NULL, CONSTRAINT "PK_615fc1d0c6b75c46aa2a7dd5f0a" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "sticky_notes" ADD CONSTRAINT "FK_a92bd7a1dc5c606db210b176a43" FOREIGN KEY ("submissionId") REFERENCES "submissions"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "sticky_notes" DROP CONSTRAINT "FK_a92bd7a1dc5c606db210b176a43"`); + await queryRunner.query(`DROP TABLE "sticky_notes"`); + } + +} From ee844407bf6fadf873a19af1df4b9804c44e9de4 Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Wed, 29 Jan 2025 17:11:31 -0500 Subject: [PATCH 301/400] Update readme.md --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index f5599024..e564fce2 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,7 @@ # DevU +Figma https://www.figma.com/design/Ei3nuzCcGCtorXXYCZxiwS/DevU---Fall-2024?node-id=0-1&p=f&m=dev + DevU is an automated software-grading platform being developed at the University at Buffalo. DevU aims to be incredibly extensible, allowing professors to add any functionality they desire without reaching a dead end. It will eventually replace Autolab and other services. From 213ff2eb25509ecce97738b9d9f21ac2291dc66e Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Wed, 29 Jan 2025 17:19:25 -0500 Subject: [PATCH 302/400] Update readme.md --- readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index e564fce2..64faa847 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,7 @@ # DevU -Figma https://www.figma.com/design/Ei3nuzCcGCtorXXYCZxiwS/DevU---Fall-2024?node-id=0-1&p=f&m=dev +Dev Deployment: https://devu.app +Figma: https://www.figma.com/design/Ei3nuzCcGCtorXXYCZxiwS/DevU---Fall-2024?node-id=0-1&p=f&m=dev DevU is an automated software-grading platform being developed at the University at Buffalo. DevU aims to be incredibly extensible, allowing professors to add any functionality they desire without reaching a dead end. It will eventually From 2847c8fbcea049beb9d5d54600b3fd90f316565b Mon Sep 17 00:00:00 2001 From: Jesse Hartloff Date: Wed, 29 Jan 2025 17:19:36 -0500 Subject: [PATCH 303/400] Update readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 64faa847..fbbe0dc5 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,7 @@ # DevU Dev Deployment: https://devu.app + Figma: https://www.figma.com/design/Ei3nuzCcGCtorXXYCZxiwS/DevU---Fall-2024?node-id=0-1&p=f&m=dev DevU is an automated software-grading platform being developed at the University at Buffalo. DevU aims to be incredibly From 32ff1d9cdf804c6f0e5f2db64ddb19ba27012443 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 2 Feb 2025 06:48:21 -0500 Subject: [PATCH 304/400] refactored docker build: combined the nginx and client build into a single image removed config image (since it already a part of the api build) cleaned up docker compose files and removed package.json which was added by accident --- client.Dockerfile | 14 +++++--- docker-compose.yml | 28 ++------------- ...-compose.yml => example-docker-compose.yml | 20 ++++------- nginx.Dockerfile | 3 -- package-lock.json | 34 ------------------- package.json | 5 --- 6 files changed, 18 insertions(+), 86 deletions(-) rename prod-docker-compose.yml => example-docker-compose.yml (82%) delete mode 100644 nginx.Dockerfile delete mode 100644 package-lock.json delete mode 100644 package.json diff --git a/client.Dockerfile b/client.Dockerfile index e74eeddb..f8e19ec7 100644 --- a/client.Dockerfile +++ b/client.Dockerfile @@ -1,4 +1,4 @@ -FROM node:16 as module_builder +FROM node:16 AS module_builder WORKDIR /tmp @@ -8,13 +8,12 @@ RUN npm install && \ npm run clean-directory && \ npm run build-docker -FROM node:16 +FROM node:16 AS frontend WORKDIR /app COPY ./devU-client/package.json ./ - RUN npm install COPY ./devU-client/ . @@ -22,4 +21,11 @@ COPY ./devU-client/ . COPY --from=module_builder /tmp/devu-shared-modules ./devu-shared-modules RUN npm run build-local -CMD cp -r /app/dist/* /out \ No newline at end of file + +# final stage serve frontend files +FROM nginx:1.23.3 + +COPY nginx.conf /etc/nginx/nginx.conf + +# Copy built frontend files to nginx serving directory +COPY --from=frontend /app/dist/local /usr/share/nginx/html diff --git a/docker-compose.yml b/docker-compose.yml index 24c8afa4..4a7e1de6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,24 +15,11 @@ services: - '' # so it starts with normal docker compose - 'dev-client' # start when developing client - client: - # Builds the front end and exports static files to ./dist + client-nginx: + # Builds the front end and serves the frontend static files using nginx build: dockerfile: client.Dockerfile context: . - volumes: - - ./dist:/out - profiles: - - '' # so it starts with normal docker compose - - 'dev-api' # start when developing api - - nginx: - # Hosts the front end static files from ./dist/local thorough a web server - build: - context: . - dockerfile: nginx.Dockerfile - volumes: - - ./dist/local:/usr/share/nginx/html ports: - '9000:80' profiles: @@ -67,17 +54,6 @@ services: MINIO_ROOT_PASSWORD: changeMe command: server /data --console-address ":9001" - config: - # Generates a config file for the API - build: - context: devU-api/scripts - command: "./generateConfig.sh config/default.yml" - image: ubautograding/devtools - volumes: - - ./devU-api/config:/config - profiles: - - 'dev-api' # start when developing api, not needed in production - # tango stuff tango: container_name: tango diff --git a/prod-docker-compose.yml b/example-docker-compose.yml similarity index 82% rename from prod-docker-compose.yml rename to example-docker-compose.yml index 926d8b13..97b03dc2 100644 --- a/prod-docker-compose.yml +++ b/example-docker-compose.yml @@ -1,12 +1,12 @@ -# example production compose file, -# remember to copy tango.config.py to the working directory from where the compose file is ran +# example compose file +# remember to copy tango.config.py to the working directory from where the compose file is run name: devu services: api: # Runs the API container_name: api - image: ghcr.io/makeopensource/devu-api:beta + image: ghcr.io/makeopensource/devu-api:dev environment: TANGO_KEY: devutangokey # TODO: load in from env file. for now this is defined in tango section below WAIT_HOSTS: db:5432 @@ -19,16 +19,8 @@ services: - '3001:3001' client: - # Builds the front end and exports static files to ./dist - image: ghcr.io/makeopensource/devu-client:beta - volumes: - - ./dist:/out - - nginx: - # Hosts the front end static files from ./dist/local thorough a web server - image: ghcr.io/makeopensource/devu-nginx:beta - volumes: - - ./dist/local:/usr/share/nginx/html + # Builds the front end and serves the frontend static files using nginx + image: ghcr.io/makeopensource/devu-client:dev ports: - '9000:80' @@ -72,7 +64,7 @@ services: container_name: tango ports: - '127.0.0.1:3000:3000' - image: ghcr.io/makeopensource/devu-tango:beta + image: ghcr.io/makeopensource/devu-tango:dev environment: - DOCKER_REDIS_HOSTNAME=redis - RESTFUL_KEY=devutangokey diff --git a/nginx.Dockerfile b/nginx.Dockerfile deleted file mode 100644 index b44fd039..00000000 --- a/nginx.Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -FROM nginx:1.23.3 - -COPY nginx.conf /etc/nginx/nginx.conf \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 84cd429b..00000000 --- a/package-lock.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "devU", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "@types/react": "^18.3.12" - } - }, - "node_modules/@types/prop-types": { - "version": "15.7.13", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "dev": true - }, - "node_modules/@types/react": { - "version": "18.3.12", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", - "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", - "dev": true, - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index ab744a68..00000000 --- a/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "devDependencies": { - "@types/react": "^18.3.12" - } -} From 9059050d0ab97a6f64c374326ed6c292746e4e28 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 2 Feb 2025 06:48:49 -0500 Subject: [PATCH 305/400] refactored ci: refactored and cleaned up workflow --- .github/workflows/{beta.yml => dev.yml} | 23 ++++++++--------------- .github/workflows/release.yml | 24 +++++++++--------------- 2 files changed, 17 insertions(+), 30 deletions(-) rename .github/workflows/{beta.yml => dev.yml} (72%) diff --git a/.github/workflows/beta.yml b/.github/workflows/dev.yml similarity index 72% rename from .github/workflows/beta.yml rename to .github/workflows/dev.yml index 2c1f9ed9..e8453764 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/dev.yml @@ -1,7 +1,7 @@ # Builds DevU images on develop -# tagged as beta +# tagged as develop -name: Build DevU beta +name: Build DevU develop on: push: branches: @@ -36,24 +36,17 @@ jobs: original_string=${{ github.repository }} echo "repo_url=$(echo $original_string | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - # beta tags - - name: API beta image + - name: api dev image run: | - docker build . -f api.Dockerfile -t ghcr.io/${{ env.repo_url }}-api:beta - docker push ghcr.io/${{ env.repo_url }}-api:beta + docker build . -f api.Dockerfile -t ghcr.io/${{ env.repo_url }}-api:beta --push + docker push ghcr.io/${{ env.repo_url }}-api:dev - - name: client beta image + - name: client dev image run: | docker build . -f client.Dockerfile -t ghcr.io/${{ env.repo_url }}-client:beta - docker push ghcr.io/${{ env.repo_url }}-client:beta - - - name: nginx beta image - run: | - docker build . -f nginx.Dockerfile -t ghcr.io/${{ env.repo_url }}-nginx:beta - docker push ghcr.io/${{ env.repo_url }}-nginx:beta + docker push ghcr.io/${{ env.repo_url }}-client:dev - name: build tango image run: | docker build ./tango -f ./tango/Dockerfile -t ghcr.io/${{ env.repo_url }}-tango:beta - docker push ghcr.io/${{ env.repo_url }}-tango:beta - + docker push ghcr.io/${{ env.repo_url }}-tango:dev diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3165d9b3..fb8c7f54 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,30 +67,24 @@ jobs: - name: Login to GHCR registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - name: API latest and version image + - name: ap latest and version image run: | - docker build . -f api.Dockerfile -t ghcr.io/${{ env.repo_url }}-api:${{ steps.tagName.outputs.tag }} - docker push ghcr.io/${{ env.repo_url }}-api:${{ steps.tagName.outputs.tag }} + docker build . -f api.Dockerfile \ + -t ghcr.io/${{ env.repo_url }}-api:${{ steps.tagName.outputs.tag }} \ + -t ghcr.io/${{ env.repo_url }}-api:latest - docker build . -f api.Dockerfile -t ghcr.io/${{ env.repo_url }}-api:latest + docker push ghcr.io/${{ env.repo_url }}-api:${{ steps.tagName.outputs.tag }} docker push ghcr.io/${{ env.repo_url }}-api:latest - name: client latest and version image run: | - docker build . -f client.Dockerfile -t ghcr.io/${{ env.repo_url }}-client:${{ steps.tagName.outputs.tag }} + docker build . -f client.Dockerfile \ + -t ghcr.io/${{ env.repo_url }}-client:${{ steps.tagName.outputs.tag }} \ + -t ghcr.io/${{ env.repo_url }}-client:latest + docker push ghcr.io/${{ env.repo_url }}-client:${{ steps.tagName.outputs.tag }} - - docker build . -f client.Dockerfile -t ghcr.io/${{ env.repo_url }}-client:latest docker push ghcr.io/${{ env.repo_url }}-client:latest - - name: nginx latest and version image - run: | - docker build . -f nginx.Dockerfile -t ghcr.io/${{ env.repo_url }}-nginx:${{ steps.tagName.outputs.tag }} - docker push ghcr.io/${{ env.repo_url }}-nginx:${{ steps.tagName.outputs.tag }} - - docker build . -f nginx.Dockerfile -t ghcr.io/${{ env.repo_url }}-nginx:latest - docker push ghcr.io/${{ env.repo_url }}-nginx:latest - - name: tango latest and version image run: | docker build ./tango -f ./tango/Dockerfile -t ghcr.io/${{ env.repo_url }}-tango:latest From 71e3fe17f7d1d4b2a7e018d6c6cdffbe0e27dc25 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Tue, 4 Feb 2025 13:37:55 -0500 Subject: [PATCH 306/400] fixed some padding issues on homepage, centered navbar logo, and cleaned up course page with consistently padded buttons. --- .../src/components/misc/globalToolbar.scss | 3 ++- .../pages/courses/courseDetailPage.scss | 17 +++++++---------- .../pages/courses/courseDetailPage.tsx | 4 ++-- .../src/components/pages/homePage/homePage.scss | 2 +- devU-client/src/index.html | 2 +- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 314d0a8c..450a45f4 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -29,7 +29,8 @@ $font-size: 16px; font-size: 32px; text-decoration: none; color: #FFF; - font-weight: 500; + font-weight: 700; + height: 51px; // Janky but makes the logo text properly vertically centered. Could probably be done better some other way } .bar { diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index fa1ebff2..0896788f 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -16,10 +16,10 @@ } -.color{ +.courseCardHeading{ background-color: $primary; color: #FFF; // always white against purple bg - width: 100%; + width: inherit; padding: 20px; text-align: center; font-size: 1.2em; @@ -87,22 +87,19 @@ padding: 20px; //border-radius: 10px; color: $text-color; - width: 70% + width: fit-content; } h1 { - margin: 0 0 10px 0; font-size: 1.5rem; text-align: left; font-weight: bold; - + margin: 5px 5px 5px 10px; } - .h2 { - padding-top: 20px; - margin: 0 0 20px 0; + h2 { font-size: 1.0rem; - padding-bottom: 10px; + margin: 5px 5px 5px 10px; } h3{ @@ -129,7 +126,7 @@ border-radius: 5px; font-weight: 600; transition: background-color 0.3s ease; - margin : 0 10px; + margin : 10px; background-color: $purple !important; color: white !important; } diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 5804beac..c0b7ed84 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -103,7 +103,7 @@ const CourseDetailPage = () => {

Section:

-
+
@@ -157,7 +157,7 @@ const CourseDetailPage = () => { - + {category} diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index 5ca2b808..ddb5e61e 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -7,7 +7,7 @@ // margin-left: 20px; // font-size: 30px; // font-weight: 550; - margin-bottom: 30px; + margin-bottom: 20px; } // h1 { diff --git a/devU-client/src/index.html b/devU-client/src/index.html index 1bc42081..2a40e4bc 100644 --- a/devU-client/src/index.html +++ b/devU-client/src/index.html @@ -7,7 +7,7 @@ name="viewport" content="initial-scale=1.0, minimum-scale=1.0, maximum-scale=6.0, shrink-to-fit=no, user-scalable=yes, width=device-width" /> - AutoFour + DevU From b5dee9ae35184d9fcc2b1b8b1a7b479ef525d4d2 Mon Sep 17 00:00:00 2001 From: dvcabiya <138388443+dvcabiya@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:16:37 -0500 Subject: [PATCH 307/400] Added submodule step to the README, since me and Chris both ran into this while setting up. If this ought to be a seperate branch or something let me know --- readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.md b/readme.md index fbbe0dc5..0a588127 100644 --- a/readme.md +++ b/readme.md @@ -97,6 +97,11 @@ cd devU docker compose up --build ``` +If the project doesn't run due to a missing dockerfile, run the following command, then repeat the docker compose step. +```bash +git submodule update --init --recursive +``` + ### 4. Connect to the Web Client In your browser, connect to `http://localhost:9000`, and you should see the login screen. From b52f5c908dd6ceeb1ebf8bd9302b9c97d457f753 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Wed, 5 Feb 2025 12:18:19 -0500 Subject: [PATCH 308/400] increased logo weight, fixed up margins on homepage and assignment page, made card border-radius consistent --- devU-client/src/assets/global.scss | 2 +- .../components/pages/courses/courseDetailPage.scss | 9 ++++----- .../components/pages/courses/courseDetailPage.tsx | 4 ++-- .../src/components/pages/homePage/homePage.scss | 12 +++++------- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index af771fb3..29e4bce7 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -14,7 +14,7 @@ } h1 { - margin: 1.75rem auto; + margin: 20px auto; text-align: center; } diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index 0896788f..437b03a7 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -43,9 +43,9 @@ height: 100%; padding: 0; background-color: rebeccapurple; - border-radius: 5px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); overflow: hidden; + border-radius: 20px; margin-bottom: 20px; .MuiList-root { @@ -83,9 +83,8 @@ justify-content: space-between; align-items: flex-start; background-color: $secondary; - // padding: 30px 60px; padding: 20px; - //border-radius: 10px; + border-radius: 20px; color: $text-color; width: fit-content; } @@ -94,12 +93,12 @@ font-size: 1.5rem; text-align: left; font-weight: bold; - margin: 5px 5px 5px 10px; + margin: 0 15px 0 10px; } h2 { font-size: 1.0rem; - margin: 5px 5px 5px 10px; + margin-left: 10px; } h3{ diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index c0b7ed84..fa522019 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -155,9 +155,9 @@ const CourseDetailPage = () => {
{Object.keys(categoryMap).map((category, index) => ( - + - + {category} diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index ddb5e61e..198523f7 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -4,10 +4,9 @@ .courses_heading { text-align: left; - // margin-left: 20px; + margin: 20px 0; // font-size: 30px; // font-weight: 550; - margin-bottom: 20px; } // h1 { @@ -19,9 +18,9 @@ // } -// .no_courses { -// margin-left: 20px; -// } + .no_courses { + margin: 0 0 10px 0; + } @@ -29,7 +28,7 @@ display: flex; flex-direction: row; gap: 20px; - // margin: 20px; + margin-bottom: 10px; } .courseCard { @@ -43,7 +42,6 @@ border-radius: 20px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); overflow: hidden; - margin-bottom: 20px; } // .create_course { From 0b78918292f096ac2f12bc80c445c357c22d689e Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Sat, 8 Feb 2025 15:27:00 -0500 Subject: [PATCH 309/400] fixed weird margin thing i put in thinking it was good, and the annoying missing space on the course page for assignment due date --- devU-client/src/components/pages/courses/courseDetailPage.tsx | 4 +--- devU-client/src/components/pages/homePage/homePage.scss | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index fa522019..f2b80676 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -182,9 +182,7 @@ const CourseDetailPage = () => { variant="body2" color="grey" > - Start: {new Date(assignment.startDate).toLocaleDateString()} - | - Due: {new Date(assignment.dueDate).toLocaleDateString()} + Start: {new Date(assignment.startDate).toLocaleDateString()} | Due: {new Date(assignment.dueDate).toLocaleDateString()} } diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index 198523f7..8e4aafe1 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -28,7 +28,6 @@ display: flex; flex-direction: row; gap: 20px; - margin-bottom: 10px; } .courseCard { From 36d38ce3b1798f929ef406fd11c90ee8f868c2dc Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Mon, 10 Feb 2025 11:40:06 -0500 Subject: [PATCH 310/400] switched up padding/spacing for course card assignment list, set global padding rules, switched heading in assignment creation from h2->h1 --- devU-client/src/assets/global.scss | 1 + .../src/components/listItems/simpleAssignmentListItem.scss | 6 ++---- .../src/components/listItems/userCourseListItem.scss | 3 +-- .../pages/forms/assignments/assignmentFormPage.tsx | 2 +- devU-client/src/components/pages/homePage/homePage.scss | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 29e4bce7..d9a63a80 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -19,6 +19,7 @@ } h2 { + margin: 20px 0; text-align: center; } diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.scss b/devU-client/src/components/listItems/simpleAssignmentListItem.scss index 1cd22c05..64774e02 100644 --- a/devU-client/src/components/listItems/simpleAssignmentListItem.scss +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.scss @@ -1,10 +1,9 @@ @import 'variables'; .title { - padding: 10px; + padding: 20px 30px; display: flex; flex-direction: row; - border-radius: 0.6rem; justify-content: space-between; transition: background-color 0.2s linear; // color:$primary; @@ -25,14 +24,13 @@ display: grid; justify-content: space-between; gap: 1.5rem; - width: 100%; + width: 25%; } .tag { display: flex; min-width: 8px; background-color: $primary; - margin-top: 10px; } .container { diff --git a/devU-client/src/components/listItems/userCourseListItem.scss b/devU-client/src/components/listItems/userCourseListItem.scss index a2e5d25a..ac1b9c16 100644 --- a/devU-client/src/components/listItems/userCourseListItem.scss +++ b/devU-client/src/components/listItems/userCourseListItem.scss @@ -37,7 +37,7 @@ display: flex; justify-content: space-around; margin-top: auto; - padding: 10px; + padding: 20px 30px; } .gradebook_button, @@ -101,7 +101,6 @@ .Buttons { flex-direction: row; justify-content: space-between; - padding: 10px; margin-top: auto; } } \ No newline at end of file diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index 3e4c935d..b1a9702c 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -122,7 +122,7 @@ const AssignmentCreatePage = () => { return ( -

Create Assignment

+

Create Assignment

Assignment Information

diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index 8e4aafe1..ae96c556 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -19,7 +19,7 @@ .no_courses { - margin: 0 0 10px 0; + margin: 0; } From 7f90b881ba06968922f1fc4180d06bfce4401e78 Mon Sep 17 00:00:00 2001 From: SameepK Date: Mon, 10 Feb 2025 13:19:52 -0500 Subject: [PATCH 311/400] Make the improvements of color scheme, aesthetics and added the error and text field css class --- .DS_Store | Bin 6148 -> 6148 bytes devU-client/package.json | 10 +-- devU-client/src/assets/global.scss | 82 ++++++++++++++++-- devU-client/src/assets/variables.scss | 20 +++++ .../components/listItems/courseListItem.scss | 3 +- .../listItems/simpleAssignmentListItem.scss | 3 +- .../listItems/submissionListItem.scss | 3 +- .../listItems/userAssignmentListItem.scss | 2 +- .../listItems/userCourseListItem.scss | 2 +- .../src/components/misc/globalToolbar.scss | 4 +- devU-client/src/components/misc/navbar.scss | 2 +- .../assignments/assignmentDetailPage.scss | 2 +- .../pages/assignments/scoreboard.scss | 3 +- .../src/components/pages/authProvider.scss | 2 +- .../pages/courses/courseDetailPage.scss | 3 +- .../components/pages/errorPage/errorPage.scss | 2 +- .../forms/assignments/assignmentFormPage.scss | 3 +- .../assignments/assignmentUpdatePage.scss | 5 +- .../containers/containerAutoGraderForm.scss | 2 +- .../nonContainerAutoGraderForm.scss | 3 +- .../pages/forms/courses/coursesFormPage.scss | 2 +- .../pages/gradebook/gradebookPage.scss | 2 +- .../components/pages/homePage/homePage.scss | 3 +- .../listPages/courses/coursesListPage.scss | 3 +- .../pages/submissions/Submissionspage.scss | 3 +- .../submissions/submissionDetailPage.scss | 3 +- .../src/components/pages/webhookURLForm.scss | 3 +- .../src/components/shared/alerts/alert.scss | 3 +- .../shared/errors/validationErrorViewer.scss | 3 +- .../src/components/shared/inputs/button.scss | 3 +- .../components/shared/inputs/checkbox.scss | 3 +- .../shared/inputs/faIconButton.scss | 3 +- .../shared/inputs/radioButtons.scss | 3 +- .../src/components/shared/inputs/toggle.scss | 3 +- .../shared/layouts/listItemWrapper.scss | 3 +- .../shared/loaders/loadingOverlay.scss | 2 +- .../shared/loaders/spinningSquares.scss | 2 +- .../src/components/utils/dragDropFile.scss | 3 +- .../components/utils/userOptionsDropdown.scss | 2 +- devU-client/webpack.config.js | 2 +- 40 files changed, 159 insertions(+), 51 deletions(-) diff --git a/.DS_Store b/.DS_Store index 4dcf6bbbef6078d240b70cd86cd79f533f5bd663..5ba5e0f5c0d30c6beae763b5435e1bd299e872f9 100644 GIT binary patch delta 53 zcmZoMXfc@J&nUGqU^g?P)Mg%*U5uP5#mPBI`T04Flh?3Hai#&eVBY36tfEYt**X65 F0|3iU5flIb delta 32 ocmZoMXfc@J&nU4mU^g?P#AY6rU5uM~*nTrkEO6V*&heKY0IMPjjQ{`u diff --git a/devU-client/package.json b/devU-client/package.json index 743abc69..7bfe63fa 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -10,8 +10,8 @@ "build-local": "cross-env NODE_ENV=local webpack --mode=production", "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", "pre-commit": "lint-staged", - "dev-backend": "docker compose -f ../compose.yml --profile dev-client up -d", - "dev-backend-stop": "docker compose -f ../compose.yml --profile dev-client stop" + "dev-backend": "docker compose -f ../docker-compose.yml --profile dev-client up -d", + "dev-backend-stop": "docker compose -f ../docker-compose.yml --profile dev-client stop" }, "lint-staged": { "./**/*.{js,ts,json,md}": [ @@ -33,7 +33,7 @@ "@mui/x-date-pickers": "^7.19.0", "color-hash": "^2.0.1", "dayjs": "^1.11.13", - "devu-shared-modules": "./devu-shared-modules", + "devu-shared-modules": "file:devu-shared-modules", "history": "^4.10.1", "moment": "^2.29.1", "npm-run-all": "^4.1.5", @@ -74,8 +74,8 @@ "husky": "^6.0.0", "lint-staged": "^11.0.0", "prettier": "^2.3.0", - "sass": "^1.34.0", - "sass-loader": "^10.2.0", + "sass": "^1.84.0", + "sass-loader": "^16.0.4", "style-loader": "^2.0.0", "ts-loader": "^8.0.15", "webpack": "^5.91.0", diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index af771fb3..ffa051c5 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -1,9 +1,9 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; -:global { +:global { // Libraries // Import css libraries here as needed - @import '~react-toggle/style'; + @use 'react-toggle/style'; html, body, @@ -49,7 +49,72 @@ color: var(--btn-delete-text); border: 3px solid var(--btn-delete-border); } + // General Error Message +.error-message { + color: var(--error-text); + font-size: 0.875rem; + font-weight: bold; + margin-top: 5px; +} + +// Validation Error Container +.error-container { + background-color: var(--error-background); + border-left: 5px solid var(--error-text); + padding: 10px; + margin: 10px 0; + border-radius: 5px; +} + +// Error Page Styling (From `errorPage.scss`) +.error-page { + background-color: var(--error-page-background); + display: flex; + align-items: center; + justify-content: center; + height: 100vh; +} + +.error-heading { + font-size: 2rem; + color: var(--error-text); +} + +.error-description { + font-size: 1.2rem; + color: var(--text-color); +} + +// Global Input Field Styles +.input-field { + width: 100%; + padding: 10px 15px; + font-size: 1rem; + color: var(--text-color); + background-color: var(--input-field-background); + border: 1px solid var(--input-border); + border-radius: var(--border-radius); + transition: border-color 0.3s ease; +} + + &:focus { + border-color: var(--focus); + outline: none; + } + + &:disabled { + background-color: var(--grey-lightest); + color: var(--grey-lighter); + cursor: not-allowed; + } +} + +// Placeholder Styling +::placeholder { + color: var(--text-color-secondary); + font-style: italic; +} body { font-family: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif; margin: 0; @@ -64,7 +129,7 @@ --text-color-secondary: #363636; --focus: var(--blue); - --primary: var(--purple); + --primary-color: var(--purple); --secondary-lighter: #cfd1d1; --secondary: #a8abab; @@ -121,6 +186,12 @@ --yellow-dark: #664600; --yellow: #F8D487; + --cyan: #00587E; + --brown-lighter: #876212; + --maroon: #8a2626; + + + transition: background-color 150ms linear; } @@ -167,5 +238,4 @@ *:focus { outline-color: var(--focus); - } -} + } \ No newline at end of file diff --git a/devU-client/src/assets/variables.scss b/devU-client/src/assets/variables.scss index b503f783..5f553209 100644 --- a/devU-client/src/assets/variables.scss +++ b/devU-client/src/assets/variables.scss @@ -31,6 +31,7 @@ $table-row-odd: var(--table-row-odd); $focus: var(--focus); + // These variables WILL NOT update with dark vs light theme $grey-lightest: var(--grey-lightest); $grey-lighter: var(--grey-lighter); @@ -42,6 +43,8 @@ $blue: var(--blue); $red-lighter: var(--red-lighter); $red: var(--red); +$brown-lighter: var(--brown-lighter); + $purple-lighter: var(--purple-lighter); $purple: var(--purple); $purple-darker: var(--purple-darker); @@ -55,7 +58,24 @@ $yellow: var(--yellow); $redText: var(--red-text); $yellowText: var(--yellow-text); +$maroon: var(--maroon); +$cyan: var(--cyan); + + +// Text Input Colors +$input-border: var(--input-border); +$input-field-background: var(--input-field-background); +$text-color-secondary: var(--text-color-secondary); + +// Error Colors +$error-text: var(--error-text); +$error-background: var(--error-background); +$error-page-background: var(--error-page-background); + + + // Non color CSS variables + $border-radius: 3px; $box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); diff --git a/devU-client/src/components/listItems/courseListItem.scss b/devU-client/src/components/listItems/courseListItem.scss index e8449c52..e23448a1 100644 --- a/devU-client/src/components/listItems/courseListItem.scss +++ b/devU-client/src/components/listItems/courseListItem.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .name { font-size: 1.2rem; diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.scss b/devU-client/src/components/listItems/simpleAssignmentListItem.scss index 1cd22c05..a82a22fc 100644 --- a/devU-client/src/components/listItems/simpleAssignmentListItem.scss +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .title { padding: 10px; diff --git a/devU-client/src/components/listItems/submissionListItem.scss b/devU-client/src/components/listItems/submissionListItem.scss index 23dce9d8..206da7f4 100644 --- a/devU-client/src/components/listItems/submissionListItem.scss +++ b/devU-client/src/components/listItems/submissionListItem.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .course { font-size: 1.25rem; diff --git a/devU-client/src/components/listItems/userAssignmentListItem.scss b/devU-client/src/components/listItems/userAssignmentListItem.scss index da559773..83afd0eb 100644 --- a/devU-client/src/components/listItems/userAssignmentListItem.scss +++ b/devU-client/src/components/listItems/userAssignmentListItem.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .name { font-size: 1.2rem; diff --git a/devU-client/src/components/listItems/userCourseListItem.scss b/devU-client/src/components/listItems/userCourseListItem.scss index a2e5d25a..e048811b 100644 --- a/devU-client/src/components/listItems/userCourseListItem.scss +++ b/devU-client/src/components/listItems/userCourseListItem.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .name { font-size: 1.2rem; diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 314d0a8c..1d7d24d6 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; $bar-height: 60px; $sidebar-width: 260px; @@ -106,4 +106,4 @@ $font-size: 16px; gap: 0.3rem; } -} +} \ No newline at end of file diff --git a/devU-client/src/components/misc/navbar.scss b/devU-client/src/components/misc/navbar.scss index 2fe47743..6f5ac94e 100644 --- a/devU-client/src/components/misc/navbar.scss +++ b/devU-client/src/components/misc/navbar.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .breadcrumbContainer { display: flex; diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss index aa3e8e49..a888493c 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .wrap { display:flex; diff --git a/devU-client/src/components/pages/assignments/scoreboard.scss b/devU-client/src/components/pages/assignments/scoreboard.scss index 2de1f3ef..151ece38 100644 --- a/devU-client/src/components/pages/assignments/scoreboard.scss +++ b/devU-client/src/components/pages/assignments/scoreboard.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .scoreboardTable { width: 100%; diff --git a/devU-client/src/components/pages/authProvider.scss b/devU-client/src/components/pages/authProvider.scss index a9b9b477..e72f3644 100644 --- a/devU-client/src/components/pages/authProvider.scss +++ b/devU-client/src/components/pages/authProvider.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .page { display: flex; diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index fa1ebff2..e4cab5eb 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -1,5 +1,4 @@ -@import 'variables'; - +@use 'src/assets/styles/variables' as *; .courseFormWrapper { display: flex; margin: 16px 50px; diff --git a/devU-client/src/components/pages/errorPage/errorPage.scss b/devU-client/src/components/pages/errorPage/errorPage.scss index 585de985..4fa10a2b 100644 --- a/devU-client/src/components/pages/errorPage/errorPage.scss +++ b/devU-client/src/components/pages/errorPage/errorPage.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .errorBackground { width: 100%; diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index e3ca442e..340c7146 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + h2 { diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss index bf048d61..d014999d 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -1,9 +1,10 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .grid { display: grid; grid-template-columns: 0.3fr 1fr 0.5fr; - grid-template-rows: 1fr 0.75fr; + grid-template-rows: 1f + r 0.75fr; grid-column-gap: 10px; grid-row-gap: 10px; width: 100%; diff --git a/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.scss b/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.scss index 7a7edfa4..3f9c84ce 100644 --- a/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.scss +++ b/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .form { display: absolute; diff --git a/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.scss b/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.scss index 9b26996e..0992d87b 100644 --- a/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.scss +++ b/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .form { display: absolute; diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss index 750743d2..377fbce6 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .courseFormWrapper { display: flex; diff --git a/devU-client/src/components/pages/gradebook/gradebookPage.scss b/devU-client/src/components/pages/gradebook/gradebookPage.scss index 1c63eb30..0e9446e6 100644 --- a/devU-client/src/components/pages/gradebook/gradebookPage.scss +++ b/devU-client/src/components/pages/gradebook/gradebookPage.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .categoryName { color: $text-color; diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index 5ca2b808..c727afe4 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + diff --git a/devU-client/src/components/pages/listPages/courses/coursesListPage.scss b/devU-client/src/components/pages/listPages/courses/coursesListPage.scss index e279b8ac..2e75daf2 100644 --- a/devU-client/src/components/pages/listPages/courses/coursesListPage.scss +++ b/devU-client/src/components/pages/listPages/courses/coursesListPage.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .addCourseBtn { diff --git a/devU-client/src/components/pages/submissions/Submissionspage.scss b/devU-client/src/components/pages/submissions/Submissionspage.scss index 823d9f3c..1658100d 100644 --- a/devU-client/src/components/pages/submissions/Submissionspage.scss +++ b/devU-client/src/components/pages/submissions/Submissionspage.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .submissionsTable { width: 100%; border-collapse: collapse; diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.scss b/devU-client/src/components/pages/submissions/submissionDetailPage.scss index 7d5e4a55..760f0273 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.scss +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .heading { text-align: center; diff --git a/devU-client/src/components/pages/webhookURLForm.scss b/devU-client/src/components/pages/webhookURLForm.scss index b0b7f5ee..403f066c 100644 --- a/devU-client/src/components/pages/webhookURLForm.scss +++ b/devU-client/src/components/pages/webhookURLForm.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .textField { align-items: center; diff --git a/devU-client/src/components/shared/alerts/alert.scss b/devU-client/src/components/shared/alerts/alert.scss index 727a4de1..b6bd908f 100644 --- a/devU-client/src/components/shared/alerts/alert.scss +++ b/devU-client/src/components/shared/alerts/alert.scss @@ -1,5 +1,4 @@ -@import 'variables'; - +@use 'src/assets/styles/variables' as *; .container { display: flex; justify-content: center; diff --git a/devU-client/src/components/shared/errors/validationErrorViewer.scss b/devU-client/src/components/shared/errors/validationErrorViewer.scss index 30b13710..d2f9b2ea 100644 --- a/devU-client/src/components/shared/errors/validationErrorViewer.scss +++ b/devU-client/src/components/shared/errors/validationErrorViewer.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .errorContainer { background: $red; diff --git a/devU-client/src/components/shared/inputs/button.scss b/devU-client/src/components/shared/inputs/button.scss index ed7e4f38..8244c329 100644 --- a/devU-client/src/components/shared/inputs/button.scss +++ b/devU-client/src/components/shared/inputs/button.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .defaultButton { background-color: $primary; diff --git a/devU-client/src/components/shared/inputs/checkbox.scss b/devU-client/src/components/shared/inputs/checkbox.scss index ae04f81f..aef16e9e 100644 --- a/devU-client/src/components/shared/inputs/checkbox.scss +++ b/devU-client/src/components/shared/inputs/checkbox.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + $box-size: 16px; diff --git a/devU-client/src/components/shared/inputs/faIconButton.scss b/devU-client/src/components/shared/inputs/faIconButton.scss index 53091b7b..b9d2a8b3 100644 --- a/devU-client/src/components/shared/inputs/faIconButton.scss +++ b/devU-client/src/components/shared/inputs/faIconButton.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .button { background: transparent; diff --git a/devU-client/src/components/shared/inputs/radioButtons.scss b/devU-client/src/components/shared/inputs/radioButtons.scss index d274c17d..15192673 100644 --- a/devU-client/src/components/shared/inputs/radioButtons.scss +++ b/devU-client/src/components/shared/inputs/radioButtons.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + $outer-circle: 18px; $inner-circle: 12px; diff --git a/devU-client/src/components/shared/inputs/toggle.scss b/devU-client/src/components/shared/inputs/toggle.scss index 1c915bd6..f86d3b08 100644 --- a/devU-client/src/components/shared/inputs/toggle.scss +++ b/devU-client/src/components/shared/inputs/toggle.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .toggle { display: flex; diff --git a/devU-client/src/components/shared/layouts/listItemWrapper.scss b/devU-client/src/components/shared/layouts/listItemWrapper.scss index 4cbb8454..f2840024 100644 --- a/devU-client/src/components/shared/layouts/listItemWrapper.scss +++ b/devU-client/src/components/shared/layouts/listItemWrapper.scss @@ -1,4 +1,5 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; + .tag { min-width: 8px; diff --git a/devU-client/src/components/shared/loaders/loadingOverlay.scss b/devU-client/src/components/shared/loaders/loadingOverlay.scss index acfc2ec6..1a49b6f4 100644 --- a/devU-client/src/components/shared/loaders/loadingOverlay.scss +++ b/devU-client/src/components/shared/loaders/loadingOverlay.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .container { position: absolute; diff --git a/devU-client/src/components/shared/loaders/spinningSquares.scss b/devU-client/src/components/shared/loaders/spinningSquares.scss index a49e37a0..4956ccc1 100644 --- a/devU-client/src/components/shared/loaders/spinningSquares.scss +++ b/devU-client/src/components/shared/loaders/spinningSquares.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; $container-size: 175px; $square-size: 125px; diff --git a/devU-client/src/components/utils/dragDropFile.scss b/devU-client/src/components/utils/dragDropFile.scss index b6b981b9..39b685aa 100644 --- a/devU-client/src/components/utils/dragDropFile.scss +++ b/devU-client/src/components/utils/dragDropFile.scss @@ -1,4 +1,5 @@ -@import "variables"; +@use 'src/assets/styles/variables' as *; + .formFileUpload { height: 16rem; diff --git a/devU-client/src/components/utils/userOptionsDropdown.scss b/devU-client/src/components/utils/userOptionsDropdown.scss index 6db877bc..7c1b1f3e 100644 --- a/devU-client/src/components/utils/userOptionsDropdown.scss +++ b/devU-client/src/components/utils/userOptionsDropdown.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'src/assets/styles/variables' as *; .dropdown { display: flex; diff --git a/devU-client/webpack.config.js b/devU-client/webpack.config.js index 40955bec..6d75032d 100644 --- a/devU-client/webpack.config.js +++ b/devU-client/webpack.config.js @@ -109,6 +109,6 @@ module.exports = () => { }, historyApiFallback: true, }, - devtool: 'inline-source-map', + devtool: 'cheap-source-map', }; }; From 89f4de2142d2410de1bb23f1b0961f1538c49a69 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Tue, 11 Feb 2025 11:44:15 -0500 Subject: [PATCH 312/400] made course page header padding less jank by switching system of button seperation from internal margin to gap in it's container class --- .../src/components/misc/globalToolbar.scss | 2 +- .../pages/courses/courseDetailPage.scss | 45 +++++++++---------- .../pages/courses/courseDetailPage.tsx | 3 +- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 450a45f4..da0254d6 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -30,7 +30,7 @@ $font-size: 16px; text-decoration: none; color: #FFF; font-weight: 700; - height: 51px; // Janky but makes the logo text properly vertically centered. Could probably be done better some other way + height: 51px; // Centers text in navbar } .bar { diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index 437b03a7..983e316e 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -83,7 +83,7 @@ justify-content: space-between; align-items: flex-start; background-color: $secondary; - padding: 20px; + padding: 30px; border-radius: 20px; color: $text-color; width: fit-content; @@ -93,12 +93,12 @@ font-size: 1.5rem; text-align: left; font-weight: bold; - margin: 0 15px 0 10px; + margin: 0; } h2 { font-size: 1.0rem; - margin-left: 10px; + margin: 0; } h3{ @@ -108,27 +108,26 @@ } } - .buttons-container { - display: flex; - flex-wrap: wrap; - - justify-content: space-around; - width:100%; - } +.buttons_container { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + gap: 10px; + margin-top: 10px; +} - .actual_button { - display: flex; - flex-direction: row; - border: 0; /* Primary button color */ - /* Button text color */ - padding: 10px 20px; /* Padding for button */ - border-radius: 5px; - font-weight: 600; - transition: background-color 0.3s ease; - margin : 10px; - background-color: $purple !important; - color: white !important; - } +.actual_button { + display: flex; + flex-direction: row; + border: 0; /* Primary button color */ + /* Button text color */ + padding: 10px 20px; /* Padding for button */ + border-radius: 5px; + font-weight: 600; + transition: background-color 0.3s ease; + background-color: $purple !important; + color: white !important; +} .assignmentName { &::after { diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index f2b80676..56b7da89 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -103,8 +103,7 @@ const CourseDetailPage = () => {

Section:

-
- +
+

DevU Home

+
+ + +
{/*
*/}
{/*
*/} {/*
*/} -

Current

+

Current Courses

{/*
*/} {/*
*/}
@@ -112,7 +118,7 @@ const HomePage = () => {
{/*
*/} {/*
*/} -

Completed

+

Completed Courses

{/*
*/} {/*
*/}
@@ -132,7 +138,7 @@ const HomePage = () => { {/*
*/} {/*
*/} -

Upcoming

+

Upcoming Courses

{/*
*/} {/*
*/} diff --git a/devU-client/src/components/shared/layouts/pageWrapper.scss b/devU-client/src/components/shared/layouts/pageWrapper.scss index 8c0cf1d1..525baf77 100644 --- a/devU-client/src/components/shared/layouts/pageWrapper.scss +++ b/devU-client/src/components/shared/layouts/pageWrapper.scss @@ -11,5 +11,5 @@ .content { // margin-top: 20px; flex-grow: 1; - padding: 0px 50px; + padding: 0px 100px; } From 09999f72f3c27c8dd913a959e38f9808258cedc4 Mon Sep 17 00:00:00 2001 From: SameepK Date: Wed, 12 Feb 2025 19:45:25 -0500 Subject: [PATCH 315/400] updates the buttons for the homepage --- devU-client/src/assets/global.scss | 7 +++-- .../components/pages/errorPage/errorPage.scss | 4 ++- .../components/pages/homePage/homePage.scss | 30 ++++++++++++++++--- .../components/pages/homePage/homePage.tsx | 15 ++++++---- 4 files changed, 43 insertions(+), 13 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index ffa051c5..1cad64b6 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -1,4 +1,6 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + + :global { // Libraries @@ -14,7 +16,7 @@ } h1 { - margin: 1.75rem auto; + margin: 0 auto; text-align: center; } @@ -41,6 +43,7 @@ background-color: var(--btn-secondary-background); color: var(--btn-secondary-text); border: 3px solid var(--btn-secondary-border); + margin-left: auto; } button.btnDelete { diff --git a/devU-client/src/components/pages/errorPage/errorPage.scss b/devU-client/src/components/pages/errorPage/errorPage.scss index 4fa10a2b..d9242217 100644 --- a/devU-client/src/components/pages/errorPage/errorPage.scss +++ b/devU-client/src/components/pages/errorPage/errorPage.scss @@ -1,4 +1,6 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + + .errorBackground { width: 100%; diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index c727afe4..d443632c 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -1,4 +1,6 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + + @@ -59,14 +61,34 @@ // } .header { - color: $text-color; - display: block; + display: flex; + justify-content: space-between; align-items: center; + margin-bottom: 20px; } -#createCourseBtn { +.buttonContainer { + display: flex; + gap: 10px; +} + +#createCourseBtn, #joinCourseBtn { display: block; margin-left: auto; + border: 2px solid $primary; + background: transparent; + color: $primary; + padding: 10px 20px; + border-radius: 5px; + text-decoration: none; + font-weight: 600; + transition: background-color 0.3s ease, color 0.3s ease; + cursor: pointer; + + &:hover { + background-color: $primary; + color: white; + } } // .courses_heading::after { diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 69a97ee1..a5faf18a 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -77,14 +77,17 @@ const HomePage = () => { return (
- {/*
*/}

Courses

- - {/*
*/} +
+ + +
+ {/*
*/} {/*
*/}

Current

From b01f4738806e2159cd09de49ada913e4bbb25165 Mon Sep 17 00:00:00 2001 From: SameepK Date: Wed, 12 Feb 2025 22:30:45 -0500 Subject: [PATCH 316/400] updated the code to change the @use to @import --- devU-client/src/assets/global.scss | 2 +- devU-client/src/components/listItems/courseListItem.scss | 3 ++- .../src/components/listItems/simpleAssignmentListItem.scss | 3 ++- devU-client/src/components/listItems/submissionListItem.scss | 3 ++- .../src/components/listItems/userAssignmentListItem.scss | 3 ++- devU-client/src/components/listItems/userCourseListItem.scss | 3 ++- devU-client/src/components/misc/globalToolbar.scss | 3 ++- devU-client/src/components/misc/navbar.scss | 3 ++- .../src/components/pages/assignments/assignmentDetailPage.scss | 3 ++- devU-client/src/components/pages/assignments/scoreboard.scss | 3 ++- devU-client/src/components/pages/authProvider.scss | 3 ++- devU-client/src/components/pages/courses/courseDetailPage.scss | 3 ++- .../components/pages/forms/assignments/assignmentFormPage.scss | 3 ++- .../pages/forms/assignments/assignmentUpdatePage.scss | 3 ++- .../pages/forms/containers/containerAutoGraderForm.scss | 3 ++- .../pages/forms/containers/nonContainerAutoGraderForm.scss | 3 ++- .../src/components/pages/forms/courses/coursesFormPage.scss | 3 ++- devU-client/src/components/pages/gradebook/gradebookPage.scss | 3 ++- .../components/pages/listPages/courses/coursesListPage.scss | 3 ++- .../src/components/pages/submissions/Submissionspage.scss | 3 ++- .../src/components/pages/submissions/submissionDetailPage.scss | 3 ++- devU-client/src/components/pages/webhookURLForm.scss | 3 ++- devU-client/src/components/shared/alerts/alert.scss | 3 ++- .../src/components/shared/errors/validationErrorViewer.scss | 3 ++- devU-client/src/components/shared/inputs/button.scss | 3 ++- devU-client/src/components/shared/inputs/checkbox.scss | 3 ++- devU-client/src/components/shared/inputs/faIconButton.scss | 3 ++- devU-client/src/components/shared/inputs/radioButtons.scss | 3 ++- devU-client/src/components/shared/inputs/toggle.scss | 3 ++- devU-client/src/components/shared/layouts/listItemWrapper.scss | 3 ++- devU-client/src/components/shared/loaders/loadingOverlay.scss | 3 ++- devU-client/src/components/shared/loaders/spinningSquares.scss | 3 ++- devU-client/src/components/utils/dragDropFile.scss | 3 ++- devU-client/src/components/utils/userOptionsDropdown.scss | 3 ++- 34 files changed, 67 insertions(+), 34 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 1cad64b6..dafbfc05 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -5,7 +5,7 @@ :global { // Libraries // Import css libraries here as needed - @use 'react-toggle/style'; + @import 'react-toggle/style'; html, body, diff --git a/devU-client/src/components/listItems/courseListItem.scss b/devU-client/src/components/listItems/courseListItem.scss index e23448a1..e3f8ccb3 100644 --- a/devU-client/src/components/listItems/courseListItem.scss +++ b/devU-client/src/components/listItems/courseListItem.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .name { diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.scss b/devU-client/src/components/listItems/simpleAssignmentListItem.scss index a82a22fc..f22235b4 100644 --- a/devU-client/src/components/listItems/simpleAssignmentListItem.scss +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .title { diff --git a/devU-client/src/components/listItems/submissionListItem.scss b/devU-client/src/components/listItems/submissionListItem.scss index 206da7f4..71c05bae 100644 --- a/devU-client/src/components/listItems/submissionListItem.scss +++ b/devU-client/src/components/listItems/submissionListItem.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .course { diff --git a/devU-client/src/components/listItems/userAssignmentListItem.scss b/devU-client/src/components/listItems/userAssignmentListItem.scss index 83afd0eb..8c42794a 100644 --- a/devU-client/src/components/listItems/userAssignmentListItem.scss +++ b/devU-client/src/components/listItems/userAssignmentListItem.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .name { font-size: 1.2rem; diff --git a/devU-client/src/components/listItems/userCourseListItem.scss b/devU-client/src/components/listItems/userCourseListItem.scss index e048811b..2688ad76 100644 --- a/devU-client/src/components/listItems/userCourseListItem.scss +++ b/devU-client/src/components/listItems/userCourseListItem.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .name { font-size: 1.2rem; diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 1d7d24d6..701db491 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + $bar-height: 60px; $sidebar-width: 260px; diff --git a/devU-client/src/components/misc/navbar.scss b/devU-client/src/components/misc/navbar.scss index 6f5ac94e..9f440bb0 100644 --- a/devU-client/src/components/misc/navbar.scss +++ b/devU-client/src/components/misc/navbar.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .breadcrumbContainer { display: flex; diff --git a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss index a888493c..fdd8f54c 100644 --- a/devU-client/src/components/pages/assignments/assignmentDetailPage.scss +++ b/devU-client/src/components/pages/assignments/assignmentDetailPage.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .wrap { display:flex; diff --git a/devU-client/src/components/pages/assignments/scoreboard.scss b/devU-client/src/components/pages/assignments/scoreboard.scss index 151ece38..e8a89b5b 100644 --- a/devU-client/src/components/pages/assignments/scoreboard.scss +++ b/devU-client/src/components/pages/assignments/scoreboard.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .scoreboardTable { diff --git a/devU-client/src/components/pages/authProvider.scss b/devU-client/src/components/pages/authProvider.scss index e72f3644..1490ee02 100644 --- a/devU-client/src/components/pages/authProvider.scss +++ b/devU-client/src/components/pages/authProvider.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .page { display: flex; diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index e4cab5eb..fa1ebff2 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .courseFormWrapper { display: flex; margin: 16px 50px; diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss index 340c7146..67c622c7 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss index d014999d..6c828938 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .grid { display: grid; diff --git a/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.scss b/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.scss index 3f9c84ce..e91a66c9 100644 --- a/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.scss +++ b/devU-client/src/components/pages/forms/containers/containerAutoGraderForm.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .form { display: absolute; diff --git a/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.scss b/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.scss index 0992d87b..33b04980 100644 --- a/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.scss +++ b/devU-client/src/components/pages/forms/containers/nonContainerAutoGraderForm.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .form { diff --git a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss index 377fbce6..7e04c84e 100644 --- a/devU-client/src/components/pages/forms/courses/coursesFormPage.scss +++ b/devU-client/src/components/pages/forms/courses/coursesFormPage.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .courseFormWrapper { display: flex; diff --git a/devU-client/src/components/pages/gradebook/gradebookPage.scss b/devU-client/src/components/pages/gradebook/gradebookPage.scss index 0e9446e6..7297b2d5 100644 --- a/devU-client/src/components/pages/gradebook/gradebookPage.scss +++ b/devU-client/src/components/pages/gradebook/gradebookPage.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .categoryName { color: $text-color; diff --git a/devU-client/src/components/pages/listPages/courses/coursesListPage.scss b/devU-client/src/components/pages/listPages/courses/coursesListPage.scss index 2e75daf2..b36fdfd7 100644 --- a/devU-client/src/components/pages/listPages/courses/coursesListPage.scss +++ b/devU-client/src/components/pages/listPages/courses/coursesListPage.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + diff --git a/devU-client/src/components/pages/submissions/Submissionspage.scss b/devU-client/src/components/pages/submissions/Submissionspage.scss index 1658100d..79af3df8 100644 --- a/devU-client/src/components/pages/submissions/Submissionspage.scss +++ b/devU-client/src/components/pages/submissions/Submissionspage.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .submissionsTable { width: 100%; diff --git a/devU-client/src/components/pages/submissions/submissionDetailPage.scss b/devU-client/src/components/pages/submissions/submissionDetailPage.scss index 760f0273..3daaab4a 100644 --- a/devU-client/src/components/pages/submissions/submissionDetailPage.scss +++ b/devU-client/src/components/pages/submissions/submissionDetailPage.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .heading { diff --git a/devU-client/src/components/pages/webhookURLForm.scss b/devU-client/src/components/pages/webhookURLForm.scss index 403f066c..3d658993 100644 --- a/devU-client/src/components/pages/webhookURLForm.scss +++ b/devU-client/src/components/pages/webhookURLForm.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .textField { diff --git a/devU-client/src/components/shared/alerts/alert.scss b/devU-client/src/components/shared/alerts/alert.scss index b6bd908f..727a4de1 100644 --- a/devU-client/src/components/shared/alerts/alert.scss +++ b/devU-client/src/components/shared/alerts/alert.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .container { display: flex; justify-content: center; diff --git a/devU-client/src/components/shared/errors/validationErrorViewer.scss b/devU-client/src/components/shared/errors/validationErrorViewer.scss index d2f9b2ea..f16be347 100644 --- a/devU-client/src/components/shared/errors/validationErrorViewer.scss +++ b/devU-client/src/components/shared/errors/validationErrorViewer.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .errorContainer { diff --git a/devU-client/src/components/shared/inputs/button.scss b/devU-client/src/components/shared/inputs/button.scss index 8244c329..0e59996d 100644 --- a/devU-client/src/components/shared/inputs/button.scss +++ b/devU-client/src/components/shared/inputs/button.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .defaultButton { diff --git a/devU-client/src/components/shared/inputs/checkbox.scss b/devU-client/src/components/shared/inputs/checkbox.scss index aef16e9e..deae8bb4 100644 --- a/devU-client/src/components/shared/inputs/checkbox.scss +++ b/devU-client/src/components/shared/inputs/checkbox.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + $box-size: 16px; diff --git a/devU-client/src/components/shared/inputs/faIconButton.scss b/devU-client/src/components/shared/inputs/faIconButton.scss index b9d2a8b3..db23bbf7 100644 --- a/devU-client/src/components/shared/inputs/faIconButton.scss +++ b/devU-client/src/components/shared/inputs/faIconButton.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .button { diff --git a/devU-client/src/components/shared/inputs/radioButtons.scss b/devU-client/src/components/shared/inputs/radioButtons.scss index 15192673..0321bdfa 100644 --- a/devU-client/src/components/shared/inputs/radioButtons.scss +++ b/devU-client/src/components/shared/inputs/radioButtons.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + $outer-circle: 18px; diff --git a/devU-client/src/components/shared/inputs/toggle.scss b/devU-client/src/components/shared/inputs/toggle.scss index f86d3b08..819c1dfb 100644 --- a/devU-client/src/components/shared/inputs/toggle.scss +++ b/devU-client/src/components/shared/inputs/toggle.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .toggle { diff --git a/devU-client/src/components/shared/layouts/listItemWrapper.scss b/devU-client/src/components/shared/layouts/listItemWrapper.scss index f2840024..0d61124e 100644 --- a/devU-client/src/components/shared/layouts/listItemWrapper.scss +++ b/devU-client/src/components/shared/layouts/listItemWrapper.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .tag { diff --git a/devU-client/src/components/shared/loaders/loadingOverlay.scss b/devU-client/src/components/shared/loaders/loadingOverlay.scss index 1a49b6f4..d9cdbdcc 100644 --- a/devU-client/src/components/shared/loaders/loadingOverlay.scss +++ b/devU-client/src/components/shared/loaders/loadingOverlay.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .container { position: absolute; diff --git a/devU-client/src/components/shared/loaders/spinningSquares.scss b/devU-client/src/components/shared/loaders/spinningSquares.scss index 4956ccc1..683e668f 100644 --- a/devU-client/src/components/shared/loaders/spinningSquares.scss +++ b/devU-client/src/components/shared/loaders/spinningSquares.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + $container-size: 175px; $square-size: 125px; diff --git a/devU-client/src/components/utils/dragDropFile.scss b/devU-client/src/components/utils/dragDropFile.scss index 39b685aa..5d6b0d74 100644 --- a/devU-client/src/components/utils/dragDropFile.scss +++ b/devU-client/src/components/utils/dragDropFile.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .formFileUpload { diff --git a/devU-client/src/components/utils/userOptionsDropdown.scss b/devU-client/src/components/utils/userOptionsDropdown.scss index 7c1b1f3e..a1dd5165 100644 --- a/devU-client/src/components/utils/userOptionsDropdown.scss +++ b/devU-client/src/components/utils/userOptionsDropdown.scss @@ -1,4 +1,5 @@ -@use 'src/assets/styles/variables' as *; +@import 'variables'; + .dropdown { display: flex; From b3e70f0422e5d2f9d610a458968834e219389852 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Thu, 13 Feb 2025 11:54:43 -0500 Subject: [PATCH 317/400] downgraded sass, fixed global.css spacing --- devU-client/package.json | 6 +- devU-client/src/assets/global.scss | 146 ++++++++---------- .../assignments/assignmentUpdatePage.scss | 3 +- 3 files changed, 71 insertions(+), 84 deletions(-) diff --git a/devU-client/package.json b/devU-client/package.json index 7bfe63fa..78eb986d 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -19,7 +19,7 @@ ] }, "imports": { - "devu-shared-modules": "file:/devu-shared-modules" + "devu-shared-modules": "./devu-shared-modules" }, "private": true, "dependencies": { @@ -74,8 +74,8 @@ "husky": "^6.0.0", "lint-staged": "^11.0.0", "prettier": "^2.3.0", - "sass": "^1.84.0", - "sass-loader": "^16.0.4", + "sass": "^1.34.0", + "sass-loader": "^10.2.0", "style-loader": "^2.0.0", "ts-loader": "^8.0.15", "webpack": "^5.91.0", diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index dafbfc05..d8e3aef8 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -1,11 +1,9 @@ @import 'variables'; - - -:global { +:global { // Libraries // Import css libraries here as needed - @import 'react-toggle/style'; + @import '~react-toggle/style'; html, body, @@ -16,7 +14,7 @@ } h1 { - margin: 0 auto; + margin: 1.75rem auto; text-align: center; } @@ -43,7 +41,6 @@ background-color: var(--btn-secondary-background); color: var(--btn-secondary-text); border: 3px solid var(--btn-secondary-border); - margin-left: auto; } button.btnDelete { @@ -52,72 +49,7 @@ color: var(--btn-delete-text); border: 3px solid var(--btn-delete-border); } - // General Error Message -.error-message { - color: var(--error-text); - font-size: 0.875rem; - font-weight: bold; - margin-top: 5px; -} - -// Validation Error Container -.error-container { - background-color: var(--error-background); - border-left: 5px solid var(--error-text); - padding: 10px; - margin: 10px 0; - border-radius: 5px; -} - -// Error Page Styling (From `errorPage.scss`) -.error-page { - background-color: var(--error-page-background); - display: flex; - align-items: center; - justify-content: center; - height: 100vh; -} - -.error-heading { - font-size: 2rem; - color: var(--error-text); -} - -.error-description { - font-size: 1.2rem; - color: var(--text-color); -} - -// Global Input Field Styles -.input-field { - width: 100%; - padding: 10px 15px; - font-size: 1rem; - color: var(--text-color); - background-color: var(--input-field-background); - border: 1px solid var(--input-border); - border-radius: var(--border-radius); - transition: border-color 0.3s ease; -} - - - &:focus { - border-color: var(--focus); - outline: none; - } - - &:disabled { - background-color: var(--grey-lightest); - color: var(--grey-lighter); - cursor: not-allowed; - } -} -// Placeholder Styling -::placeholder { - color: var(--text-color-secondary); - font-style: italic; -} body { font-family: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif; margin: 0; @@ -132,7 +64,7 @@ --text-color-secondary: #363636; --focus: var(--blue); - --primary-color: var(--purple); + --primary: var(--purple); --secondary-lighter: #cfd1d1; --secondary: #a8abab; @@ -189,12 +121,6 @@ --yellow-dark: #664600; --yellow: #F8D487; - --cyan: #00587E; - --brown-lighter: #876212; - --maroon: #8a2626; - - - transition: background-color 150ms linear; } @@ -239,6 +165,68 @@ color: var(--text-color); } + // General Error Message + .error-message { + color: var(--error-text); + font-size: 0.875rem; + font-weight: bold; + margin-top: 5px; + } + // Validation Error Container + .error-container { + background-color: var(--error-background); + border-left: 5px solid var(--error-text); + padding: 10px; + margin: 10px 0; + border-radius: 5px; + } + // Error Page Styling (From `errorPage.scss`) + .error-page { + background-color: var(--error-page-background); + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + } + .error-heading { + font-size: 2rem; + color: var(--error-text); + } + .error-description { + font-size: 1.2rem; + color: var(--text-color); + } + // Global Input Field Styles + .input-field { + width: 100%; + padding: 10px 15px; + font-size: 1rem; + color: var(--text-color); + background-color: var(--input-field-background); + border: 1px solid var(--input-border); + border-radius: var(--border-radius); + transition: border-color 0.3s ease; + + &:focus { + border-color: var(--focus); + outline: none; + } + &:disabled { + background-color: var(--grey-lightest); + color: var(--grey-lighter); + cursor: not-allowed; + } + } + + + + // Placeholder Styling + ::placeholder { + color: var(--text-color-secondary); + font-style: italic; + } + *:focus { outline-color: var(--focus); - } \ No newline at end of file + } +} diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss index 6c828938..84cb3c41 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -4,8 +4,7 @@ .grid { display: grid; grid-template-columns: 0.3fr 1fr 0.5fr; - grid-template-rows: 1f - r 0.75fr; + grid-template-rows: 1fr 0.75fr; grid-column-gap: 10px; grid-row-gap: 10px; width: 100%; From ce20c0973035c0705901cf0d606234464f28a5a8 Mon Sep 17 00:00:00 2001 From: SameepK Date: Fri, 14 Feb 2025 01:15:58 -0500 Subject: [PATCH 318/400] updated new layout of buttons --- devU-client/src/components/misc/footer.scss | 36 +++ devU-client/src/components/misc/footer.tsx | 14 + .../pages/courses/courseDetailPage.scss | 119 ++++---- .../pages/courses/courseDetailPage.tsx | 287 +++++++----------- .../components/pages/homePage/homePage.scss | 24 +- .../components/shared/layouts/pageWrapper.tsx | 2 + .../components/utils/userOptionsDropdown.scss | 17 +- .../components/utils/userOptionsDropdown.tsx | 99 +++--- 8 files changed, 316 insertions(+), 282 deletions(-) create mode 100644 devU-client/src/components/misc/footer.scss create mode 100644 devU-client/src/components/misc/footer.tsx diff --git a/devU-client/src/components/misc/footer.scss b/devU-client/src/components/misc/footer.scss new file mode 100644 index 00000000..5a2abeb0 --- /dev/null +++ b/devU-client/src/components/misc/footer.scss @@ -0,0 +1,36 @@ +@import 'variables'; + +.footer { + background-color: $primary; + color: #fff; + text-align: left; + width: 100%; + position: fixed; + bottom: 0; + left: 0; + font-size: 14px; + font-size: 16px; + padding: 10px 0; + display: flex; + flex-direction: row; + align-items: center; + gap: 84px; + + + nav { + display: flex; + gap: 15px; + justify-content: flex-start; /* Ensure it aligns left */ + align-items: center; + + a { + color: #fff; + text-decoration: none; + margin: 0 10px; + + &:hover { + text-decoration: underline; + } + } + } +} diff --git a/devU-client/src/components/misc/footer.tsx b/devU-client/src/components/misc/footer.tsx new file mode 100644 index 00000000..48283129 --- /dev/null +++ b/devU-client/src/components/misc/footer.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import styles from './footer.scss'; // Ensure you create this SCSS file + +const Footer = () => { + return ( + + ); +}; + +export default Footer; diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index fa1ebff2..86e70196 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -9,30 +9,29 @@ .categoriesContainer { display: grid; grid-template-rows: repeat(3, 1fr); - gap: 200px; /* adjust this value to set the space between the cards */ + gap: 100px; // Space between assignment categories margin-bottom: 20px; min-height: 200px; max-height: 500px; } - -.color{ +.color { background-color: $primary; - color: #FFF; // always white against purple bg + color: #FFF; // White text on purple background width: 100%; padding: 20px; text-align: center; font-size: 1.2em; - } + .coursesContainer { display: flex; flex-wrap: wrap; - //flex-direction: row; gap: 20px; - margin-top:20px; + margin-top: 20px; justify-content: flex-start; } + .courseCard { display: flex; flex: 1 0 250px; @@ -52,42 +51,34 @@ padding: 20px; .MuiListItemButton-root { - display: flex; padding-top: 0; padding-left: 0; - justify-content: center; /* Center the content horizontally */ + justify-content: center; // Center horizontally align-items: center; } - .MuiListItemText-primary { - text-align: center; - } - .MuiListItemText-secondary { - text-align: center; // Center the dates - } + .MuiListItemText-primary { + text-align: center; } - } - + .MuiListItemText-secondary { + text-align: center; // Center the dates + } + } +} .courseDetailPage { - padding: 40px; - .header { display: flex; flex-direction: column; - - justify-content: space-between; align-items: flex-start; background-color: $secondary; - // padding: 30px 60px; padding: 20px; - //border-radius: 10px; color: $text-color; - width: 70% + width: 70%; } h1 { @@ -95,45 +86,54 @@ font-size: 1.5rem; text-align: left; font-weight: bold; - } - .h2 { + h2 { padding-top: 20px; margin: 0 0 20px 0; font-size: 1.0rem; padding-bottom: 10px; } - h3{ + h3 { font-size: 1.5rem; border-bottom: 1px solid; color: $text-color; } } - .buttons-container { - display: flex; - flex-wrap: wrap; +/* Updated Button Layout */ +.buttonContainer { + display: flex; + justify-content: flex-end; // Align buttons slightly to the right + gap: 15px; // Space between buttons + margin-top: 10px; +} - justify-content: space-around; - width:100%; - } +/* Button Styling to Match the New UI */ +.outlinedButton { + background: white; + color: #4b0082; + border: 2px solid #4b0082; + padding: 8px 16px; + border-radius: 20px; + font-size: 16px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + background: #4b0082; + color: white; + } +} - .actual_button { - display: flex; - flex-direction: row; - border: 0; /* Primary button color */ - /* Button text color */ - padding: 10px 20px; /* Padding for button */ - border-radius: 5px; - font-weight: 600; - transition: background-color 0.3s ease; - margin : 0 10px; - background-color: $purple !important; - color: white !important; - } +/* Remove old button styles */ +.actual_button { + display: none; +} +/* Assignments Formatting */ .assignmentName { &::after { content: ''; @@ -143,14 +143,29 @@ border-bottom: 1px solid #ccc; } } +/* Instructor Buttons */ +.instructorActions { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 20px; +} +.editCourse, +.addAssignment { + background-color: $primary; + color: white; + border: none; + padding: 8px 15px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; +} - - - - - - +.editCourse:hover, +.addAssignment:hover { + background-color: $purple-darker; +} diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 5804beac..3dffb474 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -1,117 +1,72 @@ -import React, {useEffect, useState} from 'react' -import {useHistory, useParams} from 'react-router-dom' -import RequestService from 'services/request.service' -import {Assignment, Course} from 'devu-shared-modules' -//import {useHistory} from "react-router-dom"; -import PageWrapper from 'components/shared/layouts/pageWrapper' - -import Card from '@mui/material/Card' -import CardContent from '@mui/material/CardContent' -import Typography from '@mui/material/Typography' -import List from '@mui/material/List' -import ListItem from '@mui/material/ListItem' -import ListItemButton from '@mui/material/ListItemButton' -import ListItemText from '@mui/material/ListItemText' -//import Button from '@mui/material/Button' - - - -import styles from './courseDetailPage.scss' -import {SET_ALERT} from "../../../redux/types/active.types"; -import {useActionless, useAppSelector} from "../../../redux/hooks"; -//import TextField from "../../shared/inputs/textField"; -//import {useActionless, useAppSelector} from "redux/hooks"; - - +import React, { useEffect, useState } from 'react'; +import { useHistory, useParams } from 'react-router-dom'; +import RequestService from 'services/request.service'; +import { Assignment, Course } from 'devu-shared-modules'; +import PageWrapper from 'components/shared/layouts/pageWrapper'; + +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import Typography from '@mui/material/Typography'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; + +import styles from './courseDetailPage.scss'; +//import { SET_ALERT } from '../../../redux/types/active.types'; +import { useAppSelector} from "../../../redux/hooks"; const CourseDetailPage = () => { - //const history = useHistory() - const { courseId } = useParams<{courseId: string}>() - const [courseInfo, setCourseInfo] = useState(null) - const [categoryMap, setCategoryMap] = useState>({}) - const [setAlert] = useActionless(SET_ALERT) + const { courseId } = useParams<{ courseId: string }>(); + const [courseInfo, setCourseInfo] = useState(null); + const [categoryMap, setCategoryMap] = useState>({}); + const history = useHistory(); const role = useAppSelector((store) => store.roleMode); - // const[User, setUser]= useState < User ,preferredName>>({}) - - // const role = useAppSelector((store) => store.roleMode) - /* const fetchUserinfo = async () => { - RequestService.get< typeof User>('api/users') - .then((User) =>{ - setUser(User) - - }) -*/ - - const fetchCourseInfo = async () => { - RequestService.get(`/api/courses/${courseId}`) - .then((course) => { - setCourseInfo(course) + RequestService.get(`/api/courses/${courseId}`).then((course) => { + setCourseInfo(course); + }); - }) - RequestService.get(`/api/course/${courseId}/assignments/released`) - .then((assignments) => { - console.log(assignments) - let categoryMap : Record = {} - assignments.forEach((assignment : Assignment) => { + RequestService.get(`/api/course/${courseId}/assignments/released`).then((assignments) => { + let categoryMap: Record = {}; + assignments.forEach((assignment: Assignment) => { if (assignment.categoryName in categoryMap) { - categoryMap[assignment.categoryName].push(assignment) + categoryMap[assignment.categoryName].push(assignment); + } else { + categoryMap[assignment.categoryName] = [assignment]; } - else { - categoryMap[assignment.categoryName] = [assignment] - } - }) - setCategoryMap(categoryMap) - }) - - - } - - const handleDropCourse = () => { - //confirmation to drop course or not - var confirm = window.confirm("Are you sure you want to drop?"); - if (confirm) - { - RequestService.delete(`/api/course/${courseId}/user-courses`).then(() => { - - setAlert({autoDelete: true, type: 'success', message: 'Course Dropped'}) - history.push('/courses') - - }).catch((error: Error) => { - const message = error.message - setAlert({autoDelete: false, type: 'error', message}) }) - } - } - + }); + setCategoryMap(categoryMap); + }); + }; useEffect(() => { - fetchCourseInfo() + fetchCourseInfo(); + }, []); - }, []) - const history = useHistory() - return( + return (
- - - {courseInfo ? ( -
- + {courseInfo ? ( +
+ {/* Course Title */}
-

{courseInfo.number}: {courseInfo.name} ({courseInfo.semester})

-

Section:

- - -
- - - - + + - {role.isInstructor() &&( )} - + {role.isInstructor() &&( )} - - {role.isInstructor() &&( - - )} - - - - - - -
- - -
- - -

Assignments

- - - -
- {Object.keys(categoryMap).map((category, index) => ( - - - - - {category} - - - - {categoryMap[category].map((assignment, index) => ( - - { - history.push(`/course/${courseId}/assignment/${assignment.id}`) - }}> - - {assignment.name} - - } - secondary={ - - - Start: {new Date(assignment.startDate).toLocaleDateString()} - | - Due: {new Date(assignment.dueDate).toLocaleDateString()} - - - } - /> - - - - ))} - - - - ))}
- - - - ) : ( +

Assignments

+ +
+ {Object.keys(categoryMap).length > 0 ? ( +
+ {Object.keys(categoryMap).map((category, index) => ( + + + + {category} + + + + {categoryMap[category].map((assignment, index) => ( + + history.push(`/course/${courseId}/assignment/${assignment.id}`)}> + {assignment.name} + } + secondary={ + + Start: {new Date(assignment.startDate).toLocaleDateString()} | Due: {new Date(assignment.dueDate).toLocaleDateString()} + + } + /> + + + ))} + + + ))} +
+ ) : ( +

No assignments available

+ )} +
+
+ ) : (

Error fetching Course Information

- )} - + )}
+
+ ); +}; - - ) - } - - - export default CourseDetailPage \ No newline at end of file +export default CourseDetailPage; diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index d443632c..d0515d1d 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -72,7 +72,7 @@ gap: 10px; } -#createCourseBtn, #joinCourseBtn { +#createCourseBtn { display: block; margin-left: auto; border: 2px solid $primary; @@ -87,10 +87,30 @@ &:hover { background-color: $primary; - color: white; + color:white; } } +#joinCourseBtn { + display: block; + margin-left: auto; + border: 2px solid $primary; + background: transparent; + color: $primary; + padding: 10px 20px; + border-radius: 5px; + text-decoration: none; + font-weight: 600; + transition: background-color 0.3s ease, color 0.3s ease; + cursor: pointer; + + &:hover { + background-color: --background; + color: $primary; + } + +} + // .courses_heading::after { // content: ''; // display: block; diff --git a/devU-client/src/components/shared/layouts/pageWrapper.tsx b/devU-client/src/components/shared/layouts/pageWrapper.tsx index e54f2705..4ed9288a 100644 --- a/devU-client/src/components/shared/layouts/pageWrapper.tsx +++ b/devU-client/src/components/shared/layouts/pageWrapper.tsx @@ -2,6 +2,7 @@ import React from 'react' import GlobalToolbar from 'components/misc/globalToolbar' import Navbar from 'components/misc/navbar' +import Footer from 'components/misc/footer'; import styles from './pageWrapper.scss' @@ -15,6 +16,7 @@ const PageWrapper = ({ children, className = '' }: Props) => (
{children}
+
{/* Footer will now be displayed on all pages */}
) diff --git a/devU-client/src/components/utils/userOptionsDropdown.scss b/devU-client/src/components/utils/userOptionsDropdown.scss index a1dd5165..ccd71653 100644 --- a/devU-client/src/components/utils/userOptionsDropdown.scss +++ b/devU-client/src/components/utils/userOptionsDropdown.scss @@ -82,7 +82,7 @@ padding: 0 6px; background-color: $primary; - color: $text-color; + color: $background; height: 50px; text-align: left; @@ -93,6 +93,20 @@ background: $purple-darker; } } +.dropCourse { + @extend .option; + background-color: $primary; /* Same as Account and Logout */ + color: $background; /* Matching the text color */ + border: none; + font-size: 16px; + height: 50px; + text-align: left; + text-decoration: none; + + &:hover { + background: $purple-darker; + } +} @media (max-width: $small) { .name { @@ -110,3 +124,4 @@ display: none; } } + diff --git a/devU-client/src/components/utils/userOptionsDropdown.tsx b/devU-client/src/components/utils/userOptionsDropdown.tsx index 48eb6161..ec8757b0 100644 --- a/devU-client/src/components/utils/userOptionsDropdown.tsx +++ b/devU-client/src/components/utils/userOptionsDropdown.tsx @@ -1,46 +1,61 @@ -import React from 'react' -import {Link} from 'react-router-dom' - -import FaIcon from 'components/shared/icons/faIcon' - -import {useAppSelector} from 'redux/hooks' - -import RequestService from 'services/request.service' - -import styles from './userOptionsDropdown.scss' +import React from 'react'; +import { Link } from 'react-router-dom'; +import FaIcon from 'components/shared/icons/faIcon'; +import { useAppSelector } from 'redux/hooks'; +import RequestService from 'services/request.service'; +import styles from './userOptionsDropdown.scss'; +import { useParams, useHistory } from 'react-router-dom' +import { SET_ALERT } from 'redux/types/active.types'; +import { useActionless } from 'redux/hooks'; const UserOptionsDropdown = () => { - const name = useAppSelector((state) => state.user.preferredName || state.user.email) - const userId = useAppSelector((state) => state.user.id) - - const handleLogout = async () => { - // Clears refreshToken httpOnly cookie - requires cookie credentials to be included for them to be reset - // window.location.reload is not a normal function an cannot be invoked within a function chain - RequestService.get(`/api/logout`, { credentials: 'include' }, true).finally(() => window.location.reload()) + const name = useAppSelector((state) => state.user.preferredName || state.user.email); + const userId = useAppSelector((state) => state.user.id); + const { courseId } = useParams<{courseId: string}>() + const [setAlert] = useActionless(SET_ALERT); + const history = useHistory(); + + const handleLogout = async () => { + RequestService.get(`/api/logout`, { credentials: 'include' }, true).finally(() => window.location.reload()); + }; + + const handleDropCourse = () => { + //confirmation to drop course or not + var confirm = window.confirm("Are you sure you want to drop?"); + if (confirm) + { + RequestService.delete(`/api/course/${courseId}/user-courses`).then(() => { + + setAlert({autoDelete: true, type: 'success', message: 'Course Dropped'}) + history.push('/courses') + + }).catch((error: Error) => { + const message = error.message + setAlert({autoDelete: false, type: 'error', message}) }) + } } - return ( - // Tab index here allows this dropdown to work in safari -
- {/* Opening and closing dropdown handled via CSS - unfocusing dropdown will force close*/} - - - {/* Menu open closed controlled via CSS */} -
- - Account - - -
-
- ) -} - -export default UserOptionsDropdown + return ( +
+ + +
+ + Account + + + +
+
+ ); +}; + +export default UserOptionsDropdown; From fba2f7980e773d0b2a74507ab965cab1dc6a1500 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Fri, 14 Feb 2025 12:18:02 -0500 Subject: [PATCH 319/400] added border to course cards, changed due/end date css, changed title css, added instructor icon on course card, changed course page and gradebook buttons --- devU-client/src/assets/global.scss | 2 ++ .../listItems/simpleAssignmentListItem.scss | 25 +++++++++------- .../listItems/simpleAssignmentListItem.tsx | 8 +++-- .../listItems/userCourseListItem.scss | 30 +++++++++++-------- .../listItems/userCourseListItem.tsx | 18 ++++++----- .../src/components/misc/globalToolbar.scss | 3 +- .../components/pages/homePage/homePage.scss | 14 ++++----- .../components/pages/homePage/homePage.tsx | 10 +++---- .../src/components/shared/icons/faIcon.tsx | 1 + .../shared/layouts/listItemWrapper.scss | 2 +- devU-client/src/utils/date.utils.ts | 8 +++++ 11 files changed, 74 insertions(+), 47 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index d3e2a0a7..f38f5cf3 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -149,6 +149,8 @@ --btn-secondary-background: var(--purple-darker); --btn-secondary-text: #FFF; + --btn-text-color: var(--purple-lighter) + --btn-delete-border: var(--red-lighter); --btn-delete-background: var(--red); --btn-delete-text: #FFF; diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.scss b/devU-client/src/components/listItems/simpleAssignmentListItem.scss index 1cd22c05..430f1d40 100644 --- a/devU-client/src/components/listItems/simpleAssignmentListItem.scss +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.scss @@ -1,38 +1,43 @@ @import 'variables'; .title { - padding: 10px; display: flex; - flex-direction: row; - border-radius: 0.6rem; + flex-direction: column; justify-content: space-between; transition: background-color 0.2s linear; // color:$primary; + border-bottom: 1px solid #ddd; color: $text-color; + padding: 10px; } .meta { - display: flex; text-decoration: none; - gap: 1rem; - font-size:12px; - font-weight: 600; + display: flex; + flex-direction: row; + font-size:11px; + flex-wrap: wrap; + color: $text-color-secondary; + font-family: monospace; + font-weight: 500; } + .subText { - font-size: 12px; - font-weight: 600; + font-size: 18px; + font-weight: 500; display: grid; justify-content: space-between; gap: 1.5rem; width: 100%; + margin-bottom: 8px; + line-height: 100% } .tag { display: flex; min-width: 8px; background-color: $primary; - margin-top: 10px; } .container { diff --git a/devU-client/src/components/listItems/simpleAssignmentListItem.tsx b/devU-client/src/components/listItems/simpleAssignmentListItem.tsx index 5be917cc..a1dd63ef 100644 --- a/devU-client/src/components/listItems/simpleAssignmentListItem.tsx +++ b/devU-client/src/components/listItems/simpleAssignmentListItem.tsx @@ -1,6 +1,6 @@ import React from 'react' import ListItemWrapper from 'components/shared/layouts/listItemWrapper' -import {prettyPrintDate} from 'utils/date.utils' +import {wordPrintDate} from 'utils/date.utils' import styles from './simpleAssignmentListItem.scss' import {Assignment} from 'devu-shared-modules' @@ -13,7 +13,11 @@ const SimpleAssignmentListItem = ({assignment}: Props) => (
{assignment.name}
-
Due At: {prettyPrintDate(assignment.dueDate)}
+
+ Due: {wordPrintDate(assignment.dueDate)} |   + End: {wordPrintDate(assignment.endDate)} +
+
) diff --git a/devU-client/src/components/listItems/userCourseListItem.scss b/devU-client/src/components/listItems/userCourseListItem.scss index a2e5d25a..b69e82a9 100644 --- a/devU-client/src/components/listItems/userCourseListItem.scss +++ b/devU-client/src/components/listItems/userCourseListItem.scss @@ -4,7 +4,7 @@ font-size: 1.2rem; font-weight: 600; margin: 0; - padding: 30px; + padding: 15px 0; /* Add padding to the text inside the name block */ background: $primary; width: 100%; @@ -23,31 +23,34 @@ text-align: center; flex-grow: 1; font-size: 12px; - font-weight: 600; - border-radius: 5px; - padding: 30px; + font-weight: 500; + padding: 15px; + font-style: italic; flex-wrap: wrap; // color: $primary; color: $text-color; text-overflow: ellipsis; overflow-wrap: break-word; + border-bottom: 1px solid #ddd; + } .Buttons { display: flex; - justify-content: space-around; + justify-content: center; margin-top: auto; - padding: 10px; + padding: 15px; + gap: 20px; } -.gradebook_button, -.coursepage_button { +.sub_button { border: 0; - color: $text-color; + color: $primary; background: none; font-weight: 600; - margin-left: 15px; + margin: 0 10px; cursor: pointer; + font-size: 16px; } .tag { @@ -86,10 +89,11 @@ .container { text-decoration: none; - - border-radius: 0.6rem; - margin-bottom: 1rem; + border-radius: 25px; + border: solid 3px $primary; + border-color: $primary; color: $text-color; + background:none; } diff --git a/devU-client/src/components/listItems/userCourseListItem.tsx b/devU-client/src/components/listItems/userCourseListItem.tsx index 96a40bf8..772c6e1b 100644 --- a/devU-client/src/components/listItems/userCourseListItem.tsx +++ b/devU-client/src/components/listItems/userCourseListItem.tsx @@ -2,6 +2,7 @@ import React from 'react' import { Assignment, Course } from 'devu-shared-modules' import { useHistory } from "react-router-dom"; import ListItemWrapper from 'components/shared/layouts/listItemWrapper' +import FaIcon from 'components/shared/icons/faIcon' //import {prettyPrintDate} from 'utils/date.utils' @@ -23,20 +24,23 @@ const UserCourseListItem = ({ course, assignments, past = false, instructor = fa return ( -
{instructor ? (course.number + ": " + course.name + " Instructor") : course.number + ": " + course.name}
+
{instructor ? (course.number + ": " + course.name + " "): course.number + ": " + course.name + " "} + {instructor === true && } + +
{assignments && assignments.length > 0 ? (assignments.map((assignment) => ( ))) : ((past) ?
:
No Assignments Due Yet
)}
- - + }}>COURSE PAGE +
diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 314d0a8c..5fec7caa 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -29,7 +29,8 @@ $font-size: 16px; font-size: 32px; text-decoration: none; color: #FFF; - font-weight: 500; + font-weight: 700; + height: 51px; } .bar { diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index fbb2e94b..ea1da4b4 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -7,7 +7,7 @@ // margin-left: 20px; // font-size: 30px; // font-weight: 550; - margin-bottom: 30px; + margin: 10px 0; } // h1 { @@ -19,9 +19,10 @@ // } -// .no_courses { -// margin-left: 20px; -// } +.no_courses { + font-style: italic; + margin-bottom: 20px; +} @@ -29,6 +30,7 @@ display: flex; flex-direction: row; gap: 20px; + margin: 0; // margin: 20px; } @@ -39,10 +41,8 @@ align-items: stretch; width: 400px; padding: 0; - background-color: white; - border-radius: 20px; overflow: hidden; - margin-bottom: 20px; + margin-bottom: 10px; } // .create_course { diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 472b2b0a..0bd3b7e4 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -104,17 +104,15 @@ const HomePage = () => { instructor={true} />
))} -
-
+ {enrollCourses && enrollCourses.map((course) => (
handleCourseClick(course.id)} style={{ cursor: 'pointer' }}> -
))} - {enrollCourses.length === 0 && instructorCourses.length == 0 &&

You do not have current enrollment yet

} + {enrollCourses.length === 0 && instructorCourses.length == 0 &&
You do not have current enrollment yet
}
{/*
*/} {/*
*/} @@ -133,7 +131,7 @@ const HomePage = () => { />
))} - {pastCourses.length === 0 &&

No completed courses

} + {pastCourses.length === 0 &&
No completed courses
}
{/*
*/} @@ -150,7 +148,7 @@ const HomePage = () => {
))} - {upcomingCourses.length === 0 &&

No upcoming Courses

} + {upcomingCourses.length === 0 &&
No upcoming courses
}
diff --git a/devU-client/src/components/shared/icons/faIcon.tsx b/devU-client/src/components/shared/icons/faIcon.tsx index 58f0206f..aab6e3c1 100644 --- a/devU-client/src/components/shared/icons/faIcon.tsx +++ b/devU-client/src/components/shared/icons/faIcon.tsx @@ -45,6 +45,7 @@ export const IconLibrary = { 'caret-down': FaIcons.faCaretDown, 'user-circle': FaIcons.faUserCircle, chalkboard: FaIcons.faChalkboard, + chalkboardUser: FaIcons.faChalkboardUser, } export const RegularIconLibrary = { diff --git a/devU-client/src/components/shared/layouts/listItemWrapper.scss b/devU-client/src/components/shared/layouts/listItemWrapper.scss index 4cbb8454..49708665 100644 --- a/devU-client/src/components/shared/layouts/listItemWrapper.scss +++ b/devU-client/src/components/shared/layouts/listItemWrapper.scss @@ -13,7 +13,7 @@ flex-direction: row; background: $list-item-background; - border-radius: 0.6rem; + border-radius: 20px; padding: 1.5rem; diff --git a/devU-client/src/utils/date.utils.ts b/devU-client/src/utils/date.utils.ts index 9a2a4591..c7f0c727 100644 --- a/devU-client/src/utils/date.utils.ts +++ b/devU-client/src/utils/date.utils.ts @@ -1,6 +1,14 @@ export function prettyPrintDate(date: string) { return new Date(date).toLocaleDateString('en-us', { year: 'numeric', month: '2-digit', day: '2-digit' }) } +export function wordPrintDate(date:string){ + return new Date(date).toLocaleString('en-us', { + weekday: 'short', + month: 'short', + day: '2-digit', + hour:'2-digit', + minute: '2-digit'}) +} export function prettyPrintDateTime(date: string) { return new Date(date).toLocaleString(undefined, { From 1498289a9c976d7e783a1f936c4d5d49680ef2ba Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Fri, 14 Feb 2025 18:59:21 -0500 Subject: [PATCH 320/400] removed weird compose.yml rule, made course cards have no message with no assignments --- devU-client/package.json | 4 ++-- devU-client/src/components/listItems/userCourseListItem.scss | 4 ++-- devU-client/src/components/listItems/userCourseListItem.tsx | 2 +- devU-client/src/components/pages/homePage/homePage.scss | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/devU-client/package.json b/devU-client/package.json index 743abc69..b5532db4 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -10,8 +10,8 @@ "build-local": "cross-env NODE_ENV=local webpack --mode=production", "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", "pre-commit": "lint-staged", - "dev-backend": "docker compose -f ../compose.yml --profile dev-client up -d", - "dev-backend-stop": "docker compose -f ../compose.yml --profile dev-client stop" + "dev-backend": "docker compose -f ../docker-compose.yml --profile dev-client up -d", + "dev-backend-stop": "docker compose -f ../docker-compose.yml --profile dev-client stop" }, "lint-staged": { "./**/*.{js,ts,json,md}": [ diff --git a/devU-client/src/components/listItems/userCourseListItem.scss b/devU-client/src/components/listItems/userCourseListItem.scss index b69e82a9..219fa4be 100644 --- a/devU-client/src/components/listItems/userCourseListItem.scss +++ b/devU-client/src/components/listItems/userCourseListItem.scss @@ -10,7 +10,7 @@ width: 100%; text-align: center; color: #FFF; - border-radius: 20px 20px 0 0; + border-radius: 10px 10px 0 0; box-sizing: border-box; text-overflow: ellipsis; overflow-wrap: break-word; @@ -89,7 +89,7 @@ .container { text-decoration: none; - border-radius: 25px; + border-radius: 15px; border: solid 3px $primary; border-color: $primary; color: $text-color; diff --git a/devU-client/src/components/listItems/userCourseListItem.tsx b/devU-client/src/components/listItems/userCourseListItem.tsx index 772c6e1b..b932ffae 100644 --- a/devU-client/src/components/listItems/userCourseListItem.tsx +++ b/devU-client/src/components/listItems/userCourseListItem.tsx @@ -31,7 +31,7 @@ const UserCourseListItem = ({ course, assignments, past = false, instructor = fa
{assignments && assignments.length > 0 ? (assignments.map((assignment) => ( - ))) : ((past) ?
:
No Assignments Due Yet
)} + ))) : past &&
}
- - - {role.isInstructor() &&( - - )} - - {role.isInstructor() &&( - - )} - - {role.isInstructor() &&( - - )} - - - - - - + ) : () + } +
+
+
+
+

Instructor:

+
+
+

Section:

+
+
+

Semester:

{prettyPrintSemester(courseInfo.semester)} +
- - +
+

Course Links

+
+ +
+
+

Assignments

+ {role.isInstructor() &&( + + )} +
- -

Assignments

+
{Object.keys(categoryMap).map((category, index) => ( - - - + + + {category} @@ -169,20 +166,16 @@ const CourseDetailPage = () => { + {assignment.name} } secondary={ - - Start: {new Date(assignment.startDate).toLocaleDateString()} | Due: {new Date(assignment.dueDate).toLocaleDateString()} - +
+ Due: {wordPrintDate(assignment.dueDate)} |   + End: {wordPrintDate(assignment.endDate)} +
} /> @@ -204,7 +197,6 @@ const CourseDetailPage = () => {

Error fetching Course Information

)} -
) diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index dc56a72b..afb64d4f 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -20,6 +20,7 @@ .no_courses { font-style: italic; + margin-right: auto; // Keeps text locked left after hitting 738px margin-bottom: 20px; } @@ -54,7 +55,6 @@ // margin: 20px // } .courses_title{ - margin: 0; width: fit-content; align-self: center; grid-column-start: 2; @@ -64,8 +64,9 @@ display: grid; grid-template-columns: 1fr 1fr 1fr; justify-items: center; - padding: 20px; + align-items: center; } + .buttonContainer{ display: flex; gap: 20px; From 6258d316458d6947caa1486bf50bf283c3106312 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Thu, 20 Feb 2025 13:42:35 -0500 Subject: [PATCH 323/400] finished design for course page, added behavior for smaller screen (single column), applied 3-column design to homepage, changed homepage behavior to only show assignments that have due dates in the future --- .../listItems/userCourseListItem.tsx | 3 +- devU-client/src/components/misc/navbar.scss | 1 - .../pages/courses/courseDetailPage.scss | 38 +++++++------------ .../pages/courses/courseDetailPage.tsx | 10 ++--- .../components/pages/homePage/homePage.scss | 20 +++++----- 5 files changed, 31 insertions(+), 41 deletions(-) diff --git a/devU-client/src/components/listItems/userCourseListItem.tsx b/devU-client/src/components/listItems/userCourseListItem.tsx index b932ffae..c3f135ac 100644 --- a/devU-client/src/components/listItems/userCourseListItem.tsx +++ b/devU-client/src/components/listItems/userCourseListItem.tsx @@ -21,6 +21,7 @@ type Props = { const UserCourseListItem = ({ course, assignments, past = false, instructor = false }: Props) => { const history = useHistory() + const currentTime = new Date() return ( @@ -30,7 +31,7 @@ const UserCourseListItem = ({ course, assignments, past = false, instructor = fa
{assignments && assignments.length > 0 ? (assignments.map((assignment) => ( - + (new Date(assignment.dueDate) > currentTime) && ))) : past &&
}
))} - {pastCourses.length === 0 &&
No completed courses
} + {pastCourses.length === 0 &&
No completed courses
}
{/*
*/} @@ -148,7 +148,7 @@ const HomePage = () => {
))} - {upcomingCourses.length === 0 &&
No upcoming courses
} + {upcomingCourses.length === 0 &&
No upcoming courses
}
From fadb986f6d63d2fc26933da33ced92808cbc244f Mon Sep 17 00:00:00 2001 From: SameepK Date: Sun, 23 Feb 2025 08:55:40 -0500 Subject: [PATCH 327/400] updated the student gradebook to match the figma layout --- .../pages/courses/courseDetailPage.scss | 132 ++++++++++--- .../pages/courses/courseDetailPage.tsx | 11 +- .../pages/gradebook/gradebookPage.scss | 180 +++++++++++++++--- .../pages/gradebook/gradebookStudentPage.tsx | 141 ++++++++++---- 4 files changed, 373 insertions(+), 91 deletions(-) diff --git a/devU-client/src/components/pages/courses/courseDetailPage.scss b/devU-client/src/components/pages/courses/courseDetailPage.scss index 86e70196..8f392bc7 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.scss +++ b/devU-client/src/components/pages/courses/courseDetailPage.scss @@ -70,15 +70,17 @@ .courseDetailPage { padding: 40px; + position: relative; // Ensures absolute positioning works within this container .header { display: flex; - flex-direction: column; - align-items: flex-start; - background-color: $secondary; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; padding: 20px; color: $text-color; - width: 70%; + width: 100%; + position: relative; } h1 { @@ -88,13 +90,6 @@ font-weight: bold; } - h2 { - padding-top: 20px; - margin: 0 0 20px 0; - font-size: 1.0rem; - padding-bottom: 10px; - } - h3 { font-size: 1.5rem; border-bottom: 1px solid; @@ -105,9 +100,11 @@ /* Updated Button Layout */ .buttonContainer { display: flex; - justify-content: flex-end; // Align buttons slightly to the right - gap: 15px; // Space between buttons - margin-top: 10px; + justify-content: flex-start; // Align buttons to left under Course Links + flex-wrap: wrap; + gap: 15px; + margin-top: 20px; + } /* Button Styling to Match the New UI */ @@ -128,9 +125,39 @@ } } -/* Remove old button styles */ -.actual_button { - display: none; +.edit_actual_button { + display: flex; + flex-direction: row; + border: 0; /* Primary button color */ + /* Button text color */ + padding: 10px 20px; /* Padding for button */ + border-radius: 5px; + font-weight: 600; + transition: background-color 0.3s ease; + margin : 0 10px; + background-color: $purple !important; + color: white !important; + min-width: 160px; + position: relative; + bottom: 66px; + right: 100px; +} +.assignment_actual_button { + display: flex; + flex-direction: row; + border: 0; /* Primary button color */ + /* Button text color */ + padding: 10px 20px; /* Padding for button */ + border-radius: 5px; + font-weight: 600; + transition: background-color 0.3s ease; + margin : 0 10px; + background-color: $purple !important; + color: white !important; + min-width: 160px; + position: relative; + bottom: -57px; + left: 89px; } /* Assignments Formatting */ @@ -146,27 +173,74 @@ /* Instructor Buttons */ .instructorActions { display: flex; - justify-content: flex-end; - gap: 10px; + justify-content: flex-start; + gap: 15px; margin-top: 20px; } -.editCourse, -.addAssignment { +/* Course Links - Move to the Right */ +.courseLinks { + display: flex; + justify-content: flex-end; + gap: 15px; + margin-top: 10px; +} + +/* Style for each link button */ +.linkButton { + background: white; + color: #4b0082; + border: 2px solid #4b0082; + padding: 8px 16px; + border-radius: 20px; + font-size: 16px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + background: #4b0082; + color: white; + } +} + +/* Move "Edit Course" to the top-right just below account info */ +.editCourse { + position: absolute; + top: -60px; // Moves it higher to match the layout + right: 0; background-color: $primary; color: white; border: none; - padding: 8px 15px; - border-radius: 5px; + padding: 8px 16px; + border-radius: 20px; cursor: pointer; - font-size: 14px; -} + font-size: 16px; + font-weight: bold; + transition: all 0.3s ease; -.editCourse:hover, -.addAssignment:hover { - background-color: $purple-darker; + &:hover { + background-color: $purple-darker; + } } +/* Move "Add Assignment" to the bottom right */ +.addAssignment { + position: absolute; + bottom: -60px; // Pushes it downward + right: 0; + background-color: $primary; + color: white; + border: none; + padding: 8px 16px; + border-radius: 20px; + cursor: pointer; + font-size: 16px; + font-weight: bold; + transition: all 0.3s ease; - + &:hover { + background-color: $purple-darker; + } +} diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 3dffb474..f9ddf565 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -43,8 +43,13 @@ const CourseDetailPage = () => { useEffect(() => { fetchCourseInfo(); + console.log("User Role:", role); + console.log("Is Instructor?", role.isInstructor ? role.isInstructor() : "role.isInstructor() is undefined"); }, []); + + + return (
@@ -69,16 +74,16 @@ const CourseDetailPage = () => { {role.isInstructor() &&( - )} {role.isInstructor() &&( - )}
diff --git a/devU-client/src/components/pages/gradebook/gradebookPage.scss b/devU-client/src/components/pages/gradebook/gradebookPage.scss index 7297b2d5..d4bd9e4a 100644 --- a/devU-client/src/components/pages/gradebook/gradebookPage.scss +++ b/devU-client/src/components/pages/gradebook/gradebookPage.scss @@ -1,11 +1,12 @@ @import 'variables'; - +// Category Styling .categoryName { color: $text-color; font-size: 1.25rem; } +// Table Wrapper for layout consistency .tableWrapper { padding: 0 100px; } @@ -16,12 +17,11 @@ } table { - border-radius: 20px; - // margin: 15px auto; - border-collapse: collapse; width: 100%; + border-collapse: collapse; } +// Alternating Row Colors tr.evenRow { background-color: $table-row-even; } @@ -30,18 +30,19 @@ tr.oddRow { background-color: $table-row-odd; } +// Table Header & Cell Styling th { - background-color: $primary; - color: #FFF; - font-weight: 600; + background-color: #5a3d8a; + color: white; } -td, -th { +th, td { padding: 10px; - text-align: center; + border-bottom: 1px solid #ccc; + text-align: left; } +// Table Borders & Rounded Corners th:first-of-type { border-top-left-radius: 10px; } @@ -58,12 +59,20 @@ tr:last-of-type td:last-of-type { border-bottom-right-radius: 10px; } +// Table Options Section .tableOptions { display: flex; justify-content: space-between; align-items: center; } +.topSection { + display: flex; + justify-content: space-between; + align-items: center; +} + +// Text Colors for Status Indicators .tableOptions span:first-child, span.yellow { color: $yellowText; @@ -74,37 +83,153 @@ span.red { color: $redText; } -// STUDENT GRADEBOOK STYLING - -// .header { -// color: $text-color; -// display: flex; -// align-items: center; -// justify-content: center; -// // margin-bottom: 20px; -// } - +// Student Gradebook Styling .assignmentName { text-align: left; } -.gradebook-container { +.gradebookContainer { display: flex; + flex-direction: column; gap: 20px; - flex-wrap: wrap; } -.category { - width: calc(100%/3 - 14px); +// Section Styling +.section { + background-color: white; + padding: -3px; + border-radius: 10px; + border: 2px solid #5a3d8a; +} + +.categoryRow { + font-weight: bold; } +.categoryValue { + text-align: right; + padding-right: 10px; // 🟢 Fix: Align value to the left +} + +.categoryText { + text-align: left; + padding-left: 10px; // 🟢 Fix: Align "Category Average" text to the right +} + +// Assignment Link Styling +.assignmentLink { + color: blue; + text-decoration: underline; +} + +// Category Average Styling (Adjusted for spacing) +.categoryAverage { + font-size: 14px; + text-align: right; + padding: 10px; + font-weight: bold; + margin-top: 10px; // 🟢 FIX: Added spacing so it doesn’t overlap +} + +// Page Wrapper & Title +.pageWrapper { + padding: 20px; +} + +.gradebookTitle { + text-align: center; + font-size: 2rem; + font-weight: bold; +} + +// 🟢 FIX: "Back to Course" Button Alignment +.backToCourseButton { + background-color: #5a3d8a; + color: white; + padding: 8px 15px; + border-radius: 30px; + border: none; + cursor: pointer; + position: absolute; + top: 137px; + right: 30px; +} + +// Section Headers (Purple Background) +.sectionHeader { + background-color: #5a3d8a; + color: white; + padding: 12px 20px; + border-radius: 8px 8px 0 0; + font-weight: bold; + display: flex; + justify-content: space-between; // 🟢 FIX: Ensures Late Days & Score are in the same row + align-items: center; +} + +.headerRight { + display: flex; + gap: 20px; // 🟢 FIX: Adds spacing between Late Days and Score +} + +// Gradebook Layout (Using Grid) +.gradebookGrid { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 20px; + width: 100%; // 🟢 FIX: Ensures the grid container spans full width +} + +// 🟢 FIX: Project Section Width Adjusted +.sectionLeft { + grid-column: 1; +} + +.sectionRight { + grid-column: 2; +} + +.sectionFull { + grid-column: 1 / span 1; // 🟢 FIX: Project is same width as Homeworks +} + +// Empty Assignments Text +.noAssignments { + text-align: center; + padding: 10px; + font-style: italic; +} + +// Course Average Box Styling +.courseAverage { + border: 2px solid #5a3d8a; + padding: 14px; + display: flex; + justify-content: space-between; // 🟢 FIX: Pushes the value to the rightmost side + font-weight: bold; + width: 99.01%; + grid-column: span 2; + margin-top: 20px; + border-radius: 13px; +} + +// 🟢 FIX: Mobile Responsive Adjustments @media (max-width: 650px) { .pageWrapper { padding: 0 20px; } .category { - width: calc(100%/2 - 14px); + width: calc(100% / 2 - 14px); + } + + .gradebookGrid { + display: flex; + flex-direction: column; + } + + .section { + width: 100%; } } @@ -112,4 +237,9 @@ span.red { .category { width: 100%; } + + .backToCourseButton { + position: relative; // 🟢 FIX: Prevents it from overlapping content on small screens + margin-top: 10px; + } } \ No newline at end of file diff --git a/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx b/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx index c7c4c2e6..c88d8cad 100644 --- a/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx @@ -22,7 +22,6 @@ const GradebookStudentPage = () => { const userId = useAppSelector((store) => store.user.id); const history = useHistory(); - useEffect(() => { fetchData(); }, []); @@ -34,7 +33,6 @@ const GradebookStudentPage = () => { const assignmentScores = await RequestService.get(`/api/course/${courseId}/assignment-scores/user/${userId}`); setAssignmentScores(assignmentScores); - } catch (error: any) { setError(error); } finally { @@ -45,53 +43,128 @@ const GradebookStudentPage = () => { if (loading) return ; if (error) return ; - const categories = [...new Set(assignments.map(a => a.categoryName))]; + // Categorize assignments + const homeworks = assignments.filter(a => a.categoryName === "Homework"); + const lectureQuestions = assignments.filter(a => a.categoryName === "Lecture Questions"); + const projects = assignments.filter(a => a.categoryName === "Project"); + + const calculateAverage = () => { + if (assignmentScores.length === 0) return 0.0; + const total = assignmentScores.reduce((sum, a) => sum + (a.score || 0), 0); + return (total / assignmentScores.length).toFixed(1); + }; + + const calculateHomeworkAverage = () => { + if (homeworks.length === 0) return "N/A"; + const totalScore = homeworks.reduce((sum, assignment) => sum + (assignmentScores.find(a => a.assignmentId === assignment.id)?.score || 0), 0); + return (totalScore / homeworks.length).toFixed(1); + }; return ( -
-

Student Gradebook

+ {/* Top Section with Back to Course Button */} +
+

CSE 312 Gradebook

{role.isInstructor() && ( - )}
-
- {categories.map(category => ( -
-

{category}

- {/* Add table class */} -
- - {/* Add class for purple header */} - - - {/* */} - + +
+ {/* Homework - Left Column */} +
+
+ Homeworks + + Late Days + Score + +
+
AssignmentScore
+ + {homeworks.length > 0 ? ( + homeworks.map((assignment) => ( + + + + + + )) + ) : ( + + + + )} + + + + + +
{assignment.name}0{assignmentScores.find(a => a.assignmentId === assignment.id)?.score ?? 'N/A'}
No assignments yet
Category Average{calculateHomeworkAverage()}
+
+ + {/* Lecture Questions Section */} +
+
+ Lecture Questions + + Late Days + Score + +
+ {lectureQuestions.length > 0 ? ( + - {assignments.filter(a => a.categoryName === category).map((assignment, index) => ( - - - + {lectureQuestions.map((assignment) => ( + + + + ))}
-
- {assignment.name} -
-
- {/*
*/} - {assignmentScores.find(aScore => aScore.assignmentId === assignment.id)?.score ?? 'N/A'} - {/*
*/} -
{assignment.name}0{assignmentScores.find(a => a.assignmentId === assignment.id)?.score ?? 'N/A'}
+ ) : ( +
No assignments yet
+ )} +
+ + {/* Project Section */} +
+
+ Project + + Late Days + Score +
- ))} + {projects.length > 0 ? ( + + + {projects.map((assignment) => ( + + + + + + ))} + +
{assignment.name}0{assignmentScores.find(a => a.assignmentId === assignment.id)?.score ?? 'N/A'}
+ ) : ( +
No assignments yet
+ )} +
+ + {/* Course Average Section */} +
+ Course Average + {calculateAverage()} +
); }; -export default GradebookStudentPage; \ No newline at end of file +export default GradebookStudentPage; From 665cb1aa7976e36bf422e227b534f6fe8e798917 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Mon, 24 Feb 2025 11:44:59 -0500 Subject: [PATCH 328/400] random, but made it so buttons have the pointer icon on hover. --- devU-client/src/assets/global.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 26a662c3..615b5eb9 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -25,6 +25,7 @@ // general button template, extends to 3 types of buttons - primary, secondary, delete .btn { + cursor: pointer; padding: 5px 15px; border-radius: 100px; height: fit-content; From d0133abec2bec858c8abd5406990157cc88c0ab4 Mon Sep 17 00:00:00 2001 From: SameepK Date: Mon, 24 Feb 2025 14:35:40 -0500 Subject: [PATCH 329/400] fixed the error for the Course-Title --- .../pages/gradebook/gradebookStudentPage.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx b/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx index c88d8cad..75f090c0 100644 --- a/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx +++ b/devU-client/src/components/pages/gradebook/gradebookStudentPage.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { useHistory, useParams } from 'react-router-dom'; import { useAppSelector } from 'redux/hooks'; -import { Assignment, AssignmentScore } from 'devu-shared-modules'; +import { Assignment, AssignmentScore, Course } from 'devu-shared-modules'; import PageWrapper from 'components/shared/layouts/pageWrapper'; import LoadingOverlay from 'components/shared/loaders/loadingOverlay'; @@ -21,6 +21,7 @@ const GradebookStudentPage = () => { const { courseId } = useParams<{ courseId: string }>(); const userId = useAppSelector((store) => store.user.id); const history = useHistory(); + const [courseName, setCourseName] = useState(""); useEffect(() => { fetchData(); @@ -33,6 +34,10 @@ const GradebookStudentPage = () => { const assignmentScores = await RequestService.get(`/api/course/${courseId}/assignment-scores/user/${userId}`); setAssignmentScores(assignmentScores); + + const courseData = await RequestService.get(`/api/courses/${courseId}`); + setCourseName(courseData.name); + } catch (error: any) { setError(error); } finally { @@ -64,9 +69,9 @@ const GradebookStudentPage = () => { {/* Top Section with Back to Course Button */}
-

CSE 312 Gradebook

+

{courseName} Gradebook

{role.isInstructor() && ( - )} @@ -167,4 +172,4 @@ const GradebookStudentPage = () => { ); }; -export default GradebookStudentPage; +export default GradebookStudentPage; \ No newline at end of file From 079c7eba1a4663d56d699a0462611aea7e994eb5 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Mon, 24 Feb 2025 19:32:16 -0500 Subject: [PATCH 330/400] standardized no items yet message, tried a different thing for the navbar --- .../src/components/misc/globalToolbar.scss | 7 +++---- .../pages/courses/courseDetailPage.tsx | 17 +++++++---------- .../src/components/pages/homePage/homePage.tsx | 2 +- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index 78f28c2a..5111d606 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -30,8 +30,8 @@ $font-size: 16px; text-decoration: none; color: #FFF; font-weight: 700; - height: 100%; // Centers text in navbar - line-height: 60px; + height: 60%; // Centers text in navbar + line-height: 100%; } .bar { @@ -106,6 +106,5 @@ $font-size: 16px; @media (max-width: 300px) { .flex { gap: 0.3rem; - } -} +} \ No newline at end of file diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 1b275a92..ed6e74cf 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -19,8 +19,8 @@ import ListItemText from '@mui/material/ListItemText' import styles from './courseDetailPage.scss' -import {SET_ALERT} from "../../../redux/types/active.types"; -import {useActionless, useAppSelector} from "../../../redux/hooks"; +//import {SET_ALERT} from "../../../redux/types/active.types"; +import {useAppSelector} from "../../../redux/hooks"; //useActionless, import {prettyPrintSemester} from "../../../utils/semester.utils"; //import TextField from "../../shared/inputs/textField"; @@ -35,7 +35,7 @@ const CourseDetailPage = () => { const { courseId } = useParams<{courseId: string}>() const [courseInfo, setCourseInfo] = useState(null) const [categoryMap, setCategoryMap] = useState>({}) - const [setAlert] = useActionless(SET_ALERT) + //const [setAlert] = useActionless(SET_ALERT) const role = useAppSelector((store) => store.roleMode); // const[User, setUser]= useState < User ,preferredName>>({}) @@ -74,7 +74,7 @@ const CourseDetailPage = () => { } - const handleDropCourse = () => { + /*const handleDropCourse = () => { //confirmation to drop course or not var confirm = window.confirm("Are you sure you want to drop?"); if (confirm) @@ -88,7 +88,7 @@ const CourseDetailPage = () => { const message = error.message setAlert({autoDelete: false, type: 'error', message}) }) } - } + }*/ useEffect(() => { @@ -103,15 +103,12 @@ const CourseDetailPage = () => {

{courseInfo.number}: {courseInfo.name}

- {role.isInstructor() ? ( + {role.isInstructor() && ( - ) : () - } + )}
diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 4ba3a458..887954a1 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -112,7 +112,7 @@ const HomePage = () => {
))} - {enrollCourses.length === 0 && instructorCourses.length == 0 &&
You do not have current enrollment yet
} + {enrollCourses.length === 0 && instructorCourses.length == 0 &&
You do not have current enrollment yet
}
{/*
*/} {/*
*/} From 2beadaae2339445bb7033468947ab1354c378daa Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Mon, 24 Feb 2025 20:25:12 -0500 Subject: [PATCH 331/400] made title position consistent across home and course page --- devU-client/src/components/pages/homePage/homePage.scss | 4 ++-- devU-client/src/components/pages/homePage/homePage.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devU-client/src/components/pages/homePage/homePage.scss b/devU-client/src/components/pages/homePage/homePage.scss index 0eec8946..79e75905 100644 --- a/devU-client/src/components/pages/homePage/homePage.scss +++ b/devU-client/src/components/pages/homePage/homePage.scss @@ -51,8 +51,8 @@ // transition: background-color 0.3s ease; // margin: 20px // } -.courses_title{ - margin-top: px; +.page_title{ + margin-top: 20px; width: fit-content; align-self: center; grid-column-start: 2; diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index 887954a1..1d1d87b1 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -78,7 +78,7 @@ const HomePage = () => {
{/*
*/} -

DevU Home

+

DevU Home

@@ -134,7 +134,7 @@ const CourseDetailPage = () => {

Assignments

{role.isInstructor() &&( - From d5a840768ed38139d15a3ee173b0a76618478e27 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Tue, 25 Feb 2025 12:52:34 -0500 Subject: [PATCH 335/400] converted edit assignment page to two column, started layout reworking --- .../assignments/assignmentUpdatePage.scss | 17 +++---- .../assignments/assignmentUpdatePage.tsx | 44 ++++++++----------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss index bf048d61..28199ff9 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -2,18 +2,16 @@ .grid { display: grid; - grid-template-columns: 0.3fr 1fr 0.5fr; - grid-template-rows: 1fr 0.75fr; + grid-template-columns: 1fr 1fr; grid-column-gap: 10px; grid-row-gap: 10px; width: 100%; } -.assignmentsList, .form, .problemsList, .attachments {background-color: $list-item-background;} -.assignmentsList {grid-area: 1 / 1 / 3 / 2;} -.form {grid-area: 1 / 2 / 2 / 3;} -.problemsList {grid-area: 1 / 3 / 2 / 4;} -.attachments {grid-area: 2 / 2 / 3 / 4;} +// .assignmentsList {grid-area: 1 / 1 / 3 / 2;} +// .form {grid-area: 1 / 2 / 2 / 3;} +// .problemsList {grid-area: 1 / 3 / 2 / 4;} +// .attachments {grid-area: 2 / 2 / 3 / 4;} .textFieldContainer { @@ -72,9 +70,6 @@ input[type='date'] { // } // } -.pageWrapper { - padding:0px; - } .assignmentBtn { background-color: transparent; @@ -98,7 +93,7 @@ input[type='date'] { } .header { - text-align: center; + text-align: left; color: $text-color; } diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index 62a78db6..d040f3f9 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -8,7 +8,7 @@ import { useActionless } from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import Button from '../../../shared/inputs/button' import styles from './assignmentUpdatePage.scss' -import DragDropFile from 'components/utils/dragDropFile' +//import DragDropFile from 'components/utils/dragDropFile' import { SET_ALERT } from 'redux/types/active.types' import { applyMessageToErrorFields, removeClassFromField } from 'utils/textField.utils' import Dialog from '@mui/material/Dialog'; @@ -22,7 +22,7 @@ const AssignmentUpdatePage = () => { const { assignmentId } = useParams() as UrlParams const { courseId } = useParams<{ courseId: string }>() const [setAlert] = useActionless(SET_ALERT) - const [currentAssignmentId, setCurrentAssignmentId] = useState(parseInt(assignmentId)) + const currentAssignmentId = parseInt(assignmentId) const [assignmentsList, setAssignmentsList] = useState([]) const [assignmentProblems, setAssignmentProblems] = useState([]) const [allAssignmentProblems, setAllAssignmentProblems] = useState>(new Map()) @@ -32,7 +32,8 @@ const AssignmentUpdatePage = () => { const history = useHistory() const [theme, setTheme] = useState(getCssVariables()) - +allAssignmentProblems; // Very Bad JS Work, yell at me if i leave these till the Pull Request +setFiles; // Needs a custom observer to force an update when the css variables change // Custom observer will update the theme variables when the bodies classes change useEffect(() => { @@ -91,11 +92,11 @@ const AssignmentUpdatePage = () => { const handleEndDateChange = (e : React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, endDate: e.target.value }))} const handleDueDateChange = (e : React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, dueDate: e.target.value }))} - const handleFile = (file: File) => { + /*const handleFile = (file: File) => { if(files.length < 5) { setFiles([...files, file]) } - } + }*/ const fetchAssignmentProblems = () => { RequestService.get(`/api/course/${courseId}/assignment/${currentAssignmentId}/assignment-problems`) @@ -183,7 +184,7 @@ const AssignmentUpdatePage = () => { }) } - const handleAssignmentChange = (e: React.MouseEvent) => { + /*const handleAssignmentChange = (e: React.MouseEvent) => { const assignmentDetails = assignmentsList.find((assignment) => assignment.id === parseInt(e.currentTarget.id)) if (assignmentDetails !== undefined && assignmentDetails.id !== undefined) { setAssignmentProblems(allAssignmentProblems.get(assignmentDetails.id) || []) @@ -192,7 +193,7 @@ const AssignmentUpdatePage = () => { if (assignmentDetails !== undefined) { setFormData(assignmentDetails) } - } + }*/ const [addProblemModal, setAddProblemModal] = useState(false) const [addProblemForm, setAddProblemForm] = useState({ @@ -231,7 +232,7 @@ const AssignmentUpdatePage = () => { return ( - + @@ -257,18 +258,10 @@ const AssignmentUpdatePage = () => { -

Edit Assignment

+

Edit Assignment

-
-

Assignments

- {assignmentsList.map((assignment) => ( -
- -
- ))} -
-

Assignment Information

+

Edit Info


{ ))}
-
+
+ + ) +} + +/* yell at me if i forget to delete this +

Attachments


@@ -366,10 +365,5 @@ const AssignmentUpdatePage = () => {
))}
-
-
- - ) -} - +
*/ export default AssignmentUpdatePage From 14fc1077b06e1c73aa104df11433e64a41bcb90e Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Tue, 25 Feb 2025 17:55:23 -0500 Subject: [PATCH 336/400] altered textField and edit page to fit new color scheme --- devU-client/src/assets/global.scss | 2 +- .../assignments/assignmentUpdatePage.scss | 12 +++++------ .../assignments/assignmentUpdatePage.tsx | 20 +++++++++++-------- .../components/shared/inputs/textField.tsx | 2 +- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 373cf780..64dfd4d0 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -14,7 +14,7 @@ } h1 { - margin: 20px auto; + margin: 10px auto; text-align: center; } diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss index 28199ff9..903ef25d 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -16,14 +16,17 @@ .textFieldContainer { display:flex; + flex-direction: column; width: 100%; - justify-content: center; - align-items: center; + font-size: 16px; } .textField { align-items: center; } +.textFieldHeader{ + margin: 5px 0; +} .datepickerContainer { display: grid; @@ -118,11 +121,6 @@ input[type='date'] { width: 100%; } - .assignmentsList, .form, .problemsList, .attachments {background-color: $list-item-background;} - .assignmentsList { display: none; } - .form { grid-area: 1 / 1 / 2 / 2; } - .problemsList { grid-area: 2 / 1 / 3 / 2; } - .attachments { grid-area: 3 / 1 / 4 / 2; } } @media (max-width:450px) { diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index d040f3f9..7c62646d 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -264,18 +264,22 @@ setFiles;

Edit Info


- - - Category
+ + sx={{"& .MuiInputBase-input.MuiOutlinedInput-input.MuiInputBase-inputMultiline.css-1sqnrkk-MuiInputBase-input-MuiOutlinedInput-input": {padding : "15px"}, + width : '100%', marginLeft : 1/10}}/> + + +
Date: Wed, 26 Feb 2025 12:03:23 -0500 Subject: [PATCH 337/400] just changed the hover behavior of buttons. hopefully an easy PR to approve --- devU-client/src/assets/global.scss | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 373cf780..be57beca 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -29,6 +29,7 @@ border-radius: 100px; border: none; font-weight: 700; + transition: background-color 100ms linear; } button.btnPrimary { @@ -36,6 +37,10 @@ background-color: var(--primary); border: 3px solid var(--primary); color: #fff; // primary button always white text + &:hover{ + background-color: var(--hover-darker); + border: 3px solid var(--hover-darker); + } } button.btnSecondary { @@ -43,6 +48,9 @@ background-color: var(--btn-secondary-background); color: var(--btn-secondary-text); border: 3px solid var(--btn-secondary-border); + &:hover{ + background-color: var(--hover-lighter); + } } button.btnDelete { @@ -87,6 +95,10 @@ --btn-secondary-background: #FFF; --btn-secondary-text: var(--primary); + --hover-darker: var(--purple-darker); + --hover-lighter: var(--purple-lightest); + + --btn-delete-border: var(--red); --btn-delete-background: #FFF; --btn-delete-text: var(--red); @@ -113,6 +125,8 @@ --red-lighter: #FFA3A3; --red: #8A2626; + --purple-lightest: #efecfd; + --purple-lighterer: #9885f0; --purple-lighter: #7257EB; --purple: #52468A; --purple-darker: #2F2363; @@ -150,6 +164,9 @@ --btn-secondary-background: var(--purple-darker); --btn-secondary-text: #FFF; + --hover-darker: var(--purple-lighterer); + --hover-lighter: var(--purple); + --btn-text-color: var(--purple-lighter) --btn-delete-border: var(--red-lighter); From b301a844da0901dbe670c6568208c28e8d5c8c0e Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Wed, 26 Feb 2025 12:46:17 -0500 Subject: [PATCH 338/400] created blank AddAssignmentModal in assignmentFormPage - 41 --- devU-client/src/components/misc/globalToolbar.scss | 1 - devU-client/src/components/misc/globalToolbar.tsx | 6 +++--- .../pages/forms/assignments/assignmentFormPage.tsx | 9 ++++++++- readme.md | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/devU-client/src/components/misc/globalToolbar.scss b/devU-client/src/components/misc/globalToolbar.scss index da0254d6..da44fbd9 100644 --- a/devU-client/src/components/misc/globalToolbar.scss +++ b/devU-client/src/components/misc/globalToolbar.scss @@ -30,7 +30,6 @@ $font-size: 16px; text-decoration: none; color: #FFF; font-weight: 700; - height: 51px; // Centers text in navbar } .bar { diff --git a/devU-client/src/components/misc/globalToolbar.tsx b/devU-client/src/components/misc/globalToolbar.tsx index 29a894ef..bc98cfd7 100644 --- a/devU-client/src/components/misc/globalToolbar.tsx +++ b/devU-client/src/components/misc/globalToolbar.tsx @@ -28,9 +28,9 @@ const GlobalToolbar = () => { { - - Join a Course - + // + // Join a Course + // } {/**/} {/* My Courses*/} diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index b1a9702c..691f1188 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -218,4 +218,11 @@ const AssignmentCreatePage = () => { ) } -export default AssignmentCreatePage \ No newline at end of file +const AddAssignmentModal = () => { + + return ( + <> + ) +} + +export default { AssignmentCreatePage, AddAssignmentModal } \ No newline at end of file diff --git a/readme.md b/readme.md index be82f38a..247f3a3a 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ Dev Deployment: https://devu.app -Figma: https://www.figma.com/design/Ei3nuzCcGCtorXXYCZxiwS/DevU---Fall-2024?node-id=0-1&p=f&m=dev +Figma: https://www.figma.com/design/I1p68BKK7w1E0JgfxWE2x0/devu-2.0?node-id=199-2&p=f&t=9Bq5OiHqK8mNlKHt-0 DevU is an automated software-grading platform being developed at the University at Buffalo. DevU aims to be incredibly extensible, allowing professors to add any functionality they desire without reaching a dead end. It will eventually From 167a8ace5e5f8d45b798b62be1a565cc33007d77 Mon Sep 17 00:00:00 2001 From: dvc Date: Wed, 26 Feb 2025 15:56:33 -0500 Subject: [PATCH 339/400] starting function for category fetch --- .../pages/forms/assignments/assignmentUpdatePage.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index 7c62646d..be14bbba 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react' -import { ExpressValidationError, Assignment, AssignmentProblem } from 'devu-shared-modules' +import { ExpressValidationError, Assignment, AssignmentProblem, Category } from 'devu-shared-modules' import 'react-datepicker/dist/react-datepicker.css' import { useHistory, useParams } from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -15,6 +15,7 @@ import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import { getCssVariables } from 'utils/theme.utils' +import Dropdown from 'components/shared/inputs/dropdown' type UrlParams = { assignmentId: string } @@ -26,6 +27,8 @@ const AssignmentUpdatePage = () => { const [assignmentsList, setAssignmentsList] = useState([]) const [assignmentProblems, setAssignmentProblems] = useState([]) const [allAssignmentProblems, setAllAssignmentProblems] = useState>(new Map()) + const [allCategories, setAllCategories] = useState([]) + const [invalidFields, setInvalidFields] = useState(new Map()) const [openModal, setOpenModal] = useState(false) const [files, setFiles] = useState([]) @@ -103,6 +106,11 @@ setFiles; .then((res) => { setAssignmentProblems(res) }) } + const fetchCategories = () => { + RequestService.get(`/api/course/${courseId}/assignment/${currentAssignmentId}/assignment-problems`) + .then((res) => { setAssignmentProblems(res) }) + } + useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`).then((res) => { setFormData(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems`).then((res) => { setAssignmentProblems(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments`).then((res) => { setAssignmentsList(res) })}, []) @@ -265,7 +273,7 @@ setFiles;
Category
- Date: Fri, 28 Feb 2025 10:53:51 -0500 Subject: [PATCH 340/400] changes to due/start date picker, save and exit --- devU-client/src/assets/global.scss | 2 + .../assignments/assignmentUpdatePage.scss | 55 ++++--- .../assignments/assignmentUpdatePage.tsx | 139 +++++++++--------- .../components/shared/inputs/textField.tsx | 1 + 4 files changed, 104 insertions(+), 93 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 64dfd4d0..cd9b2c57 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -26,6 +26,7 @@ // general button template, extends to 3 types of buttons - primary, secondary, delete .btn { padding: 10px 15px; + max-width: fit-content; border-radius: 100px; border: none; font-weight: 700; @@ -40,6 +41,7 @@ button.btnSecondary { @extend .btn; + background-color: var(--btn-secondary-background); color: var(--btn-secondary-text); border: 3px solid var(--btn-secondary-border); diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss index 903ef25d..eb730e5d 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -9,57 +9,65 @@ } // .assignmentsList {grid-area: 1 / 1 / 3 / 2;} -// .form {grid-area: 1 / 2 / 2 / 3;} // .problemsList {grid-area: 1 / 3 / 2 / 4;} // .attachments {grid-area: 2 / 2 / 3 / 4;} +.form {width: 80%;} + .textFieldContainer { display:flex; flex-direction: column; - width: 100%; font-size: 16px; + gap:10px; + margin-bottom: 10px; } .textField { align-items: center; + margin-bottom: 0; } + .textFieldHeader{ margin: 5px 0; } +.submissionsContainer{ + display:grid; + grid-template-columns: 1fr 1fr; + font-size: 16px; + gap:10px; + margin-bottom: 10px; +} + .datepickerContainer { display: grid; - grid-template-columns: repeat(3, 1fr); - grid-template-rows: 1fr; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr); + grid-template-rows: 1fr 1fr; grid-column-gap: 10px; - grid-row-gap: 0px; - width: 100%; - justify-content: center; align-items: center; - text-align: center; - + text-align: left; .datepicker_start {grid-area: 1 / 1 / 2 / 2;} .datepicker_due {grid-area: 1 / 2 / 2 / 3;} .datepicker_end {grid-area: 1 / 3 / 2 / 4;} } -input[type='date'] { - height: 20px; - background-color: $input-field-background; +input[type='datetime-local'] { color: $input-field-label; - padding: 0.625rem 1rem; - border: none; - border-radius: 100px; - } + border: 2px solid #ccc; + padding: 10px; + max-width: 1/3; + border-radius: 8px; +} -.problemsList, .assignmentsList { - display: flex; +.problemsList { + display:flex; flex-direction: column; - gap: 30px; - align-items: center; - width: 100%; + align-items: left; + font-size: 16px; + gap:10px; + margin-bottom: 10px; } // .assignment { @@ -97,6 +105,7 @@ input[type='date'] { .header { text-align: left; + margin: 10px 0; color: $text-color; } @@ -121,6 +130,10 @@ input[type='date'] { width: 100%; } + .form { + width: 100%; + } + } @media (max-width:450px) { diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index be14bbba..f2f971b3 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react' -import { ExpressValidationError, Assignment, AssignmentProblem, Category } from 'devu-shared-modules' +import { ExpressValidationError, Assignment, AssignmentProblem } from 'devu-shared-modules' import 'react-datepicker/dist/react-datepicker.css' import { useHistory, useParams } from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -15,7 +15,8 @@ import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import { getCssVariables } from 'utils/theme.utils' -import Dropdown from 'components/shared/inputs/dropdown' +//import Dropdown, { Option } from 'components/shared/inputs/dropdown'; +//import Select from 'react-select/src/Select' type UrlParams = { assignmentId: string } @@ -27,7 +28,7 @@ const AssignmentUpdatePage = () => { const [assignmentsList, setAssignmentsList] = useState([]) const [assignmentProblems, setAssignmentProblems] = useState([]) const [allAssignmentProblems, setAllAssignmentProblems] = useState>(new Map()) - const [allCategories, setAllCategories] = useState([]) + //const [allCategories, setAllCategories] = useState([]) const [invalidFields, setInvalidFields] = useState(new Map()) const [openModal, setOpenModal] = useState(false) @@ -90,7 +91,8 @@ setFiles; setAssignmentProblemData(prevState => ({ ...prevState, [key]: value })) } - const handleCheckbox = (e: React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, disableHandins: e.target.checked }))} + // taken out of the design for the moment, should get incorporated later + /*const handleCheckbox = (e: React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, disableHandins: e.target.checked }))}*/ const handleStartDateChange = (e : React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, startDate: e.target.value }))} const handleEndDateChange = (e : React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, endDate: e.target.value }))} const handleDueDateChange = (e : React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, dueDate: e.target.value }))} @@ -106,13 +108,14 @@ setFiles; .then((res) => { setAssignmentProblems(res) }) } - const fetchCategories = () => { + /*const fetchCategories = () => { RequestService.get(`/api/course/${courseId}/assignment/${currentAssignmentId}/assignment-problems`) - .then((res) => { setAssignmentProblems(res) }) - } + .then((res) => { setAllCategories(res) }) + }*/ useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`).then((res) => { setFormData(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems`).then((res) => { setAssignmentProblems(res) })}, []) + //useEffect(() => {RequestService.get(`/api/course/${courseId}/categories/`).then((res) => { setAllCategories(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments`).then((res) => { setAssignmentsList(res) })}, []) useEffect(() => { for(let i : number = 0; i < assignmentsList.length; i++) { @@ -270,86 +273,75 @@ setFiles;

Edit Info

-
-
Category
- +
AssignmentCategory:
+ - - - + sx={{width: '100%'}}/> +
+
+
Assignment Name:
+ + +
+
+
Description: (optional)
+ +
- - -
- - - +
+
+
Max Submissions:
+ +
+
+
Max File Size:
+ +
-
-
-
-
- -
-
-
- -
-
-
- -
-
-
-
- - -
-
-
- + + + +
-
-

Problems

+

Add Problems

{assignmentProblems.map((problem, index) => (

{`Problem ${index + 1}`}

@@ -357,9 +349,12 @@ setFiles;
))} - +
+
+ +
) } diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 58bde153..5c142cc1 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -78,6 +78,7 @@ const TextField = ({ color: theme.textColor, borderRadius: '10px', border: '2px solid #ccc', + //backgroundColor: theme.inputFieldBackground, // padding: '0.625rem 1rem', marginBottom: '0px' }, From 603490ca5737531acb121d2867a461eda7b4318c Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 28 Feb 2025 17:03:23 -0500 Subject: [PATCH 341/400] fix workflow --- .github/workflows/api.yml | 50 +++++++++++++++++++ .github/workflows/client.yml | 49 +++++++++++++++++++ .github/workflows/dev.yml | 52 -------------------- .github/workflows/release.yml | 91 ----------------------------------- 4 files changed, 99 insertions(+), 143 deletions(-) create mode 100644 .github/workflows/api.yml create mode 100644 .github/workflows/client.yml delete mode 100644 .github/workflows/dev.yml delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml new file mode 100644 index 00000000..f627d911 --- /dev/null +++ b/.github/workflows/api.yml @@ -0,0 +1,50 @@ +# Builds DevU api +name: Build DevU api + +on: + push: + paths: + - 'devU-api/**' + branches: + - develop + +jobs: + build-api-docker: + runs-on: ubuntu-latest + permissions: + contents: write # to be able to publish a GitHub release + packages: write # to be able to publish docker image packages + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Convert image name to lowercase + run: | + original_string=${{ github.repository }} + echo "repo_url=$(echo $original_string | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: Get Branch Name + id: get_branch + run: echo "::set-output name=branch_name::${GITHUB_REF#refs/heads/}" + + - name: build api docker + run: | + IMAGE_NAME=ghcr.io/${{ env.repo_url }}/client:${{ steps.get_branch.outputs.branch_name }} + echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_ENV + + docker build . -f api.Dockerfile -t $IMAGE_NAME + docker push $IMAGE_NAME + + - name: build tango docker + run: | + IMAGE_NAME=ghcr.io/${{ env.repo_url }}/tango:${{ steps.get_branch.outputs.branch_name }} + docker build ./tango -f ./tango/Dockerfile -t $IMAGE_NAME + docker push $IMAGE_NAME diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml new file mode 100644 index 00000000..e43d792a --- /dev/null +++ b/.github/workflows/client.yml @@ -0,0 +1,49 @@ +# Builds DevU client +name: Build DevU client + +on: + push: + paths: + - 'devU-client/**' + branches: + - develop + +jobs: + build-api-docker: + runs-on: ubuntu-latest + permissions: + contents: write # to be able to publish a GitHub release + packages: write # to be able to publish packages + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Convert image name to lowercase + run: | + original_string=${{ github.repository }} + echo "repo_url=$(echo $original_string | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV + + - name: Get Branch Name + id: get_branch + run: echo "::set-output name=branch_name::${GITHUB_REF#refs/heads/}" + + - name: build client docker + run: | + IMAGE_NAME=ghcr.io/${{ env.repo_url }}/client:${{ steps.get_branch.outputs.branch_name }} + echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_ENV + + docker build . \ + -f client.Dockerfile \ + --build-arg API_URL=${{ secrets.prod_api_url }} \ + --build-arg ROOT_PATH=${{ secrets.prod_api_path }} \ + -t $IMAGE_NAME + + docker push $IMAGE_NAME diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml deleted file mode 100644 index e8453764..00000000 --- a/.github/workflows/dev.yml +++ /dev/null @@ -1,52 +0,0 @@ -# Builds DevU images on develop -# tagged as develop - -name: Build DevU develop -on: - push: - branches: - - develop - -jobs: - build-docker: - runs-on: ubuntu-latest - permissions: - contents: write # to be able to publish a GitHub release - issues: write # to be able to comment on released issues - pull-requests: write # to be able to comment on released pull requests - packages: write # to be able to publish docker image packages - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - fetch-depth: 0 - - # todo run tests - - # docker image build - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GHCR registry - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - - name: Convert image name to lowercase - run: | - original_string=${{ github.repository }} - echo "repo_url=$(echo $original_string | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - - - name: api dev image - run: | - docker build . -f api.Dockerfile -t ghcr.io/${{ env.repo_url }}-api:beta --push - docker push ghcr.io/${{ env.repo_url }}-api:dev - - - name: client dev image - run: | - docker build . -f client.Dockerfile -t ghcr.io/${{ env.repo_url }}-client:beta - docker push ghcr.io/${{ env.repo_url }}-client:dev - - - name: build tango image - run: | - docker build ./tango -f ./tango/Dockerfile -t ghcr.io/${{ env.repo_url }}-tango:beta - docker push ghcr.io/${{ env.repo_url }}-tango:dev diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index fb8c7f54..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,91 +0,0 @@ -# Builds DevU images for release -# tagged as latest and version number - -name: Release -on: - push: - branches: - - release - -jobs: - tag-release: - name: tag-release - runs-on: ubuntu-latest - permissions: - contents: write # to be able to publish a GitHub release - issues: write # to be able to comment on released issues - pull-requests: write # to be able to comment on released pull requests - packages: write # to be able to publish docker image packages - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: "lts/*" - - name: install plugins - run: npm install --no-save @semantic-release/git @semantic-release/changelog -D - - - name: tag version number release based on commits - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npx semantic-release - - build-docker: - needs: - - tag-release - runs-on: ubuntu-latest - permissions: - contents: write # to be able to publish a GitHub release - issues: write # to be able to comment on released issues - pull-requests: write # to be able to comment on released pull requests - packages: write # to be able to publish docker images - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - fetch-depth: 0 - - - name: Get version tag from git history - id: tagName - uses: "WyriHaximus/github-action-get-previous-tag@v1" - - # todo run tests - - name: Convert image name to lowercase - run: | - original_string=${{ github.repository }} - echo "repo_url=$(echo $original_string | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV - - # docker image build - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GHCR registry - run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin - - - name: ap latest and version image - run: | - docker build . -f api.Dockerfile \ - -t ghcr.io/${{ env.repo_url }}-api:${{ steps.tagName.outputs.tag }} \ - -t ghcr.io/${{ env.repo_url }}-api:latest - - docker push ghcr.io/${{ env.repo_url }}-api:${{ steps.tagName.outputs.tag }} - docker push ghcr.io/${{ env.repo_url }}-api:latest - - - name: client latest and version image - run: | - docker build . -f client.Dockerfile \ - -t ghcr.io/${{ env.repo_url }}-client:${{ steps.tagName.outputs.tag }} \ - -t ghcr.io/${{ env.repo_url }}-client:latest - - docker push ghcr.io/${{ env.repo_url }}-client:${{ steps.tagName.outputs.tag }} - docker push ghcr.io/${{ env.repo_url }}-client:latest - - - name: tango latest and version image - run: | - docker build ./tango -f ./tango/Dockerfile -t ghcr.io/${{ env.repo_url }}-tango:latest - docker push ghcr.io/${{ env.repo_url }}-tango:latest From f1c258fd988974104466f5be8c3612b054965f52 Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 28 Feb 2025 17:05:22 -0500 Subject: [PATCH 342/400] added api url as build arg --- client.Dockerfile | 11 ++++++++--- devU-client/package.json | 1 + docker-compose.yml | 13 +++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/client.Dockerfile b/client.Dockerfile index f8e19ec7..6a3d988c 100644 --- a/client.Dockerfile +++ b/client.Dockerfile @@ -1,4 +1,4 @@ -FROM node:16 AS module_builder +FROM node:20 AS module_builder WORKDIR /tmp @@ -20,7 +20,12 @@ COPY ./devU-client/ . COPY --from=module_builder /tmp/devu-shared-modules ./devu-shared-modules -RUN npm run build-local +# Pass API_URL and ROOT_PATH as build arguments +ARG API_URL +ARG ROOT_PATH + +# Use build arguments in the build command +RUN API_URL=$API_URL ROOT_PATH=$ROOT_PATH npm run build-docker # final stage serve frontend files FROM nginx:1.23.3 @@ -28,4 +33,4 @@ FROM nginx:1.23.3 COPY nginx.conf /etc/nginx/nginx.conf # Copy built frontend files to nginx serving directory -COPY --from=frontend /app/dist/local /usr/share/nginx/html +COPY --from=frontend /app/dist/local /usr/share/nginx/html \ No newline at end of file diff --git a/devU-client/package.json b/devU-client/package.json index b5532db4..bd9edbd3 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -8,6 +8,7 @@ "build-development": "cross-env NODE_ENV=development webpack --mode=production", "build-all": "concurrently \"npm run build-development\" \"npm run build\"", "build-local": "cross-env NODE_ENV=local webpack --mode=production", + "build-docker": "webpack --mode=production", "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", "pre-commit": "lint-staged", "dev-backend": "docker compose -f ../docker-compose.yml --profile dev-client up -d", diff --git a/docker-compose.yml b/docker-compose.yml index 4a7e1de6..1c20529d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,9 @@ services: build: dockerfile: client.Dockerfile context: . + args: + API_URL: http://localhost:3001 + ROOT_PATH: / ports: - '9000:80' profiles: @@ -54,6 +57,16 @@ services: MINIO_ROOT_PASSWORD: changeMe command: server /data --console-address ":9001" + leviathan: + container_name: leviathan + image: ghcr.io/makeopensource/leviathan:dev + ports: + - "9221:9221" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./levi/:/app/appdata + restart: unless-stopped + # tango stuff tango: container_name: tango From 14e62aaa85142f2504f322480904244226ac28f0 Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 28 Feb 2025 17:05:30 -0500 Subject: [PATCH 343/400] updated ignore list --- .dockerignore | 3 ++- .gitignore | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index c5288418..2717197c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ /**/node_modules /logs -/devU-api/src/tango/tests/test_files \ No newline at end of file +/devU-api/src/tango/tests/test_files +.env.* \ No newline at end of file diff --git a/.gitignore b/.gitignore index 195f41c7..e12405ca 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ node_modules/ /.vscode /logs /*/node_modules -/*/package-lock.json \ No newline at end of file +/*/package-lock.json +levi \ No newline at end of file From 12a3b9934a9fd0f1372d7b02243bce9b2d9a20cf Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 28 Feb 2025 17:05:41 -0500 Subject: [PATCH 344/400] refactored example compose --- example-docker-compose.yml | 76 ++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/example-docker-compose.yml b/example-docker-compose.yml index 97b03dc2..62276a8f 100644 --- a/example-docker-compose.yml +++ b/example-docker-compose.yml @@ -4,7 +4,6 @@ name: devu services: api: - # Runs the API container_name: api image: ghcr.io/makeopensource/devu-api:dev environment: @@ -13,8 +12,6 @@ services: depends_on: db: condition: service_started -# config: -# condition: service_completed_successfully ports: - '3001:3001' @@ -24,6 +21,39 @@ services: ports: - '9000:80' + tango: + container_name: tango + ports: + - '127.0.0.1:3000:3000' + image: ghcr.io/makeopensource/devu-tango:dev + environment: + - DOCKER_REDIS_HOSTNAME=redis + - RESTFUL_KEY=devutangokey + # - DOCKER_DEPLOYMENT + # Path to volumes within the Tango container. Does not need to be modified. + # - DOCKER_VOLUME_PATH + # TODO remember to modify the below to be the path to the absolute path of tango_files` on your host machine + - DOCKER_TANGO_HOST_VOLUME_PATH=/absolute/path/to/tango_files + + depends_on: + - redis + volumes: + - ./tango.config.py:/opt/TangoService/Tango/config.py + - /var/run/docker.sock:/var/run/docker.sock + - ./logs/tango/:/var/log/tango/ + - ./logs/tangonginx:/var/log/nginx + - ./tango_files:/opt/TangoService/Tango/volumes + restart: unless-stopped + + redis: + container_name: redis + image: redis:latest + ports: + - '127.0.0.1:6379:6379' + deploy: + replicas: 1 + restart: unless-stopped + db: # Runs the PostgreSQL database image: postgres @@ -50,49 +80,15 @@ services: MINIO_ROOT_PASSWORD: changeMe command: server /data --console-address ":9001" - # tango stuff - redis: - container_name: redis - image: redis:latest - ports: - - '127.0.0.1:6379:6379' - deploy: - replicas: 1 - restart: unless-stopped - - tango: - container_name: tango - ports: - - '127.0.0.1:3000:3000' - image: ghcr.io/makeopensource/devu-tango:dev - environment: - - DOCKER_REDIS_HOSTNAME=redis - - RESTFUL_KEY=devutangokey -# - DOCKER_DEPLOYMENT - # Path to volumes within the Tango container. Does not need to be modified. -# - DOCKER_VOLUME_PATH - # TODO remember to modify the below to be the path to the absolute path of tango_files` on your host machine - - DOCKER_TANGO_HOST_VOLUME_PATH=/absolute/path/to/tango_files - - depends_on: - - redis - volumes: - - ./tango.config.py:/opt/TangoService/Tango/config.py - - /var/run/docker.sock:/var/run/docker.sock - - ./logs/tango/:/var/log/tango/ - - ./logs/tangonginx:/var/log/nginx - - ./tango_files:/opt/TangoService/Tango/volumes - restart: unless-stopped - - # autoupdate containers + # auto update containers watchtower: container_name: watchtower image: containrrr/watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock environment: - - TZ=${TZ} - - WATCHTOWER_POLL_INTERVAL=30 + - WATCHTOWER_POLL_INTERVAL=900 # check for new image every 15 min + - WATCHTOWER_REMOVE_VOLUMES=true - WATCHTOWER_CLEANUP=true - WATCHTOWER_INCLUDE_STOPPED=true restart: unless-stopped \ No newline at end of file From a8982d168a20162872f865395d1d80d38e3137ec Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 28 Feb 2025 17:09:09 -0500 Subject: [PATCH 345/400] fixed api image name --- .github/workflows/api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml index f627d911..5c712895 100644 --- a/.github/workflows/api.yml +++ b/.github/workflows/api.yml @@ -37,7 +37,7 @@ jobs: - name: build api docker run: | - IMAGE_NAME=ghcr.io/${{ env.repo_url }}/client:${{ steps.get_branch.outputs.branch_name }} + IMAGE_NAME=ghcr.io/${{ env.repo_url }}/api:${{ steps.get_branch.outputs.branch_name }} echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_ENV docker build . -f api.Dockerfile -t $IMAGE_NAME From cd02177355e291b4b51fb3df160fae1da6b8ac86 Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 28 Feb 2025 17:12:09 -0500 Subject: [PATCH 346/400] fixed job name --- .github/workflows/client.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index e43d792a..0e508698 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -9,7 +9,7 @@ on: - develop jobs: - build-api-docker: + build-client-docker: runs-on: ubuntu-latest permissions: contents: write # to be able to publish a GitHub release From db58d1ec8054d6b636118f45dded0a6910b0779d Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 28 Feb 2025 17:30:59 -0500 Subject: [PATCH 347/400] added setup script --- setup.sh | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 setup.sh diff --git a/setup.sh b/setup.sh new file mode 100644 index 00000000..2d9eca47 --- /dev/null +++ b/setup.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# setup a devU install using the prebuilt images +# also sets up the required tango config + +owner="$1" +branch="$2" + +if [ -z "$owner" ] || [ -z "$branch" ]; then + echo "Usage: $0 " + echo "Example: $0 makeopensource develop" + exit 1 +fi + +repo="devU" + +compose_file_path="example-docker-compose.yml" +compose_local_filename="docker-compose.yml" +raw_url="https://raw.githubusercontent.com/$owner/$repo/$branch/$compose_file_path" + +echo "Downloading $compose_file_path from $owner/$repo (branch: $branch) to $compose_local_filename" + +if curl -sSL "$raw_url" -o "$compose_local_filename"; then + echo "Download successful!" +else + echo "Download failed. Check the repository, branch, and file path." + exit 1 +fi + + +tango_conf_filename="tango.config.py" +raw_url="https://raw.githubusercontent.com/$owner/$repo/$branch/$tango_conf_filename" + +echo "Downloading $tango_conf_filename from $owner/$repo (branch: $branch) to $tango_conf_filename" + +if curl -sSL "$raw_url" -o "$tango_conf_filename"; then + echo "Download successful!" +else + echo "Download failed. Check the repository, branch, and file path." + exit 1 +fi + + +# Create the 'tango_files' directory if it doesn't exist +mkdir -p tango_files + +# Get the absolute path of the 'tango_files' directory +absolute_path=$(realpath tango_files) + +docker_compose_file="docker-compose.yml" + +if [ ! -f "$docker_compose_file" ]; then + echo "Error: Docker Compose file '$docker_compose_file' not found." + exit 1 +fi + +# 5. Use sed to replace the placeholder with the absolute path +if sed -i "s|DOCKER_TANGO_HOST_VOLUME_PATH=.*|DOCKER_TANGO_HOST_VOLUME_PATH=$absolute_path|" "$docker_compose_file"; then + echo "Successfully updated $docker_compose_file with path: $absolute_path" +else + echo "Error: Failed to update $docker_compose_file." + exit 1 +fi + +docker compose up + +exit 0 + From a0a4e6a86fde729032d65385885f6dba5939bb81 Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 28 Feb 2025 17:47:19 -0500 Subject: [PATCH 348/400] fixing example compose --- example-docker-compose.yml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/example-docker-compose.yml b/example-docker-compose.yml index 62276a8f..6674c07d 100644 --- a/example-docker-compose.yml +++ b/example-docker-compose.yml @@ -1,14 +1,16 @@ # example compose file # remember to copy tango.config.py to the working directory from where the compose file is run +# or use the setup script in repo name: devu services: api: container_name: api - image: ghcr.io/makeopensource/devu-api:dev + image: ghcr.io/makeopensource/devu/api:develop environment: TANGO_KEY: devutangokey # TODO: load in from env file. for now this is defined in tango section below WAIT_HOSTS: db:5432 + CLIENT_URL: https://client.devu.app # TODO change the client url here if needed depends_on: db: condition: service_started @@ -17,7 +19,7 @@ services: client: # Builds the front end and serves the frontend static files using nginx - image: ghcr.io/makeopensource/devu-client:dev + image: ghcr.io/makeopensource/devu/client:develop ports: - '9000:80' @@ -25,16 +27,12 @@ services: container_name: tango ports: - '127.0.0.1:3000:3000' - image: ghcr.io/makeopensource/devu-tango:dev + image: ghcr.io/makeopensource/devu/tango:develop environment: - DOCKER_REDIS_HOSTNAME=redis - RESTFUL_KEY=devutangokey - # - DOCKER_DEPLOYMENT - # Path to volumes within the Tango container. Does not need to be modified. - # - DOCKER_VOLUME_PATH # TODO remember to modify the below to be the path to the absolute path of tango_files` on your host machine - DOCKER_TANGO_HOST_VOLUME_PATH=/absolute/path/to/tango_files - depends_on: - redis volumes: @@ -54,6 +52,26 @@ services: replicas: 1 restart: unless-stopped + # leviathan stuff + # testing frontend for leviathan + kraken: + container_name: kraken + image: ghcr.io/makeopensource/leviathan/kraken:dev + ports: + - "3022:3000" + environment: + - LEVIATHAN_URL=http://levi:9221 + restart: unless-stopped + + levi: + container_name: leviathan + image: ghcr.io/makeopensource/leviathan:dev + ports: + - "9221:9221" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + restart: unless-stopped + db: # Runs the PostgreSQL database image: postgres From 7aa298bec27534ca06d729205764817e3ab83535 Mon Sep 17 00:00:00 2001 From: RA341 Date: Fri, 28 Feb 2025 17:57:05 -0500 Subject: [PATCH 349/400] updated readme --- .github/workflows/api.yml | 2 +- .github/workflows/client.yml | 2 +- readme.md | 14 +++++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml index 5c712895..68a8e726 100644 --- a/.github/workflows/api.yml +++ b/.github/workflows/api.yml @@ -6,7 +6,7 @@ on: paths: - 'devU-api/**' branches: - - develop + - develop # add more branches as needed jobs: build-api-docker: diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index 0e508698..17d1ed5e 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -6,7 +6,7 @@ on: paths: - 'devU-client/**' branches: - - develop + - develop # add more branches as needed jobs: build-client-docker: diff --git a/readme.md b/readme.md index be82f38a..ccebd1ba 100644 --- a/readme.md +++ b/readme.md @@ -36,7 +36,6 @@ DevU is open-source and contributors are welcome. To contribute to the project, 12. Push updates to your branch to update the PR 13. Once the maintainers are satisfied with your PR, it wil be merged into the develop branch - ## Getting Started This process might seem like a lot if you're new to many these technologies, but I promise it's at least 10 times @@ -143,3 +142,16 @@ query: SELECT * FROM "migrations" "migrations" ORDER BY "id" DESC query: START TRANSACTION query: CREATE TABLE "users" ("id" SERIAL NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "username" character varying(32) NOT NULL, "name" character varying(128) NOT NULL, "email" character varying(128) NOT NULL, CONSTRAINT "users_primary_key_constraint" PRIMARY KEY ("id")) ``` + + +## Deploy script + +To quickly setup a production version of devU you can call the [setup script](setup.sh) + +``` + bash <(curl -sSL https://raw.githubusercontent.com/makeopensource/devU/refs/heads/develop/setup.sh) makeopensource develop +``` + +This will download our [compose file](example-docker-compose.yml) with our pre-built images + +Currently, built only for develop, and setup the required config for tango From 621a1f5058f5b7144e3d4039dd6b8c5f016b2106 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Sat, 1 Mar 2025 14:47:07 -0500 Subject: [PATCH 350/400] setup add problem and add grader section, fixed textField styling to be defaulted to list-item-background but be re-assignable --- .../assignments/assignmentUpdatePage.scss | 38 +++++-- .../assignments/assignmentUpdatePage.tsx | 103 ++++++++++-------- .../components/shared/inputs/textField.scss | 5 +- .../components/shared/inputs/textField.tsx | 8 +- 4 files changed, 95 insertions(+), 59 deletions(-) diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss index eb730e5d..ddbf293b 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -25,7 +25,10 @@ .textField { align-items: center; + padding: 0; margin-bottom: 0; + background: none; + border: 2px solid #ccc; } .textFieldHeader{ @@ -59,17 +62,19 @@ input[type='datetime-local'] { padding: 10px; max-width: 1/3; border-radius: 8px; + background: none; } .problemsList { display:flex; flex-direction: column; align-items: left; - font-size: 16px; gap:10px; margin-bottom: 10px; } - +.problemName{ + margin: 0 0 10px 0; +} // .assignment { // padding: 10px; // font-weight: bold; @@ -97,27 +102,40 @@ input[type='datetime-local'] { } .problem, .fileList{ + border-bottom: 1px solid #ddd; display: flex; + flex-direction: column; width: 100%; - align-items: center; - justify-content: center; + align-items: flex-start; + justify-content:center; } .header { text-align: left; - margin: 10px 0; + margin: 5px 0; color: $text-color; } .editProblem { - margin-right: 10px; - background-color: $background; - border : 2px solid $primary; + margin-right: 5px; + color: #075D92; + padding: 0; + background: none; + text-decoration: underline; } .deleteButton { - background-color: $background; - border : 2px solid red; + margin-left: 5px; + color: $red; + padding: 0; + background: none; + text-decoration: underline; + +} + +.buttonContainer{ + display: flex; + gap: 20px; } @media (max-width:1000px) { diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index f2f971b3..48581d54 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react' -import { ExpressValidationError, Assignment, AssignmentProblem } from 'devu-shared-modules' +import { ExpressValidationError, Assignment, AssignmentProblem, NonContainerAutoGrader, ContainerAutoGrader } from 'devu-shared-modules' import 'react-datepicker/dist/react-datepicker.css' import { useHistory, useParams } from 'react-router-dom' import PageWrapper from 'components/shared/layouts/pageWrapper' @@ -27,16 +27,18 @@ const AssignmentUpdatePage = () => { const currentAssignmentId = parseInt(assignmentId) const [assignmentsList, setAssignmentsList] = useState([]) const [assignmentProblems, setAssignmentProblems] = useState([]) - const [allAssignmentProblems, setAllAssignmentProblems] = useState>(new Map()) + const [nonContainerAutograders, setNonContainerAutograders] = useState([]) + const [containerAutograders, setContainerAutograders] = useState([]) + + //const [allCategories, setAllCategories] = useState([]) const [invalidFields, setInvalidFields] = useState(new Map()) - const [openModal, setOpenModal] = useState(false) + const [openEditModal, setOpenEditModal] = useState(false) const [files, setFiles] = useState([]) const history = useHistory() const [theme, setTheme] = useState(getCssVariables()) -allAssignmentProblems; // Very Bad JS Work, yell at me if i leave these till the Pull Request setFiles; // Needs a custom observer to force an update when the css variables change // Custom observer will update the theme variables when the bodies classes change @@ -67,16 +69,17 @@ setFiles; maxScore: -1, }) - const handleOpenModal = (problem : AssignmentProblem) => { + + const handleOpenEditModal = (problem : AssignmentProblem) => { if(problem === assignmentProblemData) { - setOpenModal(true) + setOpenEditModal(true) } else { setAssignmentProblemData(problem) } } - const handleCloseModal = () => {setOpenModal(false)} + const handleCloseModal = () => {setOpenEditModal(false)} useEffect(() => { - if (assignmentProblemData.maxScore !== -1) { setOpenModal(true) } + if (assignmentProblemData.maxScore !== -1) { setOpenEditModal(true) } }, [assignmentProblemData]) const handleChange = (value: String, e: React.ChangeEvent) => { @@ -115,19 +118,20 @@ setFiles; useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`).then((res) => { setFormData(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems`).then((res) => { setAssignmentProblems(res) })}, []) - //useEffect(() => {RequestService.get(`/api/course/${courseId}/categories/`).then((res) => { setAllCategories(res) })}, []) + useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/non-container-auto-graders`).then((res) => { setNonContainerAutograders(res) })}, []) + useEffect(() => {RequestService.get( `/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`).then((res) => { setContainerAutograders(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments`).then((res) => { setAssignmentsList(res) })}, []) useEffect(() => { - for(let i : number = 0; i < assignmentsList.length; i++) { - RequestService.get(`/api/course/${courseId}/assignment/${assignmentsList[i].id}/assignment-problems`) - .then((res) => { - setAllAssignmentProblems(prevState => { - const newMap = new Map(prevState); - newMap.set(Number(assignmentsList[i].id), res); - return newMap; - }); - }) - } + // for(let i : number = 0; i < assignmentsList.length; i++) { // this is used for swapping between assignments on edit page, which i think is no longer part of the design + // RequestService.get(`/api/course/${courseId}/assignment/${assignmentsList[i].id}/assignment-problems`) + // .then((res) => { + // setAllAssignmentProblems(prevState => { + // const newMap = new Map(prevState); + // newMap.set(Number(assignmentsList[i].id), res); + // return newMap; + // }); + // }) + // } },[assignmentsList]) const handleAssignmentUpdate = () => { @@ -190,7 +194,7 @@ setFiles; RequestService.put(`/api/course/${courseId}/assignment/${currentAssignmentId}/assignment-problems/${assignmentProblemData.id}`, finalFormData) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Problem Updated' }) - setOpenModal(false) + setOpenEditModal(false) fetchAssignmentProblems() }) } @@ -245,7 +249,7 @@ setFiles; - +

Edit Problem

@@ -275,7 +279,7 @@ setFiles;

Edit Info

-
AssignmentCategory:
+
Assignment Category:
-
- +

Add Problems

- {assignmentProblems.map((problem, index) => ( +
+ + + +
+

Add Graders

+
+ + +
+

Graders

+ {nonContainerAutograders.map((nonContainerAutograder) => (
+ {nonContainerAutograder.question} - + Non-Code Grader
))} + {containerAutograders.map((containerAutograders) => (
+ {containerAutograders.autogradingImage} - + Code Grader
))} +

Problems

+ + { assignmentProblems.map((problem) => (
-

{`Problem ${index + 1}`}

- - +

{problem.problemName}

+ +
+ | + +
))} - +
@@ -359,18 +390,4 @@ setFiles; ) } -/* yell at me if i forget to delete this -
-

Attachments

- -
-
-

Files:

- {files.map((file, index) => ( -
-

{`${file.name}, `}

-
- ))} -
-
*/ export default AssignmentUpdatePage diff --git a/devU-client/src/components/shared/inputs/textField.scss b/devU-client/src/components/shared/inputs/textField.scss index 1aa7fc1d..a9da6dcb 100644 --- a/devU-client/src/components/shared/inputs/textField.scss +++ b/devU-client/src/components/shared/inputs/textField.scss @@ -1,7 +1,10 @@ +@import 'variables'; + .textField { display: flex; flex-direction: column; - + background-color: $input-field-background; + border-radius: 10px; margin-bottom: 30px; width: 100%; } diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 5c142cc1..3e64ad3c 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -77,16 +77,14 @@ const TextField = ({ "& .MuiOutlinedInput-input" : { color: theme.textColor, borderRadius: '10px', - border: '2px solid #ccc', - //backgroundColor: theme.inputFieldBackground, - // padding: '0.625rem 1rem', - marginBottom: '0px' + marginBottom: '0px', + padding: '10px' }, // label text "& .MuiInputLabel-outlined" : { color: theme.inputFieldLabel, "&.Mui-focused": { - color: theme.focus, // Define this color in your theme + color: theme.focus, // Define this color in your theme }, }, // border From f390c57ead8dd77865ea06769a50997a6a0f6266 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Sat, 1 Mar 2025 17:22:02 -0500 Subject: [PATCH 351/400] finished page. most add problem buttons are placeholders for now, there's an issue with container autograder display that i'll bring up next meeting but all basic functionality is there. yippee! --- devU-client/src/assets/global.scss | 5 +- .../assignments/assignmentUpdatePage.scss | 44 +++++++++++++- .../assignments/assignmentUpdatePage.tsx | 58 ++++++++++++++----- .../components/shared/inputs/textField.tsx | 5 +- 4 files changed, 93 insertions(+), 19 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index cd9b2c57..b339932c 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -26,10 +26,13 @@ // general button template, extends to 3 types of buttons - primary, secondary, delete .btn { padding: 10px 15px; - max-width: fit-content; + cursor: pointer; + padding: 5px 15px; border-radius: 100px; + height: fit-content; border: none; font-weight: 700; + font-size: 15px } button.btnPrimary { diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss index ddbf293b..ee2e3d9a 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -1,5 +1,4 @@ @import 'variables'; - .grid { display: grid; grid-template-columns: 1fr 1fr; @@ -7,6 +6,24 @@ grid-row-gap: 10px; width: 100%; } +.pageHeader{ + display: grid; + grid-template-columns: 1fr 2fr 1fr; +} +.backToCourse{ + margin-left: auto; + padding: 5px 15px; + align-self: center; + cursor: pointer; + padding: 5px 15px; + border-radius: 100px; + height: fit-content; + border: none; + color: #fff; + font-weight: 700; + font-size: 15px; + max-width: fit-content; +} // .assignmentsList {grid-area: 1 / 1 / 3 / 2;} // .problemsList {grid-area: 1 / 3 / 2 / 4;} @@ -25,7 +42,6 @@ .textField { align-items: center; - padding: 0; margin-bottom: 0; background: none; border: 2px solid #ccc; @@ -50,6 +66,7 @@ grid-column-gap: 10px; align-items: center; text-align: left; + margin-bottom: 15px; .datepicker_start {grid-area: 1 / 1 / 2 / 2;} .datepicker_due {grid-area: 1 / 2 / 2 / 3;} .datepicker_end {grid-area: 1 / 3 / 2 / 4;} @@ -72,6 +89,10 @@ input[type='datetime-local'] { gap:10px; margin-bottom: 10px; } +.filesList{ + display: flex; + margin: 10px 0; +} .problemName{ margin: 0 0 10px 0; } @@ -101,7 +122,7 @@ input[type='datetime-local'] { border-radius: 10px; } -.problem, .fileList{ +.problem{ border-bottom: 1px solid #ddd; display: flex; flex-direction: column; @@ -110,6 +131,7 @@ input[type='datetime-local'] { justify-content:center; } + .header { text-align: left; margin: 5px 0; @@ -138,6 +160,22 @@ input[type='datetime-local'] { gap: 20px; } +.fileUpload{ + text-transform: none; + border: 3px solid var(--primary); + background-color: var(--primary); + border-radius: 100px; + font-weight: 700; + font-size: 14px; + color: #fff; + padding: 5px 15px; + max-width: fit-content; +} +.fileUpload:hover{ + background-color: var(--primary); +} + + @media (max-width:1000px) { .grid { display: grid; diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index 48581d54..85c5d368 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -8,13 +8,13 @@ import { useActionless } from 'redux/hooks' import TextField from 'components/shared/inputs/textField' import Button from '../../../shared/inputs/button' import styles from './assignmentUpdatePage.scss' -//import DragDropFile from 'components/utils/dragDropFile' import { SET_ALERT } from 'redux/types/active.types' import { applyMessageToErrorFields, removeClassFromField } from 'utils/textField.utils' import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import { getCssVariables } from 'utils/theme.utils' +import { Button as MuiButton, StyledEngineProvider } from '@mui/material' //import Dropdown, { Option } from 'components/shared/inputs/dropdown'; //import Select from 'react-select/src/Select' @@ -100,11 +100,14 @@ setFiles; const handleEndDateChange = (e : React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, endDate: e.target.value }))} const handleDueDateChange = (e : React.ChangeEvent) => {setFormData(prevState => ({ ...prevState, dueDate: e.target.value }))} - /*const handleFile = (file: File) => { - if(files.length < 5) { - setFiles([...files, file]) + const handleFile = (e: React.ChangeEvent) => { + if (e.target.files){ + const file = e.target.files[0] + if(files.length < 5) { + setFiles([...files, file]) + } } - }*/ + } const fetchAssignmentProblems = () => { RequestService.get(`/api/course/${courseId}/assignment/${currentAssignmentId}/assignment-problems`) @@ -119,7 +122,7 @@ setFiles; useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`).then((res) => { setFormData(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems`).then((res) => { setAssignmentProblems(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/non-container-auto-graders`).then((res) => { setNonContainerAutograders(res) })}, []) - useEffect(() => {RequestService.get( `/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`).then((res) => { setContainerAutograders(res) })}, []) + useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`).then((res) => { setContainerAutograders(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments`).then((res) => { setAssignmentsList(res) })}, []) useEffect(() => { // for(let i : number = 0; i < assignmentsList.length; i++) { // this is used for swapping between assignments on edit page, which i think is no longer part of the design @@ -272,8 +275,10 @@ setFiles; - -

Edit Assignment

+
+

Edit Assignment

+ +

Edit Info

@@ -300,7 +305,7 @@ setFiles;
Description: (optional)
-
+

Attachments

+ {(files.length != 0) ? ( +
+ Files: + {files.slice(0,-1).map((file, index) => ( +
+  {`${file.name},`} +
))} +
+  {`${files[files.length-1].name}`} +
+ +
) + :
No files attached
} + + + +

Add Problems

@@ -360,15 +389,16 @@ setFiles; }} className='btnSecondary'>Non-code Grader

Graders

- {nonContainerAutograders.map((nonContainerAutograder) => (
+ {nonContainerAutograders.length != 0 && nonContainerAutograders.map((nonContainerAutograder) => (
{nonContainerAutograder.question} - Non-Code Grader
))} - {containerAutograders.map((containerAutograders) => (
- {containerAutograders.autogradingImage} - + {containerAutograders.length != 0 && containerAutograders.map((containerAutograder) => (
+ {containerAutograder.autogradingImage} - Code Grader
))} + {nonContainerAutograders.length == 0 && containerAutograders.length == 0 &&
No graders yet
}

Problems

- { assignmentProblems.map((problem) => ( + {assignmentProblems.length != 0 ? (assignmentProblems.map((problem) => (

{problem.problemName}

{ if (problem !== undefined && problem.id !== undefined) { handleDeleteProblem(problem.id) } }}>delete
- ))} + ))) :
No problems yet
}
diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 3e64ad3c..21470018 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -78,11 +78,14 @@ const TextField = ({ color: theme.textColor, borderRadius: '10px', marginBottom: '0px', - padding: '10px' + padding:'10px', + minHeight: '35px' + }, // label text "& .MuiInputLabel-outlined" : { color: theme.inputFieldLabel, + padding: '0', "&.Mui-focused": { color: theme.focus, // Define this color in your theme }, From 3c1a3053c20ef033f0ee91e564d257d1d85f51f8 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 2 Mar 2025 22:24:09 -0500 Subject: [PATCH 352/400] updated webpack, added docker build command --- devU-client/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devU-client/package.json b/devU-client/package.json index bd9edbd3..f3b3a25b 100644 --- a/devU-client/package.json +++ b/devU-client/package.json @@ -8,7 +8,7 @@ "build-development": "cross-env NODE_ENV=development webpack --mode=production", "build-all": "concurrently \"npm run build-development\" \"npm run build\"", "build-local": "cross-env NODE_ENV=local webpack --mode=production", - "build-docker": "webpack --mode=production", + "build-docker": "SASS_DEPRECATE_IMPORT=no webpack --mode=production --no-stats", "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"", "pre-commit": "lint-staged", "dev-backend": "docker compose -f ../docker-compose.yml --profile dev-client up -d", @@ -79,7 +79,7 @@ "sass-loader": "^10.2.0", "style-loader": "^2.0.0", "ts-loader": "^8.0.15", - "webpack": "^5.91.0", + "webpack": "^5.98.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4" From 484eaf56ebba004e9e736c1422d91e6ceff1abd8 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 2 Mar 2025 22:24:52 -0500 Subject: [PATCH 353/400] reverted nginx dockerfile, updated images to slim variants --- api.Dockerfile | 3 +-- client.Dockerfile | 20 ++++--------------- docker-compose.yml | 41 +++++++++++++++++++++++++------------- example-docker-compose.yml | 22 ++++++++++++++++++-- nginx.Dockerfile | 3 +++ 5 files changed, 55 insertions(+), 34 deletions(-) create mode 100644 nginx.Dockerfile diff --git a/api.Dockerfile b/api.Dockerfile index 399bf401..1238d99c 100644 --- a/api.Dockerfile +++ b/api.Dockerfile @@ -21,7 +21,7 @@ COPY devU-api/config/ ./config RUN ./generateConfig.sh default.yml -FROM node:20 +FROM node:22-alpine WORKDIR /app @@ -43,4 +43,3 @@ RUN chmod +x /wait # TypeORM Migrations CMD /wait && npm run typeorm -- migration:run -d src/database.ts && npm start - diff --git a/client.Dockerfile b/client.Dockerfile index 6a3d988c..c1075af4 100644 --- a/client.Dockerfile +++ b/client.Dockerfile @@ -1,4 +1,4 @@ -FROM node:20 AS module_builder +FROM node:22-alpine AS module_builder WORKDIR /tmp @@ -8,7 +8,7 @@ RUN npm install && \ npm run clean-directory && \ npm run build-docker -FROM node:16 AS frontend +FROM node:22-alpine WORKDIR /app @@ -20,17 +20,5 @@ COPY ./devU-client/ . COPY --from=module_builder /tmp/devu-shared-modules ./devu-shared-modules -# Pass API_URL and ROOT_PATH as build arguments -ARG API_URL -ARG ROOT_PATH - -# Use build arguments in the build command -RUN API_URL=$API_URL ROOT_PATH=$ROOT_PATH npm run build-docker - -# final stage serve frontend files -FROM nginx:1.23.3 - -COPY nginx.conf /etc/nginx/nginx.conf - -# Copy built frontend files to nginx serving directory -COPY --from=frontend /app/dist/local /usr/share/nginx/html \ No newline at end of file +# build frontend during run so that we can modify baseurl via docker envoirment +CMD npm run --silent build-docker && rm -rf /out/* && cp -r /app/dist/* /out diff --git a/docker-compose.yml b/docker-compose.yml index 1c20529d..64a19e8c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,14 +15,27 @@ services: - '' # so it starts with normal docker compose - 'dev-client' # start when developing client - client-nginx: - # Builds the front end and serves the frontend static files using nginx + client: + # Builds the front end and exports static files to ./dist build: + context: . dockerfile: client.Dockerfile + volumes: + - ./dist:/out + profiles: + - '' # so it starts with normal docker compose + - 'dev-api' # start when developing api + + nginx: + # Hosts the front end static files from ./dist/local thorough a web server + build: context: . - args: - API_URL: http://localhost:3001 - ROOT_PATH: / + dockerfile: nginx.Dockerfile + volumes: + - ./dist/local:/usr/share/nginx/html + depends_on: + client: + condition: service_completed_successfully ports: - '9000:80' profiles: @@ -57,15 +70,15 @@ services: MINIO_ROOT_PASSWORD: changeMe command: server /data --console-address ":9001" - leviathan: - container_name: leviathan - image: ghcr.io/makeopensource/leviathan:dev - ports: - - "9221:9221" - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ./levi/:/app/appdata - restart: unless-stopped +# leviathan: +# container_name: leviathan +# image: ghcr.io/makeopensource/leviathan:dev +# ports: +# - "9221:9221" +# volumes: +# - /var/run/docker.sock:/var/run/docker.sock +# - ./levi/:/app/appdata +# restart: unless-stopped # tango stuff tango: diff --git a/example-docker-compose.yml b/example-docker-compose.yml index 6674c07d..2c697ace 100644 --- a/example-docker-compose.yml +++ b/example-docker-compose.yml @@ -5,7 +5,7 @@ name: devu services: api: - container_name: api + container_name: devu-api image: ghcr.io/makeopensource/devu/api:develop environment: TANGO_KEY: devutangokey # TODO: load in from env file. for now this is defined in tango section below @@ -16,12 +16,28 @@ services: condition: service_started ports: - '3001:3001' + restart: unless-stopped client: - # Builds the front end and serves the frontend static files using nginx image: ghcr.io/makeopensource/devu/client:develop + environment: + - API_URL=https://devu.app # todo change this to prod api url + - ROOT_PATH=/ + volumes: + - ./dist:/out + + # Hosts the front end static files from ./dist/local thorough a web server + nginx: + container_name: devu-client-nginx + image: ghcr.io/makeopensource/devu/nginx:develop + volumes: + - ./dist/local:/usr/share/nginx/html + depends_on: + client: + condition: service_completed_successfully ports: - '9000:80' + restart: unless-stopped tango: container_name: tango @@ -83,6 +99,7 @@ services: - '5432:5432' expose: - '5432' + restart: unless-stopped minio: image: minio/minio @@ -97,6 +114,7 @@ services: MINIO_ROOT_USER: typescript_user MINIO_ROOT_PASSWORD: changeMe command: server /data --console-address ":9001" + restart: unless-stopped # auto update containers watchtower: diff --git a/nginx.Dockerfile b/nginx.Dockerfile new file mode 100644 index 00000000..ad70e255 --- /dev/null +++ b/nginx.Dockerfile @@ -0,0 +1,3 @@ +FROM nginx:1-alpine + +COPY nginx.conf /etc/nginx/nginx.conf \ No newline at end of file From 3f15c2e08f8fa3b29d25649bf74e358f1e3f6285 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 2 Mar 2025 22:29:18 -0500 Subject: [PATCH 354/400] added nginx build --- .github/workflows/client.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index 17d1ed5e..8dbefbba 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -39,11 +39,14 @@ jobs: run: | IMAGE_NAME=ghcr.io/${{ env.repo_url }}/client:${{ steps.get_branch.outputs.branch_name }} echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_ENV - - docker build . \ - -f client.Dockerfile \ - --build-arg API_URL=${{ secrets.prod_api_url }} \ - --build-arg ROOT_PATH=${{ secrets.prod_api_path }} \ - -t $IMAGE_NAME - + + docker build . -f client.Dockerfile -t $IMAGE_NAME + docker push $IMAGE_NAME + + - name: build nginx + run: | + IMAGE_NAME=ghcr.io/${{ env.repo_url }}/nginx:${{ steps.get_branch.outputs.branch_name }} + echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_ENV + + docker build . -f nginx.Dockerfile -t $IMAGE_NAME docker push $IMAGE_NAME From ebadc26fb9c13845289d7a221691bbfc59d4f339 Mon Sep 17 00:00:00 2001 From: RA341 Date: Sun, 2 Mar 2025 22:51:37 -0500 Subject: [PATCH 355/400] switched to alpine --- api.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.Dockerfile b/api.Dockerfile index 1238d99c..459914d2 100644 --- a/api.Dockerfile +++ b/api.Dockerfile @@ -1,4 +1,4 @@ -FROM node:20 AS module_builder +FROM node:22-alpine AS module_builder WORKDIR /tmp From f76145149fc55f885e4fd2e5eeee0b9e286eb180 Mon Sep 17 00:00:00 2001 From: SameepK Date: Mon, 3 Mar 2025 10:48:15 -0500 Subject: [PATCH 356/400] updated the frontend for the Join Course Page --- .../src/components/authenticatedRouter.tsx | 4 + .../components/pages/homePage/homePage.tsx | 8 +- .../listPages/courses/coursesListPage.scss | 191 +++++++++++++----- .../listPages/courses/coursesListPage.tsx | 141 ++++++++----- .../pages/listPages/joinwithcodepage.scss | 47 +++++ .../pages/listPages/joinwithcodepage.tsx | 67 ++++++ 6 files changed, 357 insertions(+), 101 deletions(-) create mode 100644 devU-client/src/components/pages/listPages/joinwithcodepage.scss create mode 100644 devU-client/src/components/pages/listPages/joinwithcodepage.tsx diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index 65e87ede..ea9e43a8 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -21,6 +21,8 @@ import CoursesListPage from "./pages/listPages/courses/coursesListPage"; import AssignmentProblemFormPage from './pages/forms/assignments/assignmentProblemFormPage' import InstructorSubmissionspage from "./pages/submissions/InstructorSubmissionspage"; import SubmissionFileView from './pages/submissions/submissionFileView' +import UserCoursesListPage from "./pages/listPages/courses/coursesListPage"; +import JoinCoursePage from "./pages/listPages/joinwithcodepage"; import WebhookURLForm from './pages/webhookURLForm' @@ -55,6 +57,8 @@ const AuthenticatedRouter = () => ( + + // TBD, undecided where webhooks should be placed {/**/} diff --git a/devU-client/src/components/pages/homePage/homePage.tsx b/devU-client/src/components/pages/homePage/homePage.tsx index a5faf18a..f73914da 100644 --- a/devU-client/src/components/pages/homePage/homePage.tsx +++ b/devU-client/src/components/pages/homePage/homePage.tsx @@ -82,9 +82,11 @@ const HomePage = () => { - + +
diff --git a/devU-client/src/components/pages/listPages/courses/coursesListPage.scss b/devU-client/src/components/pages/listPages/courses/coursesListPage.scss index b36fdfd7..490f8c6d 100644 --- a/devU-client/src/components/pages/listPages/courses/coursesListPage.scss +++ b/devU-client/src/components/pages/listPages/courses/coursesListPage.scss @@ -1,65 +1,160 @@ @import 'variables'; +.coursesListPage { + font-family: $font-family; + background-color: $background; + color: $text-color; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + /* Breadcrumbs & Navigation */ + .navigation { + display: flex; + align-items: center; + width: 80%; + max-width: 1000px; + justify-content: space-between; + margin: 15px 0; + .homeLink { + font-size: 1rem; + font-weight: bold; + color: $primary; + text-decoration: none; + transition: 0.2s; -.addCourseBtn { + &:hover { + color: $secondary; + } + } - background-color: $primary; - color: $text-color; - border: 0px; - padding: 10px 40px; - border-radius: 50px; - font-size: 14px; - font-weight: 700; - text-decoration: none; - cursor: pointer; -} + .breadcrumbs { + font-size: 0.9rem; + color: $text-color-secondary; + } + } -.courseName { - color: $text-color; - text-decoration: none; - font-size: 14px; -} + /* Page Title */ + .pageTitle { + font-size: 2.5rem; + font-weight: bold; + text-align: center; + margin-bottom: 20px; + } -.filters { - display: flex; - justify-content: flex-end; + /* Search Section */ + .searchSection { + display: flex; + justify-content: space-between; + align-items: center; + width: 90%; + max-width: 1000px; + margin-bottom: 20px; + gap: 12px; - margin-bottom: 1rem; -} + input { + flex-grow: 1; + padding: 10px; + border: 1px solid $input-border; + background-color: $input-field-background; + color: $text-color; + border-radius: 12px; + font-size: 1rem; + } -.dropdown { - width: 300px; -} + button { + padding: 10px 16px; + background-color: $primary; + color: white; + border: none; + border-radius: 12px; + font-size: 1rem; + font-weight: bold; + cursor: pointer; + transition: 0.2s; -.header { - color: $text-color; - display: flex; - justify-content: space-between; - align-items: center; - cursor: default; -} + } -@media (max-width: 700px) { - .dropdown { - width: 100%; + .joinWithCodeButton { + margin-left: auto; + background-color: $primary; + } } - .header { - display: block; + /* Table Styling */ + .tableContainer { + width: 96%; + max-width: 1200px; + margin: 10px auto; + border-radius: 8px; + overflow: hidden; + + table { + width: 100%; + border-collapse: collapse; + + th, td { + text-align: center; + padding: 12px; + border-bottom: 1px solid $grey; + } + + th { + background-color: $primary; + color: white; + font-weight: bold; + text-transform: capitalize; + } + + tr:nth-child(even) { + background-color: $table-row-even; + } + + tr:nth-child(odd) { + background-color: $table-row-odd; + } + + tr:hover { + background-color: $list-item-background-hover; + } + } + + /* Join Button */ + .joinButton { + background-color: $primary; + color: white; + border: none; + padding: 10px 14px; + border-radius: 20px; /* ✅ Makes button rounded */ + cursor: pointer; + font-size: 1rem; + font-weight: bold; + transition: 0.2s; + box-shadow: $box-shadow; + + &:hover { + background-color: $secondary-darker; + } + + &:disabled { + background-color: $grey-lighter; + cursor: not-allowed; + } + } } -} -.smallLine { - width: 50px; /* adjust this value to set the length of the small line */ - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-right: 10px; /* adjust this value to set the space between the line and the text */ -} + /* Responsive Design */ + @media (max-width: $medium) { + .tableContainer { + width: 100%; + overflow-x: auto; + } -.largeLine { - flex-grow: 1; - border-top: 3px solid $text-color; /* adjust this value to set the color and thickness of the line */ - margin-left: 10px; /* adjust this value to set the space between the line and the text */ - margin-right: 10px; /* add this line to create some space between the line and the button */ -} \ No newline at end of file + .searchSection { + flex-direction: column; + align-items: flex-start; + } + } +} diff --git a/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx b/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx index 2184be95..8fd4cd9e 100644 --- a/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx +++ b/devU-client/src/components/pages/listPages/courses/coursesListPage.tsx @@ -2,32 +2,24 @@ import React, { useEffect, useState } from 'react'; import { Course } from 'devu-shared-modules'; import LoadingOverlay from 'components/shared/loaders/loadingOverlay'; import PageWrapper from 'components/shared/layouts/pageWrapper'; -import Dropdown, { Option } from 'components/shared/inputs/dropdown'; import ErrorPage from '../../errorPage/errorPage'; import RequestService from 'services/request.service'; import styles from './coursesListPage.scss'; -import CourseListItem from "../../../listItems/courseListItem"; -import Button from "@mui/material/Button"; +import { useAppSelector, useActionless } from "../../../../redux/hooks"; +import { SET_ALERT } from "../../../../redux/types/active.types"; +import Button from '@mui/material/Button'; import { useHistory } from "react-router-dom"; -import { useAppSelector } from "../../../../redux/hooks"; - -type Filter = true | false; - -const filterOptions: Option[] = [ - { label: 'Expand All', value: true }, - { label: 'Collapse All', value: false }, -]; const UserCoursesListPage = () => { const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - + const [error, setError] = useState(null); + const [searchQuery, setSearchQuery] = useState(""); const [allCourses, setAllCourses] = useState([]); - const [filter, setFilter] = useState(false); + const [joinedCourses, setJoinedCourses] = useState>(new Set()); const history = useHistory(); - // Get userId from Redux store const userId = useAppSelector((store) => store.user.id); + const [setAlert] = useActionless(SET_ALERT); // ✅ Fix: Correct way to set alerts useEffect(() => { fetchData(); @@ -35,15 +27,13 @@ const UserCoursesListPage = () => { const fetchData = async () => { try { - // Fetch user-specific courses const userCourseData = await RequestService.get<{ instructorCourses: Course[]; activeCourses: Course[]; pastCourses: Course[]; upcomingCourses: Course[]; }>(`/api/courses/user/${userId}`); - - // Flatten and combine user course data into a single array + const userCoursesList = [ ...userCourseData.instructorCourses, ...userCourseData.activeCourses, @@ -51,57 +41,108 @@ const UserCoursesListPage = () => { ...userCourseData.upcomingCourses, ]; - // Fetch all courses const allCourseData = await RequestService.get(`/api/courses`); - // Filter to get courses the user is not enrolled in - const unenrolledCourses = allCourseData.filter( - (course) => !userCoursesList.some((userCourse) => userCourse.id === course.id) - ); + setAllCourses(allCourseData); - - setAllCourses(unenrolledCourses); - } catch (error: any) { - setError(error); + + setJoinedCourses(new Set(userCoursesList.map(course => course.id ?? -1))); + } catch (error: unknown) { + setError(error as Error); } finally { setLoading(false); } }; - const handleFilterChange = (updatedFilter: Filter) => { - setFilter(updatedFilter); + const handleJoinCourse = async (courseId: number) => { + const userCourseData = { + userId: userId, + courseId: courseId, + role: 'student', + dropped: false + }; + + try { + await RequestService.post(`/api/course/${courseId}/user-courses`, userCourseData); + + + setJoinedCourses((prev) => new Set(prev).add(courseId)); + + setAlert({ autoDelete: true, type: 'success', message: 'Course Joined' }); + } catch (error: unknown) { + setAlert({ autoDelete: false, type: 'error', message: (error as Error).message }); + } }; + const filteredCourses = allCourses.filter((course) => + course.name.toLowerCase().includes(searchQuery.toLowerCase()) || + course.number.toLowerCase().includes(searchQuery.toLowerCase()) + ); + if (loading) return ; if (error) return ; - const defaultOption = filterOptions.find((o) => o.value === filter); - return ( -
-
-

All Courses

-
- - -
- +

Join Course

+
+ setSearchQuery(e.target.value)} /> + + + +
+ +
+ + + + + + {/* */} + + {/* */} + + + + + {filteredCourses.length > 0 ? ( + filteredCourses.map((course) => ( + + + + {/* */} + + + + )) + ) : ( + + + + )} + +
TitleCourse CodeInstructorSemesterSeats OpenJoin
{course.name}{course.number}{course.instructor}{course.semester} + +
No courses found.
- {allCourses.map((course) => ( - - ))} ); }; -export default UserCoursesListPage; \ No newline at end of file +export default UserCoursesListPage; diff --git a/devU-client/src/components/pages/listPages/joinwithcodepage.scss b/devU-client/src/components/pages/listPages/joinwithcodepage.scss new file mode 100644 index 00000000..a7c3f954 --- /dev/null +++ b/devU-client/src/components/pages/listPages/joinwithcodepage.scss @@ -0,0 +1,47 @@ +@import 'variables'; + +.joinCourseContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + background-color: #ffffff; +} + +h2 { + color: #000000; + font-size: 24px; + margin-bottom: 20px; +} + +.inputField { + width: 300px; + padding: 10px; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 5px; + margin-bottom: 15px; + text-align: center; +} + +.error { + color: red; + font-size: 14px; + margin-bottom: 10px; +} + +.joinButton { + background-color: #5645a9 !important; + color: white !important; + padding: 10px 20px; + font-size: 16px; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s ease-in-out; +} + +.joinButton:hover { + background-color: #5742d1 !important; +} diff --git a/devU-client/src/components/pages/listPages/joinwithcodepage.tsx b/devU-client/src/components/pages/listPages/joinwithcodepage.tsx new file mode 100644 index 00000000..1f4f5a8c --- /dev/null +++ b/devU-client/src/components/pages/listPages/joinwithcodepage.tsx @@ -0,0 +1,67 @@ +import React, { useState } from "react"; +import { useHistory } from "react-router-dom"; +import PageWrapper from "components/shared/layouts/pageWrapper"; +import RequestService from "services/request.service"; +import styles from "./joinwithcodepage.scss"; +import Button from "@mui/material/Button"; +import { useAppSelector } from "../../../redux/hooks"; + +const JoinWithCodePage = () => { + const [accessCode, setAccessCode] = useState(""); + const [error, setError] = useState(""); + const history = useHistory(); + const userId = useAppSelector((store) => store.user.id); + + const handleJoinCourse = async () => { + if (!accessCode.trim()) { + setError("Please enter a valid course code."); + return; + } + + try { + // ✅ Step 1: Find course ID using the access code + const courseResponse = await RequestService.get(`/api/courses/by-code/${accessCode}`); + const courseId = courseResponse.id; + + if (!courseId) { + setError("Invalid course code or course not found."); + return; + } + + // ✅ Step 2: Join the course using the course ID + await RequestService.post(`/api/course/${courseId}/user-courses`, { + userId: userId, + courseId: courseId, + role: 'student', + dropped: false + }); + + alert("Successfully joined the course!"); + history.push("/"); + } catch (err) { + setError("Invalid code or course not found."); + } + }; + + + return ( + +
+

Enter course access code

+ setAccessCode(e.target.value)} + className={styles.inputField} + /> + {error &&

{error}

} + +
+
+ ); +}; + +export default JoinWithCodePage; From c394a30bc9f0509de6a5ddff9a39874940087fe7 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Mon, 3 Mar 2025 11:41:14 -0500 Subject: [PATCH 357/400] removed redundant styling, and fixed mobile display for page --- devU-client/src/assets/global.scss | 1 - .../assignments/assignmentUpdatePage.scss | 17 +++-------- .../assignments/assignmentUpdatePage.tsx | 29 +++++++++++-------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index b339932c..ae43170f 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -27,7 +27,6 @@ .btn { padding: 10px 15px; cursor: pointer; - padding: 5px 15px; border-radius: 100px; height: fit-content; border: none; diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss index ee2e3d9a..7cf963a1 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.scss @@ -12,24 +12,16 @@ } .backToCourse{ margin-left: auto; - padding: 5px 15px; - align-self: center; - cursor: pointer; - padding: 5px 15px; - border-radius: 100px; - height: fit-content; - border: none; - color: #fff; - font-weight: 700; - font-size: 15px; - max-width: fit-content; } // .assignmentsList {grid-area: 1 / 1 / 3 / 2;} // .problemsList {grid-area: 1 / 3 / 2 / 4;} // .attachments {grid-area: 2 / 2 / 3 / 4;} -.form {width: 80%;} +.form { + width: 80%; + height: fit-content; +} .textFieldContainer { @@ -180,7 +172,6 @@ input[type='datetime-local'] { .grid { display: grid; grid-template-columns: 1fr; - grid-template-rows: repeat(4, 1fr); grid-column-gap: 0px; grid-row-gap: 10px; width: 100%; diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index 85c5d368..ab5ec45d 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -15,7 +15,7 @@ import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import { getCssVariables } from 'utils/theme.utils' import { Button as MuiButton, StyledEngineProvider } from '@mui/material' -//import Dropdown, { Option } from 'components/shared/inputs/dropdown'; +/*import Dropdown, { Option } from 'components/shared/inputs/dropdown';*/ //import Select from 'react-select/src/Select' type UrlParams = { assignmentId: string } @@ -30,8 +30,9 @@ const AssignmentUpdatePage = () => { const [nonContainerAutograders, setNonContainerAutograders] = useState([]) const [containerAutograders, setContainerAutograders] = useState([]) + /*const [categories, setCategories] = useState([]) + const [categoryOptions, setAllCategoryOptions] = useState[]>([])*/ - //const [allCategories, setAllCategories] = useState([]) const [invalidFields, setInvalidFields] = useState(new Map()) const [openEditModal, setOpenEditModal] = useState(false) @@ -114,18 +115,22 @@ setFiles; .then((res) => { setAssignmentProblems(res) }) } - /*const fetchCategories = () => { - RequestService.get(`/api/course/${courseId}/assignment/${currentAssignmentId}/assignment-problems`) - .then((res) => { setAllCategories(res) }) - }*/ + useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments/${assignmentId}`).then((res) => { setFormData(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/assignment-problems`).then((res) => { setAssignmentProblems(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/non-container-auto-graders`).then((res) => { setNonContainerAutograders(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignment/${assignmentId}/container-auto-graders`).then((res) => { setContainerAutograders(res) })}, []) useEffect(() => {RequestService.get(`/api/course/${courseId}/assignments`).then((res) => { setAssignmentsList(res) })}, []) + + /*useEffect(() => {RequestService.get(`/api/course/${courseId}/categories/`).then((res) => { setCategories(res) }).finally(convertToOptions)}, []) + const convertToOptions = () => { + setAllCategoryOptions(categories.map((category) => ({label: category.name, value: category}))) + }*/ + + useEffect(() => { - // for(let i : number = 0; i < assignmentsList.length; i++) { // this is used for swapping between assignments on edit page, which i think is no longer part of the design + // for(let i : number = 0; i < assignmentsList.length; i++) { // this is used for swapping between assignments on edit page, which is no longer part of the design // RequestService.get(`/api/course/${courseId}/assignment/${assignmentsList[i].id}/assignment-problems`) // .then((res) => { // setAllAssignmentProblems(prevState => { @@ -277,7 +282,7 @@ setFiles;

Edit Assignment

- +
@@ -375,9 +380,9 @@ setFiles;

Add Problems

- + - +

Add Graders

@@ -413,9 +418,9 @@ setFiles;
-
+
-
+
) } From a71a82e16a03266b8c310be0c3561b65fce48620 Mon Sep 17 00:00:00 2001 From: Diego Cabiya Date: Mon, 3 Mar 2025 12:43:09 -0500 Subject: [PATCH 358/400] chooes files looks better than add files --- devU-client/src/assets/global.scss | 2 +- .../components/pages/forms/assignments/assignmentUpdatePage.tsx | 2 +- devU-client/src/components/shared/inputs/textField.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index ae43170f..53bd5a46 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -25,7 +25,7 @@ // general button template, extends to 3 types of buttons - primary, secondary, delete .btn { - padding: 10px 15px; + padding: 5px 15px; cursor: pointer; border-radius: 100px; height: fit-content; diff --git a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx index ab5ec45d..05b5f487 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentUpdatePage.tsx @@ -370,7 +370,7 @@ setFiles; diff --git a/devU-client/src/components/shared/inputs/textField.tsx b/devU-client/src/components/shared/inputs/textField.tsx index 21470018..2e0bcdbd 100644 --- a/devU-client/src/components/shared/inputs/textField.tsx +++ b/devU-client/src/components/shared/inputs/textField.tsx @@ -87,7 +87,7 @@ const TextField = ({ color: theme.inputFieldLabel, padding: '0', "&.Mui-focused": { - color: theme.focus, // Define this color in your theme + color: theme.focus, // Define this color in your theme }, }, // border From cf8a8f93a8e0596e4f60577b2e8b475e6846c968 Mon Sep 17 00:00:00 2001 From: NeemZ16 <110858265+NeemZ16@users.noreply.github.com> Date: Mon, 3 Mar 2025 16:47:48 -0500 Subject: [PATCH 359/400] created modal and reconfigured add assignment button to show modal - 41 --- devU-client/src/assets/global.scss | 24 ++ .../src/components/authenticatedRouter.tsx | 5 +- .../components/listItems/courseListItem.tsx | 6 +- .../pages/courses/courseDetailPage.tsx | 12 +- .../forms/assignments/assignmentFormPage.tsx | 404 ++++++++++++------ .../src/components/shared/layouts/modal.tsx | 38 ++ 6 files changed, 344 insertions(+), 145 deletions(-) create mode 100644 devU-client/src/components/shared/layouts/modal.tsx diff --git a/devU-client/src/assets/global.scss b/devU-client/src/assets/global.scss index 373cf780..69b77c64 100644 --- a/devU-client/src/assets/global.scss +++ b/devU-client/src/assets/global.scss @@ -52,6 +52,30 @@ border: 3px solid var(--btn-delete-border); } + .modal { + padding: 20px; + display: flex; + flex-direction: column; + gap: 20px; + } + + .input-group { + display: flex; + flex-direction: column; + gap: 7px; + } + + .input-group>input, .input-group>textarea{ + padding: 10px; + border-radius: 10px; + border: 1px solid #555; + } + + .modal-header, .input-subgroup-2col { + display: flex; + justify-content: space-between; + } + body { font-family: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif; margin: 0; diff --git a/devU-client/src/components/authenticatedRouter.tsx b/devU-client/src/components/authenticatedRouter.tsx index 65e87ede..27fdc960 100644 --- a/devU-client/src/components/authenticatedRouter.tsx +++ b/devU-client/src/components/authenticatedRouter.tsx @@ -2,7 +2,7 @@ import React from 'react' import {Route, Switch} from 'react-router-dom' import AssignmentDetailPage from 'components/pages/assignments/assignmentDetailPage' -import AssignmentCreatePage from 'components/pages/forms/assignments/assignmentFormPage' +import AddAssignmentModal from 'components/pages/forms/assignments/assignmentFormPage' import AssignmentUpdatePage from 'components/pages/forms/assignments/assignmentUpdatePage' import CourseDetailPage from 'components/pages/courses/courseDetailPage' import EditCourseFormPage from 'components/pages/forms/courses/coursesFormPage' @@ -23,6 +23,7 @@ import InstructorSubmissionspage from "./pages/submissions/InstructorSubmissions import SubmissionFileView from './pages/submissions/submissionFileView' import WebhookURLForm from './pages/webhookURLForm' +// import AddAssignmentModal from 'components/pages/forms/assignments/assignmentFormPage' const AuthenticatedRouter = () => ( @@ -37,7 +38,7 @@ const AuthenticatedRouter = () => ( - + diff --git a/devU-client/src/components/listItems/courseListItem.tsx b/devU-client/src/components/listItems/courseListItem.tsx index 3f9b1977..6cb3d254 100644 --- a/devU-client/src/components/listItems/courseListItem.tsx +++ b/devU-client/src/components/listItems/courseListItem.tsx @@ -30,7 +30,7 @@ const CourseListItem = ({course, isOpen}: Props) => { setIsOpen(isOpen); }, [isOpen]); - if (!course || !course.isPublic) { + if (!course) { return null; } @@ -47,13 +47,13 @@ const CourseListItem = ({course, isOpen}: Props) => { {infoSection("Semester", prettyPrintSemester(course.semester))} {infoSection("Start/End Date", prettyPrintDate(course.startDate), prettyPrintDate(course.endDate))}
- {course && ( + {/* {course && ( course.isPublic ? ( Public Course ) : ( Private Course ) - )} + )} */}
diff --git a/devU-client/src/components/pages/courses/courseDetailPage.tsx b/devU-client/src/components/pages/courses/courseDetailPage.tsx index 56b7da89..5ec17783 100644 --- a/devU-client/src/components/pages/courses/courseDetailPage.tsx +++ b/devU-client/src/components/pages/courses/courseDetailPage.tsx @@ -22,6 +22,8 @@ import {useActionless, useAppSelector} from "../../../redux/hooks"; //import TextField from "../../shared/inputs/textField"; //import {useActionless, useAppSelector} from "redux/hooks"; +import AddAssignmentModal from '../forms/assignments/assignmentFormPage' + const CourseDetailPage = () => { @@ -32,6 +34,12 @@ const CourseDetailPage = () => { const [setAlert] = useActionless(SET_ALERT) const role = useAppSelector((store) => store.roleMode); + const [openModal, setOpenModal] = useState(false); + + const handleCloseModal = () => { + setOpenModal(false); + }; + // const[User, setUser]= useState < User ,preferredName>>({}) // const role = useAppSelector((store) => store.roleMode) @@ -114,7 +122,8 @@ const CourseDetailPage = () => { {role.isInstructor() &&( )} @@ -142,6 +151,7 @@ const CourseDetailPage = () => {
+
diff --git a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx index 691f1188..e3736c7e 100644 --- a/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx +++ b/devU-client/src/components/pages/forms/assignments/assignmentFormPage.tsx @@ -1,25 +1,238 @@ import React, { useState } from 'react' +import { useHistory } from 'react-router-dom' import { ExpressValidationError } from 'devu-shared-modules' import 'react-datepicker/dist/react-datepicker.css' -import PageWrapper from 'components/shared/layouts/pageWrapper' +// import PageWrapper from 'components/shared/layouts/pageWrapper' import RequestService from 'services/request.service' import { useActionless } from 'redux/hooks' -import TextField from 'components/shared/inputs/textField' +// import TextField from 'components/shared/inputs/textField' // import Button from '../../../shared/inputs/button' -import DragDropFile from 'components/utils/dragDropFile' +// import DragDropFile from 'components/utils/dragDropFile' import { SET_ALERT } from 'redux/types/active.types' -import { applyMessageToErrorFields, removeClassFromField } from "../../../../utils/textField.utils"; -import { useHistory, useParams } from 'react-router-dom' +import { applyMessageToErrorFields } from "../../../../utils/textField.utils"; +import { useParams } from 'react-router-dom' import formStyles from './assignmentFormPage.scss' +import Modal from 'components/shared/layouts/modal' -const AssignmentCreatePage = () => { - const [setAlert] = useActionless(SET_ALERT) +// const AssignmentCreatePage = () => { +// const [setAlert] = useActionless(SET_ALERT) +// const { courseId } = useParams<{ courseId: string }>() +// const history = useHistory() + +// const [formData, setFormData] = useState({ +// courseId: courseId, +// name: '', +// categoryName: '', +// description: '', +// maxFileSize: 0, +// maxSubmissions: 0, +// disableHandins: false, +// }) +// const [endDate, setEndDate] = useState('') +// const [dueDate, setDueDate] = useState('') +// const [startDate, setStartDate] = useState('') +// const [invalidFields, setInvalidFields] = useState(new Map()) +// const [files, setFiles] = useState>(new Map()) + +// const handleChange = (value: String, e: React.ChangeEvent) => { +// const key = e.target.id + +// const newInvalidFields = removeClassFromField(invalidFields, key) +// setInvalidFields(newInvalidFields) + +// setFormData(prevState => ({ ...prevState, [key]: value })) +// } + +// const handleCheckbox = (e: React.ChangeEvent) => { +// setFormData(prevState => ({ ...prevState, disableHandins: e.target.checked })) +// } + +// const handleStartDateChange = (e: React.ChangeEvent) => { setStartDate(e.target.value) } +// const handleEndDateChange = (e: React.ChangeEvent) => { setEndDate(e.target.value) } +// const handleDueDateChange = (e: React.ChangeEvent) => { setDueDate(e.target.value) } + +// const handleSubmit = () => { +// const finalFormData = { +// courseId: courseId, +// name: formData.name, +// startDate: startDate, +// dueDate: dueDate, +// endDate: endDate, +// categoryName: formData.categoryName, +// description: formData.description, +// maxFileSize: formData.maxFileSize, +// maxSubmissions: formData.maxSubmissions, +// disableHandins: formData.disableHandins, +// } + +// const multipart = new FormData +// multipart.append('courseId', finalFormData.courseId) +// multipart.append('name', finalFormData.name) +// multipart.append('startDate', finalFormData.startDate) +// multipart.append('dueDate', finalFormData.dueDate) +// multipart.append('endDate', finalFormData.endDate) +// multipart.append('categoryName', finalFormData.categoryName) +// if (finalFormData.description !== null) { multipart.append('description', finalFormData.description) } +// multipart.append('maxFileSize', finalFormData.maxFileSize.toString()) +// if (finalFormData.maxSubmissions !== null) { multipart.append('maxSubmissions', finalFormData.maxSubmissions.toString()) } +// multipart.append('disableHandins', finalFormData.disableHandins.toString()) +// for (const file of files.values()) { +// multipart.append('files', file) +// } + + +// RequestService.postMultipart(`/api/course/${courseId}/assignments/`, multipart) +// .then(() => { +// setAlert({ autoDelete: true, type: 'success', message: 'Assignment Added' }) +// history.goBack() +// }) +// .catch((err: ExpressValidationError[] | Error) => { +// const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message +// const newFields = new Map() +// Array.isArray(err) ? err.map((e) => applyMessageToErrorFields(newFields, e.param, e.msg)) : newFields +// setInvalidFields(newFields); + +// setAlert({ autoDelete: false, type: 'error', message }) +// }) +// .finally(() => { + +// }) + +// } + +// const handleFile = (file: File) => { +// if (files.size < 5) { +// setFiles(prevState => new Map(prevState).set(file.name, file)); +// } else { +// // TODO: Add alert +// console.log('Max files reached'); +// } +// }; + +// const handleFileRemoval = (e: React.MouseEvent) => { +// const key = e.currentTarget.id; +// setFiles(prevState => { +// const newFiles = new Map(prevState); +// newFiles.delete(key); +// return newFiles; +// }); +// }; + +// return ( +// +//

Create Assignment

+//
+//
+//

Assignment Information

+//
+// + +// +//
+ +// + +//
+// + +// +//
+ +//
+//
+// +//
+// +//
+//
+// +//
+// +//
+//
+// +//
+// +//
+//
+//
+//
+// +// +//
+//
+//
+// +//
+//
+//
+// {/*TODO: Whenever file uploads is available on backend, store the files + create Object URLs*/} +//

Attachments

+//

Add up to 5 attachments with this assignment:

+//

Files Uploaded (click to delete) :

+//
+// {Array.from(files.keys()).map((fileName) => { +// return ( +//
+// +//
+// ); +// })} +//
+// {/*
*/} +//
+// +//
+// {/*
*/} +//
+//
+//
+// ) +// } + +interface Props { + open: boolean; + onClose: () => void; +} + +const AddAssignmentModal = ({ open, onClose }: Props) => { const { courseId } = useParams<{ courseId: string }>() + const [setAlert] = useActionless(SET_ALERT) + // const navigate = useNavigate() const history = useHistory() + const [endDate, setEndDate] = useState('') + const [dueDate, setDueDate] = useState('') + const [startDate, setStartDate] = useState('') const [formData, setFormData] = useState({ courseId: courseId, @@ -30,25 +243,14 @@ const AssignmentCreatePage = () => { maxSubmissions: 0, disableHandins: false, }) - const [endDate, setEndDate] = useState('') - const [dueDate, setDueDate] = useState('') - const [startDate, setStartDate] = useState('') - const [invalidFields, setInvalidFields] = useState(new Map()) - const [files, setFiles] = useState>(new Map()) - const handleChange = (value: String, e: React.ChangeEvent) => { + const handleChange = (e: React.ChangeEvent) => { const key = e.target.id - - const newInvalidFields = removeClassFromField(invalidFields, key) - setInvalidFields(newInvalidFields) + const value = e.target.value setFormData(prevState => ({ ...prevState, [key]: value })) } - const handleCheckbox = (e: React.ChangeEvent) => { - setFormData(prevState => ({ ...prevState, disableHandins: e.target.checked })) - } - const handleStartDateChange = (e: React.ChangeEvent) => { setStartDate(e.target.value) } const handleEndDateChange = (e: React.ChangeEvent) => { setEndDate(e.target.value) } const handleDueDateChange = (e: React.ChangeEvent) => { setDueDate(e.target.value) } @@ -78,21 +280,22 @@ const AssignmentCreatePage = () => { multipart.append('maxFileSize', finalFormData.maxFileSize.toString()) if (finalFormData.maxSubmissions !== null) { multipart.append('maxSubmissions', finalFormData.maxSubmissions.toString()) } multipart.append('disableHandins', finalFormData.disableHandins.toString()) - for (const file of files.values()) { - multipart.append('files', file) - } + // for (const file of files.values()) { + // multipart.append('files', file) + // } RequestService.postMultipart(`/api/course/${courseId}/assignments/`, multipart) .then(() => { setAlert({ autoDelete: true, type: 'success', message: 'Assignment Added' }) - history.goBack() + // Navigate to the update page for the new assignment + // navigate(`/course/${courseId}/assignment/${response.id}/update`); + // history.goBack() }) .catch((err: ExpressValidationError[] | Error) => { const message = Array.isArray(err) ? err.map((e) => `${e.param} ${e.msg}`).join(', ') : err.message const newFields = new Map() Array.isArray(err) ? err.map((e) => applyMessageToErrorFields(newFields, e.param, e.msg)) : newFields - setInvalidFields(newFields); setAlert({ autoDelete: false, type: 'error', message }) }) @@ -102,127 +305,50 @@ const AssignmentCreatePage = () => { } - const handleFile = (file: File) => { - if (files.size < 5) { - setFiles(prevState => new Map(prevState).set(file.name, file)); - } else { - // TODO: Add alert - console.log('Max files reached'); - } - }; - - const handleFileRemoval = (e: React.MouseEvent) => { - const key = e.currentTarget.id; - setFiles(prevState => { - const newFiles = new Map(prevState); - newFiles.delete(key); - return newFiles; - }); - }; return ( - -

Create Assignment

-
-
-

Assignment Information

-
- - - -
- - - -
- - - -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
+ +
+ + +
+
+ + +
+
+ +