From b8feb04657f9b3e8aafef9748ec51165c61e0bf3 Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 5 Aug 2024 23:07:55 +0200 Subject: [PATCH] update sync server --- apps/auth-sync-server/.vscode/settings.json | 3 + apps/auth-sync-server/README.md | 22 +- apps/auth-sync-server/package-lock.json | 332 +-- apps/auth-sync-server/package.json | 1 - apps/auth-sync-server/src/lib/lectio.ts | 8 +- apps/auth-sync-server/src/lib/types/google.ts | 6 +- .../src/lib/types/location.ts | 5 + apps/auth-sync-server/src/lib/utils.ts | 14 + apps/auth-sync-server/src/routes/+page.svelte | 8 +- .../routes/events/calendarlists/+server.ts | 22 +- .../src/routes/events/delete/+server.ts | 28 +- .../src/routes/events/sync/+server.ts | 66 +- .../src/routes/locate/+server.ts | 38 + .../src/routes/locate/locations.ts | 2456 +++++++++++++++++ .../src/routes/tasks/delete/+server.ts | 29 +- .../src/routes/tasks/sync/+server.ts | 47 +- .../src/routes/tasks/tasklists/+server.ts | 22 +- .../src/routes/token/check/+server.ts | 28 + .../generate}/+server.ts | 0 apps/auth-sync-server/svelte.config.js | 2 +- 20 files changed, 2721 insertions(+), 416 deletions(-) create mode 100644 apps/auth-sync-server/.vscode/settings.json create mode 100644 apps/auth-sync-server/src/lib/types/location.ts create mode 100644 apps/auth-sync-server/src/routes/locate/+server.ts create mode 100644 apps/auth-sync-server/src/routes/locate/locations.ts create mode 100644 apps/auth-sync-server/src/routes/token/check/+server.ts rename apps/auth-sync-server/src/routes/{gen-offline-google-token => token/generate}/+server.ts (100%) diff --git a/apps/auth-sync-server/.vscode/settings.json b/apps/auth-sync-server/.vscode/settings.json new file mode 100644 index 00000000..eb3329d0 --- /dev/null +++ b/apps/auth-sync-server/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cSpell.words": ["betterlectio"] +} diff --git a/apps/auth-sync-server/README.md b/apps/auth-sync-server/README.md index bcbc17d2..c1a3b2c2 100644 --- a/apps/auth-sync-server/README.md +++ b/apps/auth-sync-server/README.md @@ -1,21 +1 @@ - - -[![Version](https://img.shields.io/github/v/release/BetterLectio/BetterLectio-next?style=for-the-badge)]() -[![GitHub Downloads](https://img.shields.io/github/downloads/BetterLectio/BetterLectio-next/total?style=for-the-badge)]() -[![Issues](https://img.shields.io/github/issues/BetterLectio/BetterLectio-next?style=for-the-badge)]() -[![LICENSE](https://img.shields.io/github/license/BetterLectio/BetterLectio-next?style=for-the-badge)]() - -# BetterLectio - -Velkommen til BetterLectios mono-repo. Her finder du alt vores kode, som vi har lavet til BetterLectio. -Det inkludere vores API, vores hjemmeside og alt det andet som får det hele til at spille sammen. - -## REST API - -## Sync server og OAuth server - -## Hjemmeside - -## App (Android og Desktop) - -## Hvordan kan jeg hjælpe? +[se siden her](https://auth.betterlectio.dk/) diff --git a/apps/auth-sync-server/package-lock.json b/apps/auth-sync-server/package-lock.json index a194716d..ebbf2c89 100644 --- a/apps/auth-sync-server/package-lock.json +++ b/apps/auth-sync-server/package-lock.json @@ -9,7 +9,6 @@ "version": "0.0.1", "dependencies": { "@jrmdayn/googleapis-batcher": "^0.8.0", - "@sveltejs/adapter-node": "^5.2.0", "@types/luxon": "^3.4.2", "bits-ui": "^0.21.1", "clsx": "^2.1.0", @@ -82,6 +81,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "aix" @@ -97,6 +97,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" @@ -112,6 +113,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -127,6 +129,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "android" @@ -142,6 +145,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -157,6 +161,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -172,6 +177,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -187,6 +193,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -202,6 +209,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -217,6 +225,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -232,6 +241,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "linux" @@ -247,6 +257,7 @@ "cpu": [ "loong64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -262,6 +273,7 @@ "cpu": [ "mips64el" ], + "dev": true, "optional": true, "os": [ "linux" @@ -277,6 +289,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -292,6 +305,7 @@ "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -307,6 +321,7 @@ "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" @@ -322,6 +337,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -337,6 +353,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "netbsd" @@ -352,6 +369,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "openbsd" @@ -367,6 +385,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "sunos" @@ -382,6 +401,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -397,6 +417,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -412,6 +433,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -1055,179 +1077,8 @@ "node_modules/@polka/url": { "version": "1.0.0-next.25", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", - "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==" - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-26.0.1.tgz", - "integrity": "sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^10.4.1", - "is-reference": "1.2.1", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=16.0.0 || 14 >= 14.17" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs/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==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/@rollup/plugin-commonjs/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@rollup/plugin-json": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", - "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", - "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", - "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", + "dev": true }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.13.2", @@ -1236,6 +1087,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" @@ -1248,6 +1100,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -1260,6 +1113,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1272,6 +1126,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -1284,6 +1139,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1296,6 +1152,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1308,6 +1165,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1320,6 +1178,7 @@ "cpu": [ "ppc64le" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1332,6 +1191,7 @@ "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1344,6 +1204,7 @@ "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1356,6 +1217,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1368,6 +1230,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -1380,6 +1243,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -1392,6 +1256,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -1404,6 +1269,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -1421,25 +1287,11 @@ "@sveltejs/kit": "^2.0.0" } }, - "node_modules/@sveltejs/adapter-node": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.0.tgz", - "integrity": "sha512-HVZoei2078XSyPmvdTHE03VXDUD0ytTvMuMHMQP0j6zX4nPDpCcKrgvU7baEblMeCCMdM/shQvstFxOJPQKlUQ==", - "license": "MIT", - "dependencies": { - "@rollup/plugin-commonjs": "^26.0.1", - "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^15.2.3", - "rollup": "^4.9.5" - }, - "peerDependencies": { - "@sveltejs/kit": "^2.4.0" - } - }, "node_modules/@sveltejs/kit": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.5.tgz", "integrity": "sha512-ULe3PB00q4+wYRL+IS5FDPsCEVnhEITofm7b9Yz8malcH3r1SAnW/JJ6T13hIMeu8QNRIuVQWo+P4+2VklbnLQ==", + "dev": true, "hasInstallScript": true, "dependencies": { "@types/cookie": "^0.6.0", @@ -1471,6 +1323,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.0.2.tgz", "integrity": "sha512-MpmF/cju2HqUls50WyTHQBZUV3ovV/Uk8k66AN2gwHogNAG8wnW8xtZDhzNBsFJJuvmq1qnzA5kE7YfMJNFv2Q==", + "dev": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0", "debug": "^4.3.4", @@ -1492,6 +1345,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.0.0.tgz", "integrity": "sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==", + "dev": true, "dependencies": { "debug": "^4.3.4" }, @@ -1553,7 +1407,8 @@ "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true }, "node_modules/@types/estree": { "version": "1.0.5", @@ -1592,12 +1447,6 @@ "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", "dev": true }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "license": "MIT" - }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -1885,18 +1734,6 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -2027,12 +1864,6 @@ "node": ">= 6" } }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2043,6 +1874,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -2108,6 +1940,7 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -2153,7 +1986,8 @@ "node_modules/devalue": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz", - "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==" + "integrity": "sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==", + "dev": true }, "node_modules/didyoumean": { "version": "1.2.2", @@ -2218,6 +2052,7 @@ "version": "0.20.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -2263,7 +2098,8 @@ "node_modules/esm-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", - "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==" + "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==", + "dev": true }, "node_modules/estree-walker": { "version": "3.0.3", @@ -2454,12 +2290,14 @@ "node_modules/globalyzer": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", - "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==" + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true }, "node_modules/globrex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true }, "node_modules/google-auth-library": { "version": "9.7.0", @@ -2630,6 +2468,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.0.0.tgz", "integrity": "sha512-okYUR7ZQPH+efeuMJGlq4f8ubUgO50kByRPyt/Cy1Io4PSRsPjxME+YlVaCOx+NIToW7hCsZNFJyTPFFKepRSA==", + "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -2670,21 +2509,6 @@ "node": ">=8" } }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "license": "MIT", - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-core-module": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", @@ -2723,12 +2547,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "license": "MIT" - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -2858,6 +2676,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, "engines": { "node": ">=6" } @@ -3024,10 +2843,9 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", "engines": { "node": ">=16 || 14 >=14.17" } @@ -3056,6 +2874,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, "engines": { "node": ">=4" } @@ -3064,6 +2883,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, "engines": { "node": ">=10" } @@ -3204,12 +3024,6 @@ "wrappy": "1" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", - "license": "BlueOak-1.0.0" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3245,16 +3059,15 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "license": "BlueOak-1.0.0", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3639,6 +3452,7 @@ "version": "4.13.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.2.tgz", "integrity": "sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==", + "dev": true, "dependencies": { "@types/estree": "1.0.5" }, @@ -3694,6 +3508,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, "dependencies": { "mri": "^1.1.0" }, @@ -3760,7 +3575,8 @@ "node_modules/set-cookie-parser": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", - "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", + "dev": true }, "node_modules/set-function-length": { "version": "1.2.2", @@ -3829,6 +3645,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", @@ -4094,6 +3911,7 @@ "version": "0.15.3", "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", + "dev": true, "engines": { "node": "^12.20 || ^14.13.1 || >= 16" }, @@ -4339,6 +4157,7 @@ "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, "dependencies": { "globalyzer": "0.1.0", "globrex": "^0.1.2" @@ -4360,6 +4179,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, "engines": { "node": ">=6" } @@ -4479,6 +4299,7 @@ "version": "5.2.7", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.7.tgz", "integrity": "sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==", + "dev": true, "dependencies": { "esbuild": "^0.20.1", "postcss": "^8.4.38", @@ -4533,6 +4354,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", + "dev": true, "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" }, diff --git a/apps/auth-sync-server/package.json b/apps/auth-sync-server/package.json index e77ea53e..d2c4ae02 100644 --- a/apps/auth-sync-server/package.json +++ b/apps/auth-sync-server/package.json @@ -32,7 +32,6 @@ "type": "module", "dependencies": { "@jrmdayn/googleapis-batcher": "^0.8.0", - "@sveltejs/adapter-node": "^5.2.0", "@types/luxon": "^3.4.2", "bits-ui": "^0.21.1", "clsx": "^2.1.0", diff --git a/apps/auth-sync-server/src/lib/lectio.ts b/apps/auth-sync-server/src/lib/lectio.ts index 95dab87e..69d57840 100644 --- a/apps/auth-sync-server/src/lib/lectio.ts +++ b/apps/auth-sync-server/src/lib/lectio.ts @@ -1,6 +1,12 @@ import { DateTime } from "luxon"; -export const LECTIO_API_URL = 'https://api.betterlectio.dk'; +export const LECTIO_API_URL = 'https://api.bedstelectio.dk'; + +export const convertLectioExamName = (name: string) => { + return name.replace('mdt.', 'mundtlig').replace('skr.', 'skriftlig') + .replace('prv.', 'prøve') + .replace('eks.', 'eksamen') +} export const convertLectioTime = (dateString: string) => { const matchArray = dateString.match(/\d+/gu); diff --git a/apps/auth-sync-server/src/lib/types/google.ts b/apps/auth-sync-server/src/lib/types/google.ts index 345064f6..b6279342 100644 --- a/apps/auth-sync-server/src/lib/types/google.ts +++ b/apps/auth-sync-server/src/lib/types/google.ts @@ -3,14 +3,16 @@ import type { calendar_v3, tasks_v1 } from "googleapis"; export type CalendarEvent = calendar_v3.Schema$Event; export interface EventSyncOptions { calendarId: string; + week: number; + year: number; blacklist: string; eventReminders?: calendar_v3.Schema$EventReminder[]; }; export type GoogleTask = tasks_v1.Schema$Task; export interface TaskSyncOptions { - tasklist: string; - addFinishedTasks: boolean; + tasklist: string; + addFinishedTasks: boolean; maxAge?: string; }; diff --git a/apps/auth-sync-server/src/lib/types/location.ts b/apps/auth-sync-server/src/lib/types/location.ts new file mode 100644 index 00000000..988751a5 --- /dev/null +++ b/apps/auth-sync-server/src/lib/types/location.ts @@ -0,0 +1,5 @@ +export interface LatLng { + lat: number + lng: number +} +export type ClosestSchool = { distance: number, name: string, id: number } \ No newline at end of file diff --git a/apps/auth-sync-server/src/lib/utils.ts b/apps/auth-sync-server/src/lib/utils.ts index bfdcdbe3..df22939a 100644 --- a/apps/auth-sync-server/src/lib/utils.ts +++ b/apps/auth-sync-server/src/lib/utils.ts @@ -3,6 +3,20 @@ import { twMerge } from "tailwind-merge"; import { cubicOut } from "svelte/easing"; import type { TransitionConfig } from "svelte/transition"; +export const CORS_HEADERS = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', + 'Access-Control-Allow-Headers': '*' +} + +export const errorResponse = (message: string, status = 400) => { + return new Response(message, { + status, + statusText: message, + headers: CORS_HEADERS + }); +} + export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } diff --git a/apps/auth-sync-server/src/routes/+page.svelte b/apps/auth-sync-server/src/routes/+page.svelte index 6573d870..46448f55 100644 --- a/apps/auth-sync-server/src/routes/+page.svelte +++ b/apps/auth-sync-server/src/routes/+page.svelte @@ -4,7 +4,7 @@ import * as Card from '$lib/components/ui/card/index.js'; async function getToken() { - const res = await fetch('/gen-offline-google-token'); + const res = await fetch('/token/generate'); const token = await res.text(); console.log(token); return token; @@ -16,8 +16,8 @@ } -
- +
+ Log ind
-
+
Læs BetterLectio prvatlivspolitik og servicevilkår Her
diff --git a/apps/auth-sync-server/src/routes/events/calendarlists/+server.ts b/apps/auth-sync-server/src/routes/events/calendarlists/+server.ts index 734d5b28..2706d1d6 100644 --- a/apps/auth-sync-server/src/routes/events/calendarlists/+server.ts +++ b/apps/auth-sync-server/src/routes/events/calendarlists/+server.ts @@ -1,14 +1,14 @@ -import { error } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { calendar_v3, google as googleLib } from 'googleapis'; import { CLIENT_ID, CLIENT_SECRET, REDIRECT_URI } from '$env/static/private'; import type { GoogleResponse } from '$lib/types/google'; +import { CORS_HEADERS, errorResponse } from '$lib/utils'; +import { calendar_v3, google as googleLib } from 'googleapis'; +import type { RequestHandler } from './$types'; export const GET: RequestHandler = async ({ request, fetch }) => { const headers = request.headers; const googleToken = headers.get('google'); - if (!googleToken) return error(400, 'Missing google auth'); + if (!googleToken) return errorResponse('Missing google auth'); const calAuth = new googleLib.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI); let decodedGoogleToken = JSON.parse(atob(googleToken)); @@ -19,7 +19,7 @@ export const GET: RequestHandler = async ({ request, fetch }) => { try { calLists = await calApi.calendarList.list(); } catch (e) { - return error(401, 'Invalid google token'); + return errorResponse('Invalid google token', 401); } const lists = calLists.data?.items?.filter((list) => list.accessRole === 'owner')?.map((list) => { return { @@ -30,20 +30,12 @@ export const GET: RequestHandler = async ({ request, fetch }) => { }); return new Response(JSON.stringify(lists), { - headers: { - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + headers: CORS_HEADERS }); }; export const OPTIONS: RequestHandler = async () => { return new Response(null, { - headers: { - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + headers: CORS_HEADERS }); }; diff --git a/apps/auth-sync-server/src/routes/events/delete/+server.ts b/apps/auth-sync-server/src/routes/events/delete/+server.ts index 441861e5..6ac437a5 100644 --- a/apps/auth-sync-server/src/routes/events/delete/+server.ts +++ b/apps/auth-sync-server/src/routes/events/delete/+server.ts @@ -1,20 +1,20 @@ -import { error } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { calendar_v3, google as googleLib } from 'googleapis'; import { CLIENT_ID, CLIENT_SECRET, REDIRECT_URI } from '$env/static/private'; -import { batchFetchImplementation } from '@jrmdayn/googleapis-batcher'; import type { GoogleResponse } from '$lib/types/google'; +import { CORS_HEADERS, errorResponse } from '$lib/utils'; +import { batchFetchImplementation } from '@jrmdayn/googleapis-batcher'; +import { calendar_v3, google as googleLib } from 'googleapis'; +import type { RequestHandler } from './$types'; export const POST: RequestHandler = async ({ request, fetch }) => { const fetchImpl = batchFetchImplementation(); const headers = request.headers; const googleToken = headers.get('google'); - if (!googleToken) return error(400, 'Missing google token header'); + if (!googleToken) return errorResponse('Missing google token header'); const options = await request.json() as { calendarId: string }; - if (!options) return error(400, 'Missing calendarId'); - if (typeof options.calendarId !== 'string') return error(400, 'calendarId must be a string'); + if (!options) return errorResponse('Missing calendarId'); + if (typeof options.calendarId !== 'string') return errorResponse('calendarId must be a string'); const calendarAuth = new googleLib.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI); let decodedGoogleToken = JSON.parse(atob(googleToken)); @@ -39,7 +39,7 @@ export const POST: RequestHandler = async ({ request, fetch }) => { maxResults: 1000 }); } catch (e) { - return error(401, 'Invalid google token'); + return errorResponse('Invalid google token', 401); } // Delete all events from the calendar with the uid "betterlectio..." @@ -55,20 +55,12 @@ export const POST: RequestHandler = async ({ request, fetch }) => { const success = deletedEvents.filter((event) => event.status === 204).length; return new Response(JSON.stringify({ total: deletedEvents.length, success, failed: deletedEvents.length - success }), { - headers: { - 'Access-Control-Allow-Methods': 'POST, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + headers: CORS_HEADERS }); }; export const OPTIONS: RequestHandler = async () => { return new Response(null, { - headers: { - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + headers: CORS_HEADERS }); }; diff --git a/apps/auth-sync-server/src/routes/events/sync/+server.ts b/apps/auth-sync-server/src/routes/events/sync/+server.ts index 448ef9aa..8eaba1e1 100644 --- a/apps/auth-sync-server/src/routes/events/sync/+server.ts +++ b/apps/auth-sync-server/src/routes/events/sync/+server.ts @@ -1,17 +1,12 @@ -import { error } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { calendar_v3, google as googleLib } from 'googleapis'; import { CLIENT_ID, CLIENT_SECRET, REDIRECT_URI } from '$env/static/private'; +import { LECTIO_API_URL, checkLectioCookie, convertLectioExamName, convertLectioInterval } from '$lib/lectio'; +import type { CalendarEvent, EventSyncOptions, GoogleResponse } from '$lib/types/google'; +import type { Modul } from '$lib/types/lectio'; +import { CORS_HEADERS, errorResponse } from '$lib/utils'; import { batchFetchImplementation } from '@jrmdayn/googleapis-batcher'; +import { calendar_v3, google as googleLib } from 'googleapis'; import { DateTime } from 'luxon'; -import type { Modul } from '$lib/types/lectio'; -import { LECTIO_API_URL, checkLectioCookie, convertLectioInterval } from '$lib/lectio'; -import type { CalendarEvent, EventSyncOptions, GoogleResponse } from '$lib/types/google'; - -function getWeekNumber(d: Date): number { - const dt = DateTime.fromJSDate(d); - return dt.weekNumber; -} +import type { RequestHandler } from './$types'; export const POST: RequestHandler = async ({ request, fetch }) => { const fetchImpl = batchFetchImplementation(); @@ -19,23 +14,25 @@ export const POST: RequestHandler = async ({ request, fetch }) => { const googleToken = headers.get('google'); const lectioCookie = headers.get('lectio'); - if (!googleToken || !lectioCookie) return error(400, 'Missing auth headers'); + if (!googleToken || !lectioCookie) return errorResponse('Missing auth headers'); const options = await request.json() as EventSyncOptions; - if (!options) return error(400, 'Missing options'); - if (!options.calendarId) return error(400, 'Missing calendarId'); - if (typeof options.blacklist !== 'string') return error(400, 'Missing blacklist'); + if (!options) return errorResponse('Missing options'); + if (!options.calendarId) return errorResponse('Missing calendarId'); + if (!options.week) return errorResponse('Missing week'); + if (!options.year) return errorResponse('Missing year'); + if (typeof options.blacklist !== 'string') return errorResponse('Missing blacklist'); if (options.blacklist === '') options.blacklist = 'YouShallNotPass'; try { new RegExp(options.blacklist); } catch (e) { - return error(400, 'Blacklist must be a valid regex'); + return errorResponse('Blacklist must be a valid regex'); } - if (!options.eventReminders) return error(400, 'Missing eventReminders'); - if (typeof options.eventReminders !== 'object') return error(400, 'eventReminders must be an array'); + if (!options.eventReminders) return errorResponse('Missing eventReminders'); + if (typeof options.eventReminders !== 'object') return errorResponse('eventReminders must be an array'); const isCookieValid = await checkLectioCookie(lectioCookie); - if (!isCookieValid) return error(401, 'Invalid lectio cookie'); + if (!isCookieValid) return errorResponse('Invalid lectio cookie', 401); const calendarAuth = new googleLib.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI); let decodedGoogleToken = JSON.parse(atob(googleToken)); @@ -48,11 +45,10 @@ export const POST: RequestHandler = async ({ request, fetch }) => { fetchImplementation: fetchImpl }); - const week = getWeekNumber(new Date()); - const year = new Date().getFullYear(); - - const startOfWeek = new Date(year, 0, 2 + (week - 1) * 7, 1); - const endOfWeek = new Date(year, 0, 2 + (week - 1) * 7 + 6, 1); + const week = options.week; + const year = options.year; + const startOfWeek = DateTime.fromObject({ weekYear: year, weekNumber: week, weekday: 1 }).toISO() ?? ''; + const endOfWeek = DateTime.fromObject({ weekYear: year, weekNumber: week, weekday: 7 }).toISO() ?? ''; // List all events from the calendar with the uid "betterlectio..." in the current week let list: GoogleResponse; @@ -61,14 +57,14 @@ export const POST: RequestHandler = async ({ request, fetch }) => { auth: calendarAuth, calendarId: options.calendarId, q: 'betterlectio', - timeMin: startOfWeek.toISOString(), - timeMax: endOfWeek.toISOString(), + timeMin: startOfWeek, + timeMax: endOfWeek, singleEvents: true, orderBy: 'startTime', maxResults: 1000 }); } catch (e) { - return error(401, 'Invalid google token'); + return errorResponse('Invalid google token', 401); } // Delete all events from the calendar with the uid "betterlectio..." from the current week @@ -103,11 +99,7 @@ export const POST: RequestHandler = async ({ request, fetch }) => { const success = insertedEvents.filter((event) => event.status === 200).length; return new Response(JSON.stringify({ total: insertedEvents.length, success, failed: insertedEvents.length - success }), { - headers: { - 'Access-Control-Allow-Methods': 'POST, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + headers: CORS_HEADERS }); }; @@ -121,7 +113,7 @@ function formatModuler(moduler: Modul[], options: EventSyncOptions): CalendarEve const [startDate, endDate] = convertLectioInterval(modul.tidspunkt!); return { - summary: modul.hold, + summary: modul.navn ? convertLectioExamName(modul.navn) : modul.hold, id: `betterlectio${modul.absid}at${DateTime.now().toFormat('yyyyMMddHHmmss')}`, description: `https://app.betterlectio.dk/modul?absid=${modul.absid}`, start: { @@ -143,10 +135,8 @@ function formatModuler(moduler: Modul[], options: EventSyncOptions): CalendarEve export const OPTIONS: RequestHandler = async () => { return new Response(null, { - headers: { - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + headers: CORS_HEADERS }); }; + + diff --git a/apps/auth-sync-server/src/routes/locate/+server.ts b/apps/auth-sync-server/src/routes/locate/+server.ts new file mode 100644 index 00000000..01933d21 --- /dev/null +++ b/apps/auth-sync-server/src/routes/locate/+server.ts @@ -0,0 +1,38 @@ +import { json, type RequestHandler } from '@sveltejs/kit'; +import { locations } from './locations'; +import type { ClosestSchool, LatLng } from '$lib/types/location'; +import { CORS_HEADERS, errorResponse } from '$lib/utils'; + +const { atan2, cos, sin, sqrt, PI } = Math; + +const distanceBetween = (a: LatLng, b: LatLng) => { + const R = 6371e3; // meters + const φ1 = a.lat * PI / 180; // φ, λ in radians + const φ2 = b.lat * PI / 180; + const Δφ = (b.lat - a.lat) * PI / 180; + const Δλ = (b.lng - a.lng) * PI / 180; + + const x = sin(Δφ / 2) * sin(Δφ / 2) + + cos(φ1) * cos(φ2) * + sin(Δλ / 2) * sin(Δλ / 2); + const y = 2 * atan2(sqrt(x), sqrt(1 - x)); + + return R * y; // meters +}; + +export const GET: RequestHandler = async ({ request: { url } }) => { + const params = new URL(url).searchParams; + if (!params) return errorResponse('No query parameters found', 400); + if (!params.has('lat') || !params.has('lng')) return errorResponse('Missing lat or lng', 400); + const lat = Number(params.get('lat')); + const lng = Number(params.get('lng')); + + let closest: ClosestSchool | null = null; + locations.forEach((location) => { + const distance = distanceBetween({ lat, lng }, { lat: location.lat, lng: location.lng }); + if (!closest || distance < closest.distance) closest = { distance, name: location.name, id: location.id }; + }); + + if (!closest) return errorResponse('No locations found', 404); + return json(closest, { headers: CORS_HEADERS }); +}; \ No newline at end of file diff --git a/apps/auth-sync-server/src/routes/locate/locations.ts b/apps/auth-sync-server/src/routes/locate/locations.ts new file mode 100644 index 00000000..896cf5c9 --- /dev/null +++ b/apps/auth-sync-server/src/routes/locate/locations.ts @@ -0,0 +1,2456 @@ +export const locations = [ + { + "id": 51, + "name": "Allerød Gymnasium", + "lat": 55.86531, + "lng": 12.33416 + }, + { + "id": 768, + "name": "Allikelund Gymnasium", + "lat": 55.682438, + "lng": 11.079667 + }, + { + "id": 161, + "name": "Alssundgymnasiet Sønderborg", + "lat": 54.899527, + "lng": 9.809921 + }, + { + "id": 550, + "name": "AU - Institut for odontologi og oral sundhed", + "lat": 56.16478, + "lng": 10.20526 + }, + { + "id": 1, + "name": "Aurehøj Gymnasium", + "lat": 55.745477, + "lng": 12.545043 + }, + { + "id": 3, + "name": "Bagsværd Kostskole og Gymnasium", + "lat": 55.764517, + "lng": 12.451367 + }, + { + "id": 52, + "name": "Birkerød Gymnasium og HF", + "lat": 55.838851, + "lng": 12.438811 + }, + { + "id": 266, + "name": "Bjerringbro Gymnasium", + "lat": 56.365263, + "lng": 9.671881 + }, + { + "id": 37, + "name": "Borupgaard Gymnasium", + "lat": 55.735741, + "lng": 12.382689 + }, + { + "id": 38, + "name": "Brøndby Gymnasium", + "lat": 55.648401, + "lng": 12.419844 + }, + { + "id": 281, + "name": "Brønderslev Gymnasium og HF", + "lat": 57.278636, + "lng": 9.943689 + }, + { + "id": 132, + "name": "Campus Bornholm EUD", + "lat": 55.106391, + "lng": 14.710609 + }, + { + "id": 137, + "name": "Campus Bornholm GYM", + "lat": 55.106391, + "lng": 14.710609 + }, + { + "id": 139, + "name": "Campus Bornholm Sprogcenter", + "lat": 55.106578, + "lng": 14.712936 + }, + { + "id": 364, + "name": "Campus Kujalleq", + "lat": 60.7184632, + "lng": -46.0453469 + }, + { + "id": 5, + "name": "Christianshavns Gymnasium", + "lat": 55.673243, + "lng": 12.595452 + }, + { + "id": 580, + "name": "Dansk Skoleforening for Sydslesvig", + "lat": 54.782303299999995, + "lng": 9.429863014924816 + }, + { + "id": 40, + "name": "Det frie Gymnasium", + "lat": 55.692508, + "lng": 12.558739 + }, + { + "id": 228, + "name": "Det Kristne Gymnasium", + "lat": 55.68558, + "lng": 12.48907 + }, + { + "id": 162, + "name": "Deutsches Gymnasium für Nordschleswig", + "lat": 55.0469446, + "lng": 9.4127934 + }, + { + "id": 292, + "name": "Dronninglund Gymnasium", + "lat": 57.154093, + "lng": 10.278604 + }, + { + "id": 788, + "name": "DTU", + "lat": 55.78605, + "lng": 12.52338 + }, + { + "id": 545, + "name": "Edutasia", + "lat": 57.031353, + "lng": 9.930147 + }, + { + "id": 62, + "name": "Egedal Gymnasium & HF", + "lat": 55.773831, + "lng": 12.197717 + }, + { + "id": 256, + "name": "Egaa Gymnasium", + "lat": 56.211969, + "lng": 10.271199 + }, + { + "id": 181, + "name": "Esbjerg Gymnasium", + "lat": 55.488893, + "lng": 8.49097 + }, + { + "id": 57, + "name": "Espergærde Gymnasium og HF", + "lat": 55.999043, + "lng": 12.531902 + }, + { + "id": 321, + "name": "EUC Nord - AMU - Frederikshavn", + "lat": 57.451407, + "lng": 10.492902 + }, + { + "id": 322, + "name": "EUC Nord – EAU", + "lat": 57.458957, + "lng": 10.013984 + }, + { + "id": 323, + "name": "EUC Nord - EUD - Frederikshavn", + "lat": 57.429367, + "lng": 10.505826 + }, + { + "id": 318, + "name": "EUC Nord - HES - Hestkærvej", + "lat": 57.460973, + "lng": 10.01249 + }, + { + "id": 319, + "name": "EUC Nord – HHX-HTX-EUD-AMU", + "lat": 55.779582, + "lng": 12.533987 + }, + { + "id": 769, + "name": "EUC Nordvestsjælland", + "lat": 55.711378, + "lng": 11.702995 + }, + { + "id": 770, + "name": "EUC Online", + "lat": 55.711378, + "lng": 11.702995 + }, + { + "id": 548, + "name": "European School Copenhagen Upper Secondary", + "lat": 55.666989, + "lng": 12.534016 + }, + { + "id": 7, + "name": "Falkonergårdens Gymnasium og HF", + "lat": 55.681596, + "lng": 12.496122 + }, + { + "id": 291, + "name": "Fjerritslev Gymnasium", + "lat": 57.095065, + "lng": 9.269228 + }, + { + "id": 568, + "name": "Folkekirkens Uddannelses- og Videnscenter", + "lat": 55.675511, + "lng": 12.572408 + }, + { + "id": 201, + "name": "Fredericia Gymnasium", + "lat": 55.577497, + "lng": 9.748067 + }, + { + "id": 8, + "name": "Frederiksberg Gymnasium", + "lat": 55.678504, + "lng": 12.533545 + }, + { + "id": 351, + "name": "Frederiksberg HF", + "lat": 55.679892, + "lng": 12.498753 + }, + { + "id": 54, + "name": "Frederiksborg Gymnasium og hf", + "lat": 55.925075, + "lng": 12.312737 + }, + { + "id": 282, + "name": "Frederikshavn Gymnasium", + "lat": 57.440737, + "lng": 10.526468 + }, + { + "id": 917, + "name": "Frederikshavn Handelsskole", + "lat": 57.43985, + "lng": 10.538837 + }, + { + "id": 55, + "name": "Frederikssund Gymnasium", + "lat": 55.846684, + "lng": 12.062646 + }, + { + "id": 60, + "name": "Frederiksværk Gymnasium og HF", + "lat": 55.973572, + "lng": 12.013493 + }, + { + "id": 150, + "name": "Faaborg Gymnasium", + "lat": 55.102305, + "lng": 10.249951 + }, + { + "id": 9, + "name": "Gammel Hellerup Gymnasium", + "lat": 55.730633, + "lng": 12.573467 + }, + { + "id": 988, + "name": "GBS/Niuernermik Ilinniarfik, Nuuk", + "lat": 64.17171454999999, + "lng": -51.73467824357034 + }, + { + "id": 17, + "name": "Gefion Gymnasium", + "lat": 55.688359, + "lng": 12.581447 + }, + { + "id": 304, + "name": "Gentofte Gymnasium", + "lat": 55.743276, + "lng": 12.566521 + }, + { + "id": 365, + "name": "Gentofte HF", + "lat": 55.747329, + "lng": 12.539406 + }, + { + "id": 11, + "name": "Gladsaxe Gymnasium", + "lat": 55.742878, + "lng": 12.49046 + }, + { + "id": 241, + "name": "Grenaa Gymnasium", + "lat": 56.408013, + "lng": 10.888835 + }, + { + "id": 71, + "name": "Greve Gymnasium", + "lat": 55.582989, + "lng": 12.290221 + }, + { + "id": 61, + "name": "Gribskov Gymnasium", + "lat": 56.023161, + "lng": 12.20922 + }, + { + "id": 539, + "name": "GUX - Sisimiut", + "lat": 66.9371906, + "lng": -53.6664816 + }, + { + "id": 354, + "name": "GUX Nuuk", + "lat": 64.178098, + "lng": -51.734311 + }, + { + "id": 362, + "name": "GUX Aasiaat", + "lat": 68.704449, + "lng": -52.8746587 + }, + { + "id": 680, + "name": "H.C. Ørsted Gymnasiet - Ballerup", + "lat": 55.733054, + "lng": 12.342349 + }, + { + "id": 679, + "name": "H.C. Ørsted Gymnasiet - Frederiksberg", + "lat": 55.682869, + "lng": 12.519146 + }, + { + "id": 681, + "name": "H.C. Ørsted Gymnasiet - Lyngby", + "lat": 55.777856, + "lng": 12.526219 + }, + { + "id": 163, + "name": "Haderslev Katedralskole", + "lat": 55.2585897, + "lng": 9.4929714 + }, + { + "id": 511, + "name": "Handelsgymnasiet Vestfyn", + "lat": 55.272303, + "lng": 10.111308 + }, + { + "id": 283, + "name": "Hasseris Gymnasium", + "lat": 57.025787, + "lng": 9.878043 + }, + { + "id": 53, + "name": "Helsingør Gymnasium", + "lat": 56.02264, + "lng": 12.5848 + }, + { + "id": 12, + "name": "Herlev Gymnasium og HF", + "lat": 55.732122, + "lng": 12.418841 + }, + { + "id": 111, + "name": "Herlufsholm Skole", + "lat": 55.245816, + "lng": 11.746338 + }, + { + "id": 221, + "name": "Herning Gymnasium", + "lat": 56.146777, + "lng": 8.978321 + }, + { + "id": 75, + "name": "Himmelev Gymnasium", + "lat": 55.656556, + "lng": 12.112311 + }, + { + "id": 82, + "name": "Hindholm STX", + "lat": 55.287381, + "lng": 11.558753 + }, + { + "id": 284, + "name": "Hjørring Gymnasium / STX og HF", + "lat": 57.458981, + "lng": 9.996456 + }, + { + "id": 222, + "name": "Holstebro Gymnasium og HF", + "lat": 56.369817, + "lng": 8.604649 + }, + { + "id": 202, + "name": "Horsens Gymnasium & HF", + "lat": 55.871377, + "lng": 9.826707 + }, + { + "id": 280, + "name": "Hovedstadens Kristne Gymnasium", + "lat": 55.6853532, + "lng": 12.4867912 + }, + { + "id": 523, + "name": "HTX Roskilde", + "lat": 55.625262, + "lng": 12.082808 + }, + { + "id": 2, + "name": "Hvidovre Gymnasium og HF", + "lat": 55.628809, + "lng": 12.452213 + }, + { + "id": 36, + "name": "Høje-Taastrup Gymnasium", + "lat": 55.649059, + "lng": 12.262488 + }, + { + "id": 97, + "name": "Høng Gymnasium og HF", + "lat": 55.506926, + "lng": 11.287275 + }, + { + "id": 942, + "name": "IBC Fredericia Middelfart", + "lat": 55.539074, + "lng": 9.71893 + }, + { + "id": 944, + "name": "IBC HF og VUC Fredericia", + "lat": 55.539074, + "lng": 9.71893 + }, + { + "id": 945, + "name": "IBC Innovationsfabrikken", + "lat": 55.509298, + "lng": 9.50712 + }, + { + "id": 943, + "name": "IBC Kolding", + "lat": 55.48438, + "lng": 9.485676 + }, + { + "id": 941, + "name": "IBC Aabenraa", + "lat": 55.043409, + "lng": 9.412687 + }, + { + "id": 223, + "name": "Ikast-Brande Gymnasium", + "lat": 56.135412, + "lng": 9.127197 + }, + { + "id": 690, + "name": "Ilisimatusarfik - Grønlands universitet", + "lat": 64.1912591, + "lng": -51.6948635 + }, + { + "id": 691, + "name": "Ilisimatusarfik - Institut for læring", + "lat": 64.1912591, + "lng": -51.6948635 + }, + { + "id": 516, + "name": "Imarsiornermik Ilinniarfik Nuuk", + "lat": 64.1816928, + "lng": -51.7218661 + }, + { + "id": 573, + "name": "Imarsiornermik Ilinniarfik Paamiut", + "lat": 43.05852, + "lng": -70.79929 + }, + { + "id": 13, + "name": "Ingrid Jespersens Gymnasieskole", + "lat": 55.700287, + "lng": 12.578547 + }, + { + "id": 484, + "name": "International School of Hellerup", + "lat": 55.722551, + "lng": 12.56019 + }, + { + "id": 521, + "name": "INUILI", + "lat": -23.60991, + "lng": -46.90889 + }, + { + "id": 16, + "name": "Johannesskolens Gymnasium", + "lat": 55.67504, + "lng": 12.501831 + }, + { + "id": 92, + "name": "Kalundborg Gymnasium og HF", + "lat": 55.6817, + "lng": 11.08284 + }, + { + "id": 203, + "name": "Kolding Gymnasium", + "lat": 55.507798, + "lng": 9.474482 + }, + { + "id": 88, + "name": "Kriminalforsorgen, UCB", + "lat": 55.767441, + "lng": 12.492693 + }, + { + "id": 32, + "name": "Københavns åbne Gymnasium", + "lat": 55.655582, + "lng": 12.524251 + }, + { + "id": 72, + "name": "Køge Gymnasium", + "lat": 55.455702, + "lng": 12.170219 + }, + { + "id": 138, + "name": "Køge Handelsskole - EUD/EUX/AMU/HF/SKP", + "lat": 55.486226, + "lng": 12.157148 + }, + { + "id": 131, + "name": "Køge Handelsskole - HHX", + "lat": 55.486226, + "lng": 12.157148 + }, + { + "id": 306, + "name": "Learnmark Horsens Handelsgymnasium HHX", + "lat": 55.868407, + "lng": 9.855812 + }, + { + "id": 305, + "name": "Learnmark Horsens Teknisk Gymnasium HTX", + "lat": 55.868407, + "lng": 9.855812 + }, + { + "id": 512, + "name": "Majoriaq", + "lat": 64.178098, + "lng": -51.734311 + }, + { + "id": 285, + "name": "Mariagerfjord Gymnasium", + "lat": 56.633773, + "lng": 9.813035 + }, + { + "id": 112, + "name": "Maribo Gymnasium", + "lat": 54.772857, + "lng": 11.508995 + }, + { + "id": 58, + "name": "Marie Kruses Skole", + "lat": 55.814328, + "lng": 12.386947 + }, + { + "id": 243, + "name": "Marselisborg Gymnasium", + "lat": 56.139194, + "lng": 10.200802 + }, + { + "id": 576, + "name": "Mercantec, Bygge- og anlæg EUD/AMU", + "lat": 56.463796, + "lng": 9.399733 + }, + { + "id": 577, + "name": "Mercantec, Midtbyens Gymnasium", + "lat": 56.448564, + "lng": 9.396541 + }, + { + "id": 575, + "name": "Mercantec, teknisk og merkantil EUD/AMU", + "lat": 56.463796, + "lng": 9.399733 + }, + { + "id": 270, + "name": "Michael Skolen", + "lat": 55.735908, + "lng": 12.40594 + }, + { + "id": 141, + "name": "Middelfart Gymnasium", + "lat": 55.49435, + "lng": 9.738697 + }, + { + "id": 225, + "name": "Miðnámsskúlin í Suðuroy", + "lat": 61.5046, + "lng": -6.77525 + }, + { + "id": 955, + "name": "Midtfyns Gymnasium", + "lat": 55.241256, + "lng": 10.467905 + }, + { + "id": 544, + "name": "Midtjysk Erhvervskøreskole og Uddannelsescenter", + "lat": 56.483253, + "lng": 10.032323 + }, + { + "id": 91, + "name": "Midtsjællands Gymnasium", + "lat": 55.3236582, + "lng": 11.9590266 + }, + { + "id": 261, + "name": "Morsø Gymnasium", + "lat": 56.78486, + "lng": 8.83458 + }, + { + "id": 143, + "name": "Mulernes Legatskole", + "lat": 55.40865, + "lng": 10.427153 + }, + { + "id": 207, + "name": "Munkensdam Gymnasium", + "lat": 55.485517, + "lng": 9.456392 + }, + { + "id": 19, + "name": "N. Zahles Gymnasieskole", + "lat": 55.682456, + "lng": 12.569828 + }, + { + "id": 402, + "name": "Nakskov Gymnasium og HF", + "lat": 54.835052, + "lng": 11.139402 + }, + { + "id": 48, + "name": "NEXT - Baltorp Gymnasium", + "lat": 55.731034, + "lng": 12.351764 + }, + { + "id": 517, + "name": "NEXT - EUX", + "lat": 55.703423, + "lng": 12.536299 + }, + { + "id": 520, + "name": "NEXT - Københavns Mediegymnasium", + "lat": 55.6818269, + "lng": 12.5537159 + }, + { + "id": 518, + "name": "NEXT - Sukkertoppen Gymnasium", + "lat": 55.654338, + "lng": 12.515746 + }, + { + "id": 44, + "name": "NEXT - Sydkysten Gymnasium", + "lat": 55.616184, + "lng": 12.363212 + }, + { + "id": 43, + "name": "NEXT - Vestskoven Gymnasium", + "lat": 55.656349, + "lng": 12.349363 + }, + { + "id": 519, + "name": "NEXT - Vibenshus Gymnasium", + "lat": 55.704946, + "lng": 12.560443 + }, + { + "id": 20, + "name": "Niels Steensens Gymnasium", + "lat": 55.708128, + "lng": 12.567054 + }, + { + "id": 513, + "name": "Nordfyns Gymnasium", + "lat": 55.490041, + "lng": 10.242874 + }, + { + "id": 63, + "name": "Nordsjællands Grundskole og Gymnasium", + "lat": 55.903231, + "lng": 12.47452 + }, + { + "id": 144, + "name": "Nyborg Gymnasium", + "lat": 55.322377, + "lng": 10.79522 + }, + { + "id": 114, + "name": "Nykøbing Katedralskole", + "lat": 54.774494, + "lng": 11.875922 + }, + { + "id": 31, + "name": "Nærum Gymnasium", + "lat": 55.819045, + "lng": 12.533154 + }, + { + "id": 115, + "name": "Næstved Gymnasium og HF", + "lat": 55.218783, + "lng": 11.768112 + }, + { + "id": 21, + "name": "Nørre Gymnasium", + "lat": 55.718373, + "lng": 12.471338 + }, + { + "id": 645, + "name": "Nørrebro Gymnasium", + "lat": 55.704846, + "lng": 12.553398 + }, + { + "id": 287, + "name": "Nørresundby Gymnasium og HF", + "lat": 57.074919, + "lng": 9.94096 + }, + { + "id": 254, + "name": "Odder Gymnasium", + "lat": 55.972147, + "lng": 10.168233 + }, + { + "id": 600, + "name": "Odense Katedralskole", + "lat": 55.398942, + "lng": 10.383801 + }, + { + "id": 96, + "name": "Odsherreds Gymnasium", + "lat": 55.816472, + "lng": 11.51043 + }, + { + "id": 22, + "name": "Ordrup Gymnasium", + "lat": 55.759934, + "lng": 12.575718 + }, + { + "id": 152, + "name": "Oure Kostgymnasium", + "lat": 55.1065377, + "lng": 10.7136533 + }, + { + "id": 253, + "name": "Paderup Gymnasium", + "lat": 56.431959, + "lng": 10.074657 + }, + { + "id": 135, + "name": "Paul Petersens Idrætsinstitut", + "lat": 55.670058, + "lng": 12.565019 + }, + { + "id": 353, + "name": "Peqqissaanermik Ilinniarfik, Nuuk", + "lat": 64.1699242, + "lng": -51.735442517849904 + }, + { + "id": 515, + "name": "Perorsaanermik Ilinniarfik SPS", + "lat": 64.1699242, + "lng": -51.735442517849904 + }, + { + "id": 547, + "name": "Pharmakon", + "lat": 55.922608, + "lng": 12.296244 + }, + { + "id": 245, + "name": "Randers Statsskole", + "lat": 56.469325, + "lng": 10.026058 + }, + { + "id": 184, + "name": "Ribe Katedralskole", + "lat": 55.326411, + "lng": 8.760442 + }, + { + "id": 246, + "name": "Risskov Gymnasium", + "lat": 56.194389, + "lng": 10.209748 + }, + { + "id": 204, + "name": "Rosborg Gymnasium og hf", + "lat": 55.707331, + "lng": 9.514423 + }, + { + "id": 73, + "name": "Roskilde Gymnasium", + "lat": 55.6420031, + "lng": 12.0777284 + }, + { + "id": 76, + "name": "Roskilde Katedralskole", + "lat": 55.6338266, + "lng": 12.0721299 + }, + { + "id": 522, + "name": "Roskilde Tekniske Skole - EUD", + "lat": 55.625532, + "lng": 12.079902 + }, + { + "id": 658, + "name": "Rudolf Steiner Skolen Kvistgård", + "lat": 55.99159, + "lng": 12.50585 + }, + { + "id": 501, + "name": "Rudolf Steiner Skolen Odense", + "lat": 55.3338072, + "lng": 10.4005408 + }, + { + "id": 59, + "name": "Rungsted Gymnasium", + "lat": 55.896122, + "lng": 12.519276 + }, + { + "id": 23, + "name": "Rysensteen Gymnasium", + "lat": 55.670167, + "lng": 12.563467 + }, + { + "id": 205, + "name": "Rødkilde Gymnasium", + "lat": 55.697006, + "lng": 12.507009 + }, + { + "id": 24, + "name": "Rødovre Gymnasium", + "lat": 55.667133, + "lng": 12.457519 + }, + { + "id": 25, + "name": "Sankt Annæ Gymnasium", + "lat": 55.656085, + "lng": 12.525845 + }, + { + "id": 542, + "name": "Scania Danmark", + "lat": 55.6180104, + "lng": 12.3322099 + }, + { + "id": 590, + "name": "Sct. Knuds Gymnasium", + "lat": 55.377867, + "lng": 10.383847 + }, + { + "id": 500, + "name": "SDU, Teoretisk Pædagogikum", + "lat": 55.6791, + "lng": 12.570609 + }, + { + "id": 248, + "name": "Silkeborg Gymnasium", + "lat": 56.182805, + "lng": 9.600569 + }, + { + "id": 249, + "name": "Skanderborg Gymnasium", + "lat": 56.044439, + "lng": 9.944548 + }, + { + "id": 558, + "name": "Skanderborg-Odder Center for Uddannelse EUD/EUX", + "lat": 56.046824, + "lng": 9.94613 + }, + { + "id": 560, + "name": "Skanderborg-Odder Center for Uddannelse HF & VUC", + "lat": 56.046824, + "lng": 9.94613 + }, + { + "id": 563, + "name": "Skanderborg-Odder Center for Uddannelse HHX", + "lat": 56.046824, + "lng": 9.94613 + }, + { + "id": 569, + "name": "Skanderborg-Odder Center for Uddannelse HTX SOCU", + "lat": 56.046824, + "lng": 9.94613 + }, + { + "id": 562, + "name": "Skive College", + "lat": 56.554388, + "lng": 9.022602 + }, + { + "id": 262, + "name": "Skive Gymnasium", + "lat": 56.554211, + "lng": 9.026466 + }, + { + "id": 153, + "name": "Skolerne i Oure Sport & Performance", + "lat": 55.1167941, + "lng": 10.7241834 + }, + { + "id": 93, + "name": "Slagelse Gymnasium", + "lat": 55.40113, + "lng": 11.336559 + }, + { + "id": 557, + "name": "Slotshaven Gymnasium", + "lat": 55.713063, + "lng": 11.702584 + }, + { + "id": 74, + "name": "Solrød Gymnasium", + "lat": 55.533317, + "lng": 12.213871 + }, + { + "id": 94, + "name": "Sorø Akademis Skole", + "lat": 55.429462, + "lng": 11.557303 + }, + { + "id": 95, + "name": "Stenhus Gymnasium", + "lat": 55.71154, + "lng": 11.682178 + }, + { + "id": 224, + "name": "Struer Statsgymnasium", + "lat": 56.490602, + "lng": 8.601241 + }, + { + "id": 286, + "name": "Støvring Gymnasium", + "lat": 56.880517, + "lng": 9.829963 + }, + { + "id": 477, + "name": "Svendborg Gymnasium", + "lat": 55.053403, + "lng": 10.589702 + }, + { + "id": 164, + "name": "Sønderborg Statsskole", + "lat": 54.908287, + "lng": 9.794375 + }, + { + "id": 307, + "name": "Sønderjyllands Gymnasium, Grundskole og Kostskole", + "lat": 55.764517, + "lng": 12.451367 + }, + { + "id": 865, + "name": "Teknikimik Ilinniarfik", + "lat": 66.9371906, + "lng": -53.6664816 + }, + { + "id": 263, + "name": "Thisted Gymnasium STX og HF", + "lat": 56.965679, + "lng": 8.698412 + }, + { + "id": 527, + "name": "Tornbjerg Gymnasium", + "lat": 55.369361, + "lng": 10.461302 + }, + { + "id": 571, + "name": "Tradium, Rådmands Boulevard", + "lat": 56.468317, + "lng": 10.027329 + }, + { + "id": 572, + "name": "Tradium, Vester Allé", + "lat": 56.467619, + "lng": 10.028024 + }, + { + "id": 543, + "name": "Trafikskolen", + "lat": 55.811459, + "lng": 12.4715 + }, + { + "id": 208, + "name": "Tørring Gymnasium", + "lat": 55.863357, + "lng": 9.484427 + }, + { + "id": 30, + "name": "Tårnby Gymnasium", + "lat": 55.637586, + "lng": 12.604097 + }, + { + "id": 345, + "name": "Taastrup City Gymnasium", + "lat": 55.64136, + "lng": 12.264797 + }, + { + "id": 510, + "name": "UCH EUD/EUX, AMU og Step10", + "lat": 56.372313, + "lng": 8.604003 + }, + { + "id": 508, + "name": "UCH Handelsgymnasium", + "lat": 55.779582, + "lng": 12.533987 + }, + { + "id": 509, + "name": "UCH Teknisk Gymnasium", + "lat": 55.925543, + "lng": 12.312451 + }, + { + "id": 238, + "name": "UNORD Frederikssund", + "lat": 55.84774, + "lng": 12.063114 + }, + { + "id": 240, + "name": "UNORD Hillerød Handelsgymnasium og Handelsskole", + "lat": 55.925841, + "lng": 12.283352 + }, + { + "id": 236, + "name": "UNORD Hillerød Teknisk Gymnasium", + "lat": 55.920765, + "lng": 12.291219 + }, + { + "id": 235, + "name": "UNORD Hillerød Tekniske Skole og kursuscenter", + "lat": 55.920765, + "lng": 12.291219 + }, + { + "id": 234, + "name": "UNORD Lyngby", + "lat": 55.779582, + "lng": 12.533987 + }, + { + "id": 185, + "name": "Varde Gymnasium", + "lat": 55.633993, + "lng": 8.495426 + }, + { + "id": 186, + "name": "Vejen Gymnasium og HF", + "lat": 55.473129, + "lng": 9.1207 + }, + { + "id": 206, + "name": "Vejlefjordskolen", + "lat": 55.714019, + "lng": 9.683858 + }, + { + "id": 148, + "name": "Vestfyns Gymnasium", + "lat": 55.272103, + "lng": 10.110978 + }, + { + "id": 288, + "name": "Vesthimmerlands Gymnasium", + "lat": 56.800433, + "lng": 9.523128 + }, + { + "id": 78, + "name": "Vestjysk Gymnasium Tarm", + "lat": 55.90564, + "lng": 8.522889 + }, + { + "id": 264, + "name": "Viborg Gymnasium", + "lat": 56.463608, + "lng": 9.450854 + }, + { + "id": 265, + "name": "Viborg Katedralskole", + "lat": 56.457336, + "lng": 9.412118 + }, + { + "id": 250, + "name": "Viby Gymnasium", + "lat": 56.1181423, + "lng": 10.1563719 + }, + { + "id": 259, + "name": "Vidar Skolen", + "lat": 55.753781, + "lng": 12.53184 + }, + { + "id": 33, + "name": "Virum Gymnasium", + "lat": 55.79057, + "lng": 12.479623 + }, + { + "id": 116, + "name": "Vordingborg Gymnasium & HF", + "lat": 11.90764, + "lng": 55.01549 + }, + { + "id": 158, + "name": "ZBC Handels- og Teknisk gymnasium Ringsted", + "lat": 55.428211, + "lng": 11.784594 + }, + { + "id": 165, + "name": "ZBC Handels- og Teknisk gymnasium Slagelse", + "lat": 55.400978, + "lng": 11.332609 + }, + { + "id": 160, + "name": "ZBC Handels- og Teknisk gymnasium Vordingborg", + "lat": 11.91551, + "lng": 55.01537 + }, + { + "id": 156, + "name": "ZBC Handelsgymnasiet Næstved", + "lat": 55.251371, + "lng": 11.784704 + }, + { + "id": 170, + "name": "ZBC Holbæk EUD/AMU", + "lat": 55.712331, + "lng": 11.701474 + }, + { + "id": 171, + "name": "ZBC Køge EUD/AMU", + "lat": 55.487669, + "lng": 12.155825 + }, + { + "id": 167, + "name": "ZBC Næstved EUD/AMU", + "lat": 55.251371, + "lng": 11.784704 + }, + { + "id": 159, + "name": "ZBC Ringsted EUD/AMU", + "lat": 55.446817, + "lng": 11.7975218 + }, + { + "id": 169, + "name": "ZBC Roskilde EUD/AMU", + "lat": 55.628065, + "lng": 12.077937 + }, + { + "id": 168, + "name": "ZBC Slagelse EUD/AMU", + "lat": 55.4041994, + "lng": 11.3357472 + }, + { + "id": 155, + "name": "ZBC Vordingborg EUD/AMU", + "lat": 55.01537, + "lng": 11.91551 + }, + { + "id": 34, + "name": "Øregård Gymnasium", + "lat": 55.737943, + "lng": 12.566701 + }, + { + "id": 26, + "name": "Ørestad Gymnasium", + "lat": 55.631753, + "lng": 12.581043 + }, + { + "id": 123, + "name": "AABC - EUX-gymnasiet", + "lat": 55.678141, + "lng": 12.564717 + }, + { + "id": 121, + "name": "AABC - HHX-gymnasiet Risskov", + "lat": 55.779582, + "lng": 12.533987 + }, + { + "id": 122, + "name": "AABC - HHX-gymnasiet Viby", + "lat": 56.129307, + "lng": 10.153977 + }, + { + "id": 124, + "name": "AABC - Aarhus Business College Skåde/Odder", + "lat": 55.979115, + "lng": 10.151352 + }, + { + "id": 166, + "name": "Aabenraa Statsskole", + "lat": 55.045063, + "lng": 9.411558 + }, + { + "id": 289, + "name": "Aalborg Katedralskole", + "lat": 57.048086, + "lng": 9.911352 + }, + { + "id": 524, + "name": "Aalborg Tekniske Gymnasium", + "lat": 57.049105, + "lng": 9.966666 + }, + { + "id": 290, + "name": "Aalborghus Gymnasium", + "lat": 57.025791, + "lng": 9.942816 + }, + { + "id": 311, + "name": "Århus Akademi", + "lat": 56.183016, + "lng": 10.195658 + }, + { + "id": 242, + "name": "Aarhus Gymnasium", + "lat": 56.156053, + "lng": 10.187526 + }, + { + "id": 627, + "name": "Aarhus Private Gymnasium", + "lat": 56.156053, + "lng": 10.187526 + }, + { + "id": 1282, + "name": "X - Frederikshavn Gymnasium", + "lat": 57.440737, + "lng": 10.526468 + }, + { + "id": 1051, + "name": "X - Allerød Gymnasium", + "lat": 55.86531, + "lng": 12.33416 + }, + { + "id": 1037, + "name": "X - Borupgaard Gymnasium", + "lat": 55.735741, + "lng": 12.382689 + }, + { + "id": 1292, + "name": "X - Dronninglund Gymnasium", + "lat": 57.154093, + "lng": 10.278604 + }, + { + "id": 1057, + "name": "X - Espergærde Gymnasium og HF", + "lat": 55.999043, + "lng": 12.531902 + }, + { + "id": 1321, + "name": "EUC Nord - AMU - Frederikshavn", + "lat": 57.451407, + "lng": 10.492902 + }, + { + "id": 1322, + "name": "EUC Nord – EAU", + "lat": 57.458957, + "lng": 10.013984 + }, + { + "id": 1323, + "name": "EUC Nord - EUD - Frederikshavn", + "lat": 57.429367, + "lng": 10.505826 + }, + { + "id": 1318, + "name": "EUC Nord - HES - Hestkærvej", + "lat": 57.460973, + "lng": 10.01249 + }, + { + "id": 1319, + "name": "EUC Nord – HHX-HTX-EUD-AMU", + "lat": 55.779582, + "lng": 12.533987 + }, + { + "id": 1768, + "name": "Allikelund Gymnasium", + "lat": 55.682438, + "lng": 11.079667 + }, + { + "id": 1769, + "name": "EUC Nordvestsjælland", + "lat": 55.711378, + "lng": 11.702995 + }, + { + "id": 1770, + "name": "EUC Online", + "lat": 55.711378, + "lng": 11.702995 + }, + { + "id": 1096, + "name": "Odsherreds Gymnasium", + "lat": 55.816472, + "lng": 11.51043 + }, + { + "id": 1557, + "name": "Slotshaven Gymnasium", + "lat": 55.713063, + "lng": 11.702584 + }, + { + "id": 1007, + "name": "X - Falkonergårdens Gymnasium og HF", + "lat": 55.681596, + "lng": 12.496122 + }, + { + "id": 1291, + "name": "X - Fjerritslev Gymnasium", + "lat": 57.095065, + "lng": 9.269228 + }, + { + "id": 1201, + "name": "X - Fredericia Gymnasium", + "lat": 55.577497, + "lng": 9.748067 + }, + { + "id": 1917, + "name": "X - Frederikshavn Handelsskole", + "lat": 57.43985, + "lng": 10.538837 + }, + { + "id": 1009, + "name": "X - Gammel Hellerup Gymnasium", + "lat": 55.730633, + "lng": 12.573467 + }, + { + "id": 1304, + "name": "X - Gentofte Gymnasium", + "lat": 55.743276, + "lng": 12.566521 + }, + { + "id": 1071, + "name": "X - Greve Gymnasium", + "lat": 55.582989, + "lng": 12.290221 + }, + { + "id": 1061, + "name": "X - Gribskov Gymnasium", + "lat": 56.023161, + "lng": 12.20922 + }, + { + "id": 1163, + "name": "X - Haderslev Katedralskole", + "lat": 55.2585897, + "lng": 9.4929714 + }, + { + "id": 1283, + "name": "X - Hasseris Gymnasium", + "lat": 57.025787, + "lng": 9.878043 + }, + { + "id": 1012, + "name": "X - Herlev Gymnasium og HF", + "lat": 55.732122, + "lng": 12.418841 + }, + { + "id": 1221, + "name": "X - Herning Gymnasium", + "lat": 56.146777, + "lng": 8.978321 + }, + { + "id": 1202, + "name": "Horsens Gymnasium & HF", + "lat": 55.871377, + "lng": 9.826707 + }, + { + "id": 1523, + "name": "HTX Roskilde", + "lat": 55.625262, + "lng": 12.082808 + }, + { + "id": 1522, + "name": "Roskilde Tekniske Skole - EUD", + "lat": 55.625532, + "lng": 12.079902 + }, + { + "id": 1942, + "name": "IBC Fredericia Middelfart", + "lat": 55.539074, + "lng": 9.71893 + }, + { + "id": 1944, + "name": "IBC HF og VUC Fredericia", + "lat": 55.539074, + "lng": 9.71893 + }, + { + "id": 1945, + "name": "IBC Innovationsfabrikken", + "lat": 55.509298, + "lng": 9.50712 + }, + { + "id": 1943, + "name": "IBC Kolding", + "lat": 55.48438, + "lng": 9.485676 + }, + { + "id": 1941, + "name": "IBC Aabenraa", + "lat": 55.043409, + "lng": 9.412687 + }, + { + "id": 1013, + "name": "Ingrid Jespersens Gymnasieskole", + "lat": 55.700287, + "lng": 12.578547 + }, + { + "id": 1092, + "name": "X - Kalundborg Gymnasium og HF", + "lat": 55.681703, + "lng": 11.082837 + }, + { + "id": 1203, + "name": "X - Kolding Gymnasium", + "lat": 55.507798, + "lng": 9.474482 + }, + { + "id": 1072, + "name": "X - Køge Gymnasium", + "lat": 55.455702, + "lng": 12.170219 + }, + { + "id": 1138, + "name": "Køge Handelsskole - EUD/EUX/AMU/HF/SKP", + "lat": 55.486226, + "lng": 12.157148 + }, + { + "id": 1131, + "name": "Køge Handelsskole - HHX", + "lat": 55.486226, + "lng": 12.157148 + }, + { + "id": 1285, + "name": "X - Mariagerfjord Gymnasium", + "lat": 56.633773, + "lng": 9.813035 + }, + { + "id": 1112, + "name": "X - Maribo Gymnasium", + "lat": 54.772857, + "lng": 11.508995 + }, + { + "id": 1058, + "name": "X - Marie Kruses Skole", + "lat": 55.814328, + "lng": 12.386947 + }, + { + "id": 1141, + "name": "X - Middelfart Gymnasium", + "lat": 55.49435, + "lng": 9.738697 + }, + { + "id": 1091, + "name": "X - Midtsjællands Gymnasium", + "lat": 55.3236582, + "lng": 11.9590266 + }, + { + "id": 1143, + "name": "X - Mulernes Legatskole", + "lat": 55.40865, + "lng": 10.427153 + }, + { + "id": 1207, + "name": "X - Munkensdam Gymnasium", + "lat": 55.485517, + "lng": 9.456392 + }, + { + "id": 1114, + "name": "X - Nykøbing Katedralskole", + "lat": 54.774494, + "lng": 11.875922 + }, + { + "id": 1115, + "name": "X - Næstved Gymnasium og HF", + "lat": 55.218783, + "lng": 11.768112 + }, + { + "id": 1645, + "name": "X - Nørrebro Gymnasium", + "lat": 55.704846, + "lng": 12.553398 + }, + { + "id": 1287, + "name": "X - Nørresundby Gymnasium og HF", + "lat": 57.074919, + "lng": 9.94096 + }, + { + "id": 1254, + "name": "X - Odder Gymnasium", + "lat": 55.972147, + "lng": 10.168233 + }, + { + "id": 1245, + "name": "X - Randers Statskole", + "lat": 56.469325, + "lng": 10.026058 + }, + { + "id": 1184, + "name": "Ribe Katedralskole", + "lat": 55.326411, + "lng": 8.760442 + }, + { + "id": 1073, + "name": "X - Roskilde Gymnasium", + "lat": 55.6420031, + "lng": 12.0777284 + }, + { + "id": 1076, + "name": "X - Roskilde Katedralskole", + "lat": 55.6338266, + "lng": 12.0721299 + }, + { + "id": 1205, + "name": "X - Rødkilde Gymnasium", + "lat": 55.712145, + "lng": 9.5468437 + }, + { + "id": 1024, + "name": "X - Rødovre Gymnasium", + "lat": 55.667133, + "lng": 12.457519 + }, + { + "id": 1590, + "name": "X - Sct. Knuds Gymnasium", + "lat": 55.377867, + "lng": 10.383847 + }, + { + "id": 1248, + "name": "X - Silkeborg Gymnasium", + "lat": 56.182805, + "lng": 9.600569 + }, + { + "id": 1562, + "name": "Skive College", + "lat": 56.554388, + "lng": 9.022602 + }, + { + "id": 1093, + "name": "X - Slagelse Gymnasium", + "lat": 55.40113, + "lng": 11.336559 + }, + { + "id": 1094, + "name": "X - Sorø Akademis Skole", + "lat": 55.429462, + "lng": 11.557303 + }, + { + "id": 1224, + "name": "X - Struer Statsgymnasium", + "lat": 56.490602, + "lng": 8.601241 + }, + { + "id": 1477, + "name": "X - Svendborg Gymnasium", + "lat": 55.053403, + "lng": 10.589702 + }, + { + "id": 1571, + "name": "Tradium, Rådmands Boulevard", + "lat": 56.468317, + "lng": 10.027329 + }, + { + "id": 1572, + "name": "Tradium, Vester Allé", + "lat": 56.467619, + "lng": 10.028024 + }, + { + "id": 1208, + "name": "X - Tørring Gymnasium", + "lat": 55.863357, + "lng": 9.484427 + }, + { + "id": 1345, + "name": "X - Taastrup City Gymnasium", + "lat": 55.64136, + "lng": 12.264797 + }, + { + "id": 1510, + "name": "UCH EUD/EUX, AMU og Step10", + "lat": 56.372313, + "lng": 8.604003 + }, + { + "id": 1508, + "name": "UCH Gymnasierne", + "lat": 56.372313, + "lng": 8.604003 + }, + { + "id": 1509, + "name": "UCH Teknisk Gymnasium", + "lat": 55.925543, + "lng": 12.312451 + }, + { + "id": 1185, + "name": "X - Varde Gymnasium", + "lat": 55.633993, + "lng": 8.495426 + }, + { + "id": 1186, + "name": "X - Vejen Gymnasium og HF", + "lat": 55.473129, + "lng": 9.1207 + }, + { + "id": 1148, + "name": "X - Vestfyns Gymnasium", + "lat": 55.272103, + "lng": 10.110978 + }, + { + "id": 1288, + "name": "X - Vesthimmerlands Gymnasium", + "lat": 56.800433, + "lng": 9.523128 + }, + { + "id": 1265, + "name": "X - Viborg Katedralskole", + "lat": 56.457336, + "lng": 9.412118 + }, + { + "id": 1250, + "name": "X - Viby Gymnasium", + "lat": 56.1181423, + "lng": 10.1563719 + }, + { + "id": 1116, + "name": "X - Vordingborg Gymnasium & HF", + "lat": 55.01549, + "lng": 11.90764 + }, + { + "id": 1123, + "name": "AABC - Aarhus Business College Campus Sønderhøj", + "lat": 56.119221, + "lng": 10.157318 + }, + { + "id": 1124, + "name": "AABC - Aarhus Business College Skåde/Odder", + "lat": 55.979115, + "lng": 10.151352 + }, + { + "id": 1121, + "name": "AABC - Aarhus Handelsgymnasium Vejlby Centervej", + "lat": 56.194126, + "lng": 10.20494 + }, + { + "id": 1122, + "name": "AABC - Aarhus Handelsgymnasium Viemosevej", + "lat": 56.129307, + "lng": 10.153977 + }, + { + "id": 1166, + "name": "X - Aabenraa Statsskole", + "lat": 55.045063, + "lng": 9.411558 + }, + { + "id": 1289, + "name": "X - Aalborg Katedralskole", + "lat": 57.048086, + "lng": 9.911352 + }, + { + "id": 1290, + "name": "X - Aalborghus Gymnasium", + "lat": 57.025791, + "lng": 9.942816 + }, + { + "id": 1264, + "name": "Y - Viborg Gymnasium", + "lat": 56.463608, + "lng": 9.450854 + }, + { + "id": 2051, + "name": "Y - Allerød Gymnasium", + "lat": 55.86531, + "lng": 12.33416 + }, + { + "id": 2768, + "name": "Allikelund Gymnasium", + "lat": 55.682438, + "lng": 11.079667 + }, + { + "id": 2769, + "name": "EUC Nordvestsjælland", + "lat": 55.711378, + "lng": 11.702995 + }, + { + "id": 2770, + "name": "EUC Online", + "lat": 55.711378, + "lng": 11.702995 + }, + { + "id": 2096, + "name": "Odsherreds Gymnasium", + "lat": 55.816472, + "lng": 11.51043 + }, + { + "id": 2557, + "name": "Slotshaven Gymnasium", + "lat": 55.713063, + "lng": 11.702584 + }, + { + "id": 2061, + "name": "Y - Gribskov Gymnasium", + "lat": 56.023161, + "lng": 12.20922 + }, + { + "id": 2207, + "name": "Y - Munkensdam Gymnasium", + "lat": 55.485517, + "lng": 9.456392 + }, + { + "id": 2254, + "name": "Y - Odder Gymnasium", + "lat": 55.972147, + "lng": 10.168233 + }, + { + "id": 2245, + "name": "Y - Randers Statsskole", + "lat": 56.469325, + "lng": 10.026058 + }, + { + "id": 2204, + "name": "Y - Rosborg Gymnasium og hf", + "lat": 55.707331, + "lng": 9.514423 + }, + { + "id": 2073, + "name": "Y - Roskilde Gymnasium", + "lat": 55.6420031, + "lng": 12.0777284 + }, + { + "id": 2205, + "name": "Y - Rødkilde Gymnasium", + "lat": 55.712145, + "lng": 9.5468437 + }, + { + "id": 2024, + "name": "Y - Rødovre Gymnasium", + "lat": 55.667133, + "lng": 12.457519 + }, + { + "id": 2025, + "name": "Y - Sankt Annæ Gymnasium", + "lat": 55.656085, + "lng": 12.525845 + }, + { + "id": 2248, + "name": "Y - Silkeborg Gymnasium", + "lat": 56.182805, + "lng": 9.600569 + }, + { + "id": 2093, + "name": "Y - Slagelse Gymnasium", + "lat": 55.40113, + "lng": 11.336559 + }, + { + "id": 2510, + "name": "UCH EUD/EUX, AMU og Step10", + "lat": 56.372313, + "lng": 8.604003 + }, + { + "id": 2508, + "name": "UCH Handelsgymnasium", + "lat": 55.779582, + "lng": 12.533987 + }, + { + "id": 2509, + "name": "UCH Teknisk Gymnasium", + "lat": 55.925543, + "lng": 12.312451 + }, + { + "id": 2238, + "name": "UNORD Frederikssund", + "lat": 55.84774, + "lng": 12.063114 + }, + { + "id": 2240, + "name": "UNORD Hillerød Handelsgymnasium og Handelsskole", + "lat": 55.925841, + "lng": 12.283352 + }, + { + "id": 2236, + "name": "UNORD Hillerød Teknisk Gymnasium", + "lat": 55.920765, + "lng": 12.291219 + }, + { + "id": 2235, + "name": "UNORD Hillerød Tekniske Skole og kursuscenter", + "lat": 55.920765, + "lng": 12.291219 + }, + { + "id": 2234, + "name": "UNORD Lyngby", + "lat": 55.779582, + "lng": 12.533987 + }, + { + "id": 2148, + "name": "Y - Vestfyns Gymnasium", + "lat": 55.272103, + "lng": 10.110978 + }, + { + "id": 2288, + "name": "Y - Vesthimmerlands Gymnasium", + "lat": 56.800433, + "lng": 9.523128 + }, + { + "id": 3321, + "name": "EUC Nord - AMU - Frederikshavn", + "lat": 57.451407, + "lng": 10.492902 + }, + { + "id": 3322, + "name": "EUC Nord – EAU", + "lat": 57.458957, + "lng": 10.013984 + }, + { + "id": 3323, + "name": "EUC Nord - EUD - Frederikshavn", + "lat": 57.429367, + "lng": 10.505826 + }, + { + "id": 3318, + "name": "EUC Nord - HES - Hestkærvej", + "lat": 57.460973, + "lng": 10.01249 + }, + { + "id": 3319, + "name": "EUC Nord – HHX-HTX-EUD-AMU", + "lat": 55.779582, + "lng": 12.533987 + }, + { + "id": 3184, + "name": "Ribe Katedralskole", + "lat": 55.326411, + "lng": 8.760442 + }, + { + "id": 3942, + "name": "IBC Fredericia Middelfart", + "lat": 55.539074, + "lng": 9.71893 + }, + { + "id": 3944, + "name": "IBC HF og VUC Fredericia", + "lat": 55.539074, + "lng": 9.71893 + }, + { + "id": 3945, + "name": "IBC Innovationsfabrikken", + "lat": 55.509298, + "lng": 9.50712 + }, + { + "id": 3943, + "name": "IBC Kolding", + "lat": 55.48438, + "lng": 9.485676 + }, + { + "id": 3941, + "name": "IBC Aabenraa", + "lat": 55.043409, + "lng": 9.412687 + }, + { + "id": 3337, + "name": "Z - Strenge lektor Poulsens institut", + "lat": 55.702084, + "lng": 12.561103 + }, + { + "id": 4062, + "name": "Z - Bogdepotkursus", + "lat": 55.7835544, + "lng": 12.5257357 + }, + { + "id": 4283, + "name": "Z - Ronni Hasseris", + "lat": 57.025787, + "lng": 9.878043 + }, + { + "id": 5009, + "name": "MSR - test", + "lat": 50.3288908, + "lng": 7.2203987 + }, + { + "id": 5007, + "name": "Å - administrativt kursus 2023", + "lat": 55.698752, + "lng": 12.533911 + }, + { + "id": 5062, + "name": "Å - Bogdepotkursus AB", + "lat": 55.699196, + "lng": 12.556926 + }, + { + "id": 5001, + "name": "Å - Eksamenskursus skabelon SP", + "lat": 55.680372, + "lng": 12.533681 + }, + { + "id": 5136, + "name": "Å - Høje-Taastrup Gymnasium René", + "lat": 55.649059, + "lng": 12.262488 + }, + { + "id": 5036, + "name": "Å - Høje-Taastrup Gymnasium René 2", + "lat": 55.649059, + "lng": 12.262488 + }, + { + "id": 5515, + "name": "Ingrid Jespersens Gymnasieskole", + "lat": 55.700287, + "lng": 12.578547 + }, + { + "id": 6358, + "name": "DTU Lyngby", + "lat": 55.7835544, + "lng": 12.5257357 + }, + { + "id": 6912, + "name": "Z - APVU 2023", + "lat": 55.317884, + "lng": 10.811251 + }, + { + "id": 6311, + "name": "UCH EUD/EUX, AMU og Step10", + "lat": 56.372313, + "lng": 8.604003 + }, + { + "id": 6310, + "name": "UCH Handelsgymnasium", + "lat": 55.779582, + "lng": 12.533987 + }, + { + "id": 6312, + "name": "UCH Teknisk Gymnasium", + "lat": 55.925543, + "lng": 12.312451 + }, + { + "id": 6111, + "name": "Ballerup VUC", + "lat": 55.725501, + "lng": 12.385201 + }, + { + "id": 6110, + "name": "Køge VUC", + "lat": 55.485752, + "lng": 12.158346 + }, + { + "id": 6156, + "name": "ZBC Handels- og Teknisk gymnasium Ringsted", + "lat": 55.428211, + "lng": 11.784594 + }, + { + "id": 6157, + "name": "ZBC Handels- og Teknisk gymnasium Slagelse", + "lat": 55.400978, + "lng": 11.332609 + }, + { + "id": 6158, + "name": "ZBC Handels- og Teknisk gymnasium Vordingborg", + "lat": 55.01537, + "lng": 11.91551 + }, + { + "id": 6155, + "name": "ZBC Handelsgymnasiet Næstved", + "lat": 55.251371, + "lng": 11.784704 + }, + { + "id": 6159, + "name": "ZBC Holbæk EUD/AMU", + "lat": 55.712331, + "lng": 11.701474 + }, + { + "id": 6160, + "name": "ZBC Køge EUD/AMU", + "lat": 55.487669, + "lng": 12.155825 + }, + { + "id": 6161, + "name": "ZBC Næstved EUD/AMU", + "lat": 55.251371, + "lng": 11.784704 + }, + { + "id": 6162, + "name": "ZBC Ringsted EUD/AMU", + "lat": 55.446817, + "lng": 11.7975218 + }, + { + "id": 6163, + "name": "ZBC Roskilde EUD/AMU", + "lat": 55.628065, + "lng": 12.077937 + }, + { + "id": 6164, + "name": "ZBC Slagelse EUD/AMU", + "lat": 55.4041994, + "lng": 11.3357472 + }, + { + "id": 6165, + "name": "ZBC Vordingborg EUD/AMU", + "lat": 55.01537, + "lng": 11.91551 + }, + { + "id": 6128, + "name": "Z - FD Workshop Skole", + "lat": 55.590255, + "lng": 12.295382 + }, + { + "id": 6331, + "name": "EUC Nord - AMU - Frederikshavn", + "lat": 57.451407, + "lng": 10.492902 + }, + { + "id": 6332, + "name": "EUC Nord - EAU - Hjørring", + "lat": 57.458957, + "lng": 10.013984 + }, + { + "id": 6333, + "name": "EUC Nord - EUD - Frederikshavn", + "lat": 57.429367, + "lng": 10.505826 + }, + { + "id": 6334, + "name": "EUC Nord - HES - Hestkærvej", + "lat": 57.460973, + "lng": 10.01249 + }, + { + "id": 6335, + "name": "EUC Nord - HTX", + "lat": 57.458957, + "lng": 10.013984 + }, + { + "id": 6350, + "name": "Z - UNORD Frederikssund", + "lat": 55.84774, + "lng": 12.063114 + }, + { + "id": 6351, + "name": "Z - UNORD Helsingør", + "lat": 56.043834, + "lng": 12.576294 + }, + { + "id": 6352, + "name": "Z - UNORD Hillerød Handelsgym", + "lat": 55.925841, + "lng": 12.283352 + }, + { + "id": 6353, + "name": "Z - UNORD Hillerød Teknisk Gym", + "lat": 55.925543, + "lng": 12.312451 + }, + { + "id": 6354, + "name": "Z - UNORD Hillerød Tekniske Skole", + "lat": 55.920765, + "lng": 12.291219 + }, + { + "id": 6355, + "name": "Z - UNORD Lyngby", + "lat": 55.779582, + "lng": 12.533987 + }, + { + "id": 8510, + "name": "W - UCH EUD/EUX, AMU og Step10", + "lat": 56.372313, + "lng": 8.604003 + }, + { + "id": 8508, + "name": "W - UCH Handelsgymnasium", + "lat": 55.779582, + "lng": 12.533987 + }, + { + "id": 8509, + "name": "W - UCH Teknisk Gymnasium", + "lat": 55.925543, + "lng": 12.312451 + }, + { + "id": 9998, + "name": "lectio.dk", + "lat": 55.672528, + "lng": 12.554955 + }, + { + "id": 20200992, + "name": "2020/21 - Københavns åbne Gymnasium", + "lat": 55.655582, + "lng": 12.524251 + }, + { + "id": 20200030, + "name": "2020/21 - Tårnby Gymnasium", + "lat": 55.637586, + "lng": 12.604097 + } +] \ No newline at end of file diff --git a/apps/auth-sync-server/src/routes/tasks/delete/+server.ts b/apps/auth-sync-server/src/routes/tasks/delete/+server.ts index e5575c2b..bc34f325 100644 --- a/apps/auth-sync-server/src/routes/tasks/delete/+server.ts +++ b/apps/auth-sync-server/src/routes/tasks/delete/+server.ts @@ -1,19 +1,18 @@ - -import { error } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { calendar_v3, google as googleLib, tasks_v1 } from 'googleapis'; import { CLIENT_ID, CLIENT_SECRET, REDIRECT_URI } from '$env/static/private'; import type { GoogleResponse } from '$lib/types/google'; +import { CORS_HEADERS, errorResponse } from '$lib/utils'; +import { google as googleLib, tasks_v1 } from 'googleapis'; +import type { RequestHandler } from './$types'; export const POST: RequestHandler = async ({ request }) => { const headers = request.headers; const googleToken = headers.get('google'); - if (!googleToken) return error(400, 'Missing google token header'); + if (!googleToken) return errorResponse('Missing google token header'); const options = await request.json() as { tasklist: string }; - if (!options) return error(400, 'Missing tasklist'); - if (typeof options.tasklist !== 'string') return error(400, 'tasklist must be a string'); + if (!options) return errorResponse('Missing tasklist'); + if (typeof options.tasklist !== 'string') return errorResponse('tasklist must be a string'); const tasksAuth = new googleLib.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI); let decodedGoogleToken = JSON.parse(atob(googleToken)); @@ -27,11 +26,11 @@ export const POST: RequestHandler = async ({ request }) => { tasklist: options.tasklist }); } catch (e) { - return error(401, 'Invalid google token'); + return errorResponse('Invalid google token', 401); } const events = tasks.data.items; - if (!events) return error(400, 'No tasks found'); + if (!events) return errorResponse('No tasks found'); for (let i = 0; i < events.length; i++) { const event = events[i]; @@ -43,20 +42,12 @@ export const POST: RequestHandler = async ({ request }) => { } return new Response("OK", { - headers: { - 'Access-Control-Allow-Methods': 'POST, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + headers: CORS_HEADERS }); }; export const OPTIONS: RequestHandler = async () => { return new Response(null, { - headers: { - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + headers: CORS_HEADERS }); }; diff --git a/apps/auth-sync-server/src/routes/tasks/sync/+server.ts b/apps/auth-sync-server/src/routes/tasks/sync/+server.ts index dd0d7670..c1ba5c30 100644 --- a/apps/auth-sync-server/src/routes/tasks/sync/+server.ts +++ b/apps/auth-sync-server/src/routes/tasks/sync/+server.ts @@ -1,29 +1,28 @@ -import { error } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { google as googleLib, tasks_v1 } from 'googleapis'; import { CLIENT_ID, CLIENT_SECRET, REDIRECT_URI } from '$env/static/private'; -import type { Opgave } from '$lib/types/lectio'; import { LECTIO_API_URL, checkLectioCookie, convertLectioTime } from '$lib/lectio'; import type { GoogleResponse, GoogleTask, TaskSyncOptions } from '$lib/types/google'; -import { compareTwoStrings } from '$lib/utils'; +import type { Opgave } from '$lib/types/lectio'; +import { CORS_HEADERS, compareTwoStrings, errorResponse } from '$lib/utils'; +import { google as googleLib, tasks_v1 } from 'googleapis'; import { DateTime } from 'luxon'; +import type { RequestHandler } from './$types'; export const POST: RequestHandler = async ({ request, fetch }) => { const headers = request.headers; const googleToken = headers.get('google'); const lectioCookie = headers.get('lectio'); - if (!googleToken || !lectioCookie) return error(400, 'Missing auth headers'); + if (!googleToken || !lectioCookie) return errorResponse('Missing auth headers'); const options = await request.json() as TaskSyncOptions; - if (!options) return error(400, 'Missing options'); - if (typeof options.tasklist !== 'string') return error(400, 'tasklist must be a string'); - if (!("addFinishedTasks" in options)) return error(400, 'Missing addFinishedTasks'); - if (options.maxAge && typeof options.maxAge !== 'string') return error(400, 'maxAge must be a string'); - if (options.maxAge && !DateTime.fromISO(options.maxAge).isValid) return error(400, 'maxAge must be a valid date'); + if (!options) return errorResponse('Missing options'); + if (typeof options.tasklist !== 'string') return errorResponse('tasklist must be a string'); + if (!("addFinishedTasks" in options)) return errorResponse('Missing addFinishedTasks'); + if (options.maxAge && typeof options.maxAge !== 'string') return errorResponse('maxAge must be a string'); + if (options.maxAge && !DateTime.fromISO(options.maxAge).isValid) return errorResponse('maxAge must be a valid date'); const isCookieValid = await checkLectioCookie(lectioCookie); - if (!isCookieValid) return error(401, 'Invalid lectio cookie'); + if (!isCookieValid) return errorResponse('Invalid lectio cookie', 401); const tasksAuth = new googleLib.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI); let decodedGoogleToken = JSON.parse(atob(googleToken)); @@ -36,7 +35,7 @@ export const POST: RequestHandler = async ({ request, fetch }) => { tasklist: options.tasklist }) } catch (e) { - return error(401, 'Invalid google token'); + return errorResponse('Invalid google token', 401); } const existingTasks = rawExistingTasks.data.items; @@ -48,6 +47,8 @@ export const POST: RequestHandler = async ({ request, fetch }) => { const opgaver = await resp.json() as Opgave[]; const tasks = formatTasks(opgaver, options); + let newTasks = 0; + let updatedTasks = 0; for (let i = 0; i < tasks.length; i++) { const task = tasks[i]; const existingTask = existingTasks?.find((t) => t.notes?.includes(`BetterLectio ID (skal beholdes): ${task.id}`)); @@ -57,24 +58,22 @@ export const POST: RequestHandler = async ({ request, fetch }) => { if (existingTask.due !== `${task.task.due?.split('T')[0]}T00:00:00.000Z` || compareTwoStrings(existingTask.notes || "", task.task.notes || "") !== 1 || existingTask.title !== task.task.title) { await tasksApi.tasks.update({ tasklist: options.tasklist, - task: task.task.id!, - requestBody: task.task + task: existingTask.id!, + requestBody: { ...task.task, id: existingTask.id! } }); + updatedTasks++; } } else { await tasksApi.tasks.insert({ tasklist: options.tasklist, requestBody: task.task }); + newTasks++; } } - return new Response("OK", { - headers: { - 'Access-Control-Allow-Methods': 'POST, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + return new Response(JSON.stringify({ new: newTasks, updated: updatedTasks }), { + headers: CORS_HEADERS }); }; @@ -106,10 +105,6 @@ function formatTasks(tasks: Opgave[], options: TaskSyncOptions): { id: string, d export const OPTIONS: RequestHandler = async () => { return new Response(null, { - headers: { - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + headers: CORS_HEADERS }); }; diff --git a/apps/auth-sync-server/src/routes/tasks/tasklists/+server.ts b/apps/auth-sync-server/src/routes/tasks/tasklists/+server.ts index 7aeef068..ea1696c8 100644 --- a/apps/auth-sync-server/src/routes/tasks/tasklists/+server.ts +++ b/apps/auth-sync-server/src/routes/tasks/tasklists/+server.ts @@ -1,14 +1,14 @@ -import { error } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { google as googleLib, tasks_v1 } from 'googleapis'; import { CLIENT_ID, CLIENT_SECRET, REDIRECT_URI } from '$env/static/private'; import type { GoogleResponse } from '$lib/types/google'; +import { CORS_HEADERS, errorResponse } from '$lib/utils'; +import { google as googleLib, tasks_v1 } from 'googleapis'; +import type { RequestHandler } from './$types'; export const GET: RequestHandler = async ({ request }) => { const headers = request.headers; const googleToken = headers.get('google'); - if (!googleToken) return error(400, 'Missing google auth'); + if (!googleToken) return errorResponse('Missing google auth'); const tasksAuth = new googleLib.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI); let decodedGoogleToken = JSON.parse(atob(googleToken)); @@ -19,7 +19,7 @@ export const GET: RequestHandler = async ({ request }) => { try { taskLists = await tasksApi.tasklists.list(); } catch (e) { - return error(401, 'Invalid google token'); + return errorResponse('Invalid google token', 401); } const lists = taskLists.data?.items?.map((list) => { return { @@ -29,20 +29,12 @@ export const GET: RequestHandler = async ({ request }) => { }); return new Response(JSON.stringify(lists), { - headers: { - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + headers: CORS_HEADERS }); }; export const OPTIONS: RequestHandler = async () => { return new Response(null, { - headers: { - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Headers': '*' - } + headers: CORS_HEADERS }); }; diff --git a/apps/auth-sync-server/src/routes/token/check/+server.ts b/apps/auth-sync-server/src/routes/token/check/+server.ts new file mode 100644 index 00000000..9d867258 --- /dev/null +++ b/apps/auth-sync-server/src/routes/token/check/+server.ts @@ -0,0 +1,28 @@ + +import { CLIENT_ID, CLIENT_SECRET, REDIRECT_URI } from '$env/static/private'; +import { google as googleLib } from 'googleapis'; +import type { RequestHandler } from './$types'; +import { CORS_HEADERS, errorResponse } from '$lib/utils'; + +export const GET: RequestHandler = async ({ request, fetch }) => { + const headers = request.headers; + const googleToken = headers.get('google'); + + if (!googleToken) return errorResponse('Missing google auth', 400); + + const calAuth = new googleLib.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI); + let decodedGoogleToken = JSON.parse(atob(googleToken)); + calAuth.setCredentials(decodedGoogleToken); + const calApi = googleLib.calendar({ version: 'v3', auth: calAuth }); + + try { + await calApi.calendarList.list(); + } catch (e) { + return errorResponse("Invalid google token", 401); + } + return new Response("OK", { headers: CORS_HEADERS }); +}; + +export const OPTIONS: RequestHandler = async () => { + return new Response(null, { headers: CORS_HEADERS }); +}; \ No newline at end of file diff --git a/apps/auth-sync-server/src/routes/gen-offline-google-token/+server.ts b/apps/auth-sync-server/src/routes/token/generate/+server.ts similarity index 100% rename from apps/auth-sync-server/src/routes/gen-offline-google-token/+server.ts rename to apps/auth-sync-server/src/routes/token/generate/+server.ts diff --git a/apps/auth-sync-server/svelte.config.js b/apps/auth-sync-server/svelte.config.js index 0a4bc06b..cf09d3a2 100644 --- a/apps/auth-sync-server/svelte.config.js +++ b/apps/auth-sync-server/svelte.config.js @@ -1,4 +1,4 @@ -import adapter from '@sveltejs/adapter-node'; +import adapter from '@sveltejs/adapter-auto'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */