diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index a4c424158..000000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,55 +0,0 @@ -// @ts-check - -/** - * @type {import('eslint').Linter.Config} - */ -const config = { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - project: ['tsconfig.json'], - sourceType: 'module', - }, - plugins: ['import', '@typescript-eslint/eslint-plugin'], - extends: [ - 'eslint:recommended', - 'plugin:import/recommended', - 'plugin:import/typescript', - 'plugin:@typescript-eslint/recommended', - 'prettier', - ], - rules: { - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - varsIgnorePattern: '^_', - caughtErrorsIgnorePattern: '^_', - }, - ], - 'import/order': [ - 'error', - { - alphabetize: { - order: 'asc', - }, - 'newlines-between': 'always', - }, - ], - }, - settings: { - 'import/resolver': { - typescript: { - project: ['tsconfig.json', 'apps/*/tsconfig.json'], - }, - }, - }, - env: { - browser: true, - es6: true, - node: true, - }, - ignorePatterns: ['*.config.*', '*rc.cjs'], -}; - -module.exports = config; diff --git a/apps/antalmanac/src/actions/AppStoreActions.ts b/apps/antalmanac/src/actions/AppStoreActions.ts index e54a5be49..138d53bb1 100644 --- a/apps/antalmanac/src/actions/AppStoreActions.ts +++ b/apps/antalmanac/src/actions/AppStoreActions.ts @@ -1,5 +1,10 @@ -import { RepeatingCustomEvent, ScheduleCourse, ShortCourseSchedule, WebsocSection } from '@packages/antalmanac-types'; -import { CourseDetails } from '@packages/antalmanac-types'; +import type { + CourseDetails, + RepeatingCustomEvent, + ScheduleCourse, + ShortCourseSchedule, + WebsocSection, +} from '@packages/antalmanac-types'; import { TRPCError } from '@trpc/server'; import { VariantType } from 'notistack'; diff --git a/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoBar.tsx b/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoBar.tsx index 28adcb083..6a1081ffb 100644 --- a/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoBar.tsx +++ b/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoBar.tsx @@ -8,9 +8,9 @@ import { useState } from 'react'; import { MOBILE_BREAKPOINT } from '../../../../globals'; +import PrereqTree from '$components/RightPane/SectionTable/PrereqTree'; import analyticsEnum, { logAnalytics } from '$lib/analytics'; import trpc from '$lib/api/trpc'; -import PrereqTree from '$components/RightPane/SectionTable/PrereqTree'; const styles = () => ({ rightSpace: { diff --git a/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoButton.tsx b/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoButton.tsx index 580e857e4..6794efa60 100644 --- a/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoButton.tsx +++ b/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoButton.tsx @@ -80,10 +80,6 @@ export const CourseInfoButton = ({ setIsClicked((prev) => !prev); } }} - // style={{ - // backgroundColor: '#385EB1', - // color: '#fff', - // }} > {icon} diff --git a/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoSearchButton.tsx b/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoSearchButton.tsx index 4ba49fd03..c3bb77013 100644 --- a/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoSearchButton.tsx +++ b/apps/antalmanac/src/components/RightPane/SectionTable/CourseInfo/CourseInfoSearchButton.tsx @@ -1,12 +1,13 @@ -import RightPaneStore from '$components/RightPane/RightPaneStore'; -import { useCoursePaneStore } from '$stores/CoursePaneStore'; -import { useTabStore } from '$stores/TabStore'; import { Search } from '@material-ui/icons'; import { Button } from '@mui/material'; import { AACourse } from '@packages/antalmanac-types'; import { useCallback } from 'react'; import { Link } from 'react-router-dom'; +import RightPaneStore from '$components/RightPane/RightPaneStore'; +import { useCoursePaneStore } from '$stores/CoursePaneStore'; +import { useTabStore } from '$stores/TabStore'; + /** * Routes the user to the corresponding search result */ diff --git a/apps/antalmanac/src/components/RightPane/SectionTable/PrereqTree.tsx b/apps/antalmanac/src/components/RightPane/SectionTable/PrereqTree.tsx index d027e5bcf..cdd25aff0 100644 --- a/apps/antalmanac/src/components/RightPane/SectionTable/PrereqTree.tsx +++ b/apps/antalmanac/src/components/RightPane/SectionTable/PrereqTree.tsx @@ -3,8 +3,7 @@ import { Button, Popover } from '@material-ui/core'; import { Prerequisite, PrerequisiteTree } from '@packages/antalmanac-types'; import { FC, useState } from 'react'; -import { CourseInfo } from './CourseInfoBar'; - +import { CourseInfo } from '$components/RightPane/SectionTable/CourseInfo/CourseInfoBar'; import { useThemeStore } from '$stores/SettingsStore'; import './PrereqTree.css'; diff --git a/apps/antalmanac/src/components/RightPane/SectionTable/SectionTable.tsx b/apps/antalmanac/src/components/RightPane/SectionTable/SectionTable.tsx index 6962fce61..7a48baa78 100644 --- a/apps/antalmanac/src/components/RightPane/SectionTable/SectionTable.tsx +++ b/apps/antalmanac/src/components/RightPane/SectionTable/SectionTable.tsx @@ -10,7 +10,7 @@ import { Typography, useMediaQuery, } from '@material-ui/core'; -import { Assessment, Help, RateReview, Search, ShowChart as ShowChartIcon } from '@material-ui/icons'; +import { Assessment, Help, RateReview, ShowChart as ShowChartIcon } from '@material-ui/icons'; import { useMemo } from 'react'; import { MOBILE_BREAKPOINT } from '../../../globals'; @@ -19,12 +19,12 @@ import { EnrollmentHistoryPopup } from './EnrollmentHistoryPopup'; import GradesPopup from './GradesPopup'; import { SectionTableProps } from './SectionTable.types'; +import { CourseInfoBar } from '$components/RightPane/SectionTable/CourseInfo/CourseInfoBar'; +import { CourseInfoButton } from '$components/RightPane/SectionTable/CourseInfo/CourseInfoButton'; +import { CourseInfoSearchButton } from '$components/RightPane/SectionTable/CourseInfo/CourseInfoSearchButton'; import { SectionTableBody } from '$components/RightPane/SectionTable/SectionTableBody/SectionTableBody'; import analyticsEnum from '$lib/analytics'; import { useColumnStore, SECTION_TABLE_COLUMNS, type SectionTableColumn } from '$stores/ColumnStore'; -import { CourseInfoButton } from '$components/RightPane/SectionTable/CourseInfo/CourseInfoButton'; -import { CourseInfoBar } from '$components/RightPane/SectionTable/CourseInfo/CourseInfoBar'; -import { CourseInfoSearchButton } from '$components/RightPane/SectionTable/CourseInfo/CourseInfoSearchButton'; import { useTabStore } from '$stores/TabStore'; const TOTAL_NUM_COLUMNS = SECTION_TABLE_COLUMNS.length; diff --git a/apps/antalmanac/src/components/RightPane/SectionTable/cells/action.tsx b/apps/antalmanac/src/components/RightPane/SectionTable/cells/action.tsx index 6e7a5aff8..92da3434d 100644 --- a/apps/antalmanac/src/components/RightPane/SectionTable/cells/action.tsx +++ b/apps/antalmanac/src/components/RightPane/SectionTable/cells/action.tsx @@ -1,7 +1,6 @@ import { Add, ArrowDropDown, Delete } from '@mui/icons-material'; import { Box, IconButton, Menu, MenuItem, TableCell, Tooltip, useMediaQuery } from '@mui/material'; -import { AASection } from '@packages/antalmanac-types'; -import { CourseDetails } from '@packages/antalmanac-types'; +import { AASection, CourseDetails } from '@packages/antalmanac-types'; import { bindMenu, bindTrigger, usePopupState } from 'material-ui-popup-state/hooks'; import { MOBILE_BREAKPOINT } from '../../../../globals'; diff --git a/apps/antalmanac/src/lib/websoc.ts b/apps/antalmanac/src/lib/websoc.ts index ea2217de5..cf7e51d0a 100644 --- a/apps/antalmanac/src/lib/websoc.ts +++ b/apps/antalmanac/src/lib/websoc.ts @@ -1,14 +1,36 @@ +import { WebsocAPIResponse } from '@packages/antalmanac-types'; + import trpc from '$lib/api/trpc'; +type CacheEntry = WebsocAPIResponse & { + timestamp: number; +}; + class _WebSOC { private aaCacheKey = Date.now().toString(10); + private cache: { [key: string]: CacheEntry }; + + constructor() { + this.cache = {}; + } clearCache() { this.aaCacheKey = Date.now().toString(10); + Object.keys(this.cache).forEach((key) => delete this.cache[key]); // https://stackoverflow.com/a/19316873/14587004 } async query(params: Record) { - return await trpc.websoc.getOne.query({ ...params, aaCacheKey: this.aaCacheKey }); + const paramsString = JSON.stringify(params); + + // hit cache if data is less than 15 minutes old + if (this.cache[paramsString]?.timestamp > Date.now() - 15 * 60 * 1000) { + return this.cache[paramsString]; + } + + const response = await trpc.websoc.getOne.query({ ...params, aaCacheKey: this.aaCacheKey }); + this.cache[paramsString] = { ...response, timestamp: Date.now() }; + + return response; } async queryMultiple(params: { [key: string]: string }, fieldName: string) { diff --git a/apps/antalmanac/src/stores/Schedules.ts b/apps/antalmanac/src/stores/Schedules.ts index 8aedc7657..b8094c0a3 100644 --- a/apps/antalmanac/src/stores/Schedules.ts +++ b/apps/antalmanac/src/stores/Schedules.ts @@ -5,8 +5,8 @@ import type { ScheduleUndoState, ShortCourseSchedule, RepeatingCustomEvent, + CourseInfo, } from '@packages/antalmanac-types'; -import type { CourseInfo } from '@packages/antalmanac-types'; import { calendarizeCourseEvents, calendarizeCustomEvents, calendarizeFinals } from './calendarizeHelpers'; diff --git a/apps/backend/src/routers/websoc.ts b/apps/backend/src/routers/websoc.ts index ad8a5db1f..3000824e9 100644 --- a/apps/backend/src/routers/websoc.ts +++ b/apps/backend/src/routers/websoc.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import type {WebsocAPIResponse, CourseInfo, WebsocCourse} from '@packages/antalmanac-types'; +import type { WebsocAPIResponse, CourseInfo, WebsocCourse } from '@packages/antalmanac-types'; import { procedure, router } from '../trpc'; function sanitizeSearchParams(params: Record) { diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..62cc0d4b0 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,95 @@ +import { fixupConfigRules, fixupPluginRules } from '@eslint/compat'; +import _import from 'eslint-plugin-import'; +import typescriptEslintEslintPlugin from '@typescript-eslint/eslint-plugin'; +import globals from 'globals'; +import tsParser from '@typescript-eslint/parser'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import js from '@eslint/js'; +import { FlatCompat } from '@eslint/eslintrc'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +export default [ + { + ignores: [ + '**/*.config.*', + '**/*rc.cjs', + '**/node_modules/', + '**/coverage/', + '**/.turbo/', + '**/build/', + '**/dist/', + '**/public/', + '**/.env', + '**/.env.*', + '!**/.env.sample', + ], + }, + ...fixupConfigRules( + compat.extends( + 'eslint:recommended', + 'plugin:import/recommended', + 'plugin:import/typescript', + 'plugin:@typescript-eslint/recommended', + 'prettier' + ) + ), + { + plugins: { + import: fixupPluginRules(_import), + '@typescript-eslint': fixupPluginRules(typescriptEslintEslintPlugin), + }, + + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + + parser: tsParser, + ecmaVersion: 5, + sourceType: 'module', + + parserOptions: { + project: ['tsconfig.json'], + }, + }, + + settings: { + 'import/resolver': { + typescript: { + project: ['tsconfig.json', 'apps/*/tsconfig.json'], + }, + }, + }, + + rules: { + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + + 'import/order': [ + 'error', + { + alphabetize: { + order: 'asc', + }, + + 'newlines-between': 'always', + }, + ], + }, + }, +]; diff --git a/package.json b/package.json index 6bb690627..3cc77d118 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,14 @@ "test": "vitest" }, "devDependencies": { + "@eslint/compat": "^1.2.4", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.17.0", "@types/eslint": "^8.56.5", "@types/node": "^20.11.6", "cross-env": "^7.0.3", "eslint-import-resolver-typescript": "^3.6.1", + "globals": "^15.14.0", "husky": "^8.0.3", "jsdom": "^22.1.0", "lint-staged": "^13.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f73333a81..b11912ce7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,15 @@ importers: .: devDependencies: + '@eslint/compat': + specifier: ^1.2.4 + version: 1.2.4(eslint@9.15.0) + '@eslint/eslintrc': + specifier: ^3.2.0 + version: 3.2.0 + '@eslint/js': + specifier: ^9.17.0 + version: 9.17.0 '@types/eslint': specifier: ^8.56.5 version: 8.56.12 @@ -20,6 +29,9 @@ importers: eslint-import-resolver-typescript: specifier: ^3.6.1 version: 3.6.3(eslint-plugin-import@2.31.0)(eslint@9.15.0) + globals: + specifier: ^15.14.0 + version: 15.14.0 husky: specifier: ^8.0.3 version: 8.0.3 @@ -1224,6 +1236,15 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/compat@1.2.4': + resolution: {integrity: sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.10.0 + peerDependenciesMeta: + eslint: + optional: true + '@eslint/config-array@0.19.0': resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1248,6 +1269,10 @@ packages: resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.17.0': + resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.4': resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3466,6 +3491,10 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globals@15.14.0: + resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -6216,6 +6245,10 @@ snapshots: '@eslint-community/regexpp@4.12.1': {} + '@eslint/compat@1.2.4(eslint@9.15.0)': + optionalDependencies: + eslint: 9.15.0 + '@eslint/config-array@0.19.0': dependencies: '@eslint/object-schema': 2.1.4 @@ -6258,6 +6291,8 @@ snapshots: '@eslint/js@9.15.0': {} + '@eslint/js@9.17.0': {} + '@eslint/object-schema@2.1.4': {} '@eslint/plugin-kit@0.2.3': @@ -8880,6 +8915,8 @@ snapshots: globals@14.0.0: {} + globals@15.14.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1