From 6a994cee31953a72cd6200d4da58856f9835dcca Mon Sep 17 00:00:00 2001 From: Trit0 Date: Sun, 15 Feb 2026 16:09:25 -0500 Subject: [PATCH] wip new thing --- eslint.config.cjs | 20 -- eslint.config.mjs | 44 ++++ package-lock.json | 270 +++++++++++++------- package.json | 3 +- src/App.vue | 20 +- src/assets/base.css | 17 +- src/assets/main.css | 41 +-- src/components/DateInput.vue | 33 ++- src/components/Errors.vue | 4 +- src/components/Header.vue | 32 +++ src/components/HelloWorld.vue | 4 +- src/components/Loader.vue | 1 - src/components/Sidebar.vue | 51 ++++ src/components/TheWelcome.vue | 133 +++++----- src/components/__tests__/HelloWorld.spec.js | 11 - src/dtos/auth.dto.ts | 10 +- src/dtos/upload-game.dto.ts | 64 +++-- src/dtos/validate-metadata.dto.ts | 68 ++--- src/interfaces/auth.ts | 15 +- src/interfaces/game.ts | 8 +- src/interfaces/user.ts | 7 + src/main.ts | 3 +- src/router/index.ts | 47 ++-- src/services/auth.service.ts | 7 +- src/services/base-service.ts | 40 +-- src/services/game.service.ts | 15 +- src/services/user.service.ts | 12 + src/shims-vue.d.ts | 8 +- src/stores/auth.ts | 8 +- src/stores/errors.ts | 26 +- src/stores/player-auth.ts | 39 ++- src/stores/users.ts | 13 + src/utils/blob.util.ts | 4 +- src/utils/conj.util.ts | 45 ++-- src/utils/file-saver.ts | 22 +- src/utils/uuid.util.ts | 10 +- src/utils/zip.util.ts | 198 +++++++------- src/views/Limited.vue | 17 +- src/views/Member.vue | 119 ++------- src/views/NotFound.vue | 32 +-- src/views/auths/Auth.vue | 24 +- src/views/auths/Login.vue | 25 +- src/views/auths/SignUp.vue | 41 +-- src/views/games/GameView.vue | 117 ++++----- src/views/games/GamesView.vue | 31 ++- src/views/games/ManualUploadView.vue | 199 ++++++++------- src/views/games/UploadView.vue | 14 +- src/views/players/Close.vue | 10 +- src/views/players/QrAction.vue | 78 +++--- src/views/users/UsersView.vue | 49 ++++ 50 files changed, 1179 insertions(+), 930 deletions(-) delete mode 100644 eslint.config.cjs create mode 100644 eslint.config.mjs create mode 100644 src/components/Header.vue create mode 100644 src/components/Sidebar.vue delete mode 100644 src/components/__tests__/HelloWorld.spec.js create mode 100644 src/interfaces/user.ts create mode 100644 src/services/user.service.ts create mode 100644 src/stores/users.ts create mode 100644 src/views/users/UsersView.vue diff --git a/eslint.config.cjs b/eslint.config.cjs deleted file mode 100644 index 4579f7d..0000000 --- a/eslint.config.cjs +++ /dev/null @@ -1,20 +0,0 @@ -/* eslint-env node */ -// require('@rushstack/eslint-patch/modern-module-resolution') - -module.exports = { - root: true, - extends: [ - 'plugin:vue/vue3-essential', - 'eslint:recommended', - '@vue/eslint-config-prettier/skip-formatting', - 'plugin:@typescript-eslint/recommended' - ], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - parser: '@typescript-eslint/parser' - }, - plugins: [ - '@typescript-eslint/eslint-plugin' - ] -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..3c9e9cc --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,44 @@ +// @ts-check + +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import vue from 'eslint-plugin-vue'; +import prettier from '@vue/eslint-config-prettier'; +import globals from 'globals'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + ...vue.configs['flat/vue3-essential'], + // This should be last to override other formatting rules + prettier, + { + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + globals: { + ...globals.browser, + ...globals.node, + // Vue compiler macros + 'defineProps': 'readonly', + 'defineEmits': 'readonly', + 'defineExpose': 'readonly', + 'withDefaults': 'readonly' + } + } + }, + { + // Limit linting to specific file types + files: ['src/**/*.vue', 'src/**/*.js', 'src/**/*.ts'], + }, + { + // Ignore files from linting + ignores: [ + 'node_modules', + 'dist', + 'src/**/*.d.ts', + '*.config.js', + '*.config.cjs' + ], + } +); diff --git a/package-lock.json b/package-lock.json index 8831201..fb7547f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "sass": "^1.68.0", "tailwindcss": "^3.3.3", "typescript": "^5.7.3", + "typescript-eslint": "^8.55.0", "vite": "^5.4.14", "vitest": "^3.0.8", "vue-tsc": "^2.2.2" @@ -522,9 +523,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -1707,20 +1708,19 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz", - "integrity": "sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.55.0.tgz", + "integrity": "sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.24.1", - "@typescript-eslint/type-utils": "8.24.1", - "@typescript-eslint/utils": "8.24.1", - "@typescript-eslint/visitor-keys": "8.24.1", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/type-utils": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.0.1" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1730,22 +1730,31 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "@typescript-eslint/parser": "^8.55.0", "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.24.1.tgz", - "integrity": "sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.55.0.tgz", + "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.24.1", - "@typescript-eslint/types": "8.24.1", - "@typescript-eslint/typescript-estree": "8.24.1", - "@typescript-eslint/visitor-keys": "8.24.1", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1756,17 +1765,38 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.55.0.tgz", + "integrity": "sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.55.0", + "@typescript-eslint/types": "^8.55.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz", - "integrity": "sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.55.0.tgz", + "integrity": "sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.24.1", - "@typescript-eslint/visitor-keys": "8.24.1" + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1776,16 +1806,33 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.55.0.tgz", + "integrity": "sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz", - "integrity": "sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.55.0.tgz", + "integrity": "sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.24.1", - "@typescript-eslint/utils": "8.24.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.0.1" + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1796,13 +1843,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.24.1.tgz", - "integrity": "sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.55.0.tgz", + "integrity": "sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1813,19 +1860,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz", - "integrity": "sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.55.0.tgz", + "integrity": "sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.24.1", - "@typescript-eslint/visitor-keys": "8.24.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" + "@typescript-eslint/project-service": "8.55.0", + "@typescript-eslint/tsconfig-utils": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/visitor-keys": "8.55.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1835,7 +1883,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -1863,15 +1911,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.24.1.tgz", - "integrity": "sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.55.0.tgz", + "integrity": "sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.24.1", - "@typescript-eslint/types": "8.24.1", - "@typescript-eslint/typescript-estree": "8.24.1" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.55.0", + "@typescript-eslint/types": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1882,17 +1930,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.24.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz", - "integrity": "sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.55.0.tgz", + "integrity": "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.24.1", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.55.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1903,9 +1951,9 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2892,9 +2940,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dependencies": { "ms": "^2.1.3" }, @@ -4028,12 +4076,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5444,9 +5486,9 @@ } }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -5854,6 +5896,51 @@ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tinypool": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", @@ -5920,9 +6007,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", - "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "engines": { "node": ">=18.12" @@ -5984,14 +6071,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.24.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.24.1.tgz", - "integrity": "sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA==", + "version": "8.55.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.55.0.tgz", + "integrity": "sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.24.1", - "@typescript-eslint/parser": "8.24.1", - "@typescript-eslint/utils": "8.24.1" + "@typescript-eslint/eslint-plugin": "8.55.0", + "@typescript-eslint/parser": "8.55.0", + "@typescript-eslint/typescript-estree": "8.55.0", + "@typescript-eslint/utils": "8.55.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6002,7 +6090,7 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/undici-types": { diff --git a/package.json b/package.json index 83d935f..03a4a70 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "vue-tsc --noEmit && vite build", "preview": "vite preview", "test:unit": "vitest", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", + "lint": "eslint . --fix", "format": "prettier --write src/" }, "dependencies": { @@ -43,6 +43,7 @@ "sass": "^1.68.0", "tailwindcss": "^3.3.3", "typescript": "^5.7.3", + "typescript-eslint": "^8.55.0", "vite": "^5.4.14", "vitest": "^3.0.8", "vue-tsc": "^2.2.2" diff --git a/src/App.vue b/src/App.vue index 54e9aaa..1fc9712 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,23 +1,23 @@ diff --git a/src/assets/base.css b/src/assets/base.css index 77e47c0..e24fb69 100644 --- a/src/assets/base.css +++ b/src/assets/base.css @@ -1,18 +1,18 @@ /* color palette from */ :root { - --vt-c-white: #FDFDFF; + --vt-c-white: #fdfdff; --vt-c-white-soft: #f8f8f8; --vt-c-white-mute: #f2f2f2; - --vt-c-black: #171A1B; - --vt-c-black-soft: #2C2F31; - --vt-c-black-mute: #393D3F; + --vt-c-black: #171a1b; + --vt-c-black-soft: #2c2f31; + --vt-c-black-mute: #393d3f; --vt-c-payne: 135, 151, 163; - --vt-c-bittersweet: #C14953; - --vt-c-silver: #C6C5B9; - --vt-c-munsell: #8EC5CC; + --vt-c-bittersweet: #c14953; + --vt-c-silver: #c6c5b9; + --vt-c-munsell: #8ec5cc; --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); @@ -23,7 +23,6 @@ --vt-c-text-light-2: rgba(60, 60, 60, 0.66); --vt-c-text-dark-1: var(--vt-c-white); --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); - } /* semantic color variables for this project */ @@ -91,4 +90,4 @@ body { } @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; diff --git a/src/assets/main.css b/src/assets/main.css index c54ada9..f59a510 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -1,47 +1,34 @@ @import 'base.css'; - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - gap: 1rem; - font-weight: normal; -} - - a.router-link-active { - color: var(--vt-c-munsell) !important; + color: var(--vt-c-munsell) !important; } - a { - text-decoration: none; - color: var(--vt-c-silver); - transition: 0.4s; + text-decoration: none; + color: var(--vt-c-silver); + transition: 0.4s; } - @media (hover: hover) { - /*a:hover,*/ - /*button:hover {*/ - /* background-color: var(--vt-c-payne);*/ - /*}*/ + /*a:hover,*/ + /*button:hover {*/ + /* background-color: var(--vt-c-payne);*/ + /*}*/ } - /* Example CSS for headings with additional styles */ h1 { - font-size: 2rem; - line-height: 4rem + font-size: 2rem; + line-height: 4rem; } h2 { - font-size: 1.5rem; - line-height: 3rem; + font-size: 1.5rem; + line-height: 3rem; } h3 { - font-size: 1.2rem; - line-height: 2.4rem; + font-size: 1.2rem; + line-height: 2.4rem; } diff --git a/src/components/DateInput.vue b/src/components/DateInput.vue index e2630e4..dc25a4e 100644 --- a/src/components/DateInput.vue +++ b/src/components/DateInput.vue @@ -18,10 +18,7 @@ @input="updateDate" > - + @@ -36,18 +33,18 @@ export default { value: { type: String, default() { - return "" - }, - }, + return '' + } + } }, data() { return { - menu: false, - }; + menu: false + } }, computed: { dateFormatted() { - return this.input ? new Date(this.input) : ""; + return this.input ? new Date(this.input) : '' }, getDate() { const date = this.input ? new Date(this.input) : new Date() @@ -59,16 +56,16 @@ export default { console.log(val) }, updateDate(val) { - this.menu = false; + this.menu = false console.log(val) this.input = val - }, - }, -}; + } + } +} \ No newline at end of file +.v-text-field input { + background-color: transparent !important; +} + diff --git a/src/components/Errors.vue b/src/components/Errors.vue index ead0b0b..155bffb 100644 --- a/src/components/Errors.vue +++ b/src/components/Errors.vue @@ -1,6 +1,6 @@ + + diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue index b6e7eda..ddb32e9 100644 --- a/src/components/HelloWorld.vue +++ b/src/components/HelloWorld.vue @@ -10,9 +10,7 @@ defineProps({ diff --git a/src/components/Loader.vue b/src/components/Loader.vue index b20e67b..b6b750d 100644 --- a/src/components/Loader.vue +++ b/src/components/Loader.vue @@ -161,7 +161,6 @@ defineProps({ diff --git a/src/components/TheWelcome.vue b/src/components/TheWelcome.vue index d470625..c3d3c43 100644 --- a/src/components/TheWelcome.vue +++ b/src/components/TheWelcome.vue @@ -9,83 +9,82 @@ import SupportIcon from './icons/IconSupport.vue' diff --git a/src/components/__tests__/HelloWorld.spec.js b/src/components/__tests__/HelloWorld.spec.js deleted file mode 100644 index 2533202..0000000 --- a/src/components/__tests__/HelloWorld.spec.js +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, it, expect } from 'vitest' - -import { mount } from '@vue/test-utils' -import HelloWorld from '../HelloWorld.vue' - -describe('HelloWorld', () => { - it('renders properly', () => { - const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } }) - expect(wrapper.text()).toContain('Hello Vitest') - }) -}) diff --git a/src/dtos/auth.dto.ts b/src/dtos/auth.dto.ts index 7d6875f..f9b25a8 100644 --- a/src/dtos/auth.dto.ts +++ b/src/dtos/auth.dto.ts @@ -1,9 +1,9 @@ export class AuthDto { - email: string; - password: string; + email: string + password: string public constructor(form: FormData) { - this.email = form.get("email") as string; - this.password = form.get("password") as string; + this.email = form.get('email') as string + this.password = form.get('password') as string } -} \ No newline at end of file +} diff --git a/src/dtos/upload-game.dto.ts b/src/dtos/upload-game.dto.ts index b4fa302..c992e80 100644 --- a/src/dtos/upload-game.dto.ts +++ b/src/dtos/upload-game.dto.ts @@ -1,50 +1,48 @@ export class UploadGameDto { - title: string; - description: string; - medias: Blob[]; - filesBlob: Blob; - files?: string; - genres: string[]; - developers: string[]; - publicRepositoryLink: string; - players: string; - release: string; - modification: string; - version: string; - leaderboard: boolean; - - thumbnail?: string; - image?: string; + title: string + description: string + medias: Blob[] + filesBlob: Blob + files?: string + genres: string[] + developers: string[] + publicRepositoryLink: string + players: string + release: string + modification: string + version: string + leaderboard: boolean + thumbnail?: string + image?: string constructor(formData: FormData, mediaFileList: FileList) { - this.title = formData.get("title") as string; - this.description = formData.get("description") as string; - this.publicRepositoryLink = formData.get("repo") as string; - this.players = formData.get("players") as string; - this.release = new Date(formData.get("release") as string).toISOString().split("T")[0]; - this.modification = new Date().toISOString().split("T")[0]; - this.version = formData.get("version") as string; - this.leaderboard = !!formData.get("leaderboard"); + this.title = formData.get('title') as string + this.description = formData.get('description') as string + this.publicRepositoryLink = formData.get('repo') as string + this.players = formData.get('players') as string + this.release = new Date(formData.get('release') as string).toISOString().split('T')[0] + this.modification = new Date().toISOString().split('T')[0] + this.version = formData.get('version') as string + this.leaderboard = !!formData.get('leaderboard') this.medias = [] for (let i = 0; i < mediaFileList.length; i++) { - this.medias[i] = mediaFileList[i]; + this.medias[i] = mediaFileList[i] } - this.filesBlob = formData.get("game") as Blob; - this.genres = (formData.get("genres") as string).split(",").map(s => s.trim()); - this.developers = (formData.get("devs") as string).split(",").map(s => s.trim()); + this.filesBlob = formData.get('game') as Blob + this.genres = (formData.get('genres') as string).split(',').map((s) => s.trim()) + this.developers = (formData.get('devs') as string).split(',').map((s) => s.trim()) } async setMediasAndFiles() { if (this.medias.length > 0) { - this.thumbnail = await this.medias[0].text(); + this.thumbnail = await this.medias[0].text() } if (this.medias.length > 1) { - this.image = await this.medias[1].text(); + this.image = await this.medias[1].text() } - this.files = await this.filesBlob.text(); + this.files = await this.filesBlob.text() } - -} \ No newline at end of file +} diff --git a/src/dtos/validate-metadata.dto.ts b/src/dtos/validate-metadata.dto.ts index 153497a..030e99b 100644 --- a/src/dtos/validate-metadata.dto.ts +++ b/src/dtos/validate-metadata.dto.ts @@ -1,39 +1,43 @@ export class ValidateMetadataDto { - id: string; - game: string; - description: string; - files: string; - genres: string[]; - developers: string[]; - publicRepositoryLink: string; - players: string; - release: string; - modification: string; - version: string; - leaderboard: boolean; - unityLibraryVersion?: string; + id: string + game: string + description: string + files: string + genres: string[] + developers: string[] + publicRepositoryLink: string + players: string + release: string + modification: string + version: string + leaderboard: boolean + unityLibraryVersion?: string - thumbnail: number[]; - image: number[]; + thumbnail: number[] + image: number[] - - constructor(formData: FormData, thumbnail: Uint8Array | null, image: Uint8Array | null, filePath: string, id: string) { + constructor( + formData: FormData, + thumbnail: Uint8Array | null, + image: Uint8Array | null, + filePath: string, + id: string + ) { this.id = id - this.game = formData.get("title") as string; - this.description = formData.get("description") as string; - this.publicRepositoryLink = formData.get("repo") as string; - this.players = formData.get("players") as string; - this.release = new Date(formData.get("release") as string).toISOString().split("T")[0]; - this.modification = new Date().toISOString().split("T")[0]; - this.version = formData.get("version") as string; - this.leaderboard = !!formData.get("leaderboard"); + this.game = formData.get('title') as string + this.description = formData.get('description') as string + this.publicRepositoryLink = formData.get('repo') as string + this.players = formData.get('players') as string + this.release = new Date(formData.get('release') as string).toISOString().split('T')[0] + this.modification = new Date().toISOString().split('T')[0] + this.version = formData.get('version') as string + this.leaderboard = !!formData.get('leaderboard') - this.thumbnail = !!thumbnail ? Array.from(thumbnail) : []; - this.image = !!image ? Array.from(image): []; - this.files = filePath; + this.thumbnail = !!thumbnail ? Array.from(thumbnail) : [] + this.image = !!image ? Array.from(image) : [] + this.files = filePath - this.genres = (formData.get("genres") as string).split(",").map(s => s.trim().toLowerCase()); - this.developers = (formData.get("devs") as string).split(",").map(s => s.trim()); + this.genres = (formData.get('genres') as string).split(',').map((s) => s.trim().toLowerCase()) + this.developers = (formData.get('devs') as string).split(',').map((s) => s.trim()) } - -} \ No newline at end of file +} diff --git a/src/interfaces/auth.ts b/src/interfaces/auth.ts index 938f224..88d33ec 100644 --- a/src/interfaces/auth.ts +++ b/src/interfaces/auth.ts @@ -1,6 +1,11 @@ -export interface AuthResponse { - id: number; - roles: string[]; - token: string; - username: string; +export interface Role { + id: number + label: string +} + +export interface AuthResponse { + id: number + role: Role + token: string + email: string } diff --git a/src/interfaces/game.ts b/src/interfaces/game.ts index df52fa6..b638201 100644 --- a/src/interfaces/game.ts +++ b/src/interfaces/game.ts @@ -1,7 +1,7 @@ export interface Game { - id: number; - title: string; - description: string; - thumbnail: any; + id: number + title: string + description: string + thumbnail: any active: boolean } diff --git a/src/interfaces/user.ts b/src/interfaces/user.ts new file mode 100644 index 0000000..00b5810 --- /dev/null +++ b/src/interfaces/user.ts @@ -0,0 +1,7 @@ +import { Role } from "./auth" + +export interface User { + id: number + email: string + role: Role +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 54bb79e..1274d7b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,14 +9,13 @@ import * as components from 'vuetify/components' import * as directives from 'vuetify/directives' import DateFnsAdapter from '@date-io/date-fns' - import App from './App.vue' import router from './router' const vuetify = createVuetify({ components, directives, - theme: false, + theme: false }) const app = createApp(App) diff --git a/src/router/index.ts b/src/router/index.ts index 4efa632..4acfc35 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -3,7 +3,6 @@ import HomeView from '../views/HomeView.vue' import { useAuthStore } from '@/stores/auth' const router = createRouter({ - history: createWebHistory(import.meta.env.BASE_URL), routes: [ { @@ -26,27 +25,28 @@ const router = createRouter({ { path: '/games', name: 'games', - component: () => import('../views/games/GamesView.vue'), + component: () => import('../views/games/GamesView.vue') }, { path: '/upload', name: 'upload', - component: () => import('../views/games/UploadView.vue'), + component: () => import('../views/games/UploadView.vue') }, { path: '/manual-upload', name: 'manual-upload', - component: () => import('../views/games/ManualUploadView.vue'), - }, - { - path: '/events', - name: 'events', - component: () => import('../views/mqtt/Events.vue'), + component: () => import('../views/games/ManualUploadView.vue') }, { path: '/games/:gameId', name: 'game', - component: () => import('../views/games/GameView.vue'), + component: () => import('../views/games/GameView.vue') + }, + { + path: '/users', + name: 'users', + component: () => import('../views/users/UsersView.vue'), + meta: { requiresAdmin: true } } ] }, @@ -60,12 +60,12 @@ const router = createRouter({ { path: '/login', name: 'login', - component: () => import('../views/auths/Login.vue'), + component: () => import('../views/auths/Login.vue') }, { path: '/sign-up', name: 'sign-up', - component: () => import('../views/auths/SignUp.vue'), + component: () => import('../views/auths/SignUp.vue') } ] }, @@ -96,10 +96,27 @@ const router = createRouter({ }) router.beforeEach((to, from, next) => { - if (to.meta.requiresAuth === undefined) - next() + const authStore = useAuthStore() + const isLoggedIn = authStore.isAuth() + const userRole = authStore.getAuth()?.role?.label - if (useAuthStore().isAuth() === to.meta.requiresAuth) { + // Handle routes that require admin access + if (to.meta.requiresAdmin) { + if (isLoggedIn && userRole === 'Admin') { + next() // Allow access + } else { + next('/games') // Redirect non-admins + } + return + } + + // Handle general authentication requirements + if (to.meta.requiresAuth === undefined) { + next() + return + } + + if (isLoggedIn === to.meta.requiresAuth) { // If the condition is met, allow access to the route next() } else if (to.meta.requiresAuth) { diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 69fb0a4..ec237c4 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -2,12 +2,11 @@ import { AuthDto } from '@/dtos/auth.dto' import { BaseService } from '@/services/base-service' export class AuthService extends BaseService { - public async login(data: AuthDto): Promise { - return this.post('login', data); + return this.post('login', data) } public async signup(dto: AuthDto): Promise { - return this.post('signup', dto); + return this.post('signup', dto) } -} \ No newline at end of file +} diff --git a/src/services/base-service.ts b/src/services/base-service.ts index 0bd217c..efcfa35 100644 --- a/src/services/base-service.ts +++ b/src/services/base-service.ts @@ -1,28 +1,32 @@ - export class BaseService { protected apiUrl: string | undefined = import.meta.env.VITE_CONJUREOS_HOST protected baseHeaders: Record = { - 'Accept': 'application/json', + Accept: 'application/json', 'API-Version': '1' } protected jsonHeaders: Record = { - 'Content-Type': 'application/json', + 'Content-Type': 'application/json' } - public constructor() { - } + public constructor() {} protected async get(path: string, headers?: HeadersInit): Promise { - return await (await fetch(this.apiUrl + path, { - method: 'GET', - headers: { - ...headers, - ...this.baseHeaders - } - })).json() as T + return (await ( + await fetch(this.apiUrl + path, { + method: 'GET', + headers: { + ...headers, + ...this.baseHeaders + } + }) + ).json()) as T } - protected async post(path: string, body: T, headers?: HeadersInit): Promise { + protected async post( + path: string, + body: T, + headers?: HeadersInit + ): Promise { return await fetch(this.apiUrl + path, { method: 'POST', body: JSON.stringify(body) as any, @@ -31,7 +35,7 @@ export class BaseService { ...this.jsonHeaders, ...this.baseHeaders } - }); + }) } protected async postForm(path: string, body: FormData, headers?: HeadersInit): Promise { @@ -42,7 +46,7 @@ export class BaseService { ...headers, ...this.baseHeaders } - }); + }) } protected async put(path: string, body: T, headers?: HeadersInit): Promise { @@ -54,7 +58,7 @@ export class BaseService { ...this.jsonHeaders, ...this.baseHeaders } - }); + }) } protected async delete(path: string, headers?: HeadersInit): Promise { @@ -64,6 +68,6 @@ export class BaseService { ...headers, ...this.baseHeaders } - }); + }) } -} \ No newline at end of file +} diff --git a/src/services/game.service.ts b/src/services/game.service.ts index edabf81..50e7a93 100644 --- a/src/services/game.service.ts +++ b/src/services/game.service.ts @@ -5,23 +5,22 @@ import { useAuthStore } from '@/stores/auth' import { AuthDto } from '@/dtos/auth.dto' export class GameService extends BaseService { - public getGames(): Promise { - return this.get("games") + return this.get('games') } public getGame(gameId: string): Promise { return this.get(`games/${gameId}`) } - public async upload(game: FormData) : Promise { - const authStr = useAuthStore(); + public async upload(game: FormData): Promise { + const authStr = useAuthStore() return this.postForm(`games`, game, { - Authorization: `Bearer ${authStr.getAuth()?.token}`, - }); + Authorization: `Bearer ${authStr.getAuth()?.token}` + }) } - public async update(game: Game) : Promise { + public async update(game: Game): Promise { await this.put(`games`, null) } @@ -60,4 +59,4 @@ export class GameService extends BaseService { public async deleteGame(gameId: string): Promise { await this.delete(`games/${gameId}`) } -} \ No newline at end of file +} diff --git a/src/services/user.service.ts b/src/services/user.service.ts new file mode 100644 index 0000000..8bdeeb4 --- /dev/null +++ b/src/services/user.service.ts @@ -0,0 +1,12 @@ +import { User } from "@/interfaces/user"; +import { BaseService } from "./base-service"; +import { useAuthStore } from "@/stores/auth"; + +export class UserService extends BaseService { + public async getUsers(): Promise { + const authStr = useAuthStore() + return this.get('user', { + Authorization: `Bearer ${authStr.getAuth()?.token}` + }); + } +} \ No newline at end of file diff --git a/src/shims-vue.d.ts b/src/shims-vue.d.ts index 1818cde..0574993 100644 --- a/src/shims-vue.d.ts +++ b/src/shims-vue.d.ts @@ -1,6 +1,6 @@ -declare module "*.vue" { - import type {DefineComponent} from "vue"; +declare module '*.vue' { + import type { DefineComponent } from 'vue' // eslint-disable-next-line @typescript-eslint/no-explicit-any - const component: DefineComponent; - export default component; + const component: DefineComponent + export default component } diff --git a/src/stores/auth.ts b/src/stores/auth.ts index af4876f..56fb74b 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -4,17 +4,13 @@ import { AuthResponse } from '@/interfaces/auth' const key = 'AUTH' export const useAuthStore = defineStore('auth', () => { - const localStorageAuth = localStorage.getItem(key) const auth: Ref = ref(localStorageAuth ? JSON.parse(localStorageAuth) : null) - function set(_auth: AuthResponse) { auth.value = _auth - if (_auth) - localStorage.setItem(key, JSON.stringify(_auth)) - else - localStorage.removeItem(key) + if (_auth) localStorage.setItem(key, JSON.stringify(_auth)) + else localStorage.removeItem(key) } function isAuth() { diff --git a/src/stores/errors.ts b/src/stores/errors.ts index 4b0ad21..6fd456a 100644 --- a/src/stores/errors.ts +++ b/src/stores/errors.ts @@ -1,20 +1,18 @@ -import {defineStore} from "pinia"; -import {ref} from "vue"; +import { defineStore } from 'pinia' +import { ref } from 'vue' export const useErrorStore = defineStore('error', () => { - /** @type {(string[])} */ - const errors = ref([] as any[]) + /** @type {(string[])} */ + const errors = ref([] as any[]) + function unshift(error: string) { + console.error('Error:', error) + errors.value.unshift(error) + } - function unshift(error: string) { - console.error('Error:', error); - errors.value.unshift(error) - } + function getErrors() { + return errors + } - - function getErrors() { - return errors - } - - return { errors, getErrors, unshift} + return { errors, getErrors, unshift } }) diff --git a/src/stores/player-auth.ts b/src/stores/player-auth.ts index 215a825..48a665d 100644 --- a/src/stores/player-auth.ts +++ b/src/stores/player-auth.ts @@ -1,28 +1,25 @@ -import {defineStore} from "pinia"; -import {ref} from "vue"; +import { defineStore } from 'pinia' +import { ref } from 'vue' -const key = "AUTH" +const key = 'AUTH' export const usePlayerAuthStore = defineStore('auth', () => { - /** @type {(undefined | string | any)} */ - const auth = ref(JSON.parse(localStorage.getItem(key)) || undefined) + /** @type {(undefined | string | any)} */ + const auth = ref(JSON.parse(localStorage.getItem(key)) || undefined) + /** @param {(undefined | string)} auth */ + function set(auth) { + this.auth = auth + if (auth) localStorage.setItem(key, auth.toString()) + else localStorage.removeItem(key) + } - /** @param {(undefined | string)} auth */ - function set(auth) { - this.auth = auth - if (auth) - localStorage.setItem(key, auth.toString()) - else - localStorage.removeItem(key) - } + function isAuth() { + return !!this.auth + } - function isAuth() { - return !!this.auth - } + function getAuth() { + return this.auth + } - function getAuth() { - return this.auth - } - - return {auth, getAuth, set, isAuth} + return { auth, getAuth, set, isAuth } }) diff --git a/src/stores/users.ts b/src/stores/users.ts new file mode 100644 index 0000000..b1a4b44 --- /dev/null +++ b/src/stores/users.ts @@ -0,0 +1,13 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { User } from '@/interfaces/user' + +export const useUsersStore = defineStore('users', () => { + const list = ref([] as User[]) + + function set(_list: User[]) { + list.value = _list + } + + return { list, set } +}) diff --git a/src/utils/blob.util.ts b/src/utils/blob.util.ts index 974918a..852458e 100644 --- a/src/utils/blob.util.ts +++ b/src/utils/blob.util.ts @@ -1,8 +1,8 @@ export class BlobUtil { static getFileName(file: File | Blob): string { if (file instanceof File) { - return file.name; + return file.name } return (file as File).name } -} \ No newline at end of file +} diff --git a/src/utils/conj.util.ts b/src/utils/conj.util.ts index 2e3aa96..82e1f22 100644 --- a/src/utils/conj.util.ts +++ b/src/utils/conj.util.ts @@ -1,46 +1,51 @@ export class ConjUtil { static scoreExecutable(gameTitle: string, fileName: string): number { - const normalizedTitle = gameTitle.toLowerCase().replace(/[^a-z0-9]/g, ''); // Remove spaces & special chars - const normalizedFile = fileName.toLowerCase().replace(/[^a-z0-9]/g, ''); + const normalizedTitle = gameTitle.toLowerCase().replace(/[^a-z0-9]/g, '') // Remove spaces & special chars + const normalizedFile = fileName.toLowerCase().replace(/[^a-z0-9]/g, '') - let score = 0; + let score = 0 // ✅ High score if exact match (ignoring case & symbols) - if (normalizedFile === normalizedTitle) score += 100; + if (normalizedFile === normalizedTitle) score += 100 // ✅ Reward filenames that contain the game title - if (normalizedFile.includes(normalizedTitle)) score += 50; + if (normalizedFile.includes(normalizedTitle)) score += 50 // ✅ Favor files that end in the game title - if (normalizedFile.endsWith(normalizedTitle)) score += 25; + if (normalizedFile.endsWith(normalizedTitle)) score += 25 // ✅ Favor filenames that are short (avoid engine-related long names) - score += Math.max(0, 20 - normalizedFile.length / 5); + score += Math.max(0, 20 - normalizedFile.length / 5) // ❌ Penalize common engine-related executables const engineFiles = [ - "unitycrashhandler", "unrealengine", "unrealeditor", - "launcher", "updater", "configtool", "settings" - ]; - if (engineFiles.some(engine => normalizedFile.includes(engine))) score -= 50; + 'unitycrashhandler', + 'unrealengine', + 'unrealeditor', + 'launcher', + 'updater', + 'configtool', + 'settings' + ] + if (engineFiles.some((engine) => normalizedFile.includes(engine))) score -= 50 - return score; + return score } static findBestExecutable(gameTitle: string, exeList: string[]): string | null { - if (exeList.length === 0) return null; + if (exeList.length === 0) return null - let bestMatch = exeList[0]; - let highestScore = -Infinity; + let bestMatch = exeList[0] + let highestScore = -Infinity for (const exe of exeList) { - const score = this.scoreExecutable(gameTitle, exe); + const score = this.scoreExecutable(gameTitle, exe) if (score > highestScore) { - highestScore = score; - bestMatch = exe; + highestScore = score + bestMatch = exe } } - return bestMatch; + return bestMatch } -} \ No newline at end of file +} diff --git a/src/utils/file-saver.ts b/src/utils/file-saver.ts index 9b8587f..cd6d64b 100644 --- a/src/utils/file-saver.ts +++ b/src/utils/file-saver.ts @@ -1,17 +1,17 @@ export class FileSaver { static saveFile(file: File) { - const a = document.createElement('a'); - document.body.appendChild(a); - a.style.display = 'none'; - const url = window.URL.createObjectURL(file); - a.href = url; - a.download = file.name; - a.click(); - window.URL.revokeObjectURL(url); - document.body.removeChild(a); + const a = document.createElement('a') + document.body.appendChild(a) + a.style.display = 'none' + const url = window.URL.createObjectURL(file) + a.href = url + a.download = file.name + a.click() + window.URL.revokeObjectURL(url) + document.body.removeChild(a) } static saveBlob(blob: Blob, filename: string) { - return this.saveFile(new File([blob], filename)); + return this.saveFile(new File([blob], filename)) } -} \ No newline at end of file +} diff --git a/src/utils/uuid.util.ts b/src/utils/uuid.util.ts index 3c960ed..de89537 100644 --- a/src/utils/uuid.util.ts +++ b/src/utils/uuid.util.ts @@ -1,9 +1,9 @@ export class GuidUtil { static generateUUIDv4(): string { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => { - const random = Math.random() * 16 | 0; - const value = char === 'x' ? random : (random & 0x3 | 0x8); - return value.toString(16); - }); + const random = (Math.random() * 16) | 0 + const value = char === 'x' ? random : (random & 0x3) | 0x8 + return value.toString(16) + }) } -} \ No newline at end of file +} diff --git a/src/utils/zip.util.ts b/src/utils/zip.util.ts index 1f52cfe..5c1af72 100644 --- a/src/utils/zip.util.ts +++ b/src/utils/zip.util.ts @@ -1,147 +1,163 @@ export class ZipUtil { zipParts: Uint8Array[] = [] - centralDirectory: Uint8Array[] = []; - offset: number = 0; + centralDirectory: Uint8Array[] = [] + offset: number = 0 private crc32(buffer: Uint8Array): number { - let table = new Uint32Array(256); + let table = new Uint32Array(256) for (let i = 0; i < 256; i++) { - let c = i; + let c = i for (let j = 0; j < 8; j++) { - c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); + c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1 } - table[i] = c; + table[i] = c } - let crc = 0xFFFFFFFF; + let crc = 0xffffffff for (let i = 0; i < buffer.length; i++) { - crc = (crc >>> 8) ^ table[(crc ^ buffer[i]) & 0xFF]; + crc = (crc >>> 8) ^ table[(crc ^ buffer[i]) & 0xff] } - return ~crc >>> 0; + return ~crc >>> 0 } private createFileHeader(fileName: string, fileData: ArrayBuffer): Uint8Array { - const encodedFileName = new TextEncoder().encode(fileName); - const crc = this.crc32(new Uint8Array(fileData)); - const fileSize = fileData.byteLength; - const header = new Uint8Array(30 + encodedFileName.length); - const view = new DataView(header.buffer); + const encodedFileName = new TextEncoder().encode(fileName) + const crc = this.crc32(new Uint8Array(fileData)) + const fileSize = fileData.byteLength + const header = new Uint8Array(30 + encodedFileName.length) + const view = new DataView(header.buffer) - view.setUint32(0, 0x04034b50, true); // Local file header signature - view.setUint16(4, 20, true); // Version - view.setUint16(6, 0, true); // General purpose flag - view.setUint16(8, 0, true); // Compression method (0 = no compression) - view.setUint16(10, 0, true); // File modification time - view.setUint16(12, 0, true); // File modification date - view.setUint32(14, crc, true); // CRC-32 - view.setUint32(18, fileSize, true); // Compressed size - view.setUint32(22, fileSize, true); // Uncompressed size - view.setUint16(26, encodedFileName.length, true); // Filename length - view.setUint16(28, 0, true); // Extra field length - header.set(encodedFileName, 30); + view.setUint32(0, 0x04034b50, true) // Local file header signature + view.setUint16(4, 20, true) // Version + view.setUint16(6, 0, true) // General purpose flag + view.setUint16(8, 0, true) // Compression method (0 = no compression) + view.setUint16(10, 0, true) // File modification time + view.setUint16(12, 0, true) // File modification date + view.setUint32(14, crc, true) // CRC-32 + view.setUint32(18, fileSize, true) // Compressed size + view.setUint32(22, fileSize, true) // Uncompressed size + view.setUint16(26, encodedFileName.length, true) // Filename length + view.setUint16(28, 0, true) // Extra field length + header.set(encodedFileName, 30) - return header; + return header } - private createCentralDirectoryEntry(fileName: string, fileSize: number, crc: number, fileOffset: number): Uint8Array { - const encodedFileName = new TextEncoder().encode(fileName); - const entry = new Uint8Array(46 + encodedFileName.length); - const view = new DataView(entry.buffer); + private createCentralDirectoryEntry( + fileName: string, + fileSize: number, + crc: number, + fileOffset: number + ): Uint8Array { + const encodedFileName = new TextEncoder().encode(fileName) + const entry = new Uint8Array(46 + encodedFileName.length) + const view = new DataView(entry.buffer) - view.setUint32(0, 0x02014b50, true); // Central file header signature - view.setUint16(4, 20, true); // Version - view.setUint16(6, 20, true); // Version needed to extract - view.setUint16(8, 0, true); // General purpose flag - view.setUint16(10, 0, true); // Compression method (0 = no compression) - view.setUint16(12, 0, true); // File modification time - view.setUint16(14, 0, true); // File modification date - view.setUint32(16, crc, true); // CRC-32 - view.setUint32(20, fileSize, true); // Compressed size - view.setUint32(24, fileSize, true); // Uncompressed size - view.setUint16(28, encodedFileName.length, true); // Filename length - view.setUint16(30, 0, true); // Extra field length - view.setUint16(32, 0, true); // File comment length - view.setUint16(34, 0, true); // Disk number start - view.setUint16(36, 0, true); // Internal file attributes - view.setUint32(38, 0, true); // External file attributes - view.setUint32(42, fileOffset, true); // Relative offset of local header - entry.set(encodedFileName, 46); + view.setUint32(0, 0x02014b50, true) // Central file header signature + view.setUint16(4, 20, true) // Version + view.setUint16(6, 20, true) // Version needed to extract + view.setUint16(8, 0, true) // General purpose flag + view.setUint16(10, 0, true) // Compression method (0 = no compression) + view.setUint16(12, 0, true) // File modification time + view.setUint16(14, 0, true) // File modification date + view.setUint32(16, crc, true) // CRC-32 + view.setUint32(20, fileSize, true) // Compressed size + view.setUint32(24, fileSize, true) // Uncompressed size + view.setUint16(28, encodedFileName.length, true) // Filename length + view.setUint16(30, 0, true) // Extra field length + view.setUint16(32, 0, true) // File comment length + view.setUint16(34, 0, true) // Disk number start + view.setUint16(36, 0, true) // Internal file attributes + view.setUint32(38, 0, true) // External file attributes + view.setUint32(42, fileOffset, true) // Relative offset of local header + entry.set(encodedFileName, 46) - return entry; + return entry } createEndOfCentralDirectory() { - const eocd = new Uint8Array(22); - const view = new DataView(eocd.buffer); - view.setUint32(0, 0x06054b50, true); // End of central directory signature - view.setUint16(4, 0, true); // Number of this disk - view.setUint16(6, 0, true); // Disk where central directory starts - view.setUint16(8, this.centralDirectory.length, true); // Number of central directory records on this disk - view.setUint16(10, this.centralDirectory.length, true); // Total number of central directory records - view.setUint32(12, this.centralDirectory.reduce((sum, entry) => sum + entry.length, 0), true); // Size of central directory - view.setUint32(16, this.offset, true); // Offset of central directory - view.setUint16(20, 0, true); // Comment length - return eocd; + const eocd = new Uint8Array(22) + const view = new DataView(eocd.buffer) + view.setUint32(0, 0x06054b50, true) // End of central directory signature + view.setUint16(4, 0, true) // Number of this disk + view.setUint16(6, 0, true) // Disk where central directory starts + view.setUint16(8, this.centralDirectory.length, true) // Number of central directory records on this disk + view.setUint16(10, this.centralDirectory.length, true) // Total number of central directory records + view.setUint32( + 12, + this.centralDirectory.reduce((sum, entry) => sum + entry.length, 0), + true + ) // Size of central directory + view.setUint32(16, this.offset, true) // Offset of central directory + view.setUint16(20, 0, true) // Comment length + return eocd } async addFile(filePath: string, fileBlob: Blob): Promise { - const fileData = await fileBlob.arrayBuffer(); - const fileHeader = this.createFileHeader(filePath, fileData); - this.zipParts.push(fileHeader, new Uint8Array(fileData)); + const fileData = await fileBlob.arrayBuffer() + const fileHeader = this.createFileHeader(filePath, fileData) + this.zipParts.push(fileHeader, new Uint8Array(fileData)) - const fileOffset = this.offset; - this.offset += fileHeader.length + fileData.byteLength; - this.centralDirectory.push(this.createCentralDirectoryEntry(filePath, fileData.byteLength, this.crc32(new Uint8Array(fileData)), fileOffset)); + const fileOffset = this.offset + this.offset += fileHeader.length + fileData.byteLength + this.centralDirectory.push( + this.createCentralDirectoryEntry( + filePath, + fileData.byteLength, + this.crc32(new Uint8Array(fileData)), + fileOffset + ) + ) } - + getZipBlob(): Blob { - this.zipParts.push(...this.centralDirectory, this.createEndOfCentralDirectory()); - return new Blob(this.zipParts, { type: "application/zip" }); + this.zipParts.push(...this.centralDirectory, this.createEndOfCentralDirectory()) + return new Blob(this.zipParts, { type: 'application/zip' }) } static async createZipBlob(files: { filePath: string; fileBlob: Blob }[]): Promise { - const util = new ZipUtil(); + const util = new ZipUtil() for (const { filePath, fileBlob } of files) { - await util.addFile(filePath, fileBlob); + await util.addFile(filePath, fileBlob) } - return util.getZipBlob(); + return util.getZipBlob() } static async getZipFileTree(zipBlob: Blob): Promise { - const fileTree: string[] = []; + const fileTree: string[] = [] // Read ZIP Blob into an ArrayBuffer - const arrayBuffer = await zipBlob.arrayBuffer(); - const dataView = new DataView(arrayBuffer); - let offset = arrayBuffer.byteLength - 22; // Start near the end of the ZIP file + const arrayBuffer = await zipBlob.arrayBuffer() + const dataView = new DataView(arrayBuffer) + let offset = arrayBuffer.byteLength - 22 // Start near the end of the ZIP file // Find End of Central Directory Record while (offset > 0) { - if (dataView.getUint32(offset, true) === 0x06054b50) break; // Signature for End of Central Directory - offset--; + if (dataView.getUint32(offset, true) === 0x06054b50) break // Signature for End of Central Directory + offset-- } - if (offset <= 0) throw new Error("Invalid ZIP file: No End of Central Directory found"); + if (offset <= 0) throw new Error('Invalid ZIP file: No End of Central Directory found') // Read the Central Directory Offset - const centralDirOffset = dataView.getUint32(offset + 16, true); - offset = centralDirOffset; + const centralDirOffset = dataView.getUint32(offset + 16, true) + offset = centralDirOffset // Parse Central Directory Headers to extract filenames while (offset < arrayBuffer.byteLength) { - if (dataView.getUint32(offset, true) !== 0x02014b50) break; // Central Directory File Header + if (dataView.getUint32(offset, true) !== 0x02014b50) break // Central Directory File Header - const fileNameLength = dataView.getUint16(offset + 28, true); - const extraFieldLength = dataView.getUint16(offset + 30, true); - const commentLength = dataView.getUint16(offset + 32, true); + const fileNameLength = dataView.getUint16(offset + 28, true) + const extraFieldLength = dataView.getUint16(offset + 30, true) + const commentLength = dataView.getUint16(offset + 32, true) // Extract the filename - const fileNameBytes = new Uint8Array(arrayBuffer, offset + 46, fileNameLength); - const fileName = new TextDecoder().decode(fileNameBytes); - fileTree.push(fileName); + const fileNameBytes = new Uint8Array(arrayBuffer, offset + 46, fileNameLength) + const fileName = new TextDecoder().decode(fileNameBytes) + fileTree.push(fileName) // Move to the next entry - offset += 46 + fileNameLength + extraFieldLength + commentLength; + offset += 46 + fileNameLength + extraFieldLength + commentLength } - return fileTree; + return fileTree } } diff --git a/src/views/Limited.vue b/src/views/Limited.vue index eb8dca3..7e4f067 100644 --- a/src/views/Limited.vue +++ b/src/views/Limited.vue @@ -8,18 +8,26 @@ import Errors from '@/components/Errors.vue' const authStr = useAuthStore() const { auth } = storeToRefs(authStr) - diff --git a/src/views/NotFound.vue b/src/views/NotFound.vue index be6e6f9..4f956bd 100644 --- a/src/views/NotFound.vue +++ b/src/views/NotFound.vue @@ -1,19 +1,16 @@ \ No newline at end of file + diff --git a/src/views/auths/Auth.vue b/src/views/auths/Auth.vue index 94441b8..f42c4ff 100644 --- a/src/views/auths/Auth.vue +++ b/src/views/auths/Auth.vue @@ -1,5 +1,4 @@ @@ -9,7 +8,13 @@ import Errors from '@/components/Errors.vue'
- +
- +
- +
\ No newline at end of file + diff --git a/src/views/auths/Login.vue b/src/views/auths/Login.vue index 2024f2b..dc526b7 100644 --- a/src/views/auths/Login.vue +++ b/src/views/auths/Login.vue @@ -1,5 +1,4 @@