Compare commits
2 Commits
174e7e57eb
...
6a994cee31
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a994cee31 | ||
|
|
fddcd1fecd |
88
Dockerfile
88
Dockerfile
@ -1,75 +1,39 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Stage 1: Build the application
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
# Comments are provided throughout this file to help you get started.
|
||||
# If you need more help, visit the Dockerfile reference guide at
|
||||
# https://docs.docker.com/engine/reference/builder/
|
||||
WORKDIR /app
|
||||
|
||||
ARG NODE_VERSION=20.8.1
|
||||
# Copy package files and install dependencies
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
################################################################################
|
||||
# Use node image for base image for all stages.
|
||||
FROM node:${NODE_VERSION}-alpine as base
|
||||
|
||||
# Set working directory for all build stages.
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
|
||||
################################################################################
|
||||
# Create a stage for installing production dependecies.
|
||||
FROM base as deps
|
||||
|
||||
# Download dependencies as a separate step to take advantage of Docker's caching.
|
||||
# Leverage a cache mount to /root/.npm to speed up subsequent builds.
|
||||
# Leverage bind mounts to package.json and package-lock.json to avoid having to copy them
|
||||
# into this layer.
|
||||
RUN --mount=type=bind,source=package.json,target=package.json \
|
||||
--mount=type=bind,source=package-lock.json,target=package-lock.json \
|
||||
--mount=type=cache,target=/root/.npm \
|
||||
npm ci --omit=dev
|
||||
|
||||
################################################################################
|
||||
# Create a stage for building the application.
|
||||
FROM deps as build
|
||||
|
||||
# Download additional development dependencies before building, as some projects require
|
||||
# "devDependencies" to be installed to build. If you don't need this, remove this step.
|
||||
RUN --mount=type=bind,source=package.json,target=package.json \
|
||||
--mount=type=bind,source=package-lock.json,target=package-lock.json \
|
||||
--mount=type=cache,target=/root/.npm \
|
||||
npm ci
|
||||
|
||||
# Copy the rest of the source files into the image.
|
||||
# Copy the rest of the application source code
|
||||
COPY . .
|
||||
# Run the build script.
|
||||
|
||||
# Build the application for production
|
||||
RUN npm run build
|
||||
|
||||
################################################################################
|
||||
# Create a new stage to run the application with minimal runtime dependencies
|
||||
# where the necessary files are copied from the build stage.
|
||||
FROM base as final
|
||||
# Stage 2: Serve the application with a simple Node.js server
|
||||
FROM node:20-alpine
|
||||
|
||||
# Install http-server globally.
|
||||
WORKDIR /app
|
||||
|
||||
# Install a simple and lightweight HTTP server globally
|
||||
RUN npm install -g http-server
|
||||
RUN npm install -g vite
|
||||
|
||||
# Use production node environment by default.
|
||||
ENV NODE_ENV production
|
||||
ENV VITE_CONJUREOS_HOST http://142.137.247.118:8080/
|
||||
# Copy the built static assets from the builder stage
|
||||
COPY --from=builder /app/dist ./dist
|
||||
|
||||
# Run the application as a non-root user.
|
||||
USER node
|
||||
# Copy the custom entrypoint script to the image and make it executable
|
||||
COPY entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
|
||||
# Copy package.json so that package manager commands can be used.
|
||||
COPY package.json .
|
||||
COPY .env.production.local .env
|
||||
|
||||
# Copy the production dependencies from the deps stage and also
|
||||
# the built application from the build stage into the image.
|
||||
COPY --from=deps /usr/src/app/node_modules ./node_modules
|
||||
COPY --from=build /usr/src/app/dist ./dist
|
||||
|
||||
# Expose the port that the application listens on.
|
||||
# Expose the port the server will run on, matching your compose file
|
||||
EXPOSE 5174
|
||||
|
||||
# Run the application.
|
||||
CMD http-server dist -p 5174
|
||||
# The entrypoint will run at container startup, performing any necessary setup.
|
||||
# It should end with 'exec "$@"' to run the CMD.
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
# The CMD specifies the main process to run: the http-server.
|
||||
CMD ["http-server", "dist", "-p", "5174"]
|
||||
|
||||
@ -13,7 +13,7 @@ services:
|
||||
context: .
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
VITE_CONJUREOS_HOST: http://142.137.247.118:8080/
|
||||
VITE_CONJUREOS_HOST: http://host.docker.internal:8080/
|
||||
ports:
|
||||
- 5174:5174
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
ROOT_DIR=/usr/share/nginx/html
|
||||
ROOT_DIR=/app/dist
|
||||
|
||||
echo "Replacing env constants in JS"
|
||||
for file in $ROOT_DIR/js/app.*.js* $ROOT_DIR/index.html $ROOT_DIR/precache-manifest*.js;
|
||||
@ -9,4 +9,6 @@ do
|
||||
|
||||
sed -i 's|VITE_CONJUREOS_HOST|'${VITE_CONJUREOS_HOST}'|g' $file
|
||||
|
||||
done
|
||||
done
|
||||
|
||||
exec "$@"
|
||||
@ -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'
|
||||
]
|
||||
}
|
||||
44
eslint.config.mjs
Normal file
44
eslint.config.mjs
Normal file
@ -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'
|
||||
],
|
||||
}
|
||||
);
|
||||
511
package-lock.json
generated
511
package-lock.json
generated
@ -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"
|
||||
@ -87,12 +88,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz",
|
||||
"integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"version": "7.28.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
|
||||
"integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@ -507,9 +505,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
|
||||
"integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
|
||||
"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eslint-visitor-keys": "^3.4.3"
|
||||
@ -525,21 +523,21 @@
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz",
|
||||
"integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==",
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
|
||||
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint/object-schema": "^2.1.6",
|
||||
"@eslint/object-schema": "^2.1.7",
|
||||
"debug": "^4.3.1",
|
||||
"minimatch": "^3.1.2"
|
||||
},
|
||||
@ -547,10 +545,22 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-helpers": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
|
||||
"integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz",
|
||||
"integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==",
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
|
||||
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
@ -560,9 +570,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
|
||||
"integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
|
||||
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.4",
|
||||
@ -571,7 +581,7 @@
|
||||
"globals": "^14.0.0",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"strip-json-comments": "^3.1.1"
|
||||
},
|
||||
@ -583,9 +593,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/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"
|
||||
@ -595,14 +605,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/espree": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
|
||||
"integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.14.0",
|
||||
"acorn": "^8.15.0",
|
||||
"acorn-jsx": "^5.3.2",
|
||||
"eslint-visitor-keys": "^4.2.0"
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -624,48 +634,39 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz",
|
||||
"integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==",
|
||||
"version": "9.39.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
|
||||
"integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://eslint.org/donate"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/object-schema": {
|
||||
"version": "2.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
|
||||
"integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
|
||||
"integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz",
|
||||
"integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==",
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
|
||||
"integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.10.0",
|
||||
"@eslint/core": "^0.17.0",
|
||||
"levn": "^0.4.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz",
|
||||
"integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@ -715,9 +716,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/retry": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
|
||||
"integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
|
||||
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=18.18"
|
||||
@ -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,13 +1883,13 @@
|
||||
"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": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@ -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"
|
||||
@ -2223,9 +2271,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/language-core/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@ -2341,9 +2389,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@ -2550,9 +2598,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.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"
|
||||
},
|
||||
@ -3034,9 +3082,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/editorconfig/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@ -3202,31 +3250,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.20.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz",
|
||||
"integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==",
|
||||
"version": "9.39.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
"@eslint/config-array": "^0.19.0",
|
||||
"@eslint/core": "^0.11.0",
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "9.20.0",
|
||||
"@eslint/plugin-kit": "^0.2.5",
|
||||
"@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.2",
|
||||
"@eslint/plugin-kit": "^0.4.1",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.4.1",
|
||||
"@humanwhocodes/retry": "^0.4.2",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"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.2.0",
|
||||
"eslint-visitor-keys": "^4.2.0",
|
||||
"espree": "^10.3.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",
|
||||
@ -3371,9 +3419,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-import-x/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@ -3420,9 +3468,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-n/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@ -3567,9 +3615,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/eslint-scope": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
|
||||
"integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
|
||||
"integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
@ -3583,9 +3631,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/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"
|
||||
@ -3595,14 +3643,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/espree": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
|
||||
"integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
|
||||
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.14.0",
|
||||
"acorn": "^8.15.0",
|
||||
"acorn-jsx": "^5.3.2",
|
||||
"eslint-visitor-keys": "^4.2.0"
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -3844,14 +3892,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
@ -3942,9 +3991,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^3.1.2",
|
||||
@ -3972,9 +4022,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
@ -4026,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",
|
||||
@ -4347,9 +4391,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@ -4472,9 +4516,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lodash.castarray": {
|
||||
@ -5271,11 +5315,6 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
|
||||
},
|
||||
"node_modules/reinterval": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz",
|
||||
@ -5447,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"
|
||||
@ -5857,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",
|
||||
@ -5923,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"
|
||||
@ -5987,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"
|
||||
@ -6005,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": {
|
||||
@ -6086,9 +6171,9 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.14",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
|
||||
"integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
|
||||
"version": "5.4.21",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
|
||||
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.21.3",
|
||||
"postcss": "^8.4.43",
|
||||
|
||||
@ -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"
|
||||
|
||||
20
src/App.vue
20
src/App.vue
@ -1,23 +1,23 @@
|
||||
<script setup>
|
||||
import {RouterLink, RouterView, useRoute} from 'vue-router'
|
||||
import { RouterLink, RouterView, useRoute } from 'vue-router'
|
||||
|
||||
import {storeToRefs} from 'pinia';
|
||||
import {useAuthStore} from '@/stores/auth';
|
||||
import router from '@/router';
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import router from '@/router'
|
||||
import Errors from '@/components/Errors.vue'
|
||||
import {ref} from 'vue';
|
||||
import { ref } from 'vue'
|
||||
|
||||
const authStr = useAuthStore()
|
||||
|
||||
const {auth} = storeToRefs(authStr)
|
||||
const { auth } = storeToRefs(authStr)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterView/>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
footer {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
footer {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
|
||||
: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;
|
||||
@tailwind utilities;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -18,10 +18,7 @@
|
||||
@input="updateDate"
|
||||
></v-text-field>
|
||||
</template>
|
||||
<v-date-picker
|
||||
:model-value="getDate"
|
||||
@update:modelValue="updateDate"
|
||||
></v-date-picker>
|
||||
<v-date-picker :model-value="getDate" @update:modelValue="updateDate"></v-date-picker>
|
||||
</v-menu>
|
||||
</div>
|
||||
</template>
|
||||
@ -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
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-text-field input {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
</style>
|
||||
.v-text-field input {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { useErrorStore } from '@/stores/errors'
|
||||
import {storeToRefs} from "pinia";
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
let isOpen = ref(false)
|
||||
|
||||
@ -21,7 +21,7 @@ const { errors } = storeToRefs(errorStore)
|
||||
type="submit"
|
||||
@click="isOpen = !isOpen"
|
||||
>
|
||||
{{ errors.length }} error(s)
|
||||
{{ errors.length }} error(s)
|
||||
</button>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
32
src/components/Header.vue
Normal file
32
src/components/Header.vue
Normal file
@ -0,0 +1,32 @@
|
||||
<script setup>
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import router from '@/router'
|
||||
import { LogOutIcon } from 'lucide-vue-next'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const logout = () => {
|
||||
authStore.set(null)
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="flex h-16 items-center justify-between border-b px-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<RouterLink to="/">
|
||||
<img alt="Conjure logo" class="h-8" src="@/assets/logo_conjure_dark.png" />
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<button
|
||||
@click="logout"
|
||||
class="flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<LogOutIcon class="h-5 w-5" />
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
@ -10,9 +10,7 @@ defineProps({
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’re successfully a foken 🍞 in the ass.
|
||||
</h3>
|
||||
<h3>You’re successfully a foken 🍞 in the ass.</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -161,7 +161,6 @@ defineProps({
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
svg {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
51
src/components/Sidebar.vue
Normal file
51
src/components/Sidebar.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<aside :class="[' text-foreground p-4 transition-all duration-300 border-r', isCollapsed ? 'w-16' : 'w-64']">
|
||||
<div class="flex items-center" :class="isCollapsed ? 'justify-center' : 'justify-end'">
|
||||
<button @click="toggleCollapse" class="p-2 rounded-full hover:bg-muted">
|
||||
<PanelLeft v-if="!isCollapsed" class="h-6 w-6" />
|
||||
<PanelRight v-if="isCollapsed" class="h-6 w-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="mt-8">
|
||||
<div v-if="isAdmin" class="rounded-lg hover:bg-muted transition-colors duration-200 p-2">
|
||||
<RouterLink
|
||||
to="/users"
|
||||
class="flex items-center"
|
||||
:title="isCollapsed ? 'Users' : ''"
|
||||
>
|
||||
<Users class="h-6 w-6" />
|
||||
<span v-if="!isCollapsed" class="ml-4">Users</span>
|
||||
</RouterLink>
|
||||
</div>
|
||||
<!-- Other links can go here -->
|
||||
</nav>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { RouterLink } from 'vue-router';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { PanelLeft, PanelRight, Users } from 'lucide-vue-next';
|
||||
|
||||
const isCollapsed = ref(false);
|
||||
|
||||
function toggleCollapse() {
|
||||
isCollapsed.value = !isCollapsed.value;
|
||||
}
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const { auth } = storeToRefs(authStore);
|
||||
|
||||
const isAdmin = ref(auth.value?.role?.label === 'Admin');
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Add any specific styles for the sidebar here */
|
||||
.router-link-exact-active {
|
||||
background-color: hsl(var(--muted-foreground) / 0.2);
|
||||
}
|
||||
</style>
|
||||
@ -9,83 +9,82 @@ import SupportIcon from './icons/IconSupport.vue'
|
||||
|
||||
<template>
|
||||
<article>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<DocumentationIcon />
|
||||
</template>
|
||||
<template #heading>Documentation</template>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<DocumentationIcon />
|
||||
</template>
|
||||
<template #heading>Documentation</template>
|
||||
Vue’s
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||
provides you with all information you need to get started.
|
||||
</WelcomeItem>
|
||||
|
||||
Vue’s
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||
provides you with all information you need to get started.
|
||||
</WelcomeItem>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<ToolingIcon />
|
||||
</template>
|
||||
<template #heading>Tooling</template>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<ToolingIcon />
|
||||
</template>
|
||||
<template #heading>Tooling</template>
|
||||
This project is served and bundled with
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||
recommended IDE setup is
|
||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
|
||||
you need to test your components and web pages, check out
|
||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
|
||||
<a href="https://on.cypress.io/component" target="_blank" rel="noopener"
|
||||
>Cypress Component Testing</a
|
||||
>.
|
||||
|
||||
This project is served and bundled with
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||
recommended IDE setup is
|
||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
|
||||
you need to test your components and web pages, check out
|
||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
|
||||
<a href="https://on.cypress.io/component" target="_blank" rel="noopener"
|
||||
>Cypress Component Testing</a
|
||||
>.
|
||||
<br />
|
||||
|
||||
<br />
|
||||
More instructions are available in <code>README.md</code>.
|
||||
</WelcomeItem>
|
||||
|
||||
More instructions are available in <code>README.md</code>.
|
||||
</WelcomeItem>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<EcosystemIcon />
|
||||
</template>
|
||||
<template #heading>Ecosystem</template>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<EcosystemIcon />
|
||||
</template>
|
||||
<template #heading>Ecosystem</template>
|
||||
Get official tools and libraries for your project:
|
||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>.
|
||||
If you need more resources, we suggest paying
|
||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||
a visit.
|
||||
</WelcomeItem>
|
||||
|
||||
Get official tools and libraries for your project:
|
||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
||||
you need more resources, we suggest paying
|
||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||
a visit.
|
||||
</WelcomeItem>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<CommunityIcon />
|
||||
</template>
|
||||
<template #heading>Community</template>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<CommunityIcon />
|
||||
</template>
|
||||
<template #heading>Community</template>
|
||||
Got stuck? Ask your question on
|
||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
|
||||
Discord server, or
|
||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||
>StackOverflow</a
|
||||
>. You should also subscribe to
|
||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and
|
||||
follow the official
|
||||
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||
twitter account for latest news in the Vue world.
|
||||
</WelcomeItem>
|
||||
|
||||
Got stuck? Ask your question on
|
||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
|
||||
Discord server, or
|
||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||
>StackOverflow</a
|
||||
>. You should also subscribe to
|
||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
|
||||
the official
|
||||
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||
twitter account for latest news in the Vue world.
|
||||
</WelcomeItem>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<SupportIcon />
|
||||
</template>
|
||||
<template #heading>Support Vue</template>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<SupportIcon />
|
||||
</template>
|
||||
<template #heading>Support Vue</template>
|
||||
|
||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
||||
us by
|
||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||
</WelcomeItem>
|
||||
As an independent project, Vue relies on community backing for its sustainability. You can
|
||||
help us by
|
||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||
</WelcomeItem>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
@ -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')
|
||||
})
|
||||
})
|
||||
@ -1,9 +1,9 @@
|
||||
export class AuthDto {
|
||||
username: string;
|
||||
password: string;
|
||||
email: string
|
||||
password: string
|
||||
|
||||
public constructor(form: FormData) {
|
||||
this.username = form.get("username") as string;
|
||||
this.password = form.get("password") as string;
|
||||
this.email = form.get('email') as string
|
||||
this.password = form.get('password') as string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
7
src/interfaces/user.ts
Normal file
7
src/interfaces/user.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Role } from "./auth"
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
email: string
|
||||
role: Role
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<Response> {
|
||||
return this.post('login', data);
|
||||
return this.post('login', data)
|
||||
}
|
||||
|
||||
public async signup(dto: AuthDto): Promise<Response> {
|
||||
return this.post('signup', dto);
|
||||
return this.post('signup', dto)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,28 +1,32 @@
|
||||
|
||||
export class BaseService {
|
||||
protected apiUrl: string | undefined = import.meta.env.VITE_CONJUREOS_HOST
|
||||
protected baseHeaders: Record<string, string> = {
|
||||
'Accept': 'application/json',
|
||||
Accept: 'application/json',
|
||||
'API-Version': '1'
|
||||
}
|
||||
protected jsonHeaders: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
public constructor() {
|
||||
}
|
||||
public constructor() {}
|
||||
|
||||
protected async get<T>(path: string, headers?: HeadersInit): Promise<T> {
|
||||
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<T extends object>(path: string, body: T, headers?: HeadersInit): Promise<Response> {
|
||||
protected async post<T extends object>(
|
||||
path: string,
|
||||
body: T,
|
||||
headers?: HeadersInit
|
||||
): Promise<Response> {
|
||||
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<Response> {
|
||||
@ -42,7 +46,7 @@ export class BaseService {
|
||||
...headers,
|
||||
...this.baseHeaders
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
protected async put<T>(path: string, body: T, headers?: HeadersInit): Promise<Response> {
|
||||
@ -54,7 +58,7 @@ export class BaseService {
|
||||
...this.jsonHeaders,
|
||||
...this.baseHeaders
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
protected async delete(path: string, headers?: HeadersInit): Promise<Response> {
|
||||
@ -64,6 +68,6 @@ export class BaseService {
|
||||
...headers,
|
||||
...this.baseHeaders
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,23 +5,22 @@ import { useAuthStore } from '@/stores/auth'
|
||||
import { AuthDto } from '@/dtos/auth.dto'
|
||||
|
||||
export class GameService extends BaseService {
|
||||
|
||||
public getGames(): Promise<Game[]> {
|
||||
return this.get<Game[]>("games")
|
||||
return this.get<Game[]>('games')
|
||||
}
|
||||
|
||||
public getGame(gameId: string): Promise<Game> {
|
||||
return this.get<Game>(`games/${gameId}`)
|
||||
}
|
||||
|
||||
public async upload(game: FormData) : Promise<Response> {
|
||||
const authStr = useAuthStore();
|
||||
public async upload(game: FormData): Promise<Response> {
|
||||
const authStr = useAuthStore()
|
||||
return this.postForm(`games`, game, {
|
||||
Authorization: `Bearer ${authStr.getAuth()?.token}`,
|
||||
});
|
||||
Authorization: `Bearer ${authStr.getAuth()?.token}`
|
||||
})
|
||||
}
|
||||
|
||||
public async update(game: Game) : Promise<void> {
|
||||
public async update(game: Game): Promise<void> {
|
||||
await this.put<any>(`games`, null)
|
||||
}
|
||||
|
||||
@ -60,4 +59,4 @@ export class GameService extends BaseService {
|
||||
public async deleteGame(gameId: string): Promise<void> {
|
||||
await this.delete(`games/${gameId}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
src/services/user.service.ts
Normal file
12
src/services/user.service.ts
Normal file
@ -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<User[]> {
|
||||
const authStr = useAuthStore()
|
||||
return this.get<User[]>('user', {
|
||||
Authorization: `Bearer ${authStr.getAuth()?.token}`
|
||||
});
|
||||
}
|
||||
}
|
||||
8
src/shims-vue.d.ts
vendored
8
src/shims-vue.d.ts
vendored
@ -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<object, object, any>;
|
||||
export default component;
|
||||
const component: DefineComponent<object, object, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
@ -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<AuthResponse | null> = 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() {
|
||||
|
||||
@ -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 }
|
||||
})
|
||||
|
||||
@ -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 }
|
||||
})
|
||||
|
||||
13
src/stores/users.ts
Normal file
13
src/stores/users.ts
Normal file
@ -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 }
|
||||
})
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,147 +1,163 @@
|
||||
export class ZipUtil {
|
||||
zipParts: Uint8Array<ArrayBuffer>[] = []
|
||||
centralDirectory: Uint8Array<ArrayBuffer>[] = [];
|
||||
offset: number = 0;
|
||||
centralDirectory: Uint8Array<ArrayBuffer>[] = []
|
||||
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<ArrayBuffer> {
|
||||
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<ArrayBuffer> {
|
||||
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<ArrayBuffer> {
|
||||
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<void> {
|
||||
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<Blob> {
|
||||
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<string[]> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,18 +8,26 @@ import Errors from '@/components/Errors.vue'
|
||||
const authStr = useAuthStore()
|
||||
|
||||
const { auth } = storeToRefs(authStr)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<img alt="Conjure logo" class="logo" src="@/assets/logo_conjure_dark.png" width="2228" height="349" />
|
||||
<img
|
||||
alt="Conjure logo"
|
||||
class="logo"
|
||||
src="@/assets/logo_conjure_dark.png"
|
||||
width="2228"
|
||||
height="349"
|
||||
/>
|
||||
</header>
|
||||
|
||||
<RouterView />
|
||||
<footer>
|
||||
<router-link v-if="auth" to="/"
|
||||
class="bg-transparent text-primary font-semibold hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded w-full">
|
||||
<router-link
|
||||
v-if="auth"
|
||||
to="/"
|
||||
class="bg-transparent text-primary font-semibold hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded w-full"
|
||||
>
|
||||
To dashboard
|
||||
</router-link>
|
||||
<Errors />
|
||||
@ -27,7 +35,6 @@ const { auth } = storeToRefs(authStr)
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.logo {
|
||||
user-drag: none;
|
||||
-webkit-user-drag: none;
|
||||
|
||||
@ -1,114 +1,25 @@
|
||||
<script setup>
|
||||
import {RouterLink, RouterView } from 'vue-router'
|
||||
|
||||
import {storeToRefs} from 'pinia';
|
||||
import {useAuthStore} from '@/stores/auth';
|
||||
import router from '@/router';
|
||||
import { RouterView } from 'vue-router'
|
||||
import Sidebar from '@/components/Sidebar.vue'
|
||||
import Header from '@/components/Header.vue'
|
||||
import Errors from '@/components/Errors.vue'
|
||||
import { LogOutIcon } from 'lucide-vue-next'
|
||||
|
||||
const authStr = useAuthStore()
|
||||
|
||||
const {auth} = storeToRefs(authStr)
|
||||
|
||||
const logout = () => {
|
||||
authStr.set(undefined)
|
||||
router.push('/')
|
||||
location.reload()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="flex flex-row justify-between items-center mb-8">
|
||||
<div class="flex flex-row items-center mb-8 gap-10">
|
||||
<RouterLink to="/">
|
||||
<img alt="Conjure logo" class="logo max-w-lg" src="@/assets/logo_conjure_dark.png"/>
|
||||
</RouterLink>
|
||||
<!-- <nav>-->
|
||||
<!-- <RouterLink to="/games">Games</RouterLink>-->
|
||||
<!-- <RouterLink to="/upload">Upload</RouterLink>-->
|
||||
<!-- </nav>-->
|
||||
<div class="flex h-screen w-screen bg-muted/40">
|
||||
<Sidebar />
|
||||
<div class="flex flex-col flex-1">
|
||||
<Header />
|
||||
<main class="flex-1 p-8 overflow-y-auto">
|
||||
<RouterView />
|
||||
</main>
|
||||
<footer class="p-4">
|
||||
<Errors />
|
||||
</footer>
|
||||
</div>
|
||||
<button id="logout" class="border-transparent font-bold text-foreground py-2 px-4 border hover:border-primary rounded mb-5"
|
||||
@click="logout()" v-if="auth">
|
||||
<LogOutIcon class="m-2" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<RouterView/>
|
||||
<footer>
|
||||
<Errors/>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
header {
|
||||
line-height: 1.5;
|
||||
max-height: 100dvh;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin: 0 auto 2rem;
|
||||
user-drag: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
nav {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: inline-block;
|
||||
padding: 0 1rem;
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
#logout {
|
||||
display: inline-block;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
nav a:first-of-type {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
|
||||
.logo {
|
||||
margin: 0 2rem 0 0;
|
||||
}
|
||||
|
||||
header .wrapper {
|
||||
display: flex;
|
||||
place-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
nav {
|
||||
text-align: left;
|
||||
margin-left: -1rem;
|
||||
font-size: 1rem;
|
||||
|
||||
padding: 1rem 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
/* Scoped styles can remain if there are any specific to Member.vue layout */
|
||||
</style>
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
<script setup>
|
||||
import { RouterLink, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import {RouterLink, useRoute, useRouter} from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
function getPathSegment() {
|
||||
const pathSegments = route.path.split('/').filter(segment => segment !== '');
|
||||
const routes = router.getRoutes().reduce((p, c) => ({...p, [c.path]: true}), {})
|
||||
const pathSegments = route.path.split('/').filter((segment) => segment !== '')
|
||||
const routes = router.getRoutes().reduce((p, c) => ({ ...p, [c.path]: true }), {})
|
||||
|
||||
const back = '/';
|
||||
const back = '/'
|
||||
for (let i = 1; i < pathSegments.length; i++) {
|
||||
const toTest = back + pathSegments.slice(0, -i).join('/');
|
||||
const toTest = back + pathSegments.slice(0, -i).join('/')
|
||||
console.log(toTest, routes[toTest])
|
||||
if (!routes[toTest]) {
|
||||
continue
|
||||
@ -23,24 +20,22 @@ function getPathSegment() {
|
||||
}
|
||||
|
||||
return back
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article>
|
||||
<h1>
|
||||
Shit not found bruh
|
||||
</h1>
|
||||
<RouterLink :to="getPathSegment()"
|
||||
class="bg-transparent font-bold text-foreground underline underline-offset-8 hover:no-underline hover:text-foreground py-2 px-4 border border-transparent hover:border-transparent rounded hover:bg-primary">
|
||||
<h1>Shit not found bruh</h1>
|
||||
<RouterLink
|
||||
:to="getPathSegment()"
|
||||
class="bg-transparent font-bold text-foreground underline underline-offset-8 hover:no-underline hover:text-foreground py-2 px-4 border border-transparent hover:border-transparent rounded hover:bg-primary"
|
||||
>
|
||||
go back
|
||||
</RouterLink>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
article {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -64,5 +59,4 @@ ul {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { RouterLink, RouterView } from 'vue-router'
|
||||
import Errors from '@/components/Errors.vue'
|
||||
</script>
|
||||
@ -9,7 +8,13 @@ import Errors from '@/components/Errors.vue'
|
||||
<div id="auth-content">
|
||||
<header>
|
||||
<RouterLink to="/">
|
||||
<img alt="Conjure logo" class="logo" src="@/assets/logo_conjure_dark.png" width="2228" height="349"/>
|
||||
<img
|
||||
alt="Conjure logo"
|
||||
class="logo"
|
||||
src="@/assets/logo_conjure_dark.png"
|
||||
width="2228"
|
||||
height="349"
|
||||
/>
|
||||
</RouterLink>
|
||||
<nav>
|
||||
<RouterLink to="/login">Login</RouterLink>
|
||||
@ -17,15 +22,24 @@ import Errors from '@/components/Errors.vue'
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<RouterView/>
|
||||
<RouterView />
|
||||
<footer>
|
||||
<Errors/>
|
||||
<Errors />
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
#auth {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
gap: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.logo {
|
||||
user-drag: none;
|
||||
-webkit-user-drag: none;
|
||||
@ -112,4 +126,4 @@ footer {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { useErrorStore } from '@/stores/errors'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import router from '@/router'
|
||||
@ -9,24 +8,24 @@ import { AuthService } from '@/services/auth.service'
|
||||
import { AuthDto } from '@/dtos/auth.dto'
|
||||
|
||||
const errorStore = useErrorStore()
|
||||
const authService = new AuthService();
|
||||
const authService = new AuthService()
|
||||
const isLoginIn = ref(false)
|
||||
const login = async (form: HTMLFormElement) => {
|
||||
const formData = new FormData(form);
|
||||
isLoginIn.value = true;
|
||||
const formData = new FormData(form)
|
||||
isLoginIn.value = true
|
||||
|
||||
const dto = new AuthDto(formData);
|
||||
const dto = new AuthDto(formData)
|
||||
const response = await authService.login(dto).catch((error) => {
|
||||
isLoginIn.value = false;
|
||||
errorStore.unshift(error);
|
||||
isLoginIn.value = false
|
||||
errorStore.unshift(error)
|
||||
})
|
||||
|
||||
isLoginIn.value = false;
|
||||
isLoginIn.value = false
|
||||
if (!response) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const result = await response.json()
|
||||
useAuthStore().set(result)
|
||||
router.push('/')
|
||||
}
|
||||
@ -35,14 +34,18 @@ const login = async (form: HTMLFormElement) => {
|
||||
<template>
|
||||
<article>
|
||||
<h1>Login</h1>
|
||||
<form ref="loginForm" enctype="multipart/form-data" @submit.prevent="login($refs.loginForm as HTMLFormElement)">
|
||||
<label for="username">Username</label>
|
||||
<form
|
||||
ref="loginForm"
|
||||
enctype="multipart/form-data"
|
||||
@submit.prevent="login($refs.loginForm as HTMLFormElement)"
|
||||
>
|
||||
<label for="email">Email</label>
|
||||
<input
|
||||
required
|
||||
type="text"
|
||||
type="email"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
name="username"
|
||||
id="username"
|
||||
name="email"
|
||||
id="email"
|
||||
/>
|
||||
<label for="password">Password</label>
|
||||
<input
|
||||
|
||||
@ -13,7 +13,7 @@ const signup = (form: HTMLFormElement) => {
|
||||
const formData = new FormData(form)
|
||||
isLoginIn.value = true
|
||||
const myHeaders = new Headers()
|
||||
myHeaders.append('API-Version', "1")
|
||||
myHeaders.append('API-Version', '1')
|
||||
fetch(apiHost + 'signup', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
@ -21,10 +21,9 @@ const signup = (form: HTMLFormElement) => {
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200)
|
||||
return response.text().then(error => {
|
||||
throw new Error(error)
|
||||
}
|
||||
)
|
||||
return response.text().then((error) => {
|
||||
throw new Error(error)
|
||||
})
|
||||
return response.text()
|
||||
})
|
||||
.then((result) => {
|
||||
@ -38,24 +37,36 @@ const signup = (form: HTMLFormElement) => {
|
||||
errorStore.unshift(error)
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article>
|
||||
<h1>Sign up</h1>
|
||||
<form ref="signupForm" enctype="multipart/form-data" @submit.prevent="signup($refs.signupForm as HTMLFormElement)">
|
||||
<label for="username">Username</label>
|
||||
<input required type="text"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
name="username" id="username" />
|
||||
<form
|
||||
ref="signupForm"
|
||||
enctype="multipart/form-data"
|
||||
@submit.prevent="signup($refs.signupForm as HTMLFormElement)"
|
||||
>
|
||||
<label for="email">Email</label>
|
||||
<input
|
||||
required
|
||||
type="email"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
name="email"
|
||||
id="email"
|
||||
/>
|
||||
<label for="password">Password</label>
|
||||
<input required type="password"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
name="password" id="password" />
|
||||
<input
|
||||
required
|
||||
type="password"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
name="password"
|
||||
id="password"
|
||||
/>
|
||||
<button
|
||||
class="bg-transparent text-primary font-semibold hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded"
|
||||
type="submit">
|
||||
type="submit"
|
||||
>
|
||||
Signup
|
||||
</button>
|
||||
</form>
|
||||
@ -79,4 +90,4 @@ input {
|
||||
label {
|
||||
margin-bottom: -1.5rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -1,108 +1,113 @@
|
||||
<script setup>
|
||||
import {onMounted, ref} from 'vue';
|
||||
import {useErrorStore} from '@/stores/errors'
|
||||
import Loader from '@/components/Loader.vue';
|
||||
import {useRoute} from 'vue-router';
|
||||
import {useAuthStore} from '@/stores/auth';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import router from '@/router';
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useErrorStore } from '@/stores/errors'
|
||||
import Loader from '@/components/Loader.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import router from '@/router'
|
||||
|
||||
const errorStore = useErrorStore();
|
||||
const authStore = useAuthStore();
|
||||
const { auth } = storeToRefs(authStore);
|
||||
const apiHost = import.meta.env.VITE_CONJUREOS_HOST;
|
||||
const errorStore = useErrorStore()
|
||||
const authStore = useAuthStore()
|
||||
const { auth } = storeToRefs(authStore)
|
||||
const apiHost = import.meta.env.VITE_CONJUREOS_HOST
|
||||
|
||||
const route = useRoute();
|
||||
const gameId = ref(route.params.gameId);
|
||||
const game = ref(undefined);
|
||||
const isActivating = ref(false);
|
||||
const confirmingDeletion = ref(false);
|
||||
const route = useRoute()
|
||||
const gameId = ref(route.params.gameId)
|
||||
const game = ref(undefined)
|
||||
const isActivating = ref(false)
|
||||
const confirmingDeletion = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
fetch(apiHost + 'games/' + gameId.value, {
|
||||
method: 'GET',
|
||||
headers: { 'API-Version': 1 },
|
||||
headers: { 'API-Version': 1 }
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((result) => {
|
||||
game.value = result;
|
||||
game.value = result
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error:', error);
|
||||
errorStore.unshift(error);
|
||||
});
|
||||
});
|
||||
console.error('Error:', error)
|
||||
errorStore.unshift(error)
|
||||
})
|
||||
})
|
||||
|
||||
function tryDeleteGame() {
|
||||
console.log("Try")
|
||||
confirmingDeletion.value = true;
|
||||
console.log('Try')
|
||||
confirmingDeletion.value = true
|
||||
new Promise((_) => setTimeout(_, 1000)).then(() => {
|
||||
confirmingDeletion.value = false;
|
||||
});
|
||||
confirmingDeletion.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function deleteGame() {
|
||||
console.log("Delete")
|
||||
console.log('Delete')
|
||||
fetch(`${apiHost}games/${gameId.value}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${auth.value.token}`,
|
||||
'API-Version': 1,
|
||||
},
|
||||
Authorization: `Bearer ${auth.value.token}`,
|
||||
'API-Version': 1
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
alert("Deleted")
|
||||
alert('Deleted')
|
||||
router.back()
|
||||
} else {
|
||||
alert('Error deleting game')
|
||||
}
|
||||
else {
|
||||
alert("Error deleting game")
|
||||
}
|
||||
return true;
|
||||
return true
|
||||
})
|
||||
.catch((error) => {
|
||||
errorStore.unshift(error);
|
||||
});
|
||||
errorStore.unshift(error)
|
||||
})
|
||||
}
|
||||
|
||||
function toggleActivation(state) {
|
||||
isActivating.value = true;
|
||||
isActivating.value = true
|
||||
fetch(`${apiHost}games/${gameId.value}/${state ? 'activate' : 'deactivate'}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${auth.value.token}`,
|
||||
'API-Version': 1,
|
||||
},
|
||||
Authorization: `Bearer ${auth.value.token}`,
|
||||
'API-Version': 1
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 204) return response.json().then((errorBody) => { throw new Error(errorBody); });
|
||||
return true;
|
||||
if (response.status !== 204)
|
||||
return response.json().then((errorBody) => {
|
||||
throw new Error(errorBody)
|
||||
})
|
||||
return true
|
||||
})
|
||||
.then(() => {
|
||||
game.value = { ...game.value, active: state };
|
||||
isActivating.value = false;
|
||||
game.value = { ...game.value, active: state }
|
||||
isActivating.value = false
|
||||
})
|
||||
.catch((error) => {
|
||||
errorStore.unshift(error);
|
||||
isActivating.value = false;
|
||||
});
|
||||
errorStore.unshift(error)
|
||||
isActivating.value = false
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<article>
|
||||
<loader v-if="game === undefined"></loader>
|
||||
<div v-else>
|
||||
<img v-if="game.image" :src="'data:image/png;base64,'+game.image" alt="thumbnail"/>
|
||||
<img v-if="game.image" :src="'data:image/png;base64,' + game.image" alt="thumbnail" />
|
||||
<h1>{{ game.game }}</h1>
|
||||
<p>{{ game.description }}</p>
|
||||
<loader :variant="2" v-if="isActivating"></loader>
|
||||
<template v-else>
|
||||
<button
|
||||
:class="game.active ? 'bg-red-500 text-white hover:bg-red-700' : 'bg-green-500 text-white hover:bg-green-700'"
|
||||
:class="
|
||||
game.active
|
||||
? 'bg-red-500 text-white hover:bg-red-700'
|
||||
: 'bg-green-500 text-white hover:bg-green-700'
|
||||
"
|
||||
class="font-bold py-2 px-4 my-2 rounded"
|
||||
@click="toggleActivation(!game.active)"
|
||||
:disabled="isActivating"
|
||||
@ -111,10 +116,10 @@ function toggleActivation(state) {
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="font-bold py-2 px-4 m-2 rounded bg-red-500 text-white hover:bg-red-700"
|
||||
@click="confirmingDeletion ? deleteGame() : tryDeleteGame()"
|
||||
class="font-bold py-2 px-4 m-2 rounded bg-red-500 text-white hover:bg-red-700"
|
||||
@click="confirmingDeletion ? deleteGame() : tryDeleteGame()"
|
||||
>
|
||||
{{ confirmingDeletion ? "Sure?" : "Delete" }}
|
||||
{{ confirmingDeletion ? 'Sure?' : 'Delete' }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
@ -122,11 +127,9 @@ function toggleActivation(state) {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
article {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -7,7 +7,7 @@ import Loader from '@/components/Loader.vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { GameService } from '@/services/game.service'
|
||||
import { DownloadIcon } from 'lucide-vue-next'
|
||||
import router from '@/router';
|
||||
import router from '@/router'
|
||||
|
||||
const errorStore = useErrorStore()
|
||||
const apiHost = import.meta.env.VITE_CONJUREOS_HOST
|
||||
@ -16,11 +16,10 @@ const { list: gamelist } = storeToRefs(gamelistStore)
|
||||
const gamesService = new GameService()
|
||||
|
||||
onMounted(async () => {
|
||||
const games = await gamesService.getGames()
|
||||
.catch((error) => {
|
||||
console.error('Error:', error)
|
||||
errorStore.unshift(error)
|
||||
})
|
||||
const games = await gamesService.getGames().catch((error) => {
|
||||
console.error('Error:', error)
|
||||
errorStore.unshift(error)
|
||||
})
|
||||
|
||||
if (games) {
|
||||
gamelistStore.set(games)
|
||||
@ -44,7 +43,7 @@ function downloadAll() {
|
||||
}
|
||||
|
||||
function newGame() {
|
||||
router.push("/upload")
|
||||
router.push('/upload')
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -70,9 +69,21 @@ function newGame() {
|
||||
</div>
|
||||
</header>
|
||||
<ul class="flex flex-col gap-4">
|
||||
<li v-for="item in gamelist" :key="item.id" class="game-item border border-gray-300 rounded-lg p-4 flex items-center gap-4 hover:border-2">
|
||||
<RouterLink :to="'/games/' + item.id" class="flex-grow flex items-center gap-4 p-2 rounded">
|
||||
<img v-if="item.thumbnail" :src="'data:image/png;base64,' + item.thumbnail" alt="thumbnail" class="h-16" />
|
||||
<li
|
||||
v-for="item in gamelist"
|
||||
:key="item.id"
|
||||
class="game-item border border-gray-300 rounded-lg p-4 flex items-center gap-4 hover:border-2"
|
||||
>
|
||||
<RouterLink
|
||||
:to="'/games/' + item.id"
|
||||
class="flex-grow flex items-center gap-4 p-2 rounded"
|
||||
>
|
||||
<img
|
||||
v-if="item.thumbnail"
|
||||
:src="'data:image/png;base64,' + item.thumbnail"
|
||||
alt="thumbnail"
|
||||
class="h-16"
|
||||
/>
|
||||
<div>
|
||||
<h2 class="font-bold">{{ item.game }}</h2>
|
||||
<p class="text-primary">{{ item.description }}</p>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { onMounted, ref } from 'vue'
|
||||
import DateInput from '@/components/DateInput.vue'
|
||||
import { UploadGameDto } from '@/dtos/upload-game.dto'
|
||||
@ -11,10 +10,8 @@ import { FileSaver } from '@/utils/file-saver'
|
||||
import { GuidUtil } from '@/utils/uuid.util'
|
||||
import { ConjUtil } from '@/utils/conj.util'
|
||||
|
||||
|
||||
|
||||
let playerValue = ref("");
|
||||
let versionValue = ref("")
|
||||
let playerValue = ref('')
|
||||
let versionValue = ref('')
|
||||
let uploadForm = ref()
|
||||
|
||||
// onMounted(() => {
|
||||
@ -22,120 +19,114 @@ let uploadForm = ref()
|
||||
// });
|
||||
|
||||
function validatePlayersInput(): void {
|
||||
let value = playerValue.value.replace(/[^1-2]/g, ""); // Allow only digits and hyphen
|
||||
value = value.slice(0, 3); // Ensure max length of 3
|
||||
if (value.length === 2 && !value.includes("-")) {
|
||||
value = value.charAt(0) + "-" + value.charAt(1); // Auto-insert hyphen
|
||||
let value = playerValue.value.replace(/[^1-2]/g, '') // Allow only digits and hyphen
|
||||
value = value.slice(0, 3) // Ensure max length of 3
|
||||
if (value.length === 2 && !value.includes('-')) {
|
||||
value = value.charAt(0) + '-' + value.charAt(1) // Auto-insert hyphen
|
||||
}
|
||||
if (value.length === 3) {
|
||||
const min = +value.charAt(0)
|
||||
let max = +value.charAt(2)
|
||||
if (max < min)
|
||||
max = min
|
||||
if (max < min) max = min
|
||||
value = `${min}-${max}`
|
||||
}
|
||||
playerValue.value = value
|
||||
}
|
||||
|
||||
function validateVersionInput(): void {
|
||||
versionValue.value = versionValue.value.replace(/[^0-9.]/g, ""); // Allow only digits and hyphen
|
||||
versionValue.value = versionValue.value.replace(/[^0-9.]/g, '') // Allow only digits and hyphen
|
||||
}
|
||||
|
||||
async function submitForm(): Promise<void> {
|
||||
console.log("Submitting...")
|
||||
console.log('Submitting...')
|
||||
const formData = new FormData(uploadForm.value)
|
||||
const files = (document.getElementById("medias") as HTMLInputElement).files
|
||||
let thumbnail = null;
|
||||
let tbnFile = null;
|
||||
const files = (document.getElementById('medias') as HTMLInputElement).files
|
||||
let thumbnail = null
|
||||
let tbnFile = null
|
||||
let image = null
|
||||
let imageFile = null;
|
||||
let imageFile = null
|
||||
if (!!files && files.length > 0) {
|
||||
tbnFile = files[0];
|
||||
thumbnail = new Uint8Array(await tbnFile.arrayBuffer());
|
||||
tbnFile = files[0]
|
||||
thumbnail = new Uint8Array(await tbnFile.arrayBuffer())
|
||||
}
|
||||
if (!!files && files.length > 1) {
|
||||
imageFile = files[1];
|
||||
image = new Uint8Array(await imageFile.arrayBuffer());
|
||||
imageFile = files[1]
|
||||
image = new Uint8Array(await imageFile.arrayBuffer())
|
||||
}
|
||||
|
||||
const game = formData.get("game") as File;
|
||||
const gameSplit = game.name.split(".");
|
||||
const ext = gameSplit[gameSplit.length - 1];
|
||||
let gamePath = "";
|
||||
if (ext === "zip") {
|
||||
const game = formData.get('game') as File
|
||||
const gameSplit = game.name.split('.')
|
||||
const ext = gameSplit[gameSplit.length - 1]
|
||||
let gamePath = ''
|
||||
if (ext === 'zip') {
|
||||
let fileTree = await ZipUtil.getZipFileTree(game)
|
||||
fileTree = fileTree.filter(file => file.includes(".exe"))
|
||||
const title = formData.get("title") as string;
|
||||
const bestMatch = ConjUtil.findBestExecutable(title, fileTree);
|
||||
if (!bestMatch) throw new Error("No executable found in zip file");
|
||||
const folder = BlobUtil.getFileName(game).split(".")[0]
|
||||
gamePath = `${folder}\\${bestMatch}`;
|
||||
}
|
||||
else if (ext === "exe") {
|
||||
gamePath = game.name;
|
||||
}
|
||||
else {
|
||||
throw new Error("Unsupported file type");
|
||||
fileTree = fileTree.filter((file) => file.includes('.exe'))
|
||||
const title = formData.get('title') as string
|
||||
const bestMatch = ConjUtil.findBestExecutable(title, fileTree)
|
||||
if (!bestMatch) throw new Error('No executable found in zip file')
|
||||
const folder = BlobUtil.getFileName(game).split('.')[0]
|
||||
gamePath = `${folder}\\${bestMatch}`
|
||||
} else if (ext === 'exe') {
|
||||
gamePath = game.name
|
||||
} else {
|
||||
throw new Error('Unsupported file type')
|
||||
}
|
||||
|
||||
const id = GuidUtil.generateUUIDv4();
|
||||
const dto = new ValidateMetadataDto(formData, thumbnail, image, gamePath, id);
|
||||
const service = new GameService();
|
||||
const response = await service.metadata(dto);
|
||||
let metadataText = await response.text();
|
||||
const id = GuidUtil.generateUUIDv4()
|
||||
const dto = new ValidateMetadataDto(formData, thumbnail, image, gamePath, id)
|
||||
const service = new GameService()
|
||||
const response = await service.metadata(dto)
|
||||
let metadataText = await response.text()
|
||||
console.log(metadataText)
|
||||
const metadataObj: any = {};
|
||||
let metadataLines = metadataText.split("\n");
|
||||
metadataLines = metadataLines.slice(0, metadataLines.length - 1);
|
||||
const metadataObj: any = {}
|
||||
let metadataLines = metadataText.split('\n')
|
||||
metadataLines = metadataLines.slice(0, metadataLines.length - 1)
|
||||
|
||||
for (const line of metadataLines) {
|
||||
const lineSplit = line.split(":");
|
||||
metadataObj[lineSplit[0].trim()] = lineSplit[1].trim();
|
||||
const lineSplit = line.split(':')
|
||||
metadataObj[lineSplit[0].trim()] = lineSplit[1].trim()
|
||||
}
|
||||
if (!!tbnFile)
|
||||
metadataObj["thumbnailPath"] = `medias\\${BlobUtil.getFileName(tbnFile)}`
|
||||
if (!!imageFile)
|
||||
metadataObj["imagePath"] = `medias\\${BlobUtil.getFileName(imageFile)}`
|
||||
if (!!tbnFile) metadataObj['thumbnailPath'] = `medias\\${BlobUtil.getFileName(tbnFile)}`
|
||||
if (!!imageFile) metadataObj['imagePath'] = `medias\\${BlobUtil.getFileName(imageFile)}`
|
||||
|
||||
console.log(metadataObj)
|
||||
metadataText = Object.keys(metadataObj).map(key => [key, metadataObj[key]].join(": ")).join("\n");
|
||||
console.log(metadataText);
|
||||
const metadataBlob = new Blob([metadataText], { type: "text/plain" });
|
||||
metadataText = Object.keys(metadataObj)
|
||||
.map((key) => [key, metadataObj[key]].join(': '))
|
||||
.join('\n')
|
||||
console.log(metadataText)
|
||||
const metadataBlob = new Blob([metadataText], { type: 'text/plain' })
|
||||
|
||||
|
||||
|
||||
const zipUtil = new ZipUtil();
|
||||
const zipUtil = new ZipUtil()
|
||||
if (!!files) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const fileName = BlobUtil.getFileName(file);
|
||||
await zipUtil.addFile(`medias/${fileName}`, file);
|
||||
const file = files[i]
|
||||
const fileName = BlobUtil.getFileName(file)
|
||||
await zipUtil.addFile(`medias/${fileName}`, file)
|
||||
}
|
||||
}
|
||||
await zipUtil.addFile("metadata.txt", metadataBlob);
|
||||
await zipUtil.addFile(BlobUtil.getFileName(game), game);
|
||||
const zipBlob = zipUtil.getZipBlob();
|
||||
await zipUtil.addFile('metadata.txt', metadataBlob)
|
||||
await zipUtil.addFile(BlobUtil.getFileName(game), game)
|
||||
const zipBlob = zipUtil.getZipBlob()
|
||||
const zipFile = new File([zipBlob], `${id}.conj`)
|
||||
FileSaver.saveFile(zipFile);
|
||||
FileSaver.saveFile(zipFile)
|
||||
|
||||
// TODO remove form data
|
||||
const conjUploadForm = new FormData();
|
||||
conjUploadForm.append("file", zipFile);
|
||||
const conjUploadForm = new FormData()
|
||||
conjUploadForm.append('file', zipFile)
|
||||
const uploadResponse = await service.upload(conjUploadForm)
|
||||
//alert(await uploadResponse.text())
|
||||
}
|
||||
|
||||
function testData(): void {
|
||||
(document.getElementById("title") as HTMLInputElement).value = "Game Test";
|
||||
(document.getElementById("description") as HTMLInputElement).value = "Ceci est un test";
|
||||
(document.getElementById("genres") as HTMLInputElement).value = "Action, Adventure";
|
||||
(document.getElementById("devs") as HTMLInputElement).value = "Jean,Yussef";
|
||||
(document.getElementById("repo") as HTMLInputElement).value = "https://repo.com";
|
||||
(document.getElementById("players") as HTMLInputElement).value = "1-2";
|
||||
(document.getElementById("release") as HTMLInputElement).value = "01/01/2021";
|
||||
(document.getElementById("version") as HTMLInputElement).value = "1.1.1";
|
||||
;(document.getElementById('title') as HTMLInputElement).value = 'Game Test'
|
||||
;(document.getElementById('description') as HTMLInputElement).value = 'Ceci est un test'
|
||||
;(document.getElementById('genres') as HTMLInputElement).value = 'Action, Adventure'
|
||||
;(document.getElementById('devs') as HTMLInputElement).value = 'Jean,Yussef'
|
||||
;(document.getElementById('repo') as HTMLInputElement).value = 'https://repo.com'
|
||||
;(document.getElementById('players') as HTMLInputElement).value = '1-2'
|
||||
;(document.getElementById('release') as HTMLInputElement).value = '01/01/2021'
|
||||
;(document.getElementById('version') as HTMLInputElement).value = '1.1.1'
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -144,29 +135,57 @@ function testData(): void {
|
||||
<div class="text-foreground w-full my-2 underline">
|
||||
<RouterLink to="upload" class="hover:color-blue-300">Or upload a .conj</RouterLink>
|
||||
</div>
|
||||
<form ref="uploadForm" enctype="multipart/form-data" class="flex flex-col gap-2" @submit.prevent="submitForm">
|
||||
<form
|
||||
ref="uploadForm"
|
||||
enctype="multipart/form-data"
|
||||
class="flex flex-col gap-2"
|
||||
@submit.prevent="submitForm"
|
||||
>
|
||||
<label for="title">Title</label>
|
||||
<input name="title" id="title" class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent" />
|
||||
<input
|
||||
name="title"
|
||||
id="title"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
/>
|
||||
|
||||
<label for="description">Description</label>
|
||||
<textarea name="description" id="description" class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent" />
|
||||
<textarea
|
||||
name="description"
|
||||
id="description"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
/>
|
||||
|
||||
<label for="medias">Media</label>
|
||||
<input type="file" name="medias" id="medias" accept="video/quicktime,image/png,image/jpeg,video/mp4" multiple />
|
||||
<input
|
||||
type="file"
|
||||
name="medias"
|
||||
id="medias"
|
||||
accept="video/quicktime,image/png,image/jpeg,video/mp4"
|
||||
multiple
|
||||
/>
|
||||
|
||||
<label for="game">Game</label>
|
||||
<input type="file" name="game" id="game" accept="application/zip,application/exe"/>
|
||||
<input type="file" name="game" id="game" accept="application/zip,application/exe" />
|
||||
|
||||
<label for="genres">Genres</label>
|
||||
<input name="genres" id="genres" class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
<input
|
||||
name="genres"
|
||||
id="genres"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
placeholder="Action, Adventure"
|
||||
/>
|
||||
<label for="devs">Devs</label>
|
||||
<input name="devs" id="devs" class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
<input
|
||||
name="devs"
|
||||
id="devs"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
placeholder="Jean, Yussef"
|
||||
/>
|
||||
<label for="repo">Public Repository</label>
|
||||
<input name="repo" id="repo" class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
<input
|
||||
name="repo"
|
||||
id="repo"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
placeholder="https://github.com/..."
|
||||
/>
|
||||
<label for="players">Player Count</label>
|
||||
@ -180,7 +199,7 @@ function testData(): void {
|
||||
@input="validatePlayersInput"
|
||||
maxlength="3"
|
||||
/>
|
||||
<!-- <date-input></date-input>-->
|
||||
<!-- <date-input></date-input>-->
|
||||
<label for="release">Release Date</label>
|
||||
<input
|
||||
name="release"
|
||||
@ -200,11 +219,7 @@ function testData(): void {
|
||||
/>
|
||||
|
||||
<label for="leaderboard">Leaderboard</label>
|
||||
<input
|
||||
name="leaderboard"
|
||||
id="leaderboard"
|
||||
type="checkbox"
|
||||
/>
|
||||
<input name="leaderboard" id="leaderboard" type="checkbox" />
|
||||
|
||||
<button
|
||||
class="bg-transparent font-bold text-foreground py-2 px-4 my-2 border border-primary rounded hover:bg-primary"
|
||||
@ -216,6 +231,4 @@ function testData(): void {
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@ -16,7 +16,7 @@ export default {
|
||||
submitForm() {
|
||||
const form = this.$refs.uploadForm
|
||||
const formData = new FormData(form)
|
||||
console.log("Upload " + JSON.stringify(authStr.getAuth()))
|
||||
console.log('Upload ' + JSON.stringify(authStr.getAuth()))
|
||||
|
||||
fetch(apiHost + 'games', {
|
||||
method: 'POST',
|
||||
@ -46,7 +46,12 @@ export default {
|
||||
<template>
|
||||
<article>
|
||||
<h1 class="text-foreground">Upload</h1>
|
||||
<form ref="uploadForm" enctype="multipart/form-data" class="flex flex-col gap-2" @submit.prevent="submitForm">
|
||||
<form
|
||||
ref="uploadForm"
|
||||
enctype="multipart/form-data"
|
||||
class="flex flex-col gap-2"
|
||||
@submit.prevent="submitForm"
|
||||
>
|
||||
<!-- <div class="name-input-wrapper">-->
|
||||
<!-- <label for="name" class="block text-sm font-medium text-gray-700">Name:</label>-->
|
||||
<!-- <input type="text" name="name" id="name" required v-model="textInput"-->
|
||||
@ -70,7 +75,9 @@ export default {
|
||||
</div>
|
||||
|
||||
<div class="text-foreground w-full my-2 underline">
|
||||
<RouterLink to="manual-upload" class="hover:color-blue-300">Or manually enter your metadata</RouterLink>
|
||||
<RouterLink to="manual-upload" class="hover:color-blue-300"
|
||||
>Or manually enter your metadata</RouterLink
|
||||
>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@ -84,7 +91,6 @@ export default {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
form {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
<script setup>
|
||||
import {ref, onMounted, onUnmounted} from 'vue';
|
||||
import mqtt from 'mqtt';
|
||||
|
||||
const wsHost = import.meta.env.VITE_CONJUREOS_MQTT
|
||||
const topic = '#';
|
||||
const receivedMessages = ref([]);
|
||||
let client;
|
||||
|
||||
onMounted(() => {
|
||||
// Connect to MQTT broker
|
||||
client = mqtt.connect(wsHost, {clientId: 'frontend', protocol: 'ws'});
|
||||
|
||||
// Subscribe to a topic
|
||||
client.subscribe(topic);
|
||||
|
||||
// Handle incoming messages
|
||||
client.on('message', (topic, message) => {
|
||||
receivedMessages.value.push({topic, message: JSON.parse(message.toString())});
|
||||
});
|
||||
|
||||
// Additional setup or event listeners if needed
|
||||
});
|
||||
|
||||
// Cleanup on component unmount
|
||||
onUnmounted(() => {
|
||||
// Unsubscribe and disconnect when the component is unmounted
|
||||
if (client) {
|
||||
client.unsubscribe(topic);
|
||||
client.end();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Received Message:</h1>
|
||||
<p v-for="receivedMessage in receivedMessages"> {{ receivedMessage.topic }} : {{receivedMessage.message["username"]}}</p>
|
||||
<!-- Your template content goes here -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Your scoped styles go here */
|
||||
</style>
|
||||
@ -1,11 +1,7 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<h1>Please close this page.</h1>
|
||||
<h1>Please close this page.</h1>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@ -1,49 +1,47 @@
|
||||
<script setup>
|
||||
import {ref} from 'vue'
|
||||
import {useRoute} from 'vue-router';
|
||||
import Loader from '@/components/Loader.vue';
|
||||
import { ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import Loader from '@/components/Loader.vue'
|
||||
import router from '@/router'
|
||||
import {useErrorStore} from '@/stores/errors'
|
||||
|
||||
import { useErrorStore } from '@/stores/errors'
|
||||
|
||||
const errorStore = useErrorStore()
|
||||
|
||||
const apiHost = import.meta.env.VITE_CONJUREOS_HOST
|
||||
const isLoginIn = ref(false)
|
||||
|
||||
const route = useRoute();
|
||||
const route = useRoute()
|
||||
|
||||
if (!route.query.token || !route.query.action) {
|
||||
router.push("/close")
|
||||
router.push('/close')
|
||||
}
|
||||
|
||||
const submit = (form) => {
|
||||
const formData = new FormData(form)
|
||||
formData.set('token', route.query.token.toString())
|
||||
isLoginIn.value = true
|
||||
const myHeaders = new Headers();
|
||||
myHeaders.append("API-Version", 1);
|
||||
const myHeaders = new Headers()
|
||||
myHeaders.append('API-Version', 1)
|
||||
fetch(apiHost + route.query.action, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: myHeaders,
|
||||
headers: myHeaders
|
||||
})
|
||||
.then((response) => {
|
||||
isLoginIn.value = false
|
||||
if (response.status !== 200)
|
||||
return response.text().then(error => {
|
||||
throw new Error(error)
|
||||
}
|
||||
)
|
||||
return response.text()
|
||||
})
|
||||
.then((result) => {
|
||||
router.push('/close')
|
||||
})
|
||||
.catch((error) => {
|
||||
isLoginIn.value = false
|
||||
errorStore.unshift(error)
|
||||
})
|
||||
.then((response) => {
|
||||
isLoginIn.value = false
|
||||
if (response.status !== 200)
|
||||
return response.text().then((error) => {
|
||||
throw new Error(error)
|
||||
})
|
||||
return response.text()
|
||||
})
|
||||
.then((result) => {
|
||||
router.push('/close')
|
||||
})
|
||||
.catch((error) => {
|
||||
isLoginIn.value = false
|
||||
errorStore.unshift(error)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -51,29 +49,29 @@ const submit = (form) => {
|
||||
<form ref="loginForm" enctype="multipart/form-data" @submit.prevent="submit($refs.loginForm)">
|
||||
<label for="username">username</label>
|
||||
<input
|
||||
required
|
||||
type="text"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
name="username"
|
||||
id="username"
|
||||
required
|
||||
type="text"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
name="username"
|
||||
id="username"
|
||||
/>
|
||||
<label for="password">password</label>
|
||||
<input
|
||||
required
|
||||
type="password"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
name="password"
|
||||
id="password"
|
||||
required
|
||||
type="password"
|
||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||
name="password"
|
||||
id="password"
|
||||
/>
|
||||
<button
|
||||
v-if="!isLoginIn"
|
||||
class="bg-transparent text-primary font-semibold hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded"
|
||||
type="submit"
|
||||
v-if="!isLoginIn"
|
||||
class="bg-transparent text-primary font-semibold hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded"
|
||||
type="submit"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
<span class="loader" v-else>
|
||||
<Loader :variant="2"/>
|
||||
<Loader :variant="2" />
|
||||
</span>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
49
src/views/users/UsersView.vue
Normal file
49
src/views/users/UsersView.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold">Users Management</h1>
|
||||
|
||||
<loader v-if="users === undefined"></loader>
|
||||
<div class="mt-4" v-else>
|
||||
<table class="min-w-full border-gray-200">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="py-2 px-4 border-b text-left">Email</th>
|
||||
<th class="py-2 px-4 border-b text-left">Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="user in users" :key="user.id">
|
||||
<td class="py-2 px-4 border-b">{{ user.email }}</td>
|
||||
<td class="py-2 px-4 border-b">{{ user.role.label }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useErrorStore } from '@/stores/errors';
|
||||
import { UserService } from '@/services/user.service';
|
||||
import { useUsersStore } from '@/stores/users';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import Loader from '@/components/Loader.vue'
|
||||
|
||||
const errorStore = useErrorStore()
|
||||
const apiHost = import.meta.env.VITE_CONJUREOS_HOST
|
||||
const usersStore = useUsersStore()
|
||||
const { list: users } = storeToRefs(usersStore)
|
||||
const userService = new UserService()
|
||||
|
||||
onMounted(async () => {
|
||||
const users = await userService.getUsers().catch((error) => {
|
||||
console.error('Error:', error)
|
||||
errorStore.unshift(error)
|
||||
})
|
||||
|
||||
if (users) {
|
||||
usersStore.set(users)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -5,7 +5,7 @@
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"strict": true,
|
||||
"strict": false,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user