mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-12 19:03:39 +03:00
[Typescript] Migrate worker to typescript
This commit is contained in:
+10
-3
@@ -251,9 +251,10 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["updater/sis-updater-scheduler/src/**/*.ts"],
|
||||
"files": ["updater/sis-updater-scheduler/src/**/*.ts", "updater/sis-updater-worker/src/**/*.ts"],
|
||||
"rules": {
|
||||
"typescript/no-misused-promises": "off"
|
||||
"typescript/no-misused-promises": "off",
|
||||
"typescript/consistent-return": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -359,6 +360,12 @@
|
||||
"no-console": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["updater/sis-updater-worker/tests/**/*.ts"],
|
||||
"rules": {
|
||||
"typescript/unbound-method": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"services/backend/**/*.js",
|
||||
@@ -378,7 +385,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["services/backend/**/*.{js,ts}", "updater/**/*.js"],
|
||||
"files": ["services/backend/**/*.{js,ts}", "updater/**/*.{js,ts,mts}"],
|
||||
"rules": {
|
||||
"no-await-in-loop": "off",
|
||||
"no-promise-executor-return": "off",
|
||||
|
||||
@@ -56,7 +56,7 @@ If you need to **revert a migration locally**, follow these steps:
|
||||
|
||||
1. Locate the `migrator.up()` function in the code:
|
||||
- For `user-db` and `kone-db`: [`services/backend/src/database/connection.ts`](../services/backend/src/database/connection.ts)
|
||||
- For `sis-db`: [`updater/sis-updater-worker/src/db/connection.js`](../updater/sis-updater-worker/src/db/connection.js)
|
||||
- For `sis-db`: [`updater/sis-updater-worker/src/db/connection.ts`](../updater/sis-updater-worker/src/db/connection.ts)
|
||||
1. Change `migrator.up()` to `migrator.down()` and save the file. The latest migration will be reverted automatically when the server reloads.
|
||||
1. **Important:** After the migration is reverted, change `migrator.down()` back to `migrator.up()` to ensure future migrations run correctly.
|
||||
1. For more instructions, see the [umzug documentation](https://github.com/sequelize/umzug?tab=readme-ov-file#usage).
|
||||
|
||||
@@ -32,4 +32,4 @@ RUN npm ci --omit=dev
|
||||
|
||||
EXPOSE 8082
|
||||
|
||||
CMD ["node", "dist/index.js"]
|
||||
CMD ["node", "dist/src/index.js"]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node ./dist/index.js",
|
||||
"start": "node ./dist/src/index.js",
|
||||
"dev": "nodemon --exec \"node --import tsx ./src/index.ts\" --ext ts,js,json --watch src"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
|
||||
"rootDir": "src",
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
|
||||
"esModuleInterop": true,
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
FROM registry.access.redhat.com/ubi10/nodejs-24-minimal AS build
|
||||
|
||||
WORKDIR /opt/app-root/src
|
||||
|
||||
ENV TZ="Europe/Helsinki"
|
||||
|
||||
COPY ./.npmrc .
|
||||
COPY ./package*.json ./
|
||||
RUN npm ci --include=dev
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
|
||||
FROM registry.access.redhat.com/ubi10/nodejs-24-minimal
|
||||
|
||||
WORKDIR /opt/app-root/src
|
||||
@@ -12,9 +25,9 @@ ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
|
||||
ARG SENTRY_RELEASE
|
||||
ENV SENTRY_RELEASE=$SENTRY_RELEASE
|
||||
|
||||
COPY ./.npmrc .
|
||||
COPY ./package*.json ./
|
||||
COPY --from=build /opt/app-root/src/.npmrc .
|
||||
COPY --from=build /opt/app-root/src/package*.json ./
|
||||
COPY --from=build /opt/app-root/src/dist ./dist
|
||||
RUN npm ci --omit=dev
|
||||
COPY . .
|
||||
|
||||
CMD ["node", "src/index.js"]
|
||||
CMD ["node", "dist/src/index.js"]
|
||||
|
||||
@@ -6,7 +6,7 @@ The **sis-updater-worker** is responsible for processing BullMQ jobs that have b
|
||||
|
||||
When a job is picked up, the worker fetches the relevant entities from the **importer** database based on the `entityIds` specified in the job data. It then processes the data as needed and saves it into the **Oodikone database**.
|
||||
|
||||
The different types of jobs are defined in [`src/processor.js`](./src/processor.js). Below is a brief overview of the job types:
|
||||
The different types of jobs are defined in [`src/processor.ts`](./src/processor.ts). Below is a brief overview of the job types:
|
||||
|
||||
- **prepurge_start**:
|
||||
|
||||
|
||||
@@ -9,4 +9,4 @@ ENV NODE_ENV=development
|
||||
COPY . .
|
||||
RUN npm ci --include=dev
|
||||
|
||||
CMD ["node_modules/.bin/nodemon", "--exec", "node src/index.js", "--watch", "src", "--watch", "--ext", "ts, js, json", "--legacy-watch"]
|
||||
CMD ["node_modules/.bin/nodemon", "--exec", "node --import tsx src/index.ts", "--watch", "src", "--ext", "ts, js, json", "--legacy-watch"]
|
||||
|
||||
+907
-8
@@ -20,7 +20,12 @@
|
||||
"winston-transport-sentry-node": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/pg": "^8.18.0",
|
||||
"nodemon": "3.1.14",
|
||||
"tsx": "^4.19.4",
|
||||
"typescript": "^7.0.2",
|
||||
"vitest": "4.1.9"
|
||||
}
|
||||
},
|
||||
@@ -75,6 +80,448 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
|
||||
"integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
|
||||
"integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
|
||||
"integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
|
||||
"integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
|
||||
"integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
|
||||
"integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
|
||||
"integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
|
||||
"integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
|
||||
"integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
|
||||
"integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@ioredis/commands": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.3.1.tgz",
|
||||
@@ -1811,6 +2258,23 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz",
|
||||
"integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash-es": {
|
||||
"version": "4.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
|
||||
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||
@@ -1828,12 +2292,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz",
|
||||
"integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==",
|
||||
"version": "24.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.5.tgz",
|
||||
"integrity": "sha512-TXyindR+lBr22aJIdMQzCFHPHR6cR4js838mRDCSz5hOKWZvZwsXSSiXDmjRj4iJmgl+sR9O+1mkoVBSMadNug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.10.0"
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/pg": {
|
||||
@@ -1841,7 +2305,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.18.0.tgz",
|
||||
"integrity": "sha512-gT+oueVQkqnj6ajGJXblFR4iavIXWsGAFCk3dP4Kki5+a9R4NMt0JARdk6s8cUKcfUoqP5dAtDSLU8xYUTFV+Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"pg-protocol": "*",
|
||||
@@ -1887,6 +2350,346 @@
|
||||
"integrity": "sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript/typescript-aix-ppc64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
|
||||
"integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-darwin-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-darwin-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-freebsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-freebsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-arm": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
|
||||
"integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-loong64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
|
||||
"integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-mips64el": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
|
||||
"integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-ppc64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
|
||||
"integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-riscv64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
|
||||
"integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-s390x": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
|
||||
"integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-linux-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-netbsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-netbsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-openbsd-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-openbsd-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-sunos-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-win32-arm64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
|
||||
"integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript/typescript-win32-x64": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
|
||||
"integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz",
|
||||
@@ -2345,6 +3148,48 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
|
||||
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.28.2",
|
||||
"@esbuild/android-arm": "0.28.2",
|
||||
"@esbuild/android-arm64": "0.28.2",
|
||||
"@esbuild/android-x64": "0.28.2",
|
||||
"@esbuild/darwin-arm64": "0.28.2",
|
||||
"@esbuild/darwin-x64": "0.28.2",
|
||||
"@esbuild/freebsd-arm64": "0.28.2",
|
||||
"@esbuild/freebsd-x64": "0.28.2",
|
||||
"@esbuild/linux-arm": "0.28.2",
|
||||
"@esbuild/linux-arm64": "0.28.2",
|
||||
"@esbuild/linux-ia32": "0.28.2",
|
||||
"@esbuild/linux-loong64": "0.28.2",
|
||||
"@esbuild/linux-mips64el": "0.28.2",
|
||||
"@esbuild/linux-ppc64": "0.28.2",
|
||||
"@esbuild/linux-riscv64": "0.28.2",
|
||||
"@esbuild/linux-s390x": "0.28.2",
|
||||
"@esbuild/linux-x64": "0.28.2",
|
||||
"@esbuild/netbsd-arm64": "0.28.2",
|
||||
"@esbuild/netbsd-x64": "0.28.2",
|
||||
"@esbuild/openbsd-arm64": "0.28.2",
|
||||
"@esbuild/openbsd-x64": "0.28.2",
|
||||
"@esbuild/openharmony-arm64": "0.28.2",
|
||||
"@esbuild/sunos-x64": "0.28.2",
|
||||
"@esbuild/win32-arm64": "0.28.2",
|
||||
"@esbuild/win32-ia32": "0.28.2",
|
||||
"@esbuild/win32-x64": "0.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
@@ -4174,6 +5019,25 @@
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.23.13",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz",
|
||||
"integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.28.0"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||
@@ -4186,6 +5050,41 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
|
||||
"integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.20.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@typescript/typescript-aix-ppc64": "7.0.2",
|
||||
"@typescript/typescript-darwin-arm64": "7.0.2",
|
||||
"@typescript/typescript-darwin-x64": "7.0.2",
|
||||
"@typescript/typescript-freebsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-freebsd-x64": "7.0.2",
|
||||
"@typescript/typescript-linux-arm": "7.0.2",
|
||||
"@typescript/typescript-linux-arm64": "7.0.2",
|
||||
"@typescript/typescript-linux-loong64": "7.0.2",
|
||||
"@typescript/typescript-linux-mips64el": "7.0.2",
|
||||
"@typescript/typescript-linux-ppc64": "7.0.2",
|
||||
"@typescript/typescript-linux-riscv64": "7.0.2",
|
||||
"@typescript/typescript-linux-s390x": "7.0.2",
|
||||
"@typescript/typescript-linux-x64": "7.0.2",
|
||||
"@typescript/typescript-netbsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-netbsd-x64": "7.0.2",
|
||||
"@typescript/typescript-openbsd-arm64": "7.0.2",
|
||||
"@typescript/typescript-openbsd-x64": "7.0.2",
|
||||
"@typescript/typescript-sunos-x64": "7.0.2",
|
||||
"@typescript/typescript-win32-arm64": "7.0.2",
|
||||
"@typescript/typescript-win32-x64": "7.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/umzug": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/umzug/-/umzug-3.8.3.tgz",
|
||||
@@ -4210,9 +5109,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "sis-updater-worker",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node ./src/index.js",
|
||||
"dev": "nodemon ./src/index.js",
|
||||
"build": "tsc && npm run copy:csv",
|
||||
"copy:csv": "cp src/updater/updateStudents/excludedPartialAttainments.csv dist/src/updater/updateStudents/",
|
||||
"start": "node ./dist/src/index.js",
|
||||
"dev": "nodemon --exec \"node --import tsx ./src/index.ts\" --ext ts,js,json --watch src",
|
||||
"test": "vitest run",
|
||||
"test:unit": "vitest run ./tests/unit",
|
||||
"test:watch": "vitest"
|
||||
@@ -24,7 +25,12 @@
|
||||
"winston-transport-sentry-node": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/pg": "^8.18.0",
|
||||
"nodemon": "3.1.14",
|
||||
"tsx": "^4.19.4",
|
||||
"typescript": "^7.0.2",
|
||||
"vitest": "4.1.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
export const isDev = process.env.NODE_ENV === 'development'
|
||||
export const isStaging = process.env.STAGING || false
|
||||
export const isStaging = process.env.STAGING === 'true'
|
||||
export const isProduction = process.env.NODE_ENV === 'production'
|
||||
export const MIGRATIONS_LOCK = 'MIGRATIONS_LOCK'
|
||||
export const PURGE_LOCK = 'PURGE_LOCK'
|
||||
export const REDIS_HOST = process.env.REDIS_HOST || 'redis-updater'
|
||||
export const REDIS_PORT = process.env.REDIS_PORT || 6379
|
||||
export const rootOrgId = process.env.ROOT_ORG_ID || 'hy-university-root-id'
|
||||
export const REDIS_HOST = process.env.REDIS_HOST ?? 'redis-updater'
|
||||
export const REDIS_PORT = Number(process.env.REDIS_PORT) || 6379
|
||||
export const rootOrgId = process.env.ROOT_ORG_ID ?? 'hy-university-root-id'
|
||||
export const runningInCI = process.env.CI === 'true'
|
||||
export const { SENTRY_DSN } = process.env
|
||||
|
||||
export const { DB_URL } = process.env
|
||||
export const { SIS_IMPORTER_HOST } = process.env
|
||||
export const { SIS_IMPORTER_PORT } = process.env
|
||||
export const SIS_IMPORTER_PORT = Number(process.env.SIS_IMPORTER_PORT) || undefined
|
||||
export const { SIS_IMPORTER_USER } = process.env
|
||||
export const { SIS_IMPORTER_PASSWORD } = process.env
|
||||
export const { SIS_IMPORTER_DATABASE } = process.env
|
||||
+12
-12
@@ -1,5 +1,5 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import knex from 'knex'
|
||||
import knex, { type Knex } from 'knex'
|
||||
import { Sequelize } from 'sequelize'
|
||||
import { Umzug, SequelizeStorage } from 'umzug'
|
||||
|
||||
@@ -14,22 +14,22 @@ import {
|
||||
SIS_IMPORTER_PASSWORD,
|
||||
SIS_IMPORTER_DATABASE,
|
||||
SIS_PASSWORD,
|
||||
} from '../config.js'
|
||||
import logger from '../utils/logger.js'
|
||||
import { lock } from '../utils/redis.js'
|
||||
} from '../config'
|
||||
import logger from '../utils/logger'
|
||||
import { lock } from '../utils/redis'
|
||||
|
||||
class DbConnections extends EventEmitter {
|
||||
RETRY_ATTEMPTS = 15
|
||||
knexConnection = false
|
||||
seqConnection = false
|
||||
/** @type {import('knex').Knex | undefined}
|
||||
knex = undefined
|
||||
/** @type {import('knex').Knex | undefined} */
|
||||
knex!: Knex
|
||||
/** @type {import('sequelize').Sequelize | undefined} */
|
||||
sequelize = undefined
|
||||
sequelize: Sequelize
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.sequelize = new Sequelize(DB_URL, {
|
||||
this.sequelize = new Sequelize(DB_URL!, {
|
||||
dialect: 'postgres',
|
||||
pool: {
|
||||
max: 25,
|
||||
@@ -46,7 +46,7 @@ class DbConnections extends EventEmitter {
|
||||
* Registers named connection as established and emits 'connect' to the scheduler.
|
||||
* @param {'knexConnection' | 'seqConnection'} conn
|
||||
*/
|
||||
establish(conn) {
|
||||
establish(conn: 'knexConnection' | 'seqConnection') {
|
||||
this[conn] = true
|
||||
if (this.knexConnection && this.seqConnection) this.emit('connect')
|
||||
}
|
||||
@@ -88,7 +88,7 @@ class DbConnections extends EventEmitter {
|
||||
return
|
||||
}
|
||||
logger.error(`Knex database connection failed! Attempt ${attempt}/${this.RETRY_ATTEMPTS}`)
|
||||
setTimeout(() => this.connect(attempt + 1), 1000 * attempt)
|
||||
setTimeout(() => void this.connect(attempt + 1), 1000 * attempt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +104,9 @@ class DbConnections extends EventEmitter {
|
||||
tableName: 'migrations',
|
||||
}),
|
||||
migrations: {
|
||||
glob: 'src/db/migrations/*.cjs',
|
||||
glob: ['migrations/*.cjs', { cwd: __dirname }],
|
||||
resolve: ({ name, path, context }) => {
|
||||
const getMigration = () => import(path)
|
||||
const getMigration = async () => await import(path!)
|
||||
return {
|
||||
// Migration names need to end in .js for legacy reasons
|
||||
// modifying this behaviour requires manually altering the db 'migrations' table
|
||||
+26
-17
@@ -1,6 +1,6 @@
|
||||
import { getLatestSnapshot, isActive, getActiveSnapshot, getLatestActiveSnapshot } from '../utils/index.js'
|
||||
import logger from '../utils/logger.js'
|
||||
import { dbConnections } from './connection.js'
|
||||
import { getLatestSnapshot, isActive, getActiveSnapshot, getLatestActiveSnapshot } from '../utils/index'
|
||||
import logger from '../utils/logger'
|
||||
import { dbConnections } from './connection'
|
||||
|
||||
/**
|
||||
* @param {string} table - Table this function is trying to query against
|
||||
@@ -9,7 +9,8 @@ import { dbConnections } from './connection.js'
|
||||
*
|
||||
* @returns {Promise<T>} Where T is the query result object
|
||||
*/
|
||||
export const selectOneById = async (table, id, col = 'id') => dbConnections.knex(table).where(col, id).first()
|
||||
export const selectOneById = async (table: string, id: string, col = 'id') =>
|
||||
dbConnections.knex(table).where(col, id).first()
|
||||
|
||||
/**
|
||||
* @param {string} table - Table this function is trying to query against
|
||||
@@ -18,7 +19,8 @@ export const selectOneById = async (table, id, col = 'id') => dbConnections.knex
|
||||
*
|
||||
* @returns {Promise<T>} Where T is the query result object
|
||||
*/
|
||||
export const selectLastById = async (table, id, col = 'id') => (await dbConnections.knex(table).where(col, id)).at(-1)
|
||||
export const selectLastById = async (table: string, id: string, col = 'id') =>
|
||||
(await dbConnections.knex(table).where(col, id)).at(-1)
|
||||
|
||||
/**
|
||||
* @param {string} table - Table this function is trying to query against
|
||||
@@ -27,7 +29,8 @@ export const selectLastById = async (table, id, col = 'id') => (await dbConnecti
|
||||
*
|
||||
* @returns {Promise<T[]>} Where T is the query result object
|
||||
*/
|
||||
export const selectFromByIds = async (table, ids, col = 'id') => dbConnections.knex(table).whereIn(col, ids)
|
||||
export const selectFromByIds = async (table: string, ids: string[], col = 'id') =>
|
||||
dbConnections.knex(table).whereIn(col, ids)
|
||||
|
||||
/**
|
||||
* @param {string} table - Table this function is trying to query against
|
||||
@@ -38,22 +41,27 @@ export const selectFromByIds = async (table, ids, col = 'id') => dbConnections.k
|
||||
*
|
||||
* @returns {Promise<T[]>} Where T is the query result object
|
||||
*/
|
||||
export const selectFromByIdsOrderBy = async (table, ids, col = 'id', by = 'id', order = 'asc') =>
|
||||
dbConnections.knex(table).whereIn(col, ids).orderBy(by, order)
|
||||
export const selectFromByIdsOrderBy = async (
|
||||
table: string,
|
||||
ids: string[],
|
||||
col = 'id',
|
||||
by = 'id',
|
||||
order: 'asc' | 'desc' = 'asc'
|
||||
) => dbConnections.knex(table).whereIn(col, ids).orderBy(by, order)
|
||||
|
||||
/**
|
||||
* @param {string} table - Table this function is trying to query against
|
||||
*
|
||||
* @returns {Promise<T[]>} Where T is the query result object
|
||||
*/
|
||||
export const selectAllFrom = async table => dbConnections.knex(table)
|
||||
export const selectAllFrom = async (table: string) => dbConnections.knex(table)
|
||||
|
||||
/**
|
||||
* @param {string} table - Table this function is trying to query against
|
||||
*
|
||||
* @returns {Promise<T[]>} Where T is the query result object
|
||||
*/
|
||||
export const selectAllFromSnapshots = async table =>
|
||||
export const selectAllFromSnapshots = async (table: string) =>
|
||||
(
|
||||
await dbConnections.knex
|
||||
.select(dbConnections.knex.raw(`array_agg(to_json(${table}.*)) as data`))
|
||||
@@ -71,7 +79,7 @@ export const selectAllFromSnapshots = async table =>
|
||||
*
|
||||
* @returns {Promise<T[]>} Where T is the query result object
|
||||
*/
|
||||
export const selectFromSnapshotsByIds = async (table, ids, col = 'id') =>
|
||||
export const selectFromSnapshotsByIds = async (table: string, ids: string[], col = 'id') =>
|
||||
(
|
||||
await dbConnections.knex
|
||||
.select(dbConnections.knex.raw(`array_agg(to_json(${table}.*)) as data`))
|
||||
@@ -90,7 +98,7 @@ export const selectFromSnapshotsByIds = async (table, ids, col = 'id') =>
|
||||
*
|
||||
* @returns {Promise<T[]>} Where T is the query result object
|
||||
*/
|
||||
export const selectLatestActiveFromSnapshotsByIds = async (table, ids, col = 'id') =>
|
||||
export const selectLatestActiveFromSnapshotsByIds = async (table: string, ids: string[], col = 'id') =>
|
||||
(
|
||||
await dbConnections.knex
|
||||
.select(dbConnections.knex.raw(`array_agg(to_json(${table}.*)) as data`))
|
||||
@@ -108,7 +116,7 @@ export const selectLatestActiveFromSnapshotsByIds = async (table, ids, col = 'id
|
||||
*
|
||||
* @returns {Promise<T[]>} Where T is the query result object
|
||||
*/
|
||||
export const selectFromActiveSnapshotsByIds = async (table, ids, col = 'id') =>
|
||||
export const selectFromActiveSnapshotsByIds = async (table: string, ids: string[], col = 'id') =>
|
||||
(
|
||||
await dbConnections.knex
|
||||
.select(dbConnections.knex.raw(`array_agg(to_json(${table}.*)) as data`))
|
||||
@@ -119,7 +127,8 @@ export const selectFromActiveSnapshotsByIds = async (table, ids, col = 'id') =>
|
||||
.map(({ data }) => getActiveSnapshot(data))
|
||||
.filter(s => !!s)
|
||||
|
||||
const getColumnsToUpdate = (model, keys) => Object.keys(model.rawAttributes).filter(a => !keys.includes(a))
|
||||
// TODO: Type this
|
||||
const getColumnsToUpdate = (model, keys: string[]) => Object.keys(model.rawAttributes).filter(a => !keys.includes(a))
|
||||
|
||||
/**
|
||||
* @param {import('sequelize').Model<T>} model - Model for the bulkCreate
|
||||
@@ -133,7 +142,7 @@ export const bulkCreate = async (
|
||||
model,
|
||||
entities,
|
||||
transaction = null,
|
||||
properties = ['id'],
|
||||
properties: string[] = ['id'],
|
||||
upsertStyle = true,
|
||||
findBy = 'id'
|
||||
) => {
|
||||
@@ -144,7 +153,7 @@ export const bulkCreate = async (
|
||||
: { ignoreDuplicates: true, transaction }
|
||||
await model.bulkCreate(entities, options)
|
||||
} catch (_error) {
|
||||
const failures = []
|
||||
const failures: any[] = []
|
||||
for (const entity of entities) {
|
||||
try {
|
||||
if (upsertStyle) await model.upsert(entity, { fields: getColumnsToUpdate(model, properties) })
|
||||
@@ -156,7 +165,7 @@ export const bulkCreate = async (
|
||||
defaults: entity,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
// Skip errors with credits pointing to non-existent course_ids. Credit just doesn't get created
|
||||
if (
|
||||
error.name === 'SequelizeForeignKeyConstraintError' &&
|
||||
+4
-2
@@ -1,7 +1,9 @@
|
||||
import { Model, ARRAY, BOOLEAN, DATE, INTEGER, JSONB, STRING } from 'sequelize'
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class SISStudyRight extends Model {}
|
||||
class SISStudyRight extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
SISStudyRight.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, BOOLEAN, DATE, INTEGER, JSONB, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class SISStudyRightElement extends Model {}
|
||||
class SISStudyRightElement extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
SISStudyRightElement.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, BOOLEAN, DATE, JSONB, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class Course extends Model {}
|
||||
class Course extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
Course.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, DATE, JSONB, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class CourseProvider extends Model {}
|
||||
class CourseProvider extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
CourseProvider.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, DATE, JSONB, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class CourseType extends Model {}
|
||||
class CourseType extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
CourseType.init(
|
||||
{
|
||||
+12
-2
@@ -1,8 +1,18 @@
|
||||
import { Model, BOOLEAN, DATE, DOUBLE, INTEGER, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class Credit extends Model {}
|
||||
class Credit extends Model {
|
||||
[key: string]: any
|
||||
|
||||
static notUnnecessary: (credit: Credit) => boolean
|
||||
|
||||
static passed: (credit: Credit) => boolean
|
||||
|
||||
static failed: (credit: Credit) => boolean
|
||||
|
||||
static improved: (credit: Credit) => boolean
|
||||
}
|
||||
|
||||
Credit.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, DATE, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class CreditTeacher extends Model {}
|
||||
class CreditTeacher extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
CreditTeacher.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, DATE, INTEGER, JSONB } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class CreditType extends Model {}
|
||||
class CreditType extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
CreditType.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, DATE, JSONB, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class CurriculumPeriod extends Model {}
|
||||
class CurriculumPeriod extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
CurriculumPeriod.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, BOOLEAN, DATE, INTEGER, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class Enrollment extends Model {}
|
||||
class Enrollment extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
Enrollment.init(
|
||||
{
|
||||
+18
-18
@@ -1,21 +1,21 @@
|
||||
import Course from './course.js'
|
||||
import CourseProvider from './courseProvider.js'
|
||||
import CourseType from './courseType.js'
|
||||
import Credit from './credit.js'
|
||||
import CreditTeacher from './creditTeacher.js'
|
||||
import CreditType from './creditType.js'
|
||||
import CurriculumPeriod from './curriculumPeriod.js'
|
||||
import Enrollment from './enrollment.js'
|
||||
import Organization from './organization.js'
|
||||
import ProgrammeModule from './programmeModule.js'
|
||||
import ProgrammeModuleChild from './programmeModuleChild.js'
|
||||
import Semester from './semester.js'
|
||||
import SISStudyRight from './SISStudyRight.js'
|
||||
import SISStudyRightElement from './SISStudyRightElement.js'
|
||||
import Student from './student.js'
|
||||
import Studyplan from './studyplan.js'
|
||||
import StudyrightExtent from './studyrightExtent.js'
|
||||
import Teacher from './teacher.js'
|
||||
import Course from './course'
|
||||
import CourseProvider from './courseProvider'
|
||||
import CourseType from './courseType'
|
||||
import Credit from './credit'
|
||||
import CreditTeacher from './creditTeacher'
|
||||
import CreditType from './creditType'
|
||||
import CurriculumPeriod from './curriculumPeriod'
|
||||
import Enrollment from './enrollment'
|
||||
import Organization from './organization'
|
||||
import ProgrammeModule from './programmeModule'
|
||||
import ProgrammeModuleChild from './programmeModuleChild'
|
||||
import Semester from './semester'
|
||||
import SISStudyRight from './SISStudyRight'
|
||||
import SISStudyRightElement from './SISStudyRightElement'
|
||||
import Student from './student'
|
||||
import Studyplan from './studyplan'
|
||||
import StudyrightExtent from './studyrightExtent'
|
||||
import Teacher from './teacher'
|
||||
|
||||
const CREDIT_TYPE_CODES = {
|
||||
PASSED: 4,
|
||||
+6
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, DATE, JSONB, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class Organization extends Model {}
|
||||
class Organization extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
Organization.init(
|
||||
{
|
||||
@@ -13,6 +15,8 @@ Organization.init(
|
||||
code: {
|
||||
type: STRING,
|
||||
references: {
|
||||
// @ts-expect-error FIXME: These fields acually don't exist. They most likely should be "model" and "key"
|
||||
// but that doesn't make any sense: Foreign key to itself with "code" as key.
|
||||
table: 'organization',
|
||||
field: 'code',
|
||||
},
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, ARRAY, DATE, INTEGER, JSONB, STRING, TEXT } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class ProgrammeModule extends Model {}
|
||||
class ProgrammeModule extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
ProgrammeModule.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, DATE, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class ProgrammeModuleChild extends Model {}
|
||||
class ProgrammeModuleChild extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
ProgrammeModuleChild.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, DATE, INTEGER, JSONB, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class Semester extends Model {}
|
||||
class Semester extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
Semester.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, DATE, INTEGER, STRING, BOOLEAN, JSONB } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class Student extends Model {}
|
||||
class Student extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
Student.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, ARRAY, DATE, DOUBLE, STRING, TEXT } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class Studyplan extends Model {}
|
||||
class Studyplan extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
Studyplan.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, DATE, INTEGER, JSONB } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class StudyrightExtent extends Model {}
|
||||
class StudyrightExtent extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
StudyrightExtent.init(
|
||||
{
|
||||
+4
-2
@@ -1,8 +1,10 @@
|
||||
import { Model, DATE, STRING } from 'sequelize'
|
||||
|
||||
import { dbConnections } from '../connection.js'
|
||||
import { dbConnections } from '../connection'
|
||||
|
||||
class Teacher extends Model {}
|
||||
class Teacher extends Model {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
Teacher.init(
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
import { dbConnections } from './db/connection.js'
|
||||
import logger from './utils/logger.js'
|
||||
import './worker.js'
|
||||
import { dbConnections } from './db/connection'
|
||||
import logger from './utils/logger'
|
||||
import './worker'
|
||||
|
||||
dbConnections
|
||||
.connect()
|
||||
+4
-7
@@ -1,12 +1,9 @@
|
||||
import { intersection } from 'lodash-es'
|
||||
|
||||
import { update } from './updater/index.js'
|
||||
import { purgeByStudentNumber, prePurge, purge } from './updater/purge.js'
|
||||
import { loadMapsOnDemand, nukeRedis } from './updater/shared.js'
|
||||
import {
|
||||
fixVarhaiskasvatusStudyRights,
|
||||
studentsThatNeedToBeFixed,
|
||||
} from './updater/updateStudents/varhaiskasvatusFixer.js'
|
||||
import { update } from './updater/index'
|
||||
import { purgeByStudentNumber, prePurge, purge } from './updater/purge'
|
||||
import { loadMapsOnDemand, nukeRedis } from './updater/shared'
|
||||
import { fixVarhaiskasvatusStudyRights, studentsThatNeedToBeFixed } from './updater/updateStudents/varhaiskasvatusFixer'
|
||||
|
||||
const updateMsgHandler = async updateMsg => {
|
||||
await update(updateMsg)
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
import { selectFromByIds, selectLatestActiveFromSnapshotsByIds } from '../db/index.js'
|
||||
import { creditTypeIdsToCreditTypes } from './shared.js'
|
||||
import { selectFromByIds, selectLatestActiveFromSnapshotsByIds } from '../db/index'
|
||||
import { creditTypeIdsToCreditTypes } from './shared'
|
||||
import {
|
||||
updateOrganisations,
|
||||
updateStudyModules,
|
||||
@@ -8,9 +8,9 @@ import {
|
||||
updateCreditTypes,
|
||||
updateStudyrightExtents,
|
||||
updateCurriculumPeriods,
|
||||
} from './updateMeta.js'
|
||||
import { updateProgrammeModules } from './updateProgrammeModules/updateProgrammeModules.js'
|
||||
import { updateStudents } from './updateStudents/index.js'
|
||||
} from './updateMeta'
|
||||
import { updateProgrammeModules } from './updateProgrammeModules/updateProgrammeModules'
|
||||
import { updateStudents } from './updateStudents/index'
|
||||
|
||||
const idToHandler = {
|
||||
students: updateStudents,
|
||||
+6
-9
@@ -1,6 +1,6 @@
|
||||
import { sortBy, flatten, uniqBy } from 'lodash-es'
|
||||
|
||||
import logger from '../utils/logger.js'
|
||||
import logger from '../utils/logger'
|
||||
import {
|
||||
educationTypeToExtentcode,
|
||||
getCountry,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
getGrade,
|
||||
getSemesterByDate,
|
||||
getUniOrgId,
|
||||
} from './shared.js'
|
||||
} from './shared'
|
||||
|
||||
// Keeping previous oodi logic:
|
||||
// 0 = Not known 'urn:code:gender:not-known'
|
||||
@@ -57,7 +57,7 @@ const calculateTotalCreditsFromAttainments = attainments => {
|
||||
if (!attainment.primary) return false
|
||||
if (attainment.expiryDate < now) return false
|
||||
// Does not have any attainments attached to it, so is not a study module whose attainments have already been counted
|
||||
if (attainment.nodes && attainment.nodes[0] !== undefined) return false
|
||||
if (attainment.nodes?.[0] !== undefined) return false
|
||||
if (!validAttainmentTypes.includes(attainment.type)) return false
|
||||
// If the state is FAILED or IMPROVED it should not be counted to total
|
||||
if (!validStates.includes(attainment.state)) return false
|
||||
@@ -349,7 +349,7 @@ export const studyplanMapper =
|
||||
const code = programmeModuleIdToCode[programmeId]
|
||||
if (!code) return null
|
||||
|
||||
const graduated = moduleAttainments[programmeId] && moduleAttainments[programmeId][studyplan.user_id]
|
||||
const graduated = moduleAttainments[programmeId]?.[studyplan.user_id]
|
||||
const id = `${studentnumber}-${code}-${studyrightId}`
|
||||
|
||||
// NB: values are course_unit_ids, matching Credit/Enrollment's course_id
|
||||
@@ -371,9 +371,7 @@ export const studyplanMapper =
|
||||
studyplan.module_selections
|
||||
.filter(
|
||||
({ moduleId }) =>
|
||||
moduleIdToParentModuleCode[moduleId]?.has(code) &&
|
||||
moduleAttainments[moduleId] &&
|
||||
moduleAttainments[moduleId][studyplan.user_id]
|
||||
moduleIdToParentModuleCode[moduleId]?.has(code) && moduleAttainments[moduleId]?.[studyplan.user_id]
|
||||
)
|
||||
.map(({ moduleId }) => getCourseIdsFromAttainment(moduleAttainments[moduleId][studyplan.user_id]))
|
||||
)
|
||||
@@ -428,8 +426,7 @@ export const studyplanMapper =
|
||||
.filter(
|
||||
({ moduleId }) =>
|
||||
moduleIdToParentModuleCode[moduleId]?.has(code) &&
|
||||
moduleAttainments[moduleId] &&
|
||||
moduleAttainments[moduleId][studyplan.user_id]
|
||||
moduleAttainments[moduleId]?.[studyplan.user_id]
|
||||
)
|
||||
.map(({ moduleId }) => getAttainmentsFromAttainment(moduleAttainments[moduleId][studyplan.user_id]))
|
||||
)
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
import { Op } from 'sequelize'
|
||||
|
||||
import { PURGE_LOCK } from '../config.js'
|
||||
import { PURGE_LOCK } from '../config'
|
||||
import {
|
||||
Organization,
|
||||
Course,
|
||||
@@ -14,9 +14,9 @@ import {
|
||||
StudyrightExtent,
|
||||
SISStudyRightElement,
|
||||
SISStudyRight,
|
||||
} from '../db/models/index.js'
|
||||
import logger from '../utils/logger.js'
|
||||
import { lock } from '../utils/redis.js'
|
||||
} from '../db/models/index'
|
||||
import logger from '../utils/logger'
|
||||
import { lock } from '../utils/redis'
|
||||
|
||||
const tableToModel = {
|
||||
course: Course,
|
||||
+20
-20
@@ -1,18 +1,18 @@
|
||||
import { each } from 'async'
|
||||
|
||||
import { selectAllFrom, selectAllFromSnapshots } from '../db/index.js'
|
||||
import { Semester, Organization, CREDIT_TYPE_CODES } from '../db/models/index.js'
|
||||
import { redisClient, lock as redisLock } from '../utils/redis.js'
|
||||
import { selectAllFrom, selectAllFromSnapshots } from '../db/index'
|
||||
import { Semester, Organization, CREDIT_TYPE_CODES } from '../db/models/index'
|
||||
import { redisClient, lock as redisLock } from '../utils/redis'
|
||||
|
||||
const getRedisJSON = async key => JSON.parse(await redisClient.get(key))
|
||||
const setRedisJSON = async (key, value) => redisClient.set(key, JSON.stringify(value))
|
||||
const getRedisJSON = async (key: string) => JSON.parse((await redisClient.get(key))!)
|
||||
const setRedisJSON = async (key: string, value) => redisClient.set(key, JSON.stringify(value))
|
||||
|
||||
const TIME_LIMIT_BETWEEN_RELOADS = 1000 * 60 * 30
|
||||
const REDIS_INITIALIZED = 'INITIALIZED'
|
||||
const SHARED_LOCK = 'SHARED_LOCK'
|
||||
const FIRST_SEMESTER_START_YEAR = 1950
|
||||
|
||||
let loadedAt = null
|
||||
let loadedAt: number | null = null
|
||||
|
||||
const localMapToRedisKey = {
|
||||
daysToSemesters: 'DAYS_TO_SEMESTERS',
|
||||
@@ -25,7 +25,7 @@ const localMapToRedisKey = {
|
||||
countries: 'COUNTRIES',
|
||||
}
|
||||
|
||||
const localMaps = {
|
||||
const localMaps: Record<keyof typeof localMapToRedisKey, any> = {
|
||||
daysToSemesters: null,
|
||||
educationTypes: null,
|
||||
organisationIdToCode: null,
|
||||
@@ -37,7 +37,7 @@ const localMaps = {
|
||||
}
|
||||
|
||||
const loadMapsFromRedis = async () =>
|
||||
each(Object.entries(localMapToRedisKey), async ([localMap, redisKey]) => {
|
||||
await each(Object.entries(localMapToRedisKey), async ([localMap, redisKey]: [any, string]) => {
|
||||
localMaps[localMap] = await getRedisJSON(redisKey)
|
||||
})
|
||||
|
||||
@@ -61,7 +61,7 @@ const initDaysToSemesters = async () => {
|
||||
)
|
||||
}
|
||||
|
||||
export const getSemesterByDate = date => localMaps.daysToSemesters[date.toDateString()]
|
||||
export const getSemesterByDate = (date: Date) => localMaps.daysToSemesters[date.toDateString()]
|
||||
|
||||
const initEducationTypes = async () =>
|
||||
setRedisJSON(
|
||||
@@ -72,7 +72,7 @@ const initEducationTypes = async () =>
|
||||
}, {})
|
||||
)
|
||||
|
||||
export const getEducationType = id => localMaps.educationTypes[id]
|
||||
export const getEducationType = (id: string) => localMaps.educationTypes[id]
|
||||
|
||||
const initOrganisationIdToCode = async () =>
|
||||
setRedisJSON(
|
||||
@@ -83,7 +83,7 @@ const initOrganisationIdToCode = async () =>
|
||||
}, {})
|
||||
)
|
||||
|
||||
export const getOrganisationCode = id => localMaps.organisationIdToCode[id]
|
||||
export const getOrganisationCode = (id: string) => localMaps.organisationIdToCode[id]
|
||||
|
||||
const initEducationIdToEducation = async () =>
|
||||
setRedisJSON(
|
||||
@@ -94,7 +94,7 @@ const initEducationIdToEducation = async () =>
|
||||
}, {})
|
||||
)
|
||||
|
||||
export const getEducation = id => localMaps.educationIdToEducation[id]
|
||||
export const getEducation = (id: string) => localMaps.educationIdToEducation[id]
|
||||
|
||||
const initGradeScaleIdToGradeIdsToGrades = async () =>
|
||||
setRedisJSON(
|
||||
@@ -114,7 +114,7 @@ const initGradeScaleIdToGradeIdsToGrades = async () =>
|
||||
}, {})
|
||||
)
|
||||
|
||||
export const getGrade = (scaleId, gradeId) => localMaps.gradeScaleIdToGradeIdsToGrades[scaleId][gradeId]
|
||||
export const getGrade = (scaleId: string, gradeId: string) => localMaps.gradeScaleIdToGradeIdsToGrades[scaleId][gradeId]
|
||||
|
||||
const initOrgToUniOrgId = async () => {
|
||||
const organisations = await selectAllFromSnapshots('organisations')
|
||||
@@ -127,7 +127,7 @@ const initOrgToUniOrgId = async () => {
|
||||
)
|
||||
}
|
||||
|
||||
export const getUniOrgId = orgId => localMaps.orgToUniOrgId[orgId]
|
||||
export const getUniOrgId = (orgId: string) => localMaps.orgToUniOrgId[orgId]
|
||||
|
||||
const initStartYearToSemesters = async () => {
|
||||
const semesters = await Semester.findAll()
|
||||
@@ -146,10 +146,10 @@ const initStartYearToSemesters = async () => {
|
||||
]
|
||||
}
|
||||
|
||||
setRedisJSON(localMapToRedisKey.startYearToSemesters, startingYearToSemesterObj)
|
||||
await setRedisJSON(localMapToRedisKey.startYearToSemesters, startingYearToSemesterObj)
|
||||
}
|
||||
|
||||
export const getSemesterCode = (studyYearStartYear, termIndex) => {
|
||||
export const getSemesterCode = (studyYearStartYear: number, termIndex: number) => {
|
||||
return localMaps.startYearToSemesters[studyYearStartYear][termIndex]
|
||||
}
|
||||
|
||||
@@ -207,9 +207,9 @@ export const nukeRedis = async () => {
|
||||
await unlock()
|
||||
}
|
||||
|
||||
export const getCountry = countryId => localMaps.countries[countryId]
|
||||
export const getCountry = (countryId: string) => localMaps.countries[countryId]
|
||||
|
||||
export const educationTypeToExtentcode = {
|
||||
export const educationTypeToExtentcode: Record<string, number | null> = {
|
||||
'urn:code:education-type:degree-education:bachelors-degree': 1,
|
||||
'urn:code:education-type:degree-education:masters-degree': 2,
|
||||
'urn:code:education-type:degree-education:lic': 3,
|
||||
@@ -237,7 +237,7 @@ export const educationTypeToExtentcode = {
|
||||
'urn:code:education-type:non-degree-education:separate-studies:adult-educator-pedagogical-studies': null, // Parent is 99
|
||||
}
|
||||
|
||||
export const getCreditTypeCodeFromAttainment = (attainment, passed) => {
|
||||
export const getCreditTypeCodeFromAttainment = (attainment, passed: boolean) => {
|
||||
const { primary, state } = attainment
|
||||
if (!passed || state === 'FAILED') return CREDIT_TYPE_CODES.FAILED
|
||||
if (!primary) return CREDIT_TYPE_CODES.IMPROVED
|
||||
@@ -264,4 +264,4 @@ const creditTypeIdToCreditType = {
|
||||
},
|
||||
}
|
||||
|
||||
export const creditTypeIdsToCreditTypes = ids => ids.map(id => creditTypeIdToCreditType[id])
|
||||
export const creditTypeIdsToCreditTypes = (ids: number[]) => ids.map(id => creditTypeIdToCreditType[id])
|
||||
+8
-14
@@ -1,6 +1,6 @@
|
||||
import { uniqBy, groupBy } from 'lodash-es'
|
||||
import { rootOrgId } from '../config.js'
|
||||
import { bulkCreate, selectFromByIdsOrderBy } from '../db/index.js'
|
||||
import { rootOrgId } from '../config'
|
||||
import { bulkCreate, selectFromByIdsOrderBy } from '../db/index'
|
||||
import {
|
||||
Course,
|
||||
CourseProvider,
|
||||
@@ -9,26 +9,20 @@ import {
|
||||
CurriculumPeriod,
|
||||
Organization,
|
||||
StudyrightExtent,
|
||||
} from '../db/models/index.js'
|
||||
import {
|
||||
courseMapper,
|
||||
courseProviderMapper,
|
||||
mapCourseType,
|
||||
mapStudyrightExtent,
|
||||
mapCurriculumPeriod,
|
||||
} from './mapper.js'
|
||||
} from '../db/models/index'
|
||||
import { courseMapper, courseProviderMapper, mapCourseType, mapStudyrightExtent, mapCurriculumPeriod } from './mapper'
|
||||
|
||||
export const updateOrganisations = async organisations => {
|
||||
await bulkCreate(Organization, organisations)
|
||||
}
|
||||
|
||||
const updateCourses = async (courseIdToAttainments, groupIdToCourse) => {
|
||||
const courseProviders = []
|
||||
const mappedCourses = []
|
||||
const courseProviders: any[] = []
|
||||
const mappedCourses: any[] = []
|
||||
|
||||
const mapCourse = courseMapper(courseIdToAttainments)
|
||||
|
||||
for (const [groupId, courses] of Object.entries(groupIdToCourse)) {
|
||||
for (const [groupId, courses] of Object.entries<any>(groupIdToCourse)) {
|
||||
const now = new Date()
|
||||
const { id: primaryCourseId } = courses.reduce(
|
||||
(acc, cur) => {
|
||||
@@ -46,7 +40,7 @@ const updateCourses = async (courseIdToAttainments, groupIdToCourse) => {
|
||||
/** @type string[][] @description nested arrays of courseUnit groupIds. Modules have no substitutions. */
|
||||
const substitutionGroups = course.substitutions?.map(subGroup => subGroup.map(sub => sub.courseUnitGroupId)) ?? []
|
||||
|
||||
const organisationsById = {}
|
||||
const organisationsById: Record<string, any> = {}
|
||||
const { organisations, validity_period: courseValidityPeriod } = course
|
||||
|
||||
if (organisations) {
|
||||
+12
-5
@@ -1,10 +1,17 @@
|
||||
import type { Knex } from 'knex'
|
||||
|
||||
const anyModule = rule => ({ id: rule.localId, name: 'Any module' })
|
||||
const anyCourse = rule => ({ id: rule.localId, name: 'Any course' })
|
||||
const unknownRule = rule => ({ type: rule.type, fact: 'Unhandled rule' })
|
||||
const unknownModule = mod => ({ name: mod.name, type: 'unknown' })
|
||||
|
||||
export class ModuleResolver {
|
||||
constructor(knex) {
|
||||
knex: Knex
|
||||
moduleCache
|
||||
orderTable: Record<string, number>
|
||||
order: number
|
||||
|
||||
constructor(knex: Knex) {
|
||||
/** @type {import('knex').Knex} */
|
||||
this.knex = knex
|
||||
this.moduleCache = {}
|
||||
@@ -12,7 +19,7 @@ export class ModuleResolver {
|
||||
this.order = 1
|
||||
}
|
||||
|
||||
getOrder(group_id) {
|
||||
getOrder(group_id: string) {
|
||||
const oldOrder = this.orderTable[group_id]
|
||||
if (oldOrder) return oldOrder
|
||||
const newOrder = this.order++
|
||||
@@ -71,7 +78,7 @@ export class ModuleResolver {
|
||||
const id = rule.moduleGroupId
|
||||
|
||||
const modules = await this.knex('modules').where({ group_id: id })
|
||||
const children = []
|
||||
const children: any[] = []
|
||||
|
||||
for (const mod of modules) {
|
||||
if (mod.code?.startsWith('KK-')) continue
|
||||
@@ -86,7 +93,7 @@ export class ModuleResolver {
|
||||
}
|
||||
|
||||
async compositeResolver(rule) {
|
||||
const children = []
|
||||
const children: any[] = []
|
||||
for (const r of rule.rules) {
|
||||
const result = await this.resolve(r)
|
||||
children.push(result)
|
||||
@@ -104,7 +111,7 @@ export class ModuleResolver {
|
||||
return courses.map(course => this.createCourse(course))
|
||||
}
|
||||
|
||||
async resolve(rule) {
|
||||
async resolve(rule): Promise<any> {
|
||||
if (rule.type === 'CreditsRule') {
|
||||
return await this.resolve(rule.rule)
|
||||
}
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
import { chunk } from 'lodash-es'
|
||||
|
||||
import { dbConnections } from '../../db/connection.js'
|
||||
import { bulkCreate, selectFromByIds } from '../../db/index.js'
|
||||
import { ProgrammeModule, ProgrammeModuleChild } from '../../db/models/index.js'
|
||||
import logger from '../../utils/logger.js'
|
||||
import { ModuleResolver } from './resolver.js'
|
||||
import { dbConnections } from '../../db/connection'
|
||||
import { bulkCreate, selectFromByIds } from '../../db/index'
|
||||
import { ProgrammeModule, ProgrammeModuleChild } from '../../db/models/index'
|
||||
import logger from '../../utils/logger'
|
||||
import { ModuleResolver } from './resolver'
|
||||
|
||||
const resolveProgramme = async programme => {
|
||||
const responsibleOrg = programme.organisations
|
||||
+37
-28
@@ -1,14 +1,14 @@
|
||||
import { selectAllFrom, bulkCreate, selectOneById, selectLastById } from '../../db/index.js'
|
||||
import { SISStudyRight, SISStudyRightElement } from '../../db/models/index.js'
|
||||
import logger from '../../utils/logger.js'
|
||||
import { termRegistrationTypeToEnrollmenttype } from '../mapper.js'
|
||||
import { selectAllFrom, bulkCreate, selectOneById, selectLastById } from '../../db/index'
|
||||
import { SISStudyRight, SISStudyRightElement } from '../../db/models/index'
|
||||
import logger from '../../utils/logger'
|
||||
import { termRegistrationTypeToEnrollmenttype } from '../mapper'
|
||||
import {
|
||||
getEducation,
|
||||
getSemesterCode,
|
||||
getEducationType,
|
||||
educationTypeToExtentcode,
|
||||
getOrganisationCode,
|
||||
} from '../shared.js'
|
||||
} from '../shared'
|
||||
|
||||
const TVEX_URN_CODE = 'urn:code:custom:hy-university-root-id:katu:katu1'
|
||||
|
||||
@@ -18,7 +18,7 @@ const normalizeDateTime = date =>
|
||||
date.toLocaleString('se-SE', { timeZone: 'Europe/Helsinki', year: 'numeric', month: '2-digit', day: '2-digit' })
|
||||
)
|
||||
|
||||
const addDaysToDate = (date, days) => {
|
||||
const addDaysToDate = (date, days: number) => {
|
||||
if (!date) return null
|
||||
const newDate = new Date(date)
|
||||
newDate.setDate(newDate.getDate() + days)
|
||||
@@ -74,7 +74,12 @@ const studyRightMapper = (personIdToStudentNumber, admissionNamesById, semesterE
|
||||
}
|
||||
}
|
||||
|
||||
const getCorrectStartDateForProgramme = (phase, isFirstProgramme, latestSnapshot, startDateFromSnapshot) => {
|
||||
const getCorrectStartDateForProgramme = (
|
||||
phase: number,
|
||||
isFirstProgramme: boolean,
|
||||
latestSnapshot,
|
||||
startDateFromSnapshot
|
||||
) => {
|
||||
if (phase === 1) {
|
||||
return isFirstProgramme ? new Date(latestSnapshot.valid.startDate) : startDateFromSnapshot
|
||||
}
|
||||
@@ -87,8 +92,8 @@ const getCorrectStartDateForProgramme = (phase, isFirstProgramme, latestSnapshot
|
||||
// Each programme (educationPhase1/2GroupId) has their own study right element.
|
||||
// Study tracks (educationPhase1/2ChildGroupId) belong to the corresponding study right element.
|
||||
const studyRightElementMapper =
|
||||
(phase, latestSnapshot) =>
|
||||
async ([groupId, { startDate, endDate, code, studyTrack: elementStudyTrack, graduated }]) => {
|
||||
(phase: number, latestSnapshot) =>
|
||||
async ([groupId, { startDate, endDate, code, studyTrack: elementStudyTrack, graduated }]: [string, any]) => {
|
||||
const educationInfo = await selectOneById('modules', groupId.replace('EDU', 'DP'), 'group_id')
|
||||
|
||||
let studyTrack = elementStudyTrack ?? null
|
||||
@@ -119,12 +124,12 @@ export const updateSISStudyRights = async (groupedStudyRights, personIdToStudent
|
||||
const mapStudyRight = studyRightMapper(personIdToStudentNumber, admissionTypes, semesterEnrollments)
|
||||
|
||||
// Latest snapshot of each study right
|
||||
const latestStudyRights = Object.values(groupedStudyRights).reduce((acc, curr) => {
|
||||
const latestStudyRights = Object.values<any>(groupedStudyRights).reduce((acc: any[], curr) => {
|
||||
acc.push(curr[0])
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
const formattedStudyRights = latestStudyRights.map(mapStudyRight).filter(Boolean)
|
||||
const formattedStudyRights = latestStudyRights.map(mapStudyRight).filter(studyRight => !!studyRight)
|
||||
|
||||
await bulkCreate(SISStudyRight, formattedStudyRights)
|
||||
return new Set(formattedStudyRights.map(studyRight => studyRight.id))
|
||||
@@ -153,9 +158,9 @@ const buildStudyRightElements = (studyRightSnapshots, groupIdToCode) => {
|
||||
const phase1Elements = {}
|
||||
const phase2Elements = {}
|
||||
|
||||
let latestValidSnapshot = null
|
||||
let lastPhase1Id = null
|
||||
let lastPhase2Id = null
|
||||
let latestValidSnapshot: any = null
|
||||
let lastPhase1Id: string | null = null
|
||||
let lastPhase2Id: string | null = null
|
||||
let graduatedPhase1 = false
|
||||
let graduatedPhase2 = false
|
||||
|
||||
@@ -180,7 +185,7 @@ const buildStudyRightElements = (studyRightSnapshots, groupIdToCode) => {
|
||||
// In that case fallback to the start of the study rights validity period.
|
||||
const normalizedDateTime = normalizeDateTime(snapshot_date_time || valid.startDate)
|
||||
|
||||
const addElementToMap = (phase, phaseElements, groupId) => {
|
||||
const addElementToMap = (phase: number, phaseElements, groupId: string) => {
|
||||
// Skip elements that don't have id matching to a programme
|
||||
if (!(groupId in groupIdToCode)) return
|
||||
|
||||
@@ -208,24 +213,24 @@ const buildStudyRightElements = (studyRightSnapshots, groupIdToCode) => {
|
||||
// (Oodikone can't display the same study right element twice with different start/enddates anyway).
|
||||
if (!isNewGroupId) {
|
||||
delete phaseElements[lastPhaseId]
|
||||
delete phaseElements[groupId]['endDate']
|
||||
delete phaseElements[groupId].endDate
|
||||
} else {
|
||||
phaseElements[lastPhaseId]['endDate'] = addDaysToDate(normalizedDateTime, -1)
|
||||
phaseElements[lastPhaseId].endDate = addDaysToDate(normalizedDateTime, -1)
|
||||
}
|
||||
}
|
||||
|
||||
if (phase === 1) {
|
||||
lastPhase1Id = groupId
|
||||
if (study_right_graduation?.phase1GraduationDate) {
|
||||
phaseElements[groupId]['endDate'] = normalizeDateTime(study_right_graduation.phase1GraduationDate)
|
||||
phaseElements[groupId]['graduated'] = true
|
||||
phaseElements[groupId].endDate = normalizeDateTime(study_right_graduation.phase1GraduationDate)
|
||||
phaseElements[groupId].graduated = true
|
||||
graduatedPhase1 = true
|
||||
}
|
||||
} else {
|
||||
lastPhase2Id = groupId
|
||||
if (study_right_graduation?.phase2GraduationDate) {
|
||||
phaseElements[groupId]['endDate'] = normalizeDateTime(study_right_graduation.phase2GraduationDate)
|
||||
phaseElements[groupId]['graduated'] = true
|
||||
phaseElements[groupId].endDate = normalizeDateTime(study_right_graduation.phase2GraduationDate)
|
||||
phaseElements[groupId].graduated = true
|
||||
graduatedPhase2 = true
|
||||
}
|
||||
}
|
||||
@@ -262,15 +267,15 @@ const buildStudyRightElements = (studyRightSnapshots, groupIdToCode) => {
|
||||
// We only take one study track per groupId (degree programme).
|
||||
// Study track from a newer snapshot will replace the previous one.
|
||||
// If there's no study track in a newer snapshot with the same groupId, we conclude that the study track has been removed.
|
||||
const addStudyTracks = (groupId, childGroupId, phaseElements) => {
|
||||
const addStudyTracks = (groupId: string, childGroupId: string, phaseElements) => {
|
||||
if (!phaseElements[groupId]) return
|
||||
if (childGroupId) {
|
||||
phaseElements[groupId]['studyTrack'] = {
|
||||
phaseElements[groupId].studyTrack = {
|
||||
id: childGroupId,
|
||||
code: groupIdToCode[childGroupId],
|
||||
}
|
||||
} else if (!childGroupId && 'studyTrack' in phaseElements[groupId]) {
|
||||
delete phaseElements[groupId]['studyTrack']
|
||||
delete phaseElements[groupId].studyTrack
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,14 +288,14 @@ const buildStudyRightElements = (studyRightSnapshots, groupIdToCode) => {
|
||||
// (it overshoots by a day so subtract that)
|
||||
const addMissingEndDates = phaseElements => {
|
||||
for (const groupId of Object.keys(phaseElements)) {
|
||||
phaseElements[groupId]['endDate'] ??= addDaysToDate(latestValidSnapshot.valid.endDate, -1)
|
||||
phaseElements[groupId].endDate ??= addDaysToDate(latestValidSnapshot.valid.endDate, -1)
|
||||
}
|
||||
}
|
||||
|
||||
addMissingEndDates(phase1Elements)
|
||||
addMissingEndDates(phase2Elements)
|
||||
|
||||
const byStartDate = (a, b) => new Date(a.startDate) - new Date(b.startDate)
|
||||
const byStartDate = (a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime()
|
||||
|
||||
return {
|
||||
phase1Elements: Object.entries(phase1Elements).sort(byStartDate),
|
||||
@@ -298,8 +303,12 @@ const buildStudyRightElements = (studyRightSnapshots, groupIdToCode) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const updateSISStudyRightElements = async (groupedStudyRights, moduleGroupIdToCode, createdStudyRights) => {
|
||||
const studyRightElements = []
|
||||
export const updateSISStudyRightElements = async (
|
||||
groupedStudyRights,
|
||||
moduleGroupIdToCode,
|
||||
createdStudyRights: Set<string>
|
||||
) => {
|
||||
const studyRightElements: any[] = []
|
||||
|
||||
for (const group of groupedStudyRights) {
|
||||
const latestSnapshot = group[0]
|
||||
+17
-14
@@ -1,8 +1,8 @@
|
||||
import { flatten, sortBy, uniqBy } from 'lodash-es'
|
||||
import { rootOrgId } from '../../config.js'
|
||||
import { dbConnections } from '../../db/connection.js'
|
||||
import { selectFromByIds, bulkCreate, selectOneById } from '../../db/index.js'
|
||||
import { Course, Teacher, Credit, CreditTeacher, CourseProvider } from '../../db/models/index.js'
|
||||
import { rootOrgId } from '../../config'
|
||||
import { dbConnections } from '../../db/connection'
|
||||
import { selectFromByIds, bulkCreate, selectOneById } from '../../db/index'
|
||||
import { Course, Teacher, Credit, CreditTeacher, CourseProvider } from '../../db/models/index'
|
||||
import {
|
||||
mapTeacher,
|
||||
creditMapper,
|
||||
@@ -10,12 +10,12 @@ import {
|
||||
validAttainmentTypes,
|
||||
customAttainmentTypes,
|
||||
isModule,
|
||||
} from '../mapper.js'
|
||||
} from '../mapper'
|
||||
|
||||
const earlierOf = (a, b) => (new Date(a) < new Date(b) ? a : b)
|
||||
const laterOf = (a, b) => (new Date(a) > new Date(b) ? a : b)
|
||||
|
||||
const updateTeachers = async attainments => {
|
||||
const updateTeachers = async (attainments: any[]) => {
|
||||
const acceptorPersonIds = flatten(
|
||||
attainments.map(attainment =>
|
||||
attainment.acceptor_persons
|
||||
@@ -33,7 +33,7 @@ const updateTeachers = async attainments => {
|
||||
export const updateAttainments = async (
|
||||
attainments,
|
||||
personIdToStudentNumber,
|
||||
attainmentsToBeExluced,
|
||||
attainmentsToBeExluced: Set<string>,
|
||||
studyRightIdToEducationType
|
||||
) => {
|
||||
await updateTeachers(attainments)
|
||||
@@ -48,7 +48,7 @@ export const updateAttainments = async (
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{ cuIds: [], modGroupIds: [] }
|
||||
{ cuIds: [] as string[], modGroupIds: [] as string[] }
|
||||
)
|
||||
|
||||
const [courseUnits, modules] = await Promise.all([
|
||||
@@ -74,10 +74,10 @@ export const updateAttainments = async (
|
||||
)
|
||||
)
|
||||
|
||||
const creditTeachers = []
|
||||
const creditTeachers: any[] = []
|
||||
|
||||
const coursesToBeCreated = new Map()
|
||||
const courseProvidersToBeCreated = []
|
||||
const courseProvidersToBeCreated: any[] = []
|
||||
|
||||
// This mayhem fixes missing course_unit references for CustomCourseUnitAttainments.
|
||||
const fixCustomCourseUnitAttainments = async attainments => {
|
||||
@@ -90,11 +90,11 @@ export const updateAttainments = async (
|
||||
if (!studyModule) {
|
||||
if (att.module_group_id === 'hy-SM-89304486') {
|
||||
const course = await Course.findOne({ attributes: ['id'], where: { code: '71066' }, raw: true })
|
||||
return { ...att, module_group_id: course.id }
|
||||
return { ...att, module_group_id: course!.id }
|
||||
}
|
||||
if (att.module_group_id === 'hy-SM-100017957') {
|
||||
const course = await Course.findOne({ attributes: ['id'], where: { code: '523102' }, raw: true })
|
||||
return { ...att, module_group_id: course.id }
|
||||
return { ...att, module_group_id: course!.id }
|
||||
}
|
||||
const education = await selectOneById('educations', att.module_group_id.replace('DP', 'EDU'))
|
||||
|
||||
@@ -241,7 +241,10 @@ export const updateAttainments = async (
|
||||
return attainmentIdCodeMap
|
||||
}
|
||||
|
||||
const attainmentIdCourseCodeMapForCustomCourseUnitAttainments = attainments.reduce(findMissingCourseCodes, {})
|
||||
const attainmentIdCourseCodeMapForCustomCourseUnitAttainments: Record<string, string> = attainments.reduce(
|
||||
findMissingCourseCodes,
|
||||
{}
|
||||
)
|
||||
const missingCodes = Object.values(attainmentIdCourseCodeMapForCustomCourseUnitAttainments)
|
||||
const courses = await selectFromByIds('course_units', missingCodes, 'code')
|
||||
|
||||
@@ -280,7 +283,7 @@ export const updateAttainments = async (
|
||||
studyRightIdToEducationType
|
||||
)
|
||||
|
||||
const credits = []
|
||||
const credits: any[] = []
|
||||
|
||||
for (const attainment of fixedAttainments) {
|
||||
if (
|
||||
+4
-6
@@ -1,15 +1,13 @@
|
||||
import { readFileSync } from 'fs'
|
||||
import logger from '../../utils/logger.js'
|
||||
|
||||
const __dir = import.meta.dirname
|
||||
import logger from '../../utils/logger'
|
||||
|
||||
export const getAttainmentsToBeExcluded = () => {
|
||||
try {
|
||||
const data = readFileSync(`${__dir}/excludedPartialAttainments.csv`).toString()
|
||||
if (!data) return new Set()
|
||||
const data = readFileSync(`${__dirname}/excludedPartialAttainments.csv`).toString()
|
||||
if (!data) return new Set<string>()
|
||||
const attainmentIds = data.split('\n')
|
||||
return new Set(attainmentIds)
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
logger.error({ message: 'Reading excluded attainments from csv failed', meta: error.stack })
|
||||
throw error
|
||||
}
|
||||
+14
-14
@@ -1,15 +1,15 @@
|
||||
import { flatten, groupBy, orderBy } from 'lodash-es'
|
||||
|
||||
import { bulkCreate, selectFromActiveSnapshotsByIds, selectFromByIds } from '../../db/index.js'
|
||||
import { Enrollment, Student } from '../../db/models/index.js'
|
||||
import logger from '../../utils/logger.js'
|
||||
import { studentMapper, enrollmentMapper } from '../mapper.js'
|
||||
import { getEducation, loadMapsIfNeeded } from '../shared.js'
|
||||
import { updateAttainments } from './attainments.js'
|
||||
import { getAttainmentsToBeExcluded } from './excludedPartialAttainments.js'
|
||||
import { updateSISStudyRights, updateSISStudyRightElements } from './SISStudyRights.js'
|
||||
import { updateStudyplans, findStudentsToReupdate } from './studyPlans.js'
|
||||
import { dbConnections } from '../../db/connection.js'
|
||||
import { bulkCreate, selectFromActiveSnapshotsByIds, selectFromByIds } from '../../db/index'
|
||||
import { Enrollment, Student } from '../../db/models/index'
|
||||
import logger from '../../utils/logger'
|
||||
import { studentMapper, enrollmentMapper } from '../mapper'
|
||||
import { getEducation, loadMapsIfNeeded } from '../shared'
|
||||
import { updateAttainments } from './attainments'
|
||||
import { getAttainmentsToBeExcluded } from './excludedPartialAttainments'
|
||||
import { updateSISStudyRights, updateSISStudyRightElements } from './SISStudyRights'
|
||||
import { updateStudyplans, findStudentsToReupdate } from './studyPlans'
|
||||
import { dbConnections } from '../../db/connection'
|
||||
|
||||
// Accepted selection path is not available when degree programme doesn't have
|
||||
// studytrack or major subject. This is a known bug on SIS and has been reported
|
||||
@@ -62,7 +62,7 @@ const groupStudyrightSnapshots = studyrightSnapshots => {
|
||||
const parseSnapshotDateTime = snapshots => {
|
||||
const oldestFirst = snapshots
|
||||
.filter(s => !!s.snapshot_date_time)
|
||||
.sort((a, b) => new Date(a.snapshot_date_time) - new Date(b.snapshot_date_time))
|
||||
.sort((a, b) => new Date(a.snapshot_date_time).getTime() - new Date(b.snapshot_date_time).getTime())
|
||||
if (oldestFirst.length) return oldestFirst[0].snapshot_date_time
|
||||
return null
|
||||
}
|
||||
@@ -108,7 +108,7 @@ const updateEnrollments = async (enrollments, personIdToStudentNumber, studyRigh
|
||||
acc.cuIds.push(enrollment.course_unit_id)
|
||||
return acc
|
||||
},
|
||||
{ curIds: [], cuIds: [] }
|
||||
{ curIds: [] as string[], cuIds: [] as string[] }
|
||||
)
|
||||
|
||||
const [courseUnitRealisations, courseUnits] = await Promise.all([
|
||||
@@ -142,7 +142,7 @@ const updateEnrollments = async (enrollments, personIdToStudentNumber, studyRigh
|
||||
}
|
||||
|
||||
const createModuleGroupIdToCodeMap = async studyRights => {
|
||||
const groupIds = new Set()
|
||||
const groupIds = new Set<string>()
|
||||
|
||||
for (const { accepted_selection_path } of studyRights) {
|
||||
const { educationPhase1GroupId, educationPhase1ChildGroupId, educationPhase2GroupId, educationPhase2ChildGroupId } =
|
||||
@@ -229,7 +229,7 @@ export const updateStudents = async (personIds, iteration = 0) => {
|
||||
return res
|
||||
}, {})
|
||||
|
||||
const logError = (error, updatedItem) => {
|
||||
const logError = (error, updatedItem: string) => {
|
||||
logger.error({ message: `Failed to update ${updatedItem}`, error })
|
||||
}
|
||||
|
||||
+20
-25
@@ -1,14 +1,14 @@
|
||||
import { flatten, flattenDeep } from 'lodash-es'
|
||||
import { Op } from 'sequelize'
|
||||
|
||||
import { selectFromByIds, bulkCreate } from '../../db/index.js'
|
||||
import { Studyplan } from '../../db/models/index.js'
|
||||
import { isBaMa } from '../../utils/index.js'
|
||||
import { studyplanMapper, sanitizeCourseCode } from '../mapper.js'
|
||||
import { getEducation } from '../shared.js'
|
||||
import { selectFromByIds, bulkCreate } from '../../db/index'
|
||||
import { Studyplan } from '../../db/models/index'
|
||||
import { isBaMa } from '../../utils/index'
|
||||
import { studyplanMapper, sanitizeCourseCode } from '../mapper'
|
||||
import { getEducation } from '../shared'
|
||||
|
||||
export const updateStudyplans = async (
|
||||
studyplansAll,
|
||||
studyplansAll: any[],
|
||||
personIds,
|
||||
personIdToStudentNumber,
|
||||
groupedStudyRightSnapshots
|
||||
@@ -31,7 +31,7 @@ export const updateStudyplans = async (
|
||||
const educationHasStudyRight = Object.keys(groupedStudyRightSnapshots).reduce((acc, k) => {
|
||||
const sorted = groupedStudyRightSnapshots[k]
|
||||
.filter(s => new Date(s.snapshot_date_time) <= new Date())
|
||||
.sort((a, b) => new Date(b.snapshot_date_time) - new Date(a.snapshot_date_time))
|
||||
.sort((a, b) => new Date(b.snapshot_date_time).getTime() - new Date(a.snapshot_date_time).getTime())
|
||||
if (!sorted.length) return acc
|
||||
const { education_id, person_id } = sorted[0]
|
||||
acc[education_id] ??= {}
|
||||
@@ -42,7 +42,7 @@ export const updateStudyplans = async (
|
||||
const educationStudyrights = Object.keys(groupedStudyRightSnapshots).reduce((acc, k) => {
|
||||
const sorted = groupedStudyRightSnapshots[k]
|
||||
.filter(s => new Date(s.snapshot_date_time) <= new Date())
|
||||
.sort((a, b) => new Date(b.snapshot_date_time) - new Date(a.snapshot_date_time))
|
||||
.sort((a, b) => new Date(b.snapshot_date_time).getTime() - new Date(a.snapshot_date_time).getTime())
|
||||
if (!sorted.length) return acc
|
||||
const { education_id, person_id, id } = sorted[0]
|
||||
const educationType = getEducation(education_id)
|
||||
@@ -56,9 +56,7 @@ export const updateStudyplans = async (
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
const filteredPlans = studyplans.filter(
|
||||
p => educationHasStudyRight[p.root_id] && educationHasStudyRight[p.root_id][p.user_id]
|
||||
)
|
||||
const filteredPlans = studyplans.filter(p => educationHasStudyRight[p.root_id]?.[p.user_id])
|
||||
|
||||
const courseUnitSelections = flatten(studyplans.map(plan => plan.course_unit_selections.map(cu => cu.courseUnitId)))
|
||||
const courseUnitSelectionSubstitutedBy = flattenDeep(
|
||||
@@ -129,13 +127,13 @@ export const updateStudyplans = async (
|
||||
return res
|
||||
}, {})
|
||||
|
||||
const findDegreeProgrammes = (programId, handledProgrammeIds) => {
|
||||
const findDegreeProgrammes = (programId: string, handledProgrammeIds: Set<string>): string[] => {
|
||||
if (programmeModuleIdToType[programId] === 'DegreeProgramme') return [programId]
|
||||
if (!childModules[programId]) return []
|
||||
handledProgrammeIds.add(programId)
|
||||
|
||||
const programmes = []
|
||||
childModules[programId].forEach(child => {
|
||||
const programmes: string[] = []
|
||||
childModules[programId].forEach((child: string) => {
|
||||
if (!handledProgrammeIds.has(child)) programmes.push(...findDegreeProgrammes(child, handledProgrammeIds))
|
||||
})
|
||||
return programmes
|
||||
@@ -181,7 +179,7 @@ export const updateStudyplans = async (
|
||||
|
||||
const getAttainmentsFromAttainment = attainment => {
|
||||
if (!attainment) return []
|
||||
if (attainment.nodes && attainment.nodes.length)
|
||||
if (attainment.nodes?.length)
|
||||
return flatten(
|
||||
attainment.nodes
|
||||
.filter(node => node.attainmentId)
|
||||
@@ -195,20 +193,20 @@ export const updateStudyplans = async (
|
||||
return res
|
||||
}, {})
|
||||
|
||||
const moduleIdToParentDegreeProgramme = {}
|
||||
const mapParentDegreeProgrammes = (moduleId, degreeProgrammeId, handledModuleIds) => {
|
||||
const moduleIdToParentDegreeProgramme: Record<string, string[]> = {}
|
||||
const mapParentDegreeProgrammes = (moduleId: string, degreeProgrammeId: string, handledModuleIds: Set<string>) => {
|
||||
moduleIdToParentDegreeProgramme[moduleId] ??= []
|
||||
moduleIdToParentDegreeProgramme[moduleId].push(degreeProgrammeId)
|
||||
handledModuleIds.add(moduleId)
|
||||
if (!childModules[moduleId]) return
|
||||
|
||||
childModules[moduleId].forEach(child => {
|
||||
childModules[moduleId].forEach((child: string) => {
|
||||
if (!handledModuleIds.has(child)) mapParentDegreeProgrammes(child, degreeProgrammeId, handledModuleIds)
|
||||
})
|
||||
}
|
||||
|
||||
studyplans.forEach(plan => {
|
||||
studyplanIdToDegreeProgrammes[plan.id].forEach(programmeId => {
|
||||
studyplanIdToDegreeProgrammes[plan.id].forEach((programmeId: string) => {
|
||||
mapParentDegreeProgrammes(programmeId, programmeId, new Set())
|
||||
})
|
||||
})
|
||||
@@ -221,7 +219,7 @@ export const updateStudyplans = async (
|
||||
|
||||
const moduleIdToParentModuleCode = Object.entries(moduleIdToParentDegreeProgramme).reduce(
|
||||
(acc, [moduleId, degreeProgrammeIds]) => {
|
||||
const codes = new Set()
|
||||
const codes = new Set<string>()
|
||||
for (const programmeId of degreeProgrammeIds) {
|
||||
codes.add(programmeModuleIdToCode[programmeId])
|
||||
}
|
||||
@@ -250,9 +248,7 @@ export const updateStudyplans = async (
|
||||
)
|
||||
|
||||
const mappedPlans = flatten(
|
||||
filteredPlans
|
||||
.filter(p => educationHasStudyRight[p.root_id] && educationHasStudyRight[p.root_id][p.user_id])
|
||||
.map(mapStudyplan)
|
||||
filteredPlans.filter(p => educationHasStudyRight[p.root_id]?.[p.user_id]).map(mapStudyplan)
|
||||
).filter(p => !!p)
|
||||
await bulkCreate(Studyplan, mappedPlans)
|
||||
}
|
||||
@@ -268,9 +264,8 @@ export const findStudentsToReupdate = async (personIds, personIdToStudentNumber,
|
||||
studentnumber: { [Op.in]: personIds.map(id => personIdToStudentNumber[id]) },
|
||||
},
|
||||
attributes: ['studentnumber'],
|
||||
distinct: true,
|
||||
})
|
||||
const studentNumberToPersonId = Object.entries(personIdToStudentNumber).reduce((obj, cur) => {
|
||||
const studentNumberToPersonId = Object.entries<any>(personIdToStudentNumber).reduce((obj, cur) => {
|
||||
const [personId, studentnumber] = cur
|
||||
obj[studentnumber] = personId
|
||||
return obj
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
import { Op } from 'sequelize'
|
||||
|
||||
import { selectFromByIds } from '../../db/index.js'
|
||||
import { SISStudyRight, SISStudyRightElement } from '../../db/models/index.js'
|
||||
import { selectFromByIds } from '../../db/index'
|
||||
import { SISStudyRight, SISStudyRightElement } from '../../db/models/index'
|
||||
|
||||
export const studentsThatNeedToBeFixed = [
|
||||
{ id: 'hy-hlo-109316006', started: '2019-04-30' },
|
||||
@@ -23,7 +23,7 @@ export const studentsThatNeedToBeFixed = [
|
||||
{ id: 'hy-hlo-62121710', started: '2020-12-19' },
|
||||
]
|
||||
|
||||
export const fixVarhaiskasvatusStudyRights = async studentsToBeFixed => {
|
||||
export const fixVarhaiskasvatusStudyRights = async (studentsToBeFixed: string[]) => {
|
||||
const students = await selectFromByIds('persons', studentsToBeFixed)
|
||||
for (const student of students) {
|
||||
const studentInfo = studentsThatNeedToBeFixed.find(s => s.id === student.id)
|
||||
@@ -45,7 +45,7 @@ export const fixVarhaiskasvatusStudyRights = async studentsToBeFixed => {
|
||||
if (studyRight) {
|
||||
const bachelorElement = studyRight.SISStudyRightElements.find(element => element.code === 'KH60_001')
|
||||
if (bachelorElement) {
|
||||
bachelorElement.startDate = new Date(studentInfo.started)
|
||||
bachelorElement.startDate = new Date(studentInfo!.started)
|
||||
await bachelorElement.save()
|
||||
if (bachelorElement.graduated) {
|
||||
const masterElement = studyRight.SISStudyRightElements.find(element => element.code === 'MH60_001')
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
import { maxBy } from 'lodash-es'
|
||||
|
||||
export const getLatestSnapshot = entities => {
|
||||
export const getLatestSnapshot = (entities: any[]) => {
|
||||
return maxBy(
|
||||
entities.filter(entity => entity.document_state !== 'DELETED'),
|
||||
entity => entity.modification_ordinal
|
||||
)
|
||||
}
|
||||
|
||||
export const getActiveSnapshot = entities => {
|
||||
export const getActiveSnapshot = (entities: any[]) => {
|
||||
const now = new Date()
|
||||
return maxBy(
|
||||
entities
|
||||
@@ -17,7 +17,7 @@ export const getActiveSnapshot = entities => {
|
||||
)
|
||||
}
|
||||
|
||||
export const getLatestActiveSnapshot = entities => {
|
||||
export const getLatestActiveSnapshot = (entities: any[]) => {
|
||||
return maxBy(
|
||||
entities.filter(entity => entity.document_state == 'ACTIVE'),
|
||||
entity => entity.modification_ordinal
|
||||
+6
-8
@@ -1,25 +1,23 @@
|
||||
import { format as winstonFormat, transports as winstonTransports, createLogger } from 'winston'
|
||||
import LokiTransport from 'winston-loki'
|
||||
import WinstonSentry from 'winston-transport-sentry-node'
|
||||
import SentryTransport from 'winston-transport-sentry-node'
|
||||
|
||||
import { isDev, isStaging, isProduction, runningInCI, SENTRY_DSN } from '../config.js'
|
||||
|
||||
const Sentry = WinstonSentry.default
|
||||
import { isDev, isStaging, isProduction, runningInCI, SENTRY_DSN } from '../config'
|
||||
|
||||
const { colorize, combine, timestamp, printf, uncolorize } = winstonFormat
|
||||
|
||||
const transports = []
|
||||
const transports: any[] = []
|
||||
|
||||
if (isProduction && !isStaging && !runningInCI && SENTRY_DSN) {
|
||||
transports.push(new Sentry({ level: 'error' }))
|
||||
transports.push(new SentryTransport({ level: 'error' }))
|
||||
}
|
||||
|
||||
const devFormat = printf(
|
||||
({ timestamp, level, message, error, ...rest }) =>
|
||||
({ timestamp, level, message, error, ...rest }: any) =>
|
||||
`${timestamp} ${level}: ${message}${error ? ` ${error?.stack}` : ''}${rest ? ` ${JSON.stringify(rest)}` : ''}`
|
||||
)
|
||||
|
||||
const prodFormat = printf(({ timestamp, level, message, error, ...rest }) => {
|
||||
const prodFormat = printf(({ timestamp, level, message, error, ...rest }: any) => {
|
||||
const log = { timestamp, level, message, ...rest }
|
||||
if (error) {
|
||||
log.error = error?.stack
|
||||
+3
-6
@@ -1,11 +1,8 @@
|
||||
import { createRequire } from 'module'
|
||||
const legacyRequire = createRequire(import.meta.url)
|
||||
const redisLock = legacyRequire('redis-lock')
|
||||
|
||||
import { createClient } from 'redis'
|
||||
import redisLock from 'redis-lock'
|
||||
|
||||
import { REDIS_HOST, REDIS_PORT } from '../config.js'
|
||||
import logger from './logger.js'
|
||||
import { REDIS_HOST, REDIS_PORT } from '../config'
|
||||
import logger from './logger'
|
||||
|
||||
export const redisClient = createClient({
|
||||
url: `redis://${REDIS_HOST}:${REDIS_PORT}`,
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Worker } from 'bullmq'
|
||||
|
||||
import { REDIS_HOST, REDIS_PORT } from './config.js'
|
||||
import processor from './processor.js'
|
||||
import logger from './utils/logger.js'
|
||||
import { REDIS_HOST, REDIS_PORT } from './config'
|
||||
import processor from './processor'
|
||||
import logger from './utils/logger'
|
||||
|
||||
const connection = {
|
||||
host: REDIS_HOST,
|
||||
@@ -16,7 +16,7 @@ worker.on('completed', job => {
|
||||
message: `Job ${job.id} completed`,
|
||||
type: job.name,
|
||||
count: job.data?.length,
|
||||
timems: job.finishedOn - job.processedOn,
|
||||
timems: job.finishedOn! - job.processedOn!,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -26,7 +26,7 @@ worker.on('error', error => {
|
||||
})
|
||||
|
||||
worker.on('failed', (job, error) => {
|
||||
logger.error(`Job ${job.id} failed. ${error.stack ?? ''}`)
|
||||
logger.error(`Job ${job?.id} failed. ${error.stack ?? ''}`)
|
||||
})
|
||||
|
||||
worker.on('active', job => {
|
||||
+12
-11
@@ -24,7 +24,7 @@ const { knexMock, bulkCreateMock, selectFromByIdsMock, selectOneByIdMock } = vi.
|
||||
}
|
||||
|
||||
const knexMock = Object.assign(
|
||||
vi.fn(table => makeChainableQuery(tableResults[table] ?? [])),
|
||||
vi.fn((table: string) => makeChainableQuery(tableResults[table] ?? [])),
|
||||
{ select: () => makeChainableQuery([]) }
|
||||
)
|
||||
|
||||
@@ -36,15 +36,15 @@ const { knexMock, bulkCreateMock, selectFromByIdsMock, selectOneByIdMock } = vi.
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/db/connection.js', () => ({ dbConnections: { knex: knexMock } }))
|
||||
vi.mock('@/db/connection', () => ({ dbConnections: { knex: knexMock } }))
|
||||
|
||||
vi.mock('@/db/index.js', () => ({
|
||||
vi.mock('@/db/index', () => ({
|
||||
selectFromByIds: selectFromByIdsMock,
|
||||
selectOneById: selectOneByIdMock,
|
||||
bulkCreate: bulkCreateMock,
|
||||
}))
|
||||
|
||||
vi.mock('@/db/models/index.js', () => ({
|
||||
vi.mock('@/db/models/index', () => ({
|
||||
Course: { name: 'Course', findOne: vi.fn() },
|
||||
Teacher: { name: 'Teacher', findOne: vi.fn() },
|
||||
Credit: { name: 'Credit' },
|
||||
@@ -52,13 +52,13 @@ vi.mock('@/db/models/index.js', () => ({
|
||||
CourseProvider: { name: 'CourseProvider', findOne: vi.fn() },
|
||||
}))
|
||||
|
||||
vi.mock('@/utils/logger.js', () => ({
|
||||
vi.mock('@/utils/logger', () => ({
|
||||
default: { error: vi.fn(), info: vi.fn(), debug: vi.fn(), warn: vi.fn() },
|
||||
}))
|
||||
|
||||
// updateAttainments builds its credit rows via mapper.js's real creditMapper, which in turn reads
|
||||
// from shared.js's Redis-backed lookup maps - mocked here the same way as in mapper.test.js.
|
||||
vi.mock('@/updater/shared.js', () => ({
|
||||
vi.mock('@/updater/shared', () => ({
|
||||
getSemesterByDate: vi.fn(() => ({ semestercode: 100, composite: '2020-2021-fall' })),
|
||||
getGrade: vi.fn(() => ({ value: '5', passed: true })),
|
||||
getUniOrgId: vi.fn(() => 'uni-org1'),
|
||||
@@ -67,8 +67,8 @@ vi.mock('@/updater/shared.js', () => ({
|
||||
educationTypeToExtentcode: {},
|
||||
}))
|
||||
|
||||
import { Course } from '@/db/models/index.js'
|
||||
import { updateAttainments } from '@/updater/updateStudents/attainments.js'
|
||||
import { Course } from '@/db/models/index'
|
||||
import { updateAttainments } from '@/updater/updateStudents/attainments'
|
||||
|
||||
describe('updateAttainments', () => {
|
||||
beforeEach(() => {
|
||||
@@ -76,7 +76,8 @@ describe('updateAttainments', () => {
|
||||
})
|
||||
|
||||
/** Get what calls were directed to the wanted model by bulkCreate */
|
||||
const findBulkCreateCall = modelName => bulkCreateMock.mock.calls.find(([model]) => model.name === modelName)
|
||||
const findBulkCreateCall = (modelName: string) =>
|
||||
bulkCreateMock.mock.calls.find(([model]) => model.name === modelName)!
|
||||
|
||||
it('maps a plain course unit attainment into a Credit bulkCreate call', async () => {
|
||||
const attainment = {
|
||||
@@ -150,7 +151,7 @@ describe('updateAttainments', () => {
|
||||
})
|
||||
|
||||
it('updates custom course unit with fields from a new attainment', async () => {
|
||||
Course.findOne.mockResolvedValueOnce({
|
||||
vi.mocked(Course.findOne).mockResolvedValueOnce({
|
||||
id: 'XYZ-123',
|
||||
groupId: null, // Group id missing
|
||||
code: 'XYZ-123',
|
||||
@@ -162,7 +163,7 @@ describe('updateAttainments', () => {
|
||||
minAttainmentDate: '2019-01-01',
|
||||
substitutionGroups: [],
|
||||
courseUnitType: 'type1',
|
||||
})
|
||||
} as any)
|
||||
|
||||
const attainment = {
|
||||
id: 'att-custom-2',
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { courseProviderMapper } from '@/updater/mapper.js'
|
||||
import { courseProviderMapper } from '@/updater/mapper'
|
||||
|
||||
describe('courseProviderMapper', () => {
|
||||
it('maps organisation shares to a course provider row', () => {
|
||||
+22
-22
@@ -1,9 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { creditMapper } from '@/updater/mapper.js'
|
||||
import { getCreditTypeCodeFromAttainment, getGrade, getSemesterByDate, getUniOrgId } from '@/updater/shared.js'
|
||||
import { creditMapper } from '@/updater/mapper'
|
||||
import { getCreditTypeCodeFromAttainment, getGrade, getSemesterByDate, getUniOrgId } from '@/updater/shared'
|
||||
|
||||
describe('creditMapper', () => {
|
||||
const baseAttainment = {
|
||||
@@ -40,10 +40,10 @@ describe('creditMapper', () => {
|
||||
)
|
||||
|
||||
it('maps attainment correctly into credit', () => {
|
||||
getUniOrgId.mockReturnValue('uni-org1')
|
||||
getSemesterByDate.mockReturnValue({ semestercode: 100, composite: '2020-2021-fall' })
|
||||
getGrade.mockReturnValue({ value: '5', passed: true })
|
||||
getCreditTypeCodeFromAttainment.mockReturnValue('PASSED')
|
||||
vi.mocked(getUniOrgId).mockReturnValue('uni-org1')
|
||||
vi.mocked(getSemesterByDate).mockReturnValue({ semestercode: 100, composite: '2020-2021-fall' })
|
||||
vi.mocked(getGrade).mockReturnValue({ value: '5', passed: true })
|
||||
vi.mocked(getCreditTypeCodeFromAttainment).mockReturnValue('PASSED' as any)
|
||||
|
||||
const result = mapCredit(lookupMaps())(baseAttainment)
|
||||
|
||||
@@ -68,15 +68,15 @@ describe('creditMapper', () => {
|
||||
})
|
||||
|
||||
it('returns null when there is no target semester for the attainment date', () => {
|
||||
getUniOrgId.mockReturnValue('uni-org1')
|
||||
getSemesterByDate.mockReturnValue(undefined)
|
||||
vi.mocked(getUniOrgId).mockReturnValue('uni-org1')
|
||||
vi.mocked(getSemesterByDate).mockReturnValue(undefined)
|
||||
|
||||
expect(mapCredit(lookupMaps())(baseAttainment)).toBeNull()
|
||||
})
|
||||
|
||||
it('returns null when the course code cannot be resolved', () => {
|
||||
getUniOrgId.mockReturnValue('uni-org1')
|
||||
getSemesterByDate.mockReturnValue({ semestercode: 100, composite: '2020-2021-fall' })
|
||||
vi.mocked(getUniOrgId).mockReturnValue('uni-org1')
|
||||
vi.mocked(getSemesterByDate).mockReturnValue({ semestercode: 100, composite: '2020-2021-fall' })
|
||||
|
||||
const result = mapCredit(lookupMaps({ courseUnitIdToCourseCode: {} }))(baseAttainment)
|
||||
|
||||
@@ -84,21 +84,21 @@ describe('creditMapper', () => {
|
||||
})
|
||||
|
||||
it('marks open university attainments via the AY course code prefix', () => {
|
||||
getUniOrgId.mockReturnValue('uni-org1')
|
||||
getSemesterByDate.mockReturnValue({ semestercode: 100, composite: '2020-2021-fall' })
|
||||
getGrade.mockReturnValue({ value: '5', passed: true })
|
||||
getCreditTypeCodeFromAttainment.mockReturnValue('PASSED')
|
||||
vi.mocked(getUniOrgId).mockReturnValue('uni-org1')
|
||||
vi.mocked(getSemesterByDate).mockReturnValue({ semestercode: 100, composite: '2020-2021-fall' })
|
||||
vi.mocked(getGrade).mockReturnValue({ value: '5', passed: true })
|
||||
vi.mocked(getCreditTypeCodeFromAttainment).mockReturnValue('PASSED' as any)
|
||||
|
||||
const result = mapCredit(lookupMaps({ courseUnitIdToCourseCode: { cu1: 'AY123' } }))(baseAttainment)
|
||||
|
||||
expect(result.is_open).toBe(true)
|
||||
expect(result!.is_open).toBe(true)
|
||||
})
|
||||
|
||||
it('marks open university attainments via the study right education type', () => {
|
||||
getUniOrgId.mockReturnValue('uni-org1')
|
||||
getSemesterByDate.mockReturnValue({ semestercode: 100, composite: '2020-2021-fall' })
|
||||
getGrade.mockReturnValue({ value: '5', passed: true })
|
||||
getCreditTypeCodeFromAttainment.mockReturnValue('PASSED')
|
||||
vi.mocked(getUniOrgId).mockReturnValue('uni-org1')
|
||||
vi.mocked(getSemesterByDate).mockReturnValue({ semestercode: 100, composite: '2020-2021-fall' })
|
||||
vi.mocked(getGrade).mockReturnValue({ value: '5', passed: true })
|
||||
vi.mocked(getCreditTypeCodeFromAttainment).mockReturnValue('PASSED' as any)
|
||||
|
||||
const attainment = { ...baseAttainment, study_right_id: 'sr1' }
|
||||
const result = mapCredit(
|
||||
@@ -107,7 +107,7 @@ describe('creditMapper', () => {
|
||||
})
|
||||
)(attainment)
|
||||
|
||||
expect(result.is_open).toBe(true)
|
||||
expect(result!.is_open).toBe(true)
|
||||
})
|
||||
|
||||
it('returns null with missing organization', () => {
|
||||
+6
-6
@@ -1,9 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { enrollmentMapper } from '@/updater/mapper.js'
|
||||
import { getSemesterByDate } from '@/updater/shared.js'
|
||||
import { enrollmentMapper } from '@/updater/mapper'
|
||||
import { getSemesterByDate } from '@/updater/shared'
|
||||
|
||||
describe('enrollmentMapper', () => {
|
||||
const baseEnrollment = {
|
||||
@@ -16,7 +16,7 @@ describe('enrollmentMapper', () => {
|
||||
study_right_id: null,
|
||||
}
|
||||
|
||||
const mapEnrollment = (overrides = {}) =>
|
||||
const mapEnrollment = (overrides: any = {}) =>
|
||||
enrollmentMapper(
|
||||
overrides.personIdToStudentNumber ?? { p1: '014111111' },
|
||||
overrides.courseUnitIdToCourseUnit ?? { cu1: { code: 'TKT10', group_id: 'grp1' } },
|
||||
@@ -33,7 +33,7 @@ describe('enrollmentMapper', () => {
|
||||
})
|
||||
|
||||
it('maps an enrollment correctly', () => {
|
||||
getSemesterByDate.mockReturnValue({ semestercode: 5, composite: 'sem5' })
|
||||
vi.mocked(getSemesterByDate).mockReturnValue({ semestercode: 5, composite: 'sem5' })
|
||||
|
||||
expect(mapEnrollment()(baseEnrollment)).toEqual({
|
||||
id: 'e1',
|
||||
+2
-2
@@ -8,11 +8,11 @@ import { vi } from 'vitest'
|
||||
// this directory. Tests that need per-case control over a shared.js lookup (e.g. creditMapper,
|
||||
// enrollmentMapper) can still `import { getSemesterByDate, ... } from '.../updater/shared.js'`
|
||||
// afterwards and call `.mockReturnValue(...)` on it - it's the same mocked function instance.
|
||||
vi.mock('@/utils/logger.js', () => ({
|
||||
vi.mock('@/utils/logger', () => ({
|
||||
default: { error: vi.fn(), info: vi.fn(), debug: vi.fn(), warn: vi.fn() },
|
||||
}))
|
||||
|
||||
vi.mock('@/updater/shared.js', () => ({
|
||||
vi.mock('@/updater/shared', () => ({
|
||||
getSemesterByDate: vi.fn(),
|
||||
getGrade: vi.fn(),
|
||||
getUniOrgId: vi.fn(),
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { isModule } from '@/updater/mapper.js'
|
||||
import { isModule } from '@/updater/mapper'
|
||||
|
||||
describe('isModule', () => {
|
||||
it('is true for module attainment types', () => {
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { mapCourseType } from '@/updater/mapper.js'
|
||||
import { mapCourseType } from '@/updater/mapper'
|
||||
|
||||
describe('mapCourseType', () => {
|
||||
it('maps a study level to a course type row', () => {
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { mapCurriculumPeriod } from '@/updater/mapper.js'
|
||||
import { mapCurriculumPeriod } from '@/updater/mapper'
|
||||
|
||||
describe('mapCurriculumPeriod', () => {
|
||||
it('maps a curriculum period correctly', () => {
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { mapStudyrightExtent } from '@/updater/mapper.js'
|
||||
import { mapStudyrightExtent } from '@/updater/mapper'
|
||||
|
||||
describe('mapStudyrightExtent', () => {
|
||||
it('resolves the extent code from educationTypeToExtentcode', () => {
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { mapTeacher } from '@/updater/mapper.js'
|
||||
import { mapTeacher } from '@/updater/mapper'
|
||||
|
||||
describe('mapTeacher', () => {
|
||||
it('combines last and first names when present', () => {
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { sanitizeCourseCode } from '@/updater/mapper.js'
|
||||
import { sanitizeCourseCode } from '@/updater/mapper'
|
||||
|
||||
describe('sanitizeCourseCode', () => {
|
||||
it('returns null for falsy input', () => {
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { studentMapper } from '@/updater/mapper.js'
|
||||
import { studentMapper } from '@/updater/mapper'
|
||||
|
||||
describe('studentMapper', () => {
|
||||
const baseStudent = {
|
||||
@@ -74,9 +74,9 @@ describe('studentMapper', () => {
|
||||
|
||||
const result = studentMapper(attainments, [], new Set())(baseStudent)
|
||||
|
||||
expect(result.creditcount).toBe(10)
|
||||
expect(result.studentnumber).toBe('014999999')
|
||||
expect(result.abbreviatedname).toBe('Doe Jane')
|
||||
expect(result.dateofuniversityenrollment).toBeNull()
|
||||
expect(result!.creditcount).toBe(10)
|
||||
expect(result!.studentnumber).toBe('014999999')
|
||||
expect(result!.abbreviatedname).toBe('Doe Jane')
|
||||
expect(result!.dateofuniversityenrollment).toBeNull()
|
||||
})
|
||||
})
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { termRegistrationTypeToEnrollmenttype } from '@/updater/mapper.js'
|
||||
import { termRegistrationTypeToEnrollmenttype } from '@/updater/mapper'
|
||||
|
||||
describe('termRegistrationTypeToEnrollmenttype', () => {
|
||||
it('maps known types and falls back to 3', () => {
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { educationTypeToExtentcode } from '@/updater/shared.js'
|
||||
import { educationTypeToExtentcode } from '@/updater/shared'
|
||||
|
||||
describe('educationTypeToExtentcode', () => {
|
||||
it('maps known education types to their extent code', () => {
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import './initTests.js'
|
||||
import './initTests'
|
||||
|
||||
import { getCreditTypeCodeFromAttainment } from '@/updater/shared.js'
|
||||
import { getCreditTypeCodeFromAttainment } from '@/updater/shared'
|
||||
|
||||
describe('getCreditTypeCodeFromAttainment', () => {
|
||||
it('is FAILED when the grade did not pass', () => {
|
||||
+3
-3
@@ -5,17 +5,17 @@ import { vi } from 'vitest'
|
||||
//
|
||||
// Import this module first - before importing anything from shared.js - in every test file in
|
||||
// this directory.
|
||||
vi.mock('@/utils/redis.js', () => ({
|
||||
vi.mock('@/utils/redis', () => ({
|
||||
redisClient: { get: vi.fn(), set: vi.fn(), connect: vi.fn(), on: vi.fn() },
|
||||
lock: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/db/index.js', () => ({
|
||||
vi.mock('@/db/index', () => ({
|
||||
selectAllFrom: vi.fn(),
|
||||
selectAllFromSnapshots: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/db/models/index.js', () => ({
|
||||
vi.mock('@/db/models/index', () => ({
|
||||
Semester: {},
|
||||
Organization: {},
|
||||
CREDIT_TYPE_CODES: { PASSED: 4, FAILED: 10, IMPROVED: 7, APPROVED: 9 },
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2024",
|
||||
"lib": ["ES2024"],
|
||||
"allowJs": true,
|
||||
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
|
||||
"outDir": "dist",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"noErrorTruncation": true,
|
||||
"sourceMap": true,
|
||||
"sourceRoot": "/",
|
||||
"inlineSources": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src", "tests", "vitest.config.mts"]
|
||||
}
|
||||
Reference in New Issue
Block a user