Compare commits
6 Commits
43331ae226
...
4b47cf9c96
Author | SHA1 | Date | |
---|---|---|---|
4b47cf9c96 | |||
0a7e92be06 | |||
df241f9a30 | |||
cfa1674421 | |||
dc8921c679 | |||
a19d7f8dfe |
@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
server/node_modules
|
||||
Dockerfile
|
||||
.env
|
39
Dockerfile
39
Dockerfile
@ -1,20 +1,37 @@
|
||||
FROM node:lts-alpine
|
||||
FROM node:lts-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache --update git
|
||||
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
WORKDIR /app/server
|
||||
COPY server/package*.json ./
|
||||
RUN npm ci
|
||||
COPY server/prisma/schema.prisma ./prisma/schema.prisma
|
||||
RUN npx prisma generate
|
||||
|
||||
WORKDIR /app
|
||||
COPY ./ ./
|
||||
RUN npm run build
|
||||
|
||||
WORKDIR /app/server
|
||||
CMD ["node", "index.js"]
|
||||
FROM nginx
|
||||
COPY <<EOF /etc/nginx/conf.d/default.conf
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index non-existent-to-prevent-caching.html;
|
||||
try_files \$uri @index;
|
||||
}
|
||||
|
||||
location @index {
|
||||
root /usr/share/nginx/html;
|
||||
add_header Cache-Control no-cache;
|
||||
expires 0;
|
||||
try_files /index.html =404;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
@ -3,20 +3,4 @@ services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- 3000:3000
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://postgres:postgres@db:5432/postgres
|
||||
depends_on:
|
||||
- db
|
||||
env_file:
|
||||
- ./server/.env
|
||||
command: /bin/sh -c "npx prisma db push && node index.js"
|
||||
db:
|
||||
image: postgres
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
- 3000:80
|
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Timetable V2</title>
|
||||
<title>Timetable V2 Demo</title>
|
||||
<meta name="description" content="Timetable and Substitution plan viewer" />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#212121" />
|
||||
|
561
package-lock.json
generated
561
package-lock.json
generated
@ -51,89 +51,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.22.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
|
||||
"integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.22.10",
|
||||
"chalk": "^2.4.2"
|
||||
"@babel/helper-validator-identifier": "^7.27.1",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.22.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz",
|
||||
@ -183,15 +114,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.22.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
|
||||
"integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz",
|
||||
"integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.10",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@babel/types": "^7.28.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.12",
|
||||
"@jridgewell/trace-mapping": "^0.3.28",
|
||||
"jsesc": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -357,6 +290,16 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-globals": {
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
||||
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-hoist-variables": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
|
||||
@ -504,19 +447,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
||||
"dev": true,
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
|
||||
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
|
||||
"dev": true,
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
|
||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@ -545,108 +488,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.22.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.11.tgz",
|
||||
"integrity": "sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==",
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
|
||||
"integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.5",
|
||||
"@babel/traverse": "^7.22.11",
|
||||
"@babel/types": "^7.22.11"
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.27.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.22.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
|
||||
"integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight/node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.22.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.11.tgz",
|
||||
"integrity": "sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g==",
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz",
|
||||
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.28.0"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@ -1838,70 +1700,57 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.22.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz",
|
||||
"integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==",
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
|
||||
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
|
||||
"integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.5",
|
||||
"@babel/parser": "^7.22.5",
|
||||
"@babel/types": "^7.22.5"
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/parser": "^7.27.2",
|
||||
"@babel/types": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.22.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.11.tgz",
|
||||
"integrity": "sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==",
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz",
|
||||
"integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.10",
|
||||
"@babel/generator": "^7.22.10",
|
||||
"@babel/helper-environment-visitor": "^7.22.5",
|
||||
"@babel/helper-function-name": "^7.22.5",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.22.11",
|
||||
"@babel/types": "^7.22.11",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.28.0",
|
||||
"@babel/helper-globals": "^7.28.0",
|
||||
"@babel/parser": "^7.28.0",
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.28.0",
|
||||
"debug": "^4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse/node_modules/globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.22.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.11.tgz",
|
||||
"integrity": "sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==",
|
||||
"dev": true,
|
||||
"version": "7.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz",
|
||||
"integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
"@babel/helper-string-parser": "^7.27.1",
|
||||
"@babel/helper-validator-identifier": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -2349,74 +2198,58 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.2.2.tgz",
|
||||
"integrity": "sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==",
|
||||
"version": "9.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.4.tgz",
|
||||
"integrity": "sha512-vtZCt7NqWhKEtHa3SD/322DlgP5uR9MqWxnE0y8Q0tjDs9H5Lxhss+b5wv8rmuXRoHKLESNgw9d+EN9ybBbj9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/devtools-if": "9.2.2",
|
||||
"@intlify/message-compiler": "9.2.2",
|
||||
"@intlify/shared": "9.2.2",
|
||||
"@intlify/vue-devtools": "9.2.2"
|
||||
"@intlify/message-compiler": "9.14.4",
|
||||
"@intlify/shared": "9.14.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/devtools-if": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/devtools-if/-/devtools-if-9.2.2.tgz",
|
||||
"integrity": "sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "9.2.2"
|
||||
"node": ">= 16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/message-compiler": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.2.2.tgz",
|
||||
"integrity": "sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==",
|
||||
"version": "9.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.4.tgz",
|
||||
"integrity": "sha512-vcyCLiVRN628U38c3PbahrhbbXrckrM9zpy0KZVlDk2Z0OnGwv8uQNNXP3twwGtfLsCf4gu3ci6FMIZnPaqZsw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "9.2.2",
|
||||
"source-map": "0.6.1"
|
||||
"@intlify/shared": "9.14.4",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/shared": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.2.2.tgz",
|
||||
"integrity": "sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==",
|
||||
"version": "9.14.4",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.4.tgz",
|
||||
"integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/vue-devtools": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz",
|
||||
"integrity": "sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "9.2.2",
|
||||
"@intlify/shared": "9.2.2"
|
||||
"node": ">= 16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
|
||||
"version": "0.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
|
||||
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.0.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
@ -2428,15 +2261,6 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/set-array": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
|
||||
@ -2448,15 +2272,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
|
||||
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
|
||||
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
|
||||
"version": "0.3.29",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
|
||||
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
@ -2914,22 +2740,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -3117,10 +2945,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
@ -3273,10 +3102,11 @@
|
||||
"integrity": "sha512-LN1gO7+u9xjU5oEScGFKvXhYf7Y/empUIIEAGBs1LzUq/rg5duiDrkuH5A2lQGd5jfMOb9X9usDa2oVXwJ0U/Q=="
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
},
|
||||
@ -3761,10 +3591,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filelist/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@ -3782,10 +3613,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@ -4367,6 +4199,7 @@
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
@ -4585,7 +4418,8 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
@ -4600,15 +4434,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jsesc": "bin/jsesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema": {
|
||||
@ -4770,12 +4605,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.2",
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -4813,15 +4649,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"version": "3.3.11",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
@ -5051,9 +4888,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
@ -5068,9 +4906,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.28",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
|
||||
"integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==",
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -5085,10 +4923,11 @@
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
@ -5211,12 +5050,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
||||
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/regenerator-transform": {
|
||||
"version": "0.15.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
|
||||
@ -5342,10 +5175,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "3.28.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz",
|
||||
"integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==",
|
||||
"version": "3.29.5",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz",
|
||||
"integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
@ -5593,14 +5427,16 @@
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -5878,20 +5714,12 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
@ -6144,10 +5972,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.4.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
|
||||
"integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
|
||||
"version": "4.5.14",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz",
|
||||
"integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
"postcss": "^8.4.27",
|
||||
@ -6259,17 +6088,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue-i18n": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.2.2.tgz",
|
||||
"integrity": "sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==",
|
||||
"version": "9.14.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.4.tgz",
|
||||
"integrity": "sha512-B934C8yUyWLT0EMud3DySrwSUJI7ZNiWYsEEz2gknTthqKiG4dzWE/WSa8AzCuSQzwBEv4HtG1jZDhgzPfWSKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "9.2.2",
|
||||
"@intlify/shared": "9.2.2",
|
||||
"@intlify/vue-devtools": "9.2.2",
|
||||
"@vue/devtools-api": "^6.2.1"
|
||||
"@intlify/core-base": "9.14.4",
|
||||
"@intlify/shared": "9.14.4",
|
||||
"@vue/devtools-api": "^6.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/kazupon"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0"
|
||||
@ -6563,10 +6395,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/workbox-build/node_modules/rollup": {
|
||||
"version": "2.79.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||
"version": "2.79.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
|
||||
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
|
@ -1,17 +0,0 @@
|
||||
# For production, the database credentials should be changed
|
||||
# here and in the docker-compose file for the postgres container,
|
||||
# but they will work for testing or development
|
||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/postgres?schema=public
|
||||
|
||||
# These credentials are required for fetching substitution plan
|
||||
# files from BOLLE. More information on how to get them can be found
|
||||
# in the BOLLE parser (./server/parser/bolle.js)
|
||||
# (If you are using a different file provider than bolle, you can
|
||||
# remove these and add the ones required by your file provider)
|
||||
BOLLE_URL=
|
||||
BOLLE_USER=
|
||||
BOLLE_KEY=
|
||||
|
||||
# This password is required for logging into your timetable v2
|
||||
# instance. You can leave it empty to disable authentication.
|
||||
AUTH_PASSWORD=
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"node": true
|
||||
}
|
||||
}
|
2
server/.gitignore
vendored
2
server/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
.env
|
@ -1,152 +0,0 @@
|
||||
import Prisma from "@prisma/client";
|
||||
const prisma = new Prisma.PrismaClient();
|
||||
|
||||
export function registerAdmin(app) {
|
||||
app.get("/api/admin/timetable", listTimetables);
|
||||
app.post("/api/admin/timetable", createTimetable);
|
||||
app.put("/api/admin/timetable", editTimetable);
|
||||
app.delete("/api/admin/timetable", deleteTimetable);
|
||||
|
||||
app.get("/api/admin/key", listKeys);
|
||||
app.post("/api/admin/key", createKey);
|
||||
app.put("/api/admin/key", editKey);
|
||||
app.delete("/api/admin/key", deleteKey);
|
||||
}
|
||||
|
||||
function sendMissingArguments(res) {
|
||||
res.status(400).send({
|
||||
success: false,
|
||||
error: "missing_arguments",
|
||||
});
|
||||
}
|
||||
|
||||
async function listTimetables(_, res) {
|
||||
res.send(
|
||||
await prisma.timetable.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
class: true,
|
||||
source: true,
|
||||
trusted: true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function createTimetable(req, res) {
|
||||
let data = req.body;
|
||||
if (!data.title || !data.data || !data.class) {
|
||||
sendMissingArguments(res);
|
||||
return;
|
||||
}
|
||||
const timetable = await prisma.timetable.create({
|
||||
data: req.body,
|
||||
});
|
||||
res.status(201).send(timetable);
|
||||
}
|
||||
|
||||
async function editTimetable(req, res) {
|
||||
let id = parseInt(req.query.id);
|
||||
if (!id) {
|
||||
sendMissingArguments(res);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const timetable = await prisma.timetable.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: req.body,
|
||||
});
|
||||
res.status(201).send(timetable);
|
||||
} catch (e) {
|
||||
res.status(500).send(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTimetable(req, res) {
|
||||
if (!req.query.id) {
|
||||
sendMissingArguments(res);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await prisma.timetable.delete({
|
||||
where: {
|
||||
id: parseInt(req.query.id),
|
||||
},
|
||||
});
|
||||
res.status(200).send();
|
||||
} catch (e) {
|
||||
res.status(500).send(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function listKeys(_, res) {
|
||||
res.send(await prisma.key.findMany());
|
||||
}
|
||||
|
||||
async function createKey(req, res) {
|
||||
let data = req.body;
|
||||
if (!data.key) {
|
||||
sendMissingArguments(res);
|
||||
return;
|
||||
}
|
||||
const existingKey = await prisma.key.findUnique({
|
||||
where: {
|
||||
key: data.key,
|
||||
},
|
||||
});
|
||||
if (existingKey) {
|
||||
res.status(400).send({
|
||||
success: false,
|
||||
error: "key_already_exists",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const key = await prisma.key.create({
|
||||
data: {
|
||||
key: data.key,
|
||||
permissions: data.permissions || [],
|
||||
validUntil: data.validUntil,
|
||||
notes: data.notes,
|
||||
},
|
||||
});
|
||||
res.status(201).send(key);
|
||||
}
|
||||
|
||||
async function editKey(req, res) {
|
||||
if (!req.query.id) {
|
||||
sendMissingArguments(res);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const timetable = await prisma.key.update({
|
||||
where: {
|
||||
key: req.query.id,
|
||||
},
|
||||
data: req.body,
|
||||
});
|
||||
res.status(201).send(timetable);
|
||||
} catch (e) {
|
||||
res.status(500).send(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteKey(req, res) {
|
||||
if (!req.query.id) {
|
||||
sendMissingArguments(res);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await prisma.key.delete({
|
||||
where: {
|
||||
key: req.query.id,
|
||||
},
|
||||
});
|
||||
res.status(200).send();
|
||||
} catch (e) {
|
||||
res.status(500).send(e);
|
||||
}
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
import Prisma from "@prisma/client";
|
||||
const prisma = new Prisma.PrismaClient();
|
||||
|
||||
import { log } from "../logs.js";
|
||||
|
||||
async function isLoggedIn(req) {
|
||||
// If AUTH_PASSWORD env variable is not present don't require any login
|
||||
if (!process.env.AUTH_PASSWORD) {
|
||||
return true;
|
||||
}
|
||||
// If no session cookie is set and no token query
|
||||
// parameter is provided the user can't be logged in
|
||||
const token = req.query.token || req.cookies.session;
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
// If there is a session cookie check it
|
||||
const session = await prisma.session.findUnique({
|
||||
where: {
|
||||
token,
|
||||
},
|
||||
});
|
||||
// If no session is found (the session probably
|
||||
// exired) the user is not logged in
|
||||
if (!session) {
|
||||
return false;
|
||||
}
|
||||
// If no checks failed the user is logged in
|
||||
return session;
|
||||
}
|
||||
|
||||
async function renewSession(session) {
|
||||
// Don't try to renew sessions if auth is disabled
|
||||
if (session === true) return;
|
||||
|
||||
await prisma.session.update({
|
||||
where: {
|
||||
token: session.token,
|
||||
},
|
||||
data: {
|
||||
// 14 Days from now on
|
||||
validUntil: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function login(req, res) {
|
||||
// Check if the user is already logged in
|
||||
let session = await isLoggedIn(req);
|
||||
if (session) {
|
||||
renewSession(session);
|
||||
res.redirect("/");
|
||||
return;
|
||||
}
|
||||
// Check password
|
||||
if (!req.body.password || req.body.password != process.env.AUTH_PASSWORD) {
|
||||
res.redirect("/login");
|
||||
return;
|
||||
}
|
||||
// Create a new auth session
|
||||
session = await prisma.session.create({
|
||||
data: {
|
||||
// Expires after 14 days of inactivity
|
||||
validUntil: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14),
|
||||
},
|
||||
});
|
||||
res.cookie("session", session.token, {
|
||||
httpOnly: true,
|
||||
// Expire "never"
|
||||
expires: new Date(253402300000000),
|
||||
});
|
||||
log("API / Auth", `New session: ${session.token}`);
|
||||
res.redirect("/");
|
||||
}
|
||||
|
||||
async function logout(req, res) {
|
||||
const session = await isLoggedIn(req);
|
||||
if (!session) {
|
||||
res.sendStatus(401);
|
||||
return;
|
||||
}
|
||||
await prisma.session.deleteMany({
|
||||
where: {
|
||||
token: session.token,
|
||||
},
|
||||
});
|
||||
log("API / Auth", `Removed session: ${session.token}`);
|
||||
res.redirect("/");
|
||||
}
|
||||
|
||||
async function checkLogin(req, res, next) {
|
||||
// Allow requests to `/api/token`
|
||||
if (req.path == "/token") {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
const session = await isLoggedIn(req);
|
||||
if (!session) {
|
||||
// send 401 Unauthorized so the
|
||||
// app redirects to the login page
|
||||
res.sendStatus(401);
|
||||
return;
|
||||
}
|
||||
req.locals = {
|
||||
session: session.token,
|
||||
};
|
||||
renewSession(session);
|
||||
next();
|
||||
}
|
||||
|
||||
async function token(req, res) {
|
||||
if (!req.query.password || req.query.password != process.env.AUTH_PASSWORD) {
|
||||
res.status(401).send({
|
||||
success: false,
|
||||
error: "wrong_auth",
|
||||
message: "Wrong password",
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Create a new auth session
|
||||
const session = await prisma.session.create({
|
||||
data: {
|
||||
// API token expires after 1 hour
|
||||
validUntil: new Date(Date.now() + 1000 * 60 * 60),
|
||||
},
|
||||
});
|
||||
log("API / Auth", `New token: ${session.token}`);
|
||||
|
||||
res.send({
|
||||
success: true,
|
||||
token: session.token,
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
login,
|
||||
logout,
|
||||
checkLogin,
|
||||
token,
|
||||
};
|
||||
|
||||
// Clean up expired sessions every hour
|
||||
setInterval(
|
||||
async () => {
|
||||
const sessions = await prisma.session.findMany();
|
||||
for (const session of sessions) {
|
||||
if (session.validUntil < new Date()) {
|
||||
log("API / Auth", `Removed expired session: ${session.token}`);
|
||||
await prisma.session.delete({
|
||||
where: {
|
||||
token: session.token,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
1000 * 60 * 60,
|
||||
);
|
@ -1,275 +0,0 @@
|
||||
import Prisma from "@prisma/client";
|
||||
const prisma = new Prisma.PrismaClient();
|
||||
|
||||
import {
|
||||
applyKey,
|
||||
hasPermission,
|
||||
listPermissions,
|
||||
revokeKey,
|
||||
} from "./permission.js";
|
||||
|
||||
// Get info API endpoint (/api/info)
|
||||
// Returns information about the requesting session
|
||||
export async function getInfo(req, res) {
|
||||
// If server has auth disabled
|
||||
if (!req.locals.session) {
|
||||
res.send({
|
||||
authenticated: true,
|
||||
appliedKeys: [],
|
||||
permissions: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await prisma.session.findUnique({
|
||||
where: {
|
||||
token: req.locals.session,
|
||||
},
|
||||
include: {
|
||||
appliedKeys: {
|
||||
select: {
|
||||
key: true,
|
||||
permissions: true,
|
||||
validUntil: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
res.send({
|
||||
authenticated: true,
|
||||
appliedKeys: session.appliedKeys,
|
||||
permissions: await listPermissions(session.token),
|
||||
});
|
||||
}
|
||||
|
||||
// Put and Delete key API endpoints (/api/key)
|
||||
// Applies or revokes a key from the requesting user's session
|
||||
export async function putKey(req, res) {
|
||||
if (await applyKey(req.locals.session, req.query.key)) {
|
||||
res.status(200).send();
|
||||
} else {
|
||||
res.status(400).send({
|
||||
success: false,
|
||||
error: "invalid_key",
|
||||
message: "This key does not exist",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteKey(req, res) {
|
||||
if (await revokeKey(req.locals.session, req.query.key)) {
|
||||
res.status(200).send();
|
||||
} else {
|
||||
res.status(400).send();
|
||||
}
|
||||
}
|
||||
|
||||
// Get timetable API endpoint (/api/timetable)
|
||||
// Returns timetable data for requested class if available
|
||||
export async function getTimetable(req, res) {
|
||||
if (!req.query.class) {
|
||||
res.status(400).send({
|
||||
success: false,
|
||||
error: "missing_parameter",
|
||||
message: "No class parameter provided",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const requestedClass = req.query.class.toLowerCase();
|
||||
const timetables = await prisma.timetable.findMany({
|
||||
where: {
|
||||
class: requestedClass,
|
||||
},
|
||||
orderBy: {
|
||||
updatedAt: "desc",
|
||||
},
|
||||
});
|
||||
const times = await prisma.time.findMany();
|
||||
res.send({
|
||||
timetables,
|
||||
times,
|
||||
});
|
||||
}
|
||||
|
||||
// Edit timetable API endpoint (/api/timetable)
|
||||
// Updates a remote timetable with the requested data
|
||||
export async function putTimetable(req, res) {
|
||||
const timetableId = parseInt(req.query.id);
|
||||
const data = req.body.data;
|
||||
if (
|
||||
!(await hasPermission(req.locals.session, "timetable.update", timetableId))
|
||||
) {
|
||||
res.status(401).send({
|
||||
success: false,
|
||||
error: "missing_permission",
|
||||
message: "You don't have permission to update this timetable!",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await prisma.timetable.update({
|
||||
where: {
|
||||
id: timetableId,
|
||||
},
|
||||
data: {
|
||||
data,
|
||||
title: req.body.title,
|
||||
},
|
||||
});
|
||||
res.status(201).send();
|
||||
}
|
||||
|
||||
// Helper function for converting a date string
|
||||
// (eg. "2022-06-02" or "1654128000000") to a
|
||||
// unix timestamp
|
||||
function convertToDate(dateQuery) {
|
||||
var date;
|
||||
if (dateQuery.match(/^[0-9]+$/) != null) date = parseInt(dateQuery);
|
||||
else date = dateQuery;
|
||||
date = new Date(date).setUTCHours(0, 0, 0, 0);
|
||||
return new Date(date);
|
||||
}
|
||||
|
||||
// Get substitutions API endpoint (/api/substitutions)
|
||||
// Returns all known substitutions for requested date / class
|
||||
// If no class is supplied, all substitutions are returned
|
||||
export async function getSubstitutions(req, res) {
|
||||
const requestedClass = (req.query.class || "").toLowerCase();
|
||||
var from, to, date;
|
||||
// Check if from or to date is set in request
|
||||
if (req.query.from && req.query.to) {
|
||||
from = convertToDate(req.query.from);
|
||||
to = convertToDate(req.query.to);
|
||||
} else if (req.query.date) {
|
||||
date = convertToDate(req.query.date);
|
||||
}
|
||||
|
||||
const prismaOptions = {
|
||||
where: {
|
||||
removed: false,
|
||||
},
|
||||
orderBy: {
|
||||
lesson: "asc",
|
||||
},
|
||||
};
|
||||
if (requestedClass) {
|
||||
prismaOptions.where.class = { has: requestedClass };
|
||||
}
|
||||
// Choose which date to use in database query
|
||||
if (from && to) {
|
||||
prismaOptions.where.date = {
|
||||
gte: from,
|
||||
lte: to,
|
||||
};
|
||||
} else if (date) {
|
||||
prismaOptions.where.date = date;
|
||||
} else {
|
||||
// Default to all substitutions for today and in the future
|
||||
prismaOptions.where.date = {
|
||||
gte: new Date(new Date().setUTCHours(0, 0, 0, 0)),
|
||||
};
|
||||
}
|
||||
|
||||
const rawSubstitutions = await prisma.substitution.findMany(prismaOptions);
|
||||
const substitutions = rawSubstitutions.map((element) => {
|
||||
const substitution = {
|
||||
id: element.id,
|
||||
class: element.class,
|
||||
type: element.type,
|
||||
rawType: element.rawType,
|
||||
lesson: element.lesson,
|
||||
date: new Date(element.date).getTime(),
|
||||
notes: element.notes,
|
||||
teacher: element.teacher,
|
||||
change: {},
|
||||
};
|
||||
if (element.changedRoom) substitution.change.room = element.changedRoom;
|
||||
if (element.changedTeacher)
|
||||
substitution.change.teacher = element.changedTeacher;
|
||||
if (element.changedSubject)
|
||||
substitution.change.subject = element.changedSubject;
|
||||
return substitution;
|
||||
});
|
||||
res.send(substitutions);
|
||||
}
|
||||
|
||||
// Get history API endpoint (/api/history)
|
||||
// Returns history of changes for all substituions in the date range
|
||||
// for the requested class if supplied
|
||||
export async function getHistory(req, res) {
|
||||
const requestedClass = (req.query.class || "").toLowerCase();
|
||||
var from, to, date;
|
||||
// Check if from or to date is set in request
|
||||
if (req.query.from && req.query.to) {
|
||||
from = convertToDate(req.query.from);
|
||||
to = convertToDate(req.query.to);
|
||||
} else if (req.query.date) {
|
||||
date = convertToDate(req.query.date);
|
||||
}
|
||||
|
||||
const prismaOptions = {
|
||||
where: {
|
||||
substitution: {},
|
||||
},
|
||||
include: {
|
||||
substitution: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
};
|
||||
if (requestedClass) {
|
||||
prismaOptions.where.substitution.class = { has: requestedClass };
|
||||
}
|
||||
// Choose which date to use in database query
|
||||
if (from && to) {
|
||||
prismaOptions.where.substitution.date = {
|
||||
gte: from,
|
||||
lte: to,
|
||||
};
|
||||
} else if (date) {
|
||||
prismaOptions.where.substitution.date = date;
|
||||
} else {
|
||||
// Default to history of all substitutions for today and in the future
|
||||
prismaOptions.where.substitution.date = {
|
||||
gte: new Date(new Date().setUTCHours(0, 0, 0, 0)),
|
||||
};
|
||||
}
|
||||
|
||||
const rawChanges = await prisma.substitutionChange.findMany(prismaOptions);
|
||||
const changes = rawChanges.map((element) => {
|
||||
return {
|
||||
id: element.id,
|
||||
type: element.type,
|
||||
class: element.substitution.class,
|
||||
substitutionId: element.substitutionId,
|
||||
lesson: element.substitution.lesson,
|
||||
updatedAt: new Date(element.createdAt).getTime(),
|
||||
date: new Date(element.substitution.date).getTime(),
|
||||
teacher: element.teacher,
|
||||
change: element.changes,
|
||||
notes: element.notes,
|
||||
parseEventId: element.parseEventId,
|
||||
};
|
||||
});
|
||||
res.send(changes);
|
||||
}
|
||||
|
||||
// Get classes API endpoints (/api/classes)
|
||||
// Get all available classes where timetable and
|
||||
// substitutions can be requested for
|
||||
export async function getClasses(_req, res) {
|
||||
const classes = await prisma.class.findMany({
|
||||
select: {
|
||||
name: true,
|
||||
regex: false,
|
||||
},
|
||||
orderBy: {
|
||||
name: "asc",
|
||||
},
|
||||
});
|
||||
// Only return the name of the class
|
||||
const classList = classes.map((element) => element.name);
|
||||
res.send(classList);
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
import Prisma from "@prisma/client";
|
||||
import { log } from "../logs.js";
|
||||
const prisma = new Prisma.PrismaClient();
|
||||
|
||||
export async function listPermissions(sessionToken) {
|
||||
const session = await prisma.session.findUnique({
|
||||
where: {
|
||||
token: sessionToken,
|
||||
},
|
||||
include: {
|
||||
appliedKeys: true,
|
||||
},
|
||||
});
|
||||
if (!session) return [];
|
||||
|
||||
const perms = [];
|
||||
for (const key of session.appliedKeys) {
|
||||
if (key.validUntil && new Date() > key.validUntil) continue;
|
||||
for (const perm of key.permissions) {
|
||||
perms.push(perm);
|
||||
}
|
||||
}
|
||||
return perms;
|
||||
}
|
||||
|
||||
export async function hasPermission(sessionToken, permission, forValue) {
|
||||
let hasPermission = false;
|
||||
for (const perm of await listPermissions(sessionToken)) {
|
||||
if (perm == permission) hasPermission = true;
|
||||
else if (perm == permission + ":" + forValue) hasPermission = true;
|
||||
}
|
||||
return hasPermission;
|
||||
}
|
||||
|
||||
export async function checkAdmin(req, res, next) {
|
||||
if (!(await hasPermission(req.locals.session, "admin"))) {
|
||||
res.status(401).send({
|
||||
success: false,
|
||||
error: "admin_only",
|
||||
message: "You need to be admin to do this!",
|
||||
});
|
||||
return;
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
export async function applyKey(sessionToken, key) {
|
||||
if (!key) return false;
|
||||
const foundKey = await prisma.key.findUnique({
|
||||
where: {
|
||||
key,
|
||||
},
|
||||
});
|
||||
if (!foundKey) return false;
|
||||
|
||||
await prisma.session.update({
|
||||
where: {
|
||||
token: sessionToken,
|
||||
},
|
||||
data: {
|
||||
appliedKeys: {
|
||||
connect: {
|
||||
key: foundKey.key,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function revokeKey(sessionToken, key) {
|
||||
if (!key) return false;
|
||||
|
||||
await prisma.session.update({
|
||||
where: {
|
||||
token: sessionToken,
|
||||
},
|
||||
data: {
|
||||
appliedKeys: {
|
||||
disconnect: {
|
||||
key: key,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clean up expired keys every hour
|
||||
setInterval(
|
||||
async () => {
|
||||
const keys = await prisma.key.findMany();
|
||||
for (const key of keys) {
|
||||
if (key.validUntil && key.validUntil < new Date()) {
|
||||
log(
|
||||
"API / Permissions",
|
||||
`Removed expired key: ${key.key}; Permissions: ${key.permissions.join(
|
||||
", ",
|
||||
)}`,
|
||||
);
|
||||
await prisma.key.delete({
|
||||
where: {
|
||||
key: key.key,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
1000 * 60 * 60,
|
||||
);
|
@ -1,13 +0,0 @@
|
||||
import Prisma from "@prisma/client";
|
||||
|
||||
const prisma = new Prisma.PrismaClient();
|
||||
(async () => {
|
||||
const key = await prisma.key.create({
|
||||
data: {
|
||||
permissions: ["admin"],
|
||||
notes: `Created at ${new Date().toLocaleString()} using the "createAdminKeys.js" script`,
|
||||
},
|
||||
});
|
||||
console.log("Created admin key:");
|
||||
console.log(key.key);
|
||||
})();
|
@ -1,91 +0,0 @@
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import cookieParser from "cookie-parser";
|
||||
|
||||
// Import API endpoints
|
||||
import {
|
||||
getTimetable,
|
||||
getSubstitutions,
|
||||
getHistory,
|
||||
getClasses,
|
||||
putTimetable,
|
||||
getInfo,
|
||||
putKey,
|
||||
deleteKey,
|
||||
} from "./api/index.js";
|
||||
import auth from "./api/auth.js";
|
||||
import { Parser } from "./parser/index.js";
|
||||
import { BolleClient } from "./parser/bolle.js";
|
||||
import { parseSubstitutionPlan } from "./parser/untis.js";
|
||||
import { registerAdmin } from "./api/admin.js";
|
||||
import { checkAdmin } from "./api/permission.js";
|
||||
|
||||
// Check that credentials are supplied
|
||||
if (
|
||||
!process.env.BOLLE_URL ||
|
||||
!process.env.BOLLE_USER ||
|
||||
!process.env.BOLLE_KEY
|
||||
) {
|
||||
console.error("Error: Bolle Auth environment variables missing!");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create the Webserver
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
app.use(cors());
|
||||
app.use(cookieParser());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(express.json());
|
||||
|
||||
// Initialize the Parser and set it to update the
|
||||
// substitution plan at the specified update interval
|
||||
new Parser(
|
||||
new BolleClient(
|
||||
process.env.BOLLE_URL,
|
||||
process.env.BOLLE_USER,
|
||||
process.env.BOLLE_KEY,
|
||||
),
|
||||
parseSubstitutionPlan,
|
||||
process.env.UPDATE_INTERVAL || 1 * 60 * 1000, // Default to 1 minute
|
||||
);
|
||||
|
||||
// Create new Auth class to store sessions
|
||||
app.post("/auth/login", auth.login);
|
||||
app.get("/auth/logout", auth.logout);
|
||||
// Check login for every API request
|
||||
app.use("/api", auth.checkLogin);
|
||||
// Provide check endpoint so the frontend
|
||||
// can check if the user is logged in
|
||||
app.get("/api/check", (_req, res) => {
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
// Register API endpoints
|
||||
app.get("/api/info", getInfo);
|
||||
app.put("/api/key", putKey);
|
||||
app.delete("/api/key", deleteKey);
|
||||
app.get("/api/timetable", getTimetable);
|
||||
app.put("/api/timetable", putTimetable);
|
||||
app.get("/api/substitutions", getSubstitutions);
|
||||
app.get("/api/history", getHistory);
|
||||
app.get("/api/classes", getClasses);
|
||||
app.post("/api/token", auth.token);
|
||||
|
||||
// Register Admin endpoints
|
||||
app.use("/api/admin", checkAdmin);
|
||||
registerAdmin(app);
|
||||
|
||||
// Respond with 400 for non-existent endpoints
|
||||
app.get("/api/*", (_req, res) => {
|
||||
res.sendStatus(400);
|
||||
});
|
||||
|
||||
// Supply frontend files if any other url
|
||||
// is requested to make vue router work
|
||||
app.use("/", express.static("../dist"));
|
||||
app.use("/*", express.static("../dist"));
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server listening on http://localhost:${port}`);
|
||||
});
|
@ -1,24 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"checkJs": false,
|
||||
"resolveJsonModule": true,
|
||||
"moduleResolution": "node",
|
||||
"target": "es2020",
|
||||
"module": "es2015"
|
||||
},
|
||||
"exclude": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
"build",
|
||||
".vscode",
|
||||
"coverage",
|
||||
".npm",
|
||||
".yarn"
|
||||
],
|
||||
"typeAcquisition": {
|
||||
"enable": true,
|
||||
"include": [
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import fs from "fs";
|
||||
|
||||
export function log(type, text) {
|
||||
if (!fs.existsSync("logs")) fs.mkdirSync("logs");
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const logName = now.split("T")[0] + ".log";
|
||||
const timestamp = now.replace("T", " ").split(".")[0];
|
||||
const logLine = `<${timestamp}> [${type}]: ${text}`;
|
||||
fs.appendFileSync("logs/" + logName, logLine + "\n");
|
||||
console.log(logLine);
|
||||
}
|
||||
|
||||
export function getLogPath() {
|
||||
const logName = new Date().toISOString().split("T")[0] + ".log";
|
||||
return "logs/" + logName;
|
||||
}
|
952
server/package-lock.json
generated
952
server/package-lock.json
generated
@ -1,952 +0,0 @@
|
||||
{
|
||||
"name": "timetable-server",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "timetable-server",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.2.0",
|
||||
"axios": "^1.4.0",
|
||||
"cheerio": "^1.0.0-rc.11",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prisma": "^5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.2.0.tgz",
|
||||
"integrity": "sha512-AiTjJwR4J5Rh6Z/9ZKrBBLel3/5DzUNntMohOy7yObVnVoTNVFi2kvpLZlFuKO50d7yDspOtW6XBpiAd0BVXbQ==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/engines-version": "5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.13"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prisma": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"prisma": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.2.0.tgz",
|
||||
"integrity": "sha512-dT7FOLUCdZmq+AunLqB1Iz+ZH/IIS1Fz2THmKZQ6aFONrQD/BQ5ecJ7g2wGS2OgyUFf4OaLam6/bxmgdOBDqig==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f.tgz",
|
||||
"integrity": "sha512-jsnKT5JIDIE01lAeCj2ghY9IwxkedhKNvxQeoyLs6dr4ZXynetD0vTy7u6wMJt8vVPv8I5DPy/I4CFaoXAgbtg=="
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
|
||||
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"on-finished": "2.4.1",
|
||||
"qs": "6.11.0",
|
||||
"raw-body": "2.5.1",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio": {
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
|
||||
"dependencies": {
|
||||
"cheerio-select": "^2.1.0",
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1",
|
||||
"htmlparser2": "^8.0.1",
|
||||
"parse5": "^7.0.0",
|
||||
"parse5-htmlparser2-tree-adapter": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio-select": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
|
||||
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-select": "^5.1.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||
"dependencies": {
|
||||
"cookie": "0.4.1",
|
||||
"cookie-signature": "1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"domutils": "^3.0.1",
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.18.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
||||
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.20.1",
|
||||
"content-disposition": "0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.5.0",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.2.0",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.11.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "0.18.0",
|
||||
"serve-static": "1.15.0",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/cookie": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "2.0.1",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
||||
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
|
||||
"funding": [
|
||||
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1",
|
||||
"entities": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"dependencies": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.12.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||
"integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
|
||||
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
|
||||
"dependencies": {
|
||||
"entities": "^4.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5-htmlparser2-tree-adapter": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
|
||||
"integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
|
||||
"dependencies": {
|
||||
"domhandler": "^5.0.2",
|
||||
"parse5": "^7.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.2.0.tgz",
|
||||
"integrity": "sha512-FfFlpjVCkZwrqxDnP4smlNYSH1so+CbfjgdpioFzGGqlQAEm6VHAYSzV7jJgC3ebtY9dNOhDMS2+4/1DDSM7bQ==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/engines": "5.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.13"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
|
||||
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
|
||||
"dependencies": {
|
||||
"bytes": "3.1.2",
|
||||
"http-errors": "2.0.0",
|
||||
"iconv-lite": "0.4.24",
|
||||
"unpipe": "1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||
"dependencies": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
{
|
||||
"name": "timetable-server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "minie4",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.2.0",
|
||||
"axios": "^1.4.0",
|
||||
"cheerio": "^1.0.0-rc.11",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prisma": "^5.2.0"
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
import axios from "axios";
|
||||
import crypto from "node:crypto";
|
||||
import { log } from "../logs.js";
|
||||
|
||||
/*
|
||||
This BOLLE (https://bolle-software.de/) parser tries to download
|
||||
the latest substitution plan HTML files from the "Pinnwand".
|
||||
|
||||
It uses the same API that the mobile app (v0.3.6) uses.
|
||||
To get your login credentials you need to log into your BOLLE account
|
||||
and register a new device in the settings (/einstellungen/geraete_personal).
|
||||
Click on "Manuelle Logindaten Einblenden" and use "ID" as apiUser
|
||||
and "Token" as apiKey. You need to make at least one request with
|
||||
this API token before closing the registration window or else the
|
||||
token will be invalidated immediately.
|
||||
*/
|
||||
|
||||
// Files to download from the "Pinnwand"
|
||||
const filenames = ["vp_heute", "vp_morgen"];
|
||||
|
||||
export class BolleClient {
|
||||
constructor(bolleInstance, apiUser, apiKey) {
|
||||
this.bolleInstance = bolleInstance;
|
||||
this.apiUser = apiUser;
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
async getFiles() {
|
||||
try {
|
||||
let contents = [];
|
||||
for (let file of filenames) {
|
||||
contents.push(await this.getFile(file));
|
||||
}
|
||||
return contents;
|
||||
} catch (error) {
|
||||
log("Parser / Bolle", "Error getting data: " + error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getFile(filename) {
|
||||
// Generate the BOLLE api payload
|
||||
let payload = {
|
||||
api_payload: JSON.stringify({
|
||||
method: "vertretungsplan_html",
|
||||
payload: { content: filename },
|
||||
}),
|
||||
};
|
||||
// Generate request headers
|
||||
let headers = this.buildRequestHeaders(payload.api_payload);
|
||||
// Send the POST request
|
||||
let response = await axios.post(this.getRequestUrl(), payload, {
|
||||
headers,
|
||||
});
|
||||
// The server responds with a json object
|
||||
// containing the base64 encoded html data
|
||||
let base64 = response.data["html_base64"];
|
||||
// Decode the base64 data using the latin1 (ISO 8859-1) character set
|
||||
return Buffer.from(base64, "base64").toString("latin1");
|
||||
}
|
||||
getRequestUrl() {
|
||||
// The API that the bolle mobile app uses is available at /app/basic
|
||||
return `https://${this.bolleInstance}/app/basic`;
|
||||
}
|
||||
buildRequestHeaders(payload) {
|
||||
// Bolle needs the sha1 hash of the payload
|
||||
// to be set as the "b-hash" header
|
||||
let hash = crypto.createHash("sha1");
|
||||
hash.update(payload);
|
||||
|
||||
return {
|
||||
Accept: "application/json",
|
||||
// Set the auth headers
|
||||
"X-Auth-User": this.apiUser,
|
||||
"X-Auth-Token": this.apiKey,
|
||||
"App-Version": "5",
|
||||
// Set the hash
|
||||
"B-Hash": hash.digest("hex"),
|
||||
"Content-Type": "application/json",
|
||||
Connection: "Keep-Alive",
|
||||
"Accept-Encoding": "gzip",
|
||||
"User-Agent": "okhttp/4.9.2",
|
||||
};
|
||||
}
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
import axios from "axios";
|
||||
import { log } from "../logs.js";
|
||||
|
||||
const baseUrl = "https://mobileapi.dsbcontrol.de";
|
||||
|
||||
export async function getAuthtoken(username, password) {
|
||||
const response = await axios.get(
|
||||
`${baseUrl}/authid?user=${username}&password=${password}&bundleid&appversion&osversion&pushid`,
|
||||
);
|
||||
if (response.data == "") throw "Wrong DSB username or password";
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getTimetables(authtoken) {
|
||||
const response = await axios.get(
|
||||
`${baseUrl}/dsbtimetables?authid=${authtoken}`,
|
||||
);
|
||||
const timetables = response.data;
|
||||
|
||||
const urls = [];
|
||||
timetables.forEach((timetable) => {
|
||||
const rawTimestamp = timetable.Date;
|
||||
// Convert the timestamp to the correct
|
||||
// format so new Date() accepts it
|
||||
const date = rawTimestamp.split(" ")[0].split(".").reverse().join("-");
|
||||
const time = rawTimestamp.split(" ")[1];
|
||||
const timestamp = date + " " + time;
|
||||
urls.push({
|
||||
title: timetable.Title,
|
||||
url: timetable.Childs[0].Detail,
|
||||
updatedAt: new Date(timestamp),
|
||||
});
|
||||
});
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
// List of files that include timetable data
|
||||
const dsbFiles = ["Schüler_Monitor - subst_001", "Schüler Morgen - subst_001"];
|
||||
|
||||
export class DSBClient {
|
||||
constructor(dsbUser, dsbPassword) {
|
||||
this.dsbUser = dsbUser;
|
||||
this.dsbPassword = dsbPassword;
|
||||
}
|
||||
async getFiles() {
|
||||
try {
|
||||
// Get authtoken
|
||||
const token = await getAuthtoken(this.dsbUser, this.dsbPassword);
|
||||
// Fetch available files
|
||||
const response = await getTimetables(token);
|
||||
// Filter files that should be parsed
|
||||
const timetables = response.filter((e) => dsbFiles.includes(e.title));
|
||||
// Fetch the contents
|
||||
const files = [];
|
||||
for (let timetable of timetables) {
|
||||
const result = await axios.request({
|
||||
method: "GET",
|
||||
url: timetable.url,
|
||||
responseEncoding: "binary",
|
||||
});
|
||||
files.push(result.data);
|
||||
}
|
||||
|
||||
return files;
|
||||
} catch (error) {
|
||||
log("Parser / DSB Mobile", "Error getting data: " + error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import fs from "fs";
|
||||
|
||||
/*
|
||||
This file provider allows the application to use a local folder containing
|
||||
parsable (.html) files, instead of downloading them from the internet.
|
||||
|
||||
This can be especially useful for development.
|
||||
*/
|
||||
|
||||
export class FileSystemClient {
|
||||
constructor(path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
getFiles() {
|
||||
const contents = [];
|
||||
const files = fs.readdirSync(this.path);
|
||||
for (const file of files) {
|
||||
const data = fs.readFileSync(this.path + "/" + file).toString();
|
||||
contents.push(data);
|
||||
}
|
||||
return contents;
|
||||
}
|
||||
}
|
@ -1,286 +0,0 @@
|
||||
import Prisma from "@prisma/client";
|
||||
import { log, getLogPath } from "../logs.js";
|
||||
|
||||
const prisma = new Prisma.PrismaClient();
|
||||
|
||||
export class Parser {
|
||||
constructor(fileProvider, documentParser, interval) {
|
||||
this.fileProvider = fileProvider;
|
||||
this.documentParser = documentParser;
|
||||
|
||||
// Schedule plan updates
|
||||
setInterval(() => this.updatePlan(), interval);
|
||||
// Do the first update instantly
|
||||
this.updatePlan();
|
||||
}
|
||||
async updatePlan() {
|
||||
const startedAt = new Date();
|
||||
try {
|
||||
// Request substitution plan files using the fileProvider
|
||||
const files = await this.fileProvider.getFiles();
|
||||
const plans = [];
|
||||
// Parse them using the provided parser
|
||||
for (const file of files) {
|
||||
// Parse the substitution plan
|
||||
const parsed = this.documentParser(file);
|
||||
plans.push(parsed);
|
||||
}
|
||||
// Create a new parse event
|
||||
const parseEvent = await prisma.parseEvent.create({
|
||||
data: {
|
||||
logFile: getLogPath(),
|
||||
originalData: "",
|
||||
duration: new Date() - startedAt,
|
||||
succeeded: true,
|
||||
},
|
||||
});
|
||||
// Group plans by date to prevent having
|
||||
// multiple plan files with the same date
|
||||
const dayPlans = [];
|
||||
for (const plan of plans) {
|
||||
const foundPlan = dayPlans.find((e) => e.date == plan.date);
|
||||
if (!foundPlan) {
|
||||
// Make sure to not insert duplicate substitutions within a file
|
||||
const changes = structuredClone(plan.changes);
|
||||
const cleanedChanges = [];
|
||||
for (const change of changes) {
|
||||
const changeExists = cleanedChanges.find(
|
||||
(e) => JSON.stringify(e) == JSON.stringify(change),
|
||||
);
|
||||
if (!changeExists) {
|
||||
cleanedChanges.push(change);
|
||||
}
|
||||
// Use the new array of changes
|
||||
plan.changes = cleanedChanges;
|
||||
}
|
||||
dayPlans.push(plan);
|
||||
} else {
|
||||
for (const change of plan.changes) {
|
||||
// Make sure to not insert a substitution that already exists in the changes
|
||||
const changeExists = foundPlan.changes.find(
|
||||
(e) => JSON.stringify(e) == JSON.stringify(change),
|
||||
);
|
||||
if (!changeExists) {
|
||||
foundPlan.changes.push(change);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Insert substitutions of all substitution plans
|
||||
for (const plan of dayPlans) {
|
||||
await this.insertSubstitutions(plan, parseEvent);
|
||||
}
|
||||
} catch (error) {
|
||||
// If something went wrong, create a failed
|
||||
// parse event with the error message
|
||||
await prisma.parseEvent.create({
|
||||
data: {
|
||||
logFile: getLogPath(),
|
||||
originalData: error.toString(),
|
||||
duration: new Date() - startedAt,
|
||||
succeeded: false,
|
||||
},
|
||||
});
|
||||
// Log the error
|
||||
log("Parser / Main", "Parse event failed: " + error);
|
||||
}
|
||||
}
|
||||
async insertSubstitutions(parsedData, parseEvent) {
|
||||
const { date, changes } = parsedData;
|
||||
const classList = await prisma.class.findMany();
|
||||
|
||||
const knownSubstitutions = await prisma.substitution.findMany({
|
||||
where: {
|
||||
date: new Date(new Date(date).setUTCHours(0, 0, 0, 0)),
|
||||
removed: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Loop through every change of the substitution plan
|
||||
for (const change of changes) {
|
||||
// Find all classes the substitution belongs to
|
||||
const classes = this.getSubstitutionClasses(classList, change.class);
|
||||
// If the substitution does not belong to any classes known
|
||||
// by the server, use the provied class string instead
|
||||
if (classes.length == 0) classes.push(change.class || "unknown");
|
||||
|
||||
// Workaround: no correct match possible for subsitutions of this
|
||||
// type beacuse they do not have a class and a subject attribute
|
||||
if (change.type == "Sondereins." && !change.subject) {
|
||||
change.subject = change.notes;
|
||||
}
|
||||
if (change.type == "Sondereins." && !change.teacher) {
|
||||
change.teacher = change.changedTeacher;
|
||||
change.changedTeacher = "";
|
||||
}
|
||||
|
||||
// Check if a substitution exists in the database that
|
||||
// it similar enough to the entry in the substitution
|
||||
// plan to be considered the same substitution
|
||||
// (Date, Type, Lesson, Classes and Subject need to be the same)
|
||||
const matchingSubstitutionId = knownSubstitutions.findIndex(
|
||||
(substitution) => {
|
||||
return (
|
||||
substitution.date.getTime() ==
|
||||
new Date(date).setUTCHours(0, 0, 0, 0) &&
|
||||
substitution.rawType == change.type &&
|
||||
substitution.lesson == change.lesson &&
|
||||
classes.sort().join(",") == substitution.class.sort().join(",") &&
|
||||
substitution.changedSubject == change.subject &&
|
||||
substitution.teacher == (change.teacher || "")
|
||||
);
|
||||
},
|
||||
);
|
||||
const matchingSubstitution = knownSubstitutions[matchingSubstitutionId];
|
||||
|
||||
if (!matchingSubstitution) {
|
||||
// If the substitution is new, create it in the database
|
||||
const newSubstitution = await prisma.substitution.create({
|
||||
data: {
|
||||
class: classes,
|
||||
date: new Date(date),
|
||||
type:
|
||||
change.type == "Entfall" ||
|
||||
change.type == "eigenverantwortliches Arbeiten"
|
||||
? "cancellation"
|
||||
: "change",
|
||||
rawType: change.type,
|
||||
lesson: parseInt(change.lesson),
|
||||
teacher: change.teacher || "",
|
||||
changedTeacher: change.changedTeacher,
|
||||
changedRoom: change.room || undefined,
|
||||
changedSubject: change.subject,
|
||||
notes: change.notes,
|
||||
removed: false,
|
||||
},
|
||||
});
|
||||
// Also create a change entry for it
|
||||
const substitutionChange = await prisma.substitutionChange.create({
|
||||
data: {
|
||||
substitutionId: newSubstitution.id,
|
||||
type: "addition",
|
||||
changes: {
|
||||
class: classes,
|
||||
type: change.type == "Entfall" ? "cancellation" : "change",
|
||||
rawType: change.type,
|
||||
lesson: parseInt(change.lesson),
|
||||
date: new Date(date),
|
||||
notes: change.notes,
|
||||
teacher: change.teacher || "",
|
||||
change: {
|
||||
teacher: change.changedTeacher,
|
||||
room: change.room || undefined,
|
||||
subject: change.subject,
|
||||
},
|
||||
},
|
||||
parseEventId: parseEvent.id,
|
||||
},
|
||||
});
|
||||
log(
|
||||
"Insert / DB",
|
||||
`Created new substitution: S:${newSubstitution.id} C:${substitutionChange.id}`,
|
||||
);
|
||||
} else {
|
||||
// If the entry was updated, find the differences
|
||||
const differences = this.findDifferences(matchingSubstitution, change);
|
||||
if (Object.keys(differences).length > 0) {
|
||||
// If differences were found, update the entry in the database
|
||||
const prismaOptions = {
|
||||
where: {
|
||||
id: matchingSubstitution.id,
|
||||
},
|
||||
data: {},
|
||||
};
|
||||
if (differences.teacher)
|
||||
prismaOptions.data.changedTeacher = change.changedTeacher;
|
||||
if (differences.room) prismaOptions.data.changedRoom = change.room;
|
||||
if (differences.notes) prismaOptions.data.notes = change.notes;
|
||||
|
||||
await prisma.substitution.update(prismaOptions);
|
||||
// And create a change event for it
|
||||
const substitutionChange = await prisma.substitutionChange.create({
|
||||
data: {
|
||||
substitutionId: matchingSubstitution.id,
|
||||
type: "change",
|
||||
changes: differences,
|
||||
parseEventId: parseEvent.id,
|
||||
},
|
||||
});
|
||||
log(
|
||||
"Insert / DB",
|
||||
`Found changed substitution: S:${matchingSubstitution.id} C:${substitutionChange.id}`,
|
||||
);
|
||||
}
|
||||
// Remove the substitution from the array to later know the
|
||||
// entries that are not present in the substitution plan
|
||||
knownSubstitutions.splice(matchingSubstitutionId, 1);
|
||||
}
|
||||
}
|
||||
// Mark all entries as removed that were
|
||||
// not found in the substitution plan
|
||||
for (const remainingSubstitution of knownSubstitutions) {
|
||||
await prisma.substitution.update({
|
||||
where: {
|
||||
id: remainingSubstitution.id,
|
||||
},
|
||||
data: {
|
||||
removed: true,
|
||||
},
|
||||
});
|
||||
const substitutionChange = await prisma.substitutionChange.create({
|
||||
data: {
|
||||
substitutionId: remainingSubstitution.id,
|
||||
type: "deletion",
|
||||
changes: {
|
||||
class: remainingSubstitution.class,
|
||||
type: remainingSubstitution.type,
|
||||
rawType: remainingSubstitution.rawType,
|
||||
lesson: remainingSubstitution.lesson,
|
||||
date: remainingSubstitution.date.getTime(),
|
||||
notes: remainingSubstitution.notes,
|
||||
teacher: remainingSubstitution.teacher,
|
||||
change: {
|
||||
teacher: remainingSubstitution.changedTeacher,
|
||||
room: remainingSubstitution.changedRoom,
|
||||
subject: remainingSubstitution.changedSubject,
|
||||
},
|
||||
},
|
||||
parseEventId: parseEvent.id,
|
||||
},
|
||||
});
|
||||
log(
|
||||
"Insert / DB",
|
||||
`Deleted removed substitution: S:${remainingSubstitution.id} C:${substitutionChange.id}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
getSubstitutionClasses(classList, classString) {
|
||||
const matchingClasses = classList.filter((element) => {
|
||||
const regex = new RegExp(element.regex);
|
||||
return (classString || "").toLowerCase().match(regex);
|
||||
});
|
||||
return matchingClasses.map((e) => e.name);
|
||||
}
|
||||
findDifferences(currentSubstitution, newChange) {
|
||||
const differences = {};
|
||||
if (newChange.changedTeacher != currentSubstitution.changedTeacher) {
|
||||
differences.teacher = {
|
||||
before: currentSubstitution.changedTeacher,
|
||||
after: newChange.changedTeacher,
|
||||
};
|
||||
}
|
||||
if (newChange.room != currentSubstitution.changedRoom) {
|
||||
differences.room = {
|
||||
before: currentSubstitution.changedRoom,
|
||||
after: newChange.room,
|
||||
};
|
||||
}
|
||||
if (newChange.notes != currentSubstitution.notes) {
|
||||
differences.notes = {
|
||||
before: currentSubstitution.notes,
|
||||
after: newChange.notes,
|
||||
};
|
||||
}
|
||||
return differences;
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
import * as cheerio from "cheerio";
|
||||
const titleTranslations = {
|
||||
"Klasse(n)": "class",
|
||||
Datum: "date",
|
||||
Stunde: "lesson",
|
||||
"(Lehrer)": "teacher",
|
||||
Vertreter: "changedTeacher",
|
||||
Fach: "subject",
|
||||
Raum: "room",
|
||||
Art: "type",
|
||||
Text: "notes",
|
||||
};
|
||||
|
||||
export function parseSubstitutionPlan(html) {
|
||||
const infos = {};
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const data = [];
|
||||
const tables = $("table.mon_list");
|
||||
tables.each((tableIndex, tableElement) => {
|
||||
// Extract the date, weekday and a/b week from the title
|
||||
const title = $(tableElement)
|
||||
.parent()
|
||||
.siblings(".mon_title")
|
||||
.text()
|
||||
.split(" ");
|
||||
const rawDate = title[0];
|
||||
const date = rawDate
|
||||
.split(".")
|
||||
.reverse()
|
||||
.map((e) => e.padStart(2, 0))
|
||||
.join("-");
|
||||
|
||||
if (tableIndex == 0) {
|
||||
infos.date = new Date(date).setUTCHours(0, 0, 0, 0);
|
||||
infos.week = title[3];
|
||||
|
||||
// Get the export timestamp
|
||||
const rawTimestamp = $(".mon_head")
|
||||
.first()
|
||||
.find("td")
|
||||
.text()
|
||||
.split("Stand: ")[1]
|
||||
.replace(/[\s\n]*$/g, "");
|
||||
const exportDate = rawTimestamp
|
||||
.split(" ")[0]
|
||||
.split(".")
|
||||
.reverse()
|
||||
.join("-");
|
||||
const timestamp = exportDate + " " + rawTimestamp.split(" ")[1];
|
||||
infos.updatedAt = new Date(timestamp).getTime();
|
||||
} else {
|
||||
// If there are multiple days in one file,
|
||||
// ignore all except the first one
|
||||
if (new Date(date).setUTCHours(0, 0, 0, 0) != infos.date) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const titles = [];
|
||||
const titleElements = $(tableElement).find("tr.list th");
|
||||
titleElements.each((index, titleElement) => {
|
||||
const title = $(titleElement).text();
|
||||
titles[index] = titleTranslations[title];
|
||||
});
|
||||
|
||||
const subsitutionTable = $(tableElement).find("tr.list");
|
||||
// Loop through each table row
|
||||
subsitutionTable.each((_rowcnt, row) => {
|
||||
const rowData = {};
|
||||
|
||||
// Find the columns and ignore empty ones
|
||||
const columns = $(row).find("td");
|
||||
if (columns.text() == "") return;
|
||||
// Ignore columns that include "Keine Vertretungen"
|
||||
// to have an empty array if there are no substitutions
|
||||
if (columns.text().includes("Keine Vertretungen")) return;
|
||||
|
||||
columns.each((columncnt, column) => {
|
||||
const text = $(column).text();
|
||||
// Clean the text by removing new lines, tabs, ...
|
||||
var cleantext = text.replace(/^\n\s*/g, "").replace(/\s*$/, "");
|
||||
if (cleantext == "" || cleantext == "---") cleantext = null;
|
||||
|
||||
const columntitle = titles[columncnt];
|
||||
rowData[columntitle] = cleantext;
|
||||
});
|
||||
|
||||
// Split change if it spans over multiple lessons
|
||||
const rawLesson = rowData.lesson || "0";
|
||||
const fromToLessons = rawLesson.match(/\d+/g).map(Number);
|
||||
const from = fromToLessons[0];
|
||||
const to = fromToLessons[1] || fromToLessons[0];
|
||||
|
||||
// Generate numbers from `from` to `to`
|
||||
const lessons = Array(to - from + 1)
|
||||
.fill()
|
||||
.map((_e, i) => i + from);
|
||||
|
||||
// Create new change for each lesson the change spans over
|
||||
for (const lesson of lessons) {
|
||||
rowData.lesson = lesson;
|
||||
data.push({ ...rowData });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
infos.changes = data;
|
||||
return infos;
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Timetable {
|
||||
id Int @id @unique @default(autoincrement())
|
||||
title String @default("Default")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
class String
|
||||
validFrom DateTime @default(now())
|
||||
validUntil DateTime?
|
||||
data Json
|
||||
source String?
|
||||
trusted Boolean @default(true)
|
||||
}
|
||||
|
||||
model Substitution {
|
||||
id Int @id @unique @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
class String[]
|
||||
date DateTime
|
||||
type String
|
||||
rawType String @default("unknown")
|
||||
lesson Int
|
||||
teacher String
|
||||
changedTeacher String?
|
||||
changedRoom String?
|
||||
changedSubject String?
|
||||
notes String?
|
||||
removed Boolean @default(false)
|
||||
|
||||
SubstitutionChange SubstitutionChange[]
|
||||
}
|
||||
|
||||
model SubstitutionChange {
|
||||
id Int @id @unique @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
substitution Substitution @relation(fields: [substitutionId], references: [id])
|
||||
substitutionId Int
|
||||
type String
|
||||
teacher String?
|
||||
changes Json?
|
||||
parseEvent ParseEvent @relation(fields: [parseEventId], references: [id])
|
||||
parseEventId Int
|
||||
}
|
||||
|
||||
model ParseEvent {
|
||||
id Int @id @unique @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
logFile String
|
||||
originalData String
|
||||
duration Int
|
||||
succeeded Boolean
|
||||
|
||||
SubstitutionChange SubstitutionChange[]
|
||||
}
|
||||
|
||||
model Class {
|
||||
name String @id @unique
|
||||
regex String
|
||||
}
|
||||
|
||||
model Time {
|
||||
lesson Int @unique
|
||||
start DateTime
|
||||
end DateTime
|
||||
}
|
||||
|
||||
model Session {
|
||||
token String @id @unique @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
validUntil DateTime
|
||||
appliedKeys Key[]
|
||||
}
|
||||
|
||||
model Key {
|
||||
key String @id @unique @default(uuid())
|
||||
createdAt DateTime? @default(now())
|
||||
validUntil DateTime?
|
||||
permissions String[]
|
||||
notes String?
|
||||
sessions Session[]
|
||||
}
|
21
src/App.vue
21
src/App.vue
@ -13,6 +13,7 @@ import {
|
||||
selectedDay,
|
||||
changeDay,
|
||||
changeDate,
|
||||
activeProfile,
|
||||
} from "@/store";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
@ -50,6 +51,19 @@ const isDataView = computed(() => route.meta.dataView || false);
|
||||
v-show="isDataView"
|
||||
/>
|
||||
<main>
|
||||
<div>
|
||||
<div
|
||||
v-if="
|
||||
activeProfile.classFilter == 'Demo' ||
|
||||
$route.fullPath.startsWith('/settings')
|
||||
"
|
||||
class="demoNotice"
|
||||
>
|
||||
<span v-if="activeProfile.classFilter == 'Demo'">
|
||||
{{ $t("demoNotice") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<RouterView />
|
||||
</div>
|
||||
@ -95,7 +109,7 @@ main {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -110,4 +124,9 @@ main {
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.demoNotice {
|
||||
padding: 15px 15px;
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
334
src/demoData.js
Normal file
334
src/demoData.js
Normal file
@ -0,0 +1,334 @@
|
||||
export const DEMO_SESSION_INFO = {
|
||||
authenticated: true,
|
||||
appliedKeys: [],
|
||||
permissions: [],
|
||||
};
|
||||
|
||||
export const DEMO_CLASS_LIST = ["Demo", "Empty"];
|
||||
|
||||
export const DEMO_TIMETABLE = {
|
||||
timetables: [
|
||||
{
|
||||
id: 1,
|
||||
title: "Demo",
|
||||
createdAt: "2023-08-28T08:00:53.233Z",
|
||||
updatedAt: "2025-01-07T11:37:42.590Z",
|
||||
class: "Demo",
|
||||
validFrom: "2023-08-28T08:00:53.233Z",
|
||||
validUntil: null,
|
||||
data: [
|
||||
[
|
||||
[{}],
|
||||
[{ room: "R 206", length: 1, subject: "MA1", teacher: "Wen" }],
|
||||
[{ room: "R 307", length: 2, subject: "en1", teacher: "Fre" }],
|
||||
[{ room: "R 005", length: 2, subject: "de2", teacher: "Str" }],
|
||||
],
|
||||
[
|
||||
[{ room: "R 107", length: 2, subject: "IN2", teacher: "Kom" }],
|
||||
[{ room: "R 206", length: 2, subject: "MA1", teacher: "Wen" }],
|
||||
[{ room: "R 403", subject: "geo2", teacher: "Spl" }],
|
||||
[{ room: "", length: 1, subject: "", teacher: "" }],
|
||||
[{ room: "R 007", subject: "Sp-Th", teacher: "Bun" }],
|
||||
[{ room: "R 313", subject: "pw1", teacher: "Göl" }],
|
||||
],
|
||||
[
|
||||
[{ room: "R 313", length: 2, subject: "pw1", teacher: "Göl" }],
|
||||
[{ room: "R 314", length: 2, subject: "ge1", teacher: "Mog" }],
|
||||
[{ room: "R 307", subject: "en1", teacher: "Fre" }],
|
||||
[{ room: "R 403", length: 2, subject: "geo2", teacher: "Spl" }],
|
||||
[{}],
|
||||
[{ room: "R 002", length: 2, subject: "Sp-Fit", teacher: "Gan" }],
|
||||
],
|
||||
[
|
||||
[{ room: "R 206", length: 2, subject: "MA1", teacher: "Wen" }],
|
||||
[{ room: "R 107", length: 2, subject: "IN2", teacher: "Kom" }],
|
||||
[{ room: "R 209", subject: "ph1", teacher: "And" }],
|
||||
[{ room: "R 005", length: 2, subject: "Sp-Th", teacher: "Bun" }],
|
||||
],
|
||||
[
|
||||
[{ room: "R 107", length: 2, subject: "IN2", teacher: "Kom" }],
|
||||
[{ room: "R 314", length: 1, subject: "ge1", teacher: "Mog" }],
|
||||
[{ room: "R 005", subject: "de2", teacher: "Str" }],
|
||||
[{ room: "R 209", length: 2, subject: "ph1", teacher: "And" }],
|
||||
],
|
||||
],
|
||||
source: "Demo Provider",
|
||||
trusted: true,
|
||||
},
|
||||
],
|
||||
times: [
|
||||
{
|
||||
lesson: 1,
|
||||
start: "1970-01-01T07:00:00.000Z",
|
||||
end: "1970-01-01T07:45:00.000Z",
|
||||
},
|
||||
{
|
||||
lesson: 3,
|
||||
start: "1970-01-01T08:50:00.000Z",
|
||||
end: "1970-01-01T09:35:00.000Z",
|
||||
},
|
||||
{
|
||||
lesson: 5,
|
||||
start: "1970-01-01T11:10:00.000Z",
|
||||
end: "1970-01-01T11:55:00.000Z",
|
||||
},
|
||||
{
|
||||
lesson: 6,
|
||||
start: "1970-01-01T12:00:00.000Z",
|
||||
end: "1970-01-01T12:45:00.000Z",
|
||||
},
|
||||
{
|
||||
lesson: 7,
|
||||
start: "1970-01-01T12:50:00.000Z",
|
||||
end: "1970-01-01T13:35:00.000Z",
|
||||
},
|
||||
{
|
||||
lesson: 8,
|
||||
start: "1970-01-01T13:40:00.000Z",
|
||||
end: "1970-01-01T14:25:00.000Z",
|
||||
},
|
||||
{
|
||||
lesson: 9,
|
||||
start: "1970-01-01T14:30:00.000Z",
|
||||
end: "1970-01-01T15:15:00.000Z",
|
||||
},
|
||||
{
|
||||
lesson: 10,
|
||||
start: "1970-01-01T15:20:00.000Z",
|
||||
end: "1970-01-01T16:05:00.000Z",
|
||||
},
|
||||
{
|
||||
lesson: 11,
|
||||
start: "1970-01-01T16:10:00.000Z",
|
||||
end: "1970-01-01T16:55:00.000Z",
|
||||
},
|
||||
{
|
||||
lesson: 2,
|
||||
start: "1970-01-01T07:45:00.000Z",
|
||||
end: "1970-01-01T08:30:00.000Z",
|
||||
},
|
||||
{
|
||||
lesson: 4,
|
||||
start: "1970-01-01T09:40:00.000Z",
|
||||
end: "1970-01-01T10:25:00.000Z",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export function getDemoSubstitutions(date) {
|
||||
let weekday = new Date(date).getDay();
|
||||
|
||||
switch (weekday) {
|
||||
case 1: {
|
||||
return [
|
||||
{
|
||||
id: 0,
|
||||
class: ["Demo"],
|
||||
type: "cancellation",
|
||||
rawType: "Entfall",
|
||||
lesson: 5,
|
||||
date: date,
|
||||
notes: null,
|
||||
teacher: "Str",
|
||||
change: {},
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
class: ["Demo"],
|
||||
type: "cancellation",
|
||||
rawType: "Entfall",
|
||||
lesson: 6,
|
||||
date: date,
|
||||
notes: null,
|
||||
teacher: "Str",
|
||||
change: {},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
class: ["Demo"],
|
||||
type: "change",
|
||||
rawType: "Raum-Vtr.",
|
||||
lesson: 3,
|
||||
date: date,
|
||||
notes: null,
|
||||
teacher: "Fre",
|
||||
change: {
|
||||
room: "308",
|
||||
teacher: "Fre",
|
||||
subject: "en1",
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
case 2: {
|
||||
return [
|
||||
{
|
||||
id: 3,
|
||||
class: ["Demo"],
|
||||
type: "cancellation",
|
||||
rawType: "Entfall",
|
||||
lesson: 7,
|
||||
date: date,
|
||||
notes: null,
|
||||
teacher: "Bun",
|
||||
change: {},
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
class: ["Demo"],
|
||||
type: "change",
|
||||
rawType: "Vertretung",
|
||||
lesson: 8,
|
||||
date: date,
|
||||
notes: null,
|
||||
teacher: "Bun",
|
||||
change: {
|
||||
room: "007",
|
||||
teacher: "Aci",
|
||||
subject: "Sp-Th",
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
case 3: {
|
||||
return [
|
||||
{
|
||||
id: 5,
|
||||
class: ["Demo"],
|
||||
type: "cancellation",
|
||||
rawType: "Entfall",
|
||||
lesson: 9,
|
||||
date: date,
|
||||
notes: "Aufgaben im Sekretariat",
|
||||
teacher: "Gan",
|
||||
change: {},
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
class: ["Demo"],
|
||||
type: "cancellation",
|
||||
rawType: "Entfall",
|
||||
lesson: 10,
|
||||
date: date,
|
||||
notes: null,
|
||||
teacher: "Gan",
|
||||
change: {},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
export function getDemoHistory(date) {
|
||||
let weekday = new Date(date).getDay();
|
||||
|
||||
switch (weekday) {
|
||||
case 1: {
|
||||
return [
|
||||
...getDemoSubstitutions(date).map((e) => substitutionToChange(e)),
|
||||
];
|
||||
}
|
||||
case 2: {
|
||||
return [
|
||||
...getDemoSubstitutions(date).map((e) => substitutionToChange(e)),
|
||||
{
|
||||
id: 101,
|
||||
type: "deletion",
|
||||
class: ["Demo"],
|
||||
substitutionId: 100,
|
||||
lesson: 5,
|
||||
updatedAt: date - 47800000,
|
||||
date: date,
|
||||
teacher: null,
|
||||
change: {
|
||||
date: date,
|
||||
type: "cancellation",
|
||||
class: ["Demo"],
|
||||
notes: null,
|
||||
change: {},
|
||||
lesson: 5,
|
||||
rawType: "Entfall",
|
||||
teacher: "Spl",
|
||||
},
|
||||
parseEventId: 0,
|
||||
},
|
||||
{
|
||||
id: 100,
|
||||
type: "addition",
|
||||
class: ["Demo"],
|
||||
substitutionId: 100,
|
||||
lesson: 5,
|
||||
updatedAt: date - 57800000,
|
||||
date: date,
|
||||
teacher: null,
|
||||
change: {
|
||||
date: date,
|
||||
type: "cancellation",
|
||||
class: ["Demo"],
|
||||
notes: null,
|
||||
change: {},
|
||||
lesson: 5,
|
||||
rawType: "Entfall",
|
||||
teacher: "Spl",
|
||||
},
|
||||
parseEventId: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
case 3: {
|
||||
return [
|
||||
{
|
||||
id: 102,
|
||||
type: "change",
|
||||
class: ["Demo"],
|
||||
substitutionId: 5,
|
||||
lesson: 9,
|
||||
updatedAt: date - 57800000,
|
||||
date: date,
|
||||
teacher: null,
|
||||
change: {
|
||||
notes: {
|
||||
before: null,
|
||||
after: "Aufgaben im Sekretariat",
|
||||
},
|
||||
},
|
||||
parseEventId: 0,
|
||||
},
|
||||
...getDemoSubstitutions(date)
|
||||
.map((e) => substitutionToChange(e))
|
||||
.map((e) => {
|
||||
e.change.notes = null;
|
||||
return e;
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function substitutionToChange(substitution) {
|
||||
return {
|
||||
id: substitution.id,
|
||||
type: "addition",
|
||||
class: substitution.class,
|
||||
substitutionId: substitution.id,
|
||||
lesson: substitution.lesson,
|
||||
updatedAt: substitution.date - 46800000,
|
||||
date: substitution.date,
|
||||
teacher: null,
|
||||
change: {
|
||||
date: substitution.date,
|
||||
type: substitution.type,
|
||||
class: substitution.class,
|
||||
notes: substitution.notes,
|
||||
change: substitution.change,
|
||||
lesson: substitution.lesson,
|
||||
rawType: substitution.rawType,
|
||||
teacher: substitution.teacher,
|
||||
},
|
||||
parseEventId: 0,
|
||||
};
|
||||
}
|
71
src/store.js
71
src/store.js
@ -1,6 +1,13 @@
|
||||
import { ref, watch, computed } from "vue";
|
||||
import { getNextAndPrevDay, setUTCMidnight } from "@/util";
|
||||
import i18n from "@/i18n";
|
||||
import {
|
||||
DEMO_CLASS_LIST,
|
||||
DEMO_SESSION_INFO,
|
||||
DEMO_TIMETABLE,
|
||||
getDemoHistory,
|
||||
getDemoSubstitutions,
|
||||
} from "./demoData";
|
||||
|
||||
/* Router */
|
||||
export const shouldLogin = ref(false);
|
||||
@ -15,7 +22,7 @@ export const profiles = ref(
|
||||
id: 0,
|
||||
name: "Default Profile",
|
||||
classFilter: "none",
|
||||
timetableId: "none",
|
||||
timetableId: 1,
|
||||
timetableGroups: [],
|
||||
},
|
||||
],
|
||||
@ -162,16 +169,7 @@ watch(selectedDate, () =>
|
||||
|
||||
export async function fetchSessionInfo() {
|
||||
try {
|
||||
const checkResponse = await fetch(`${baseUrl}/info`);
|
||||
if (checkResponse.status == 401) {
|
||||
shouldLogin.value = true;
|
||||
return false;
|
||||
} else if (checkResponse.status != 200) {
|
||||
console.log("Other error while fetching data: " + checkResponse.status);
|
||||
return false;
|
||||
} else {
|
||||
sessionInfo.value = await checkResponse.json();
|
||||
}
|
||||
sessionInfo.value = DEMO_SESSION_INFO;
|
||||
} catch {
|
||||
console.log("Error while fetching data: No internet connection!");
|
||||
return false;
|
||||
@ -180,53 +178,32 @@ export async function fetchSessionInfo() {
|
||||
}
|
||||
|
||||
export async function fetchClassList() {
|
||||
const classListResponse = await fetch(`${baseUrl}/classes`);
|
||||
const classListData = await classListResponse.json();
|
||||
classList.value = classListData;
|
||||
classList.value = DEMO_CLASS_LIST;
|
||||
}
|
||||
|
||||
export async function fetchTimetables() {
|
||||
const timetableResponse = await fetch(
|
||||
`${baseUrl}/timetable?class=${activeProfile.value.classFilter}`,
|
||||
);
|
||||
const timetableData = await timetableResponse.json();
|
||||
if (timetableData.error) {
|
||||
console.warn("API Error: " + timetableData.error);
|
||||
timetables.value = [];
|
||||
if (activeProfile.value.classFilter == "Demo") {
|
||||
timetables.value = DEMO_TIMETABLE.timetables;
|
||||
} else {
|
||||
timetables.value = timetableData.timetables;
|
||||
times.value = timetableData.times;
|
||||
|
||||
cachedTimetables.value[activeProfileId.value] =
|
||||
structuredClone(timetableData);
|
||||
for (const timetable of cachedTimetables.value[activeProfileId.value]
|
||||
.timetables) {
|
||||
timetable.fromCache = true;
|
||||
}
|
||||
timetables.value = [];
|
||||
}
|
||||
times.value = DEMO_TIMETABLE.times;
|
||||
}
|
||||
|
||||
export async function fetchSubstitutions(day) {
|
||||
const requestDate = `?date=${day}`;
|
||||
const substitutionResponse = await fetch(
|
||||
activeProfile.value.classFilter == "none"
|
||||
? `${baseUrl}/substitutions${requestDate}`
|
||||
: `${baseUrl}/substitutions${requestDate}&class=${activeProfile.value.classFilter}`,
|
||||
);
|
||||
const substitutionData = await substitutionResponse.json();
|
||||
substitutions.value[day] = substitutionData;
|
||||
if (activeProfile.value.classFilter == "Demo") {
|
||||
substitutions.value[day] = getDemoSubstitutions(day);
|
||||
} else {
|
||||
substitutions.value[day] = [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchHistory(day) {
|
||||
const requestDate = `?date=${day}`;
|
||||
const historyResponse = await fetch(
|
||||
activeProfile.value.classFilter == "none"
|
||||
? `${baseUrl}/history${requestDate}`
|
||||
: `${baseUrl}/history${requestDate}&class=${activeProfile.value.classFilter}`,
|
||||
);
|
||||
const historyData = await historyResponse.json();
|
||||
if (historyData.error) console.warn("API Error: " + historyData.error);
|
||||
else history.value[day] = historyData;
|
||||
if (activeProfile.value.classFilter == "Demo") {
|
||||
history.value[day] = getDemoHistory(day);
|
||||
} else {
|
||||
history.value[day] = [];
|
||||
}
|
||||
}
|
||||
|
||||
/* Preprocess the timetable data */
|
||||
|
@ -1,5 +1,7 @@
|
||||
export const strings = {
|
||||
en: {
|
||||
demoNotice:
|
||||
"This is a demo instance of Timetable V2. The displayed data is entirely fictional.",
|
||||
title: {
|
||||
timetable: "Timetable",
|
||||
substitutions: "Substitutions",
|
||||
@ -133,6 +135,8 @@ export const strings = {
|
||||
},
|
||||
},
|
||||
de: {
|
||||
demoNotice:
|
||||
"Dies ist eine Demo-Instanz von Timetable V2. Die angezeigten Daten sind nicht echt.",
|
||||
title: {
|
||||
timetable: "Stundenplan",
|
||||
substitutions: "Vertretungsplan",
|
||||
|
@ -81,7 +81,7 @@ import {
|
||||
|
||||
<style scoped>
|
||||
.settings {
|
||||
padding: 20px 10px 0px 10px;
|
||||
padding: 0px 10px 0px 10px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
|
@ -27,8 +27,8 @@ export default defineConfig({
|
||||
],
|
||||
registerType: "autoUpdate",
|
||||
manifest: {
|
||||
name: "Timetable V2",
|
||||
short_name: "Timetable",
|
||||
name: "Timetable V2 Demo",
|
||||
short_name: "Timetable Demo",
|
||||
description: "Timetable and Substitution plan viewer",
|
||||
theme_color: "#212121",
|
||||
background_color: "#353535",
|
||||
@ -61,15 +61,5 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
port: 3001,
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://localhost:3000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
"/auth": {
|
||||
target: "http://localhost:3000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
Reference in New Issue
Block a user