update eslint and refactor backend
This commit is contained in:
57
bun.lock
57
bun.lock
@@ -9,24 +9,19 @@
|
||||
"@fontsource/roboto": "5.2.8",
|
||||
"@mui/icons-material": "7.3.5",
|
||||
"@mui/material": "7.3.5",
|
||||
"cookie": "^1.0.2",
|
||||
"mobx-react-lite": "^4.1.1",
|
||||
"react": "19",
|
||||
"react-dom": "19",
|
||||
"react-router-dom": "^7.9.5",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.39.1",
|
||||
"@stylistic/eslint-plugin": "5.5.0",
|
||||
"@sebastianbrenner/eslint-config": "git+https://gitea.sebastianbrenner.dev/sebastianbrenner/eslint-config.git/#1.2.0",
|
||||
"@types/bun": "1.3.1",
|
||||
"@types/react": "19",
|
||||
"@types/react-dom": "19",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-hooks": "7.0.1",
|
||||
"eslint-plugin-unused-imports": "^4.3.0",
|
||||
"prettier": "3.6.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.46.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -153,6 +148,10 @@
|
||||
|
||||
"@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="],
|
||||
|
||||
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
||||
|
||||
"@sebastianbrenner/eslint-config": ["@sebastianbrenner/eslint-config@git+https://gitea.sebastianbrenner.dev/sebastianbrenner/eslint-config.git/#98e16522c315347de4203ffcbe0fff554f22c370", { "peerDependencies": { "@stylistic/eslint-plugin": "1.7.0", "@typescript-eslint/parser": "8.48.1", "eslint": "^9.0.0", "eslint-plugin-import": "2.32.0", "eslint-plugin-jsx-a11y": "6.10.2", "eslint-plugin-react": "7.37.5", "eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-unused-imports": "4.3.0", "typescript": "5" } }, "98e16522c315347de4203ffcbe0fff554f22c370"],
|
||||
|
||||
"@stylistic/eslint-plugin": ["@stylistic/eslint-plugin@5.5.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.0", "@typescript-eslint/types": "^8.46.1", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-IeZF+8H0ns6prg4VrkhgL+yrvDXWDH2cKchrbh80ejG9dQgZWp10epHMbgRuQvgchLII/lfh6Xn3lu6+6L86Hw=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
|
||||
@@ -161,6 +160,8 @@
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
|
||||
|
||||
"@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
|
||||
|
||||
"@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
|
||||
@@ -211,6 +212,8 @@
|
||||
|
||||
"array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="],
|
||||
|
||||
"array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="],
|
||||
|
||||
"array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="],
|
||||
|
||||
"array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="],
|
||||
@@ -263,7 +266,7 @@
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
"convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
||||
|
||||
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
|
||||
|
||||
@@ -323,6 +326,12 @@
|
||||
|
||||
"eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
|
||||
|
||||
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="],
|
||||
|
||||
"eslint-module-utils": ["eslint-module-utils@2.12.1", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw=="],
|
||||
|
||||
"eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="],
|
||||
|
||||
"eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="],
|
||||
|
||||
"eslint-plugin-react": ["eslint-plugin-react@7.37.5", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.9", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA=="],
|
||||
@@ -495,7 +504,7 @@
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
"json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
|
||||
|
||||
"jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
|
||||
|
||||
@@ -525,6 +534,12 @@
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"mobx": ["mobx@6.15.0", "", {}, "sha512-UczzB+0nnwGotYSgllfARAqWCJ5e/skuV2K/l+Zyck/H6pJIhLXuBnz+6vn2i211o7DtbE78HQtsYEKICHGI+g=="],
|
||||
|
||||
"mobx-react-lite": ["mobx-react-lite@4.1.1", "", { "dependencies": { "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "mobx": "^6.9.0", "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-iUxiMpsvNraCKXU+yPotsOncNNmyeS2B5DKL+TL6Tar/xm+wwNJAubJmtRSeAoYawdZqwv8Z/+5nPRHeQxTiXg=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
@@ -543,6 +558,8 @@
|
||||
|
||||
"object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="],
|
||||
|
||||
"object.groupby": ["object.groupby@1.0.3", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2" } }, "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ=="],
|
||||
|
||||
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
@@ -651,6 +668,8 @@
|
||||
|
||||
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
||||
|
||||
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"stylis": ["stylis@4.2.0", "", {}, "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="],
|
||||
@@ -663,6 +682,8 @@
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
|
||||
|
||||
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
|
||||
@@ -675,8 +696,6 @@
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.46.3", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.46.3", "@typescript-eslint/parser": "8.46.3", "@typescript-eslint/typescript-estree": "8.46.3", "@typescript-eslint/utils": "8.46.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA=="],
|
||||
|
||||
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
@@ -685,6 +704,8 @@
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||
@@ -707,7 +728,9 @@
|
||||
|
||||
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||
|
||||
"@emotion/babel-plugin/convert-source-map": ["convert-source-map@1.9.0", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
||||
"@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
@@ -719,6 +742,14 @@
|
||||
|
||||
"babel-plugin-macros/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
||||
|
||||
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"eslint-import-resolver-node/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
||||
|
||||
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||
|
||||
BIN
data.sqlite
Normal file
BIN
data.sqlite
Normal file
Binary file not shown.
@@ -1,54 +1,22 @@
|
||||
// eslint.config.js
|
||||
import js from '@eslint/js';
|
||||
import stylistic from '@stylistic/eslint-plugin';
|
||||
import jsxA11y from 'eslint-plugin-jsx-a11y';
|
||||
import react from 'eslint-plugin-react';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
import tseslint from 'typescript-eslint';
|
||||
import unusedImports from 'eslint-plugin-unused-imports';
|
||||
import config from '@sebastianbrenner/eslint-config/src/index.js';
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
{
|
||||
plugins: {
|
||||
react,
|
||||
'react-hooks': reactHooks,
|
||||
'jsx-a11y': jsxA11y,
|
||||
'@stylistic': stylistic,
|
||||
'unused-imports': unusedImports,
|
||||
},
|
||||
rules: {
|
||||
// React 19 JSX transform requires no React in scope
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
|
||||
// Hooks rules
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
|
||||
// Accessibility
|
||||
'jsx-a11y/alt-text': 'warn',
|
||||
|
||||
// Style rules (using @stylistic)
|
||||
'@stylistic/indent': ['error', 4],
|
||||
'@stylistic/semi': ['error', 'always'],
|
||||
'@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
|
||||
'@stylistic/no-trailing-spaces': 'error',
|
||||
'@stylistic/no-multiple-empty-lines': 'error',
|
||||
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
},
|
||||
...config,
|
||||
{
|
||||
ignores: [
|
||||
'dist',
|
||||
'build',
|
||||
'node_modules',
|
||||
],
|
||||
rules: {
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/jsx-newline": ["error", { "prevent": true }],
|
||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||
"indent": ["error", 4]
|
||||
},
|
||||
settings: {
|
||||
'import/core-modules': ['bun' ]
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
14
package.json
14
package.json
@@ -6,7 +6,8 @@
|
||||
"scripts": {
|
||||
"dev": "bun --hot src/index.ts",
|
||||
"build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*'",
|
||||
"start": "NODE_ENV=production bun src/index.ts"
|
||||
"start": "NODE_ENV=production bun src/index.ts",
|
||||
"eslint-quiet": "eslint 'src/**/*.{ts,tsx}' --quiet"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "11.14.0",
|
||||
@@ -14,23 +15,18 @@
|
||||
"@fontsource/roboto": "5.2.8",
|
||||
"@mui/icons-material": "7.3.5",
|
||||
"@mui/material": "7.3.5",
|
||||
"cookie": "^1.0.2",
|
||||
"mobx-react-lite": "^4.1.1",
|
||||
"react": "19",
|
||||
"react-dom": "19",
|
||||
"react-router-dom": "^7.9.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "9.39.1",
|
||||
"@stylistic/eslint-plugin": "5.5.0",
|
||||
"@types/bun": "1.3.1",
|
||||
"@types/react": "19",
|
||||
"@types/react-dom": "19",
|
||||
"eslint": "9.39.1",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-hooks": "7.0.1",
|
||||
"eslint-plugin-unused-imports": "^4.3.0",
|
||||
"prettier": "3.6.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.46.3"
|
||||
"@sebastianbrenner/eslint-config": "git+https://gitea.sebastianbrenner.dev/sebastianbrenner/eslint-config.git/#1.2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,29 @@
|
||||
import SignIn from './components/SignIn';
|
||||
import { createTheme, ThemeProvider } from '@mui/material';
|
||||
import { useStore } from './Store';
|
||||
import Group from './components/Group';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
const App = () => {
|
||||
const App = observer(() => {
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
},
|
||||
});
|
||||
|
||||
const store = useStore();
|
||||
const { loggedIn } = store;
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<div className="app">
|
||||
{loggedIn ?
|
||||
<Group /> :
|
||||
<SignIn />
|
||||
}
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default App;
|
||||
|
||||
39
src/client/Store.ts
Normal file
39
src/client/Store.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import { makeAutoObservable } from 'mobx';
|
||||
import cookie from 'cookie';
|
||||
import type { Group, User } from '@/interfaces';
|
||||
|
||||
export type CookieData = {
|
||||
user: User;
|
||||
group: Group;
|
||||
};
|
||||
|
||||
export class Store {
|
||||
cookieData: CookieData | null = null;
|
||||
|
||||
processCookie() {
|
||||
const parsed = cookie.parse(document.cookie);
|
||||
|
||||
this.cookieData = {
|
||||
user: parsed.user ? JSON.parse(parsed.user) : null,
|
||||
group: parsed.group ? JSON.parse(parsed.group) : null,
|
||||
} as CookieData;
|
||||
}
|
||||
|
||||
get loggedIn(): boolean {
|
||||
if (this.cookieData === null) {
|
||||
return false;
|
||||
};
|
||||
return !!this.cookieData.user && !!this.cookieData.group;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
console.log('Initializing Store');
|
||||
this.processCookie();
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
}
|
||||
|
||||
export const StoreContext = createContext<Store>(new Store());
|
||||
|
||||
export const useStore = (): Store => useContext(StoreContext);
|
||||
64
src/client/components/Group.tsx
Normal file
64
src/client/components/Group.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Card from './Card';
|
||||
import { useStore } from '../Store';
|
||||
|
||||
const Group = () => {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
const store = useStore();
|
||||
const { group } = store.cookieData!;
|
||||
|
||||
console.log('Group component rendered with group:', group.name);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
backgroundImage: 'radial-gradient(at 50% 50%, hsla(210, 100%, 16%, 0.5), hsl(220, 30%, 5%))',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}>
|
||||
<CssBaseline enableColorScheme />
|
||||
<Stack direction="column" justifyContent="space-between"
|
||||
sx={{
|
||||
height: 'calc((1 - var(--template-frame-height, 0)) * 100dvh)',
|
||||
minHeight: '100%',
|
||||
padding: theme.spacing(2),
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
padding: theme.spacing(4),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Card variant="outlined">
|
||||
<Typography
|
||||
component="h1"
|
||||
variant="h4"
|
||||
sx={{ width: '100%', fontSize: 'clamp(2rem, 10vw, 2.15rem)' }}
|
||||
>
|
||||
{group.name}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
width: '100%',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Typography>
|
||||
{'Willkommen in der Gruppe!'}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{'Admin: ' + group.mail}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Card>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Group;
|
||||
@@ -13,65 +13,88 @@ import { useTheme } from '@mui/material/styles';
|
||||
import GroupAddIcon from '@mui/icons-material/GroupAdd';
|
||||
import ForgotPassword from './ForgotPassword';
|
||||
import Card from './Card';
|
||||
import { createUser, fetchGroupByCode, fetchUser } from '../serverApi';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
const SignIn = () => {
|
||||
const SignIn = observer(() => {
|
||||
const [email, setEmail] = React.useState('');
|
||||
const [emailError, setEmailError] = React.useState(false);
|
||||
const [emailError, setEmailError] = React.useState('');
|
||||
const [group, setGroup] = React.useState('');
|
||||
const [groupError, setGroupError] = React.useState(false);
|
||||
const [groupError, setGroupError] = React.useState('');
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
React.useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const groupParam = params.get('gruppe');
|
||||
if (groupParam) {
|
||||
setGroup(groupParam);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
|
||||
if ((emailError || email === '')) {
|
||||
const handleEnterGroup = async (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
event.preventDefault();
|
||||
setEmailError(true);
|
||||
|
||||
if (!isMailValid() || !isGroupValid()) return;
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const groupData = await fetchGroupByCode(group);
|
||||
|
||||
if (!groupData) {
|
||||
setGroupError('Gruppencode existiert nicht.');
|
||||
return;
|
||||
}
|
||||
|
||||
if ((groupError || group === '')) {
|
||||
event.preventDefault();
|
||||
setGroupError(true);
|
||||
return;
|
||||
let user = await fetchUser(email);
|
||||
|
||||
if (!user) {
|
||||
user = await createUser(email);
|
||||
if (!user) throw new Error('Error creating user');
|
||||
}
|
||||
|
||||
await cookieStore.set('user', JSON.stringify(user));
|
||||
await cookieStore.set('group', JSON.stringify(groupData));
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error during sign-in process:', error);
|
||||
} finally {
|
||||
setLoading(false); }
|
||||
};
|
||||
|
||||
const validateEmail = (): boolean => {
|
||||
const isMailValid = (): boolean => {
|
||||
let isValid = true;
|
||||
if (email !== '' && !/\S+@\S+\.\S+/.test(email)) {
|
||||
setEmailError(true);;
|
||||
if (!/\S+@\S+\.\S+/.test(email)) {
|
||||
setEmailError('Bitte gib eine gültige E-Mail Adresse ein.');
|
||||
isValid = false;
|
||||
} else {
|
||||
setEmailError(false);
|
||||
setEmailError('');
|
||||
}
|
||||
return isValid;
|
||||
};
|
||||
|
||||
const validateGroup = (): boolean => {
|
||||
const isGroupValid = (): boolean => {
|
||||
let isValid = true;
|
||||
if (group !== '' && !/\S+@\S+\.\S+/.test(email)) {
|
||||
setGroupError(true);
|
||||
if (!/^[A-Z0-9]{6}$/.test(group)) {
|
||||
setGroupError('Bitte gib einen gültigen Gruppencode ein.');
|
||||
isValid = false;
|
||||
} else {
|
||||
setGroupError(false);
|
||||
setGroupError('');
|
||||
}
|
||||
return isValid;
|
||||
};
|
||||
|
||||
const validateInputs = () => {
|
||||
validateEmail();
|
||||
validateGroup();
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
backgroundImage: 'radial-gradient(at 50% 50%, hsla(210, 100%, 16%, 0.5), hsl(220, 30%, 5%))',
|
||||
@@ -96,11 +119,7 @@ const SignIn = () => {
|
||||
>
|
||||
{'Gruppe beitreten'}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={handleSubmit}
|
||||
noValidate
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -110,10 +129,9 @@ const SignIn = () => {
|
||||
>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="email">E-Mail</FormLabel>
|
||||
|
||||
<TextField
|
||||
error={emailError}
|
||||
helperText={emailError && 'Bitte gib eine gültige E-Mail Adresse ein.'}
|
||||
error={emailError !== ''}
|
||||
helperText={emailError}
|
||||
id="email"
|
||||
type="email"
|
||||
name="email"
|
||||
@@ -123,15 +141,16 @@ const SignIn = () => {
|
||||
required
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onBlur={validateEmail}
|
||||
onBlur={isMailValid}
|
||||
onChange={event => setEmail(event.target.value)}
|
||||
value={email}
|
||||
color={emailError ? 'error' : 'primary'} />
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel htmlFor="password">{'Gruppencode'}</FormLabel>
|
||||
<TextField
|
||||
error={groupError}
|
||||
helperText={groupError && 'Bitte gib einen gültigen Gruppencode ein.'}
|
||||
error={groupError !== ''}
|
||||
helperText={groupError}
|
||||
name="groupcode"
|
||||
placeholder="Gruppe123"
|
||||
type="text"
|
||||
@@ -140,22 +159,20 @@ const SignIn = () => {
|
||||
required
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
onBlur={validateGroup}
|
||||
onBlur={isGroupValid}
|
||||
onChange={event => setGroup(event.target.value)}
|
||||
value={group}
|
||||
color={groupError ? 'error' : 'primary'} />
|
||||
</FormControl>
|
||||
|
||||
<ForgotPassword open={open} handleClose={handleClose} />
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
variant="contained"
|
||||
onClick={validateInputs}
|
||||
onClick={event => {void handleEnterGroup(event)}}
|
||||
loading={loading}
|
||||
>
|
||||
{'Beitreten'}
|
||||
</Button>
|
||||
|
||||
<Link
|
||||
component="button"
|
||||
type="button"
|
||||
@@ -166,9 +183,7 @@ const SignIn = () => {
|
||||
{'Gruppencode vergessen?'}
|
||||
</Link>
|
||||
</Box>
|
||||
|
||||
<Divider>oder</Divider>
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Button
|
||||
fullWidth
|
||||
@@ -183,6 +198,6 @@ const SignIn = () => {
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default SignIn;
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/**
|
||||
* This file is the entry point for the React app, it sets up the root
|
||||
* element and renders the App component to the DOM.
|
||||
@@ -6,19 +7,25 @@
|
||||
*/
|
||||
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
import App from './App';
|
||||
import { Store, StoreContext } from './Store';
|
||||
|
||||
const elem = document.getElementById('root')!;
|
||||
|
||||
const store = new Store();
|
||||
|
||||
const app = (
|
||||
<StrictMode>
|
||||
<StoreContext.Provider value={store}>
|
||||
<App />
|
||||
</StoreContext.Provider>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
if (import.meta.hot) {
|
||||
// With hot module reloading, `import.meta.hot.data` is persisted.
|
||||
const root = (import.meta.hot.data.root ??= createRoot(elem));
|
||||
const root: Root = (import.meta.hot.data.root ??= createRoot(elem));
|
||||
root.render(app);
|
||||
} else {
|
||||
// The hot module reloading API is not available in production.
|
||||
|
||||
@@ -1,8 +1,38 @@
|
||||
import type { Group } from '@/interfaces';
|
||||
import type { Group, User } from '@/interfaces';
|
||||
|
||||
export async function fetchGroup(id: string): Promise<Group | null> {
|
||||
export async function fetchUser(mail: string): Promise<User | null> {
|
||||
try {
|
||||
const res = await fetch(`/api/group/${id}`);
|
||||
const res = await fetch(`/api/user/${mail}`);
|
||||
if (!res.ok) return null;
|
||||
const data: User = await res.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch user:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createUser(mail: string): Promise<User | null> {
|
||||
try {
|
||||
const res = await fetch('/api/user', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ mail }),
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data: User = await res.json();
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('Failed to create user:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchGroupByCode(code: string): Promise<Group | null> {
|
||||
try {
|
||||
const res = await fetch(`/api/group/${code}`);
|
||||
if (!res.ok) return null;
|
||||
const data: Group = await res.json();
|
||||
return data;
|
||||
|
||||
51
src/index.ts
51
src/index.ts
@@ -2,31 +2,66 @@ import { serve } from 'bun';
|
||||
import index from './index.html';
|
||||
import DB from './server/db';
|
||||
|
||||
const db = new DB();
|
||||
|
||||
const server = serve({
|
||||
routes: {
|
||||
// Serve index.html for all unmatched routes.
|
||||
'/*': index,
|
||||
|
||||
'/api/group/:id': async req => {
|
||||
const id = req.params.id;
|
||||
const group = await new DB().getGroup(id);
|
||||
console.log('Fetching group with ID:', id, 'Result:', group);
|
||||
if (!group) return new Response(JSON.stringify({ error: 'Not found' }), { status: 404 });
|
||||
'/api/user/:mail': async req => {
|
||||
const mail = req.params.mail;
|
||||
console.log('Received request for user:', mail);
|
||||
const user = await db.getUserByMail(mail);
|
||||
console.log('Fetching user with mail:', mail, 'Result:', user);
|
||||
if (!user) return new Response(JSON.stringify({ error: 'User not found' }), { status: 404 });
|
||||
return new Response(JSON.stringify(user), { headers: { 'Content-Type': 'application/json' } });
|
||||
},
|
||||
|
||||
'/api/user': {
|
||||
async POST(req) {
|
||||
try {
|
||||
const { mail } = await req.json() as { mail: string };
|
||||
console.log('Received request to create user with mail:', mail);
|
||||
const user = await db.createUser(mail);
|
||||
return new Response(JSON.stringify(user), { headers: { 'Content-Type': 'application/json' } });
|
||||
} catch (err) {
|
||||
console.error('Error creating user:', err);
|
||||
return new Response(JSON.stringify({ error: 'Failed to create user' }), { status: 500 });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
'/api/group/:code': async req => {
|
||||
const code = req.params.code;
|
||||
console.log('Received request for group:', code);
|
||||
const group = await db.getGroupByCode(code);
|
||||
console.log('Fetching group with ID:', code, 'Result:', group);
|
||||
if (!group) return new Response(JSON.stringify({ error: 'Group not found' }), { status: 404 });
|
||||
return new Response(JSON.stringify(group), { headers: { 'Content-Type': 'application/json' } });
|
||||
},
|
||||
|
||||
'/api/group': {
|
||||
async POST(req) {
|
||||
try {
|
||||
const group = (await req.formData() as FormData).get('code') as string;
|
||||
const response = await new DB().createGroup(group);
|
||||
const { name, mail } = await req.json() as { name: string; mail: string };
|
||||
console.log('Received request to create group with name:', name, 'and mail:', mail);
|
||||
const response = db.createGroup({ name, mail });
|
||||
return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } });
|
||||
} catch (err) {
|
||||
console.error('Error creating group:', err);
|
||||
return new Response(JSON.stringify({ error: 'Failed to create group' }), { status: 500 });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'/api/group_members/:groupId': async req => {
|
||||
const groupId = parseInt(req.params.groupId, 10);
|
||||
console.log('Received request for group members of group ID:', groupId);
|
||||
const members = await db.getGroupMembers(groupId);
|
||||
console.log('Fetching members for group ID:', groupId, 'Result:', members);
|
||||
return new Response(JSON.stringify(members), { headers: { 'Content-Type': 'application/json' } });
|
||||
},
|
||||
},
|
||||
|
||||
development: process.env.NODE_ENV !== 'production' && {
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
export type Group = {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
mail: string;
|
||||
image: Uint8Array | null;
|
||||
phase: string;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type User = {
|
||||
id: number;
|
||||
mail: string;
|
||||
image: Uint8Array | null;
|
||||
created_at: string;
|
||||
};
|
||||
125
src/server/db.ts
125
src/server/db.ts
@@ -1,14 +1,16 @@
|
||||
import { Database } from 'bun:sqlite';
|
||||
import type { Group, User } from '@/interfaces';
|
||||
import { SQL } from 'bun';
|
||||
|
||||
// create tables if they don't exist
|
||||
const createTableScript = `
|
||||
-- GROUP TRABLE --
|
||||
CREATE TABLE IF NOT EXISTS group (
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
mail TEXT NOT NULL,
|
||||
image BLOB,
|
||||
phase TEXT DEFAULT 'gathering',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
@@ -17,20 +19,21 @@ const createTableScript = `
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
mail TEXT NOT NULL,
|
||||
image BLOB,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- GROUP_MEMBER TABLE --
|
||||
CREATE TABLE IF NOT EXISTS group_member (
|
||||
-- MEMBERSHIP TABLE --
|
||||
CREATE TABLE IF NOT EXISTS membership (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_id INTEGER,
|
||||
user_id INTEGER,
|
||||
UNIQUE(email, group_id),
|
||||
UNIQUE(user_id, group_id),
|
||||
FOREIGN KEY(group_id) REFERENCES groups(id),
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
);
|
||||
|
||||
-- BAN TABLE --
|
||||
CREATE TABLE IF NOT EXISTS ban (
|
||||
CREATE TABLE IF NOT EXISTS bans (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
group_id INTEGER,
|
||||
user_id INTEGER,
|
||||
@@ -38,7 +41,7 @@ const createTableScript = `
|
||||
FOREIGN KEY(group_id) REFERENCES groups(id),
|
||||
FOREIGN KEY(user_id) REFERENCES users(id),
|
||||
FOREIGN KEY(user_id2) REFERENCES users(id)
|
||||
UNIQUE(group_id, user_id, user_id2),
|
||||
UNIQUE(group_id, user_id, user_id2)
|
||||
);
|
||||
|
||||
-- WHISHLIST TABLE --
|
||||
@@ -53,33 +56,111 @@ const createTableScript = `
|
||||
`;
|
||||
|
||||
class DB {
|
||||
private instance: Database = new Database();
|
||||
private instance: SQL;
|
||||
|
||||
private prepareDB () {
|
||||
this.instance.run(createTableScript);
|
||||
private executeScript(script: string, name: string) {
|
||||
try {
|
||||
void this.instance`${script}`;
|
||||
} catch (err) {
|
||||
console.error(`error executing script ${name}: ${err as Error}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async getGroup(id: string) {
|
||||
const stmt = this.instance.prepare('SELECT * FROM groups WHERE code = ?');
|
||||
const group = await stmt.get(id);
|
||||
/**
|
||||
* Prepare the database by creating necessary tables.
|
||||
*/
|
||||
private prepareDB () {
|
||||
this.executeScript(createTableScript, 'createTableScript');
|
||||
}
|
||||
|
||||
/* USERS */
|
||||
|
||||
/**
|
||||
* Create a new user
|
||||
* @param mail: string
|
||||
* @returns created user
|
||||
*/
|
||||
public async createUser(mail: string): Promise<User> {
|
||||
const user: User = await this.instance`
|
||||
INSERT INTO users (mail) VALUES (${mail})
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user by mail
|
||||
* @param mail: string
|
||||
* @returns user object or undefined
|
||||
*/
|
||||
public async getUserByMail(mail: string): Promise<User | undefined> {
|
||||
const user: User = await this.instance`
|
||||
SELECT * FROM users WHERE mail = ${mail}
|
||||
`;
|
||||
return user;
|
||||
}
|
||||
|
||||
/* GROUPS */
|
||||
|
||||
/**
|
||||
* Create a new group
|
||||
* @param name: string
|
||||
* @param mail: string
|
||||
* @returns object with id of the created group
|
||||
*/
|
||||
public async createGroup( {name, mail}: {name: string, mail: string}): Promise<Group> {
|
||||
const code = Math.random().toString(36).substring(2, 8).toUpperCase();
|
||||
const group: Group = await this.instance`
|
||||
INSERT INTO groups (code, name, mail) VALUES (${code}, ${name}, ${mail})
|
||||
RETURNING *
|
||||
`;
|
||||
return group;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get group by ID
|
||||
* @param id: string
|
||||
* @returns group object or undefined
|
||||
*/
|
||||
public async getGroupByCode(code: string): Promise<Group | undefined> {
|
||||
const group: Group | undefined = await this.instance`
|
||||
SELECT * FROM groups WHERE code = ${code}
|
||||
`;
|
||||
return group;
|
||||
}
|
||||
|
||||
public createGroup(code: string): { id: number | bigint} {
|
||||
const stmt = this.instance.prepare('INSERT INTO groups (code) VALUES (?)');
|
||||
const changes = stmt.run(code);
|
||||
console.log('Inserted group with ID:', changes);
|
||||
return { id: changes.lastInsertRowid };
|
||||
};
|
||||
/* GROUP MEMBER */
|
||||
|
||||
/**
|
||||
* Add user to group
|
||||
* @param userId: number
|
||||
* @param groupId: number
|
||||
* @returns object with id of the created group member entry
|
||||
*/
|
||||
public async UserEntersGroup(userId: number, groupId: number): Promise<{ id: number | bigint; }> {
|
||||
const membership: { id: number | bigint} = await this.instance`
|
||||
INSERT OR IGNORE INTO membership (user_id, group_id) VALUES (${userId}, ${groupId})
|
||||
RETURNING *
|
||||
`;
|
||||
return membership;
|
||||
}
|
||||
|
||||
public async getGroupMembers(groupId: number): Promise<User[]> {
|
||||
const users: User[] = await this.instance`
|
||||
SELECT *
|
||||
FROM users u
|
||||
JOIN membership m ON u.id = m.user_id
|
||||
WHERE m.group_id = ${groupId}
|
||||
`;
|
||||
return users;
|
||||
}
|
||||
|
||||
constructor(url: string = './data.sqlite') {
|
||||
if (this.instance) {
|
||||
this.instance = new Database(url);
|
||||
this.instance = new SQL(url, {adapter: 'sqlite'});
|
||||
this.prepareDB();
|
||||
console.log('Database initialized at', url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DB;
|
||||
Reference in New Issue
Block a user