├── backend
├── apps
│ ├── __init__.py
│ ├── accounts
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ ├── models.py
│ │ └── views.py
│ ├── admin
│ │ ├── __init__.py
│ │ └── config.py
│ └── questions
│ │ ├── __init__.py
│ │ ├── models.py
│ │ ├── routes.py
│ │ └── views.py
├── .gitignore
├── .env.example
├── requirements.txt
├── README.md
└── app.py
├── frontend
├── public
│ └── favicon.ico
├── README.md
├── .gitignore
├── vite.config.js
├── index.html
├── src
│ ├── main.js
│ ├── components
│ │ ├── questions
│ │ │ ├── Categories.vue
│ │ │ ├── QuestionsByTag.vue
│ │ │ ├── ProfileAnswersEdit.vue
│ │ │ ├── ProfileAnswers.vue
│ │ │ ├── ProfileQuestions.vue
│ │ │ ├── ProfileQuestionsEdit.vue
│ │ │ ├── CreateQuestion.vue
│ │ │ ├── Questions.vue
│ │ │ └── Question.vue
│ │ └── accounts
│ │ │ ├── Profile.vue
│ │ │ ├── Login.vue
│ │ │ ├── Dashboard.vue
│ │ │ ├── DashboardAnswers.vue
│ │ │ ├── DashboardQuestions.vue
│ │ │ └── Register.vue
│ ├── views
│ │ └── Home.vue
│ ├── App.vue
│ ├── router
│ │ └── index.js
│ └── store
│ │ └── index.js
├── package.json
└── package-lock.json
├── README.md
└── LICENSE
/backend/apps/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/apps/accounts/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/apps/admin/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/apps/questions/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | __pycache__
3 | .vscode
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sinisaos/starlette-vue/HEAD/frontend/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # frontend
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run dev
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Example of single page application made with [Starlette](https://www.starlette.io/), [Vue JS](https://vuejs.org/), [Tortoise ORM](https://tortoise-orm.readthedocs.io/en/latest/) and PostgreSQL.
2 |
3 | For backend installation look at backend readme.
4 |
5 | For frontend installation look at frontend readme.
6 |
--------------------------------------------------------------------------------
/backend/.env.example:
--------------------------------------------------------------------------------
1 | DB_URI=postgres://username:password@localhost:5432/your_db_name
2 | SECRET_KEY=your_secret_key
3 | ADMIN_USER_MODEL=BaseUser
4 | ADMIN_USER_MODEL_USERNAME_FIELD=username
5 | ADMIN_SECRET_KEY=your_admin_secret_key
6 | ADMIN_SITE_NAME=Q&A Admin
7 | ADMIN_USER=admin
8 | ADMIN_EMAIL=admin@example.com
9 | ADMIN_PASSWORD=admin123
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | node_modules
13 | dist-ssr
14 | *.local
15 |
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
--------------------------------------------------------------------------------
/frontend/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 | import Components from 'unplugin-vue-components/vite'
4 | import { BootstrapVueNextResolver } from 'bootstrap-vue-next'
5 |
6 | // https://vite.dev/config/
7 | export default defineConfig({
8 | plugins: [vue(),
9 | Components({
10 | resolvers: [BootstrapVueNextResolver()],
11 | }),
12 | ],
13 | })
14 |
--------------------------------------------------------------------------------
/backend/requirements.txt:
--------------------------------------------------------------------------------
1 | aiocontextvars
2 | aiofiles
3 | asgiref
4 | asn1crypto
5 | asyncpg
6 | cffi
7 | ciso8601
8 | Click
9 | contextvars
10 | cryptography
11 | fastadmin[fastapi]
12 | gunicorn
13 | h11
14 | httptools
15 | immutables
16 | itsdangerous
17 | Jinja2
18 | marshmallow
19 | MarkupSafe
20 | py-bcrypt
21 | pycparser
22 | PyJWT
23 | PyPika
24 | python-multipart
25 | python-dotenv
26 | pytz
27 | secure
28 | six
29 | starlette
30 | tortoise-orm<=0.20.1
31 | uvicorn
32 | uvloop
33 | webargs
34 | WTForms
35 |
--------------------------------------------------------------------------------
/backend/README.md:
--------------------------------------------------------------------------------
1 | ### Instalation
2 |
3 | Clone repository in fresh virtualenv.
4 |
5 | ```bash
6 | git clone https://github.com/sinisaos/starlette-vue.git
7 | ```
8 |
9 | ### Install requirements
10 |
11 |
12 | ```bash
13 | cd backend
14 | pip install -r requirements.txt
15 | ```
16 |
17 | ### Creating an `.env` file.
18 |
19 | ```bash
20 | cp .env.example .env && rm .env.example
21 | ```
22 |
23 | ### Start server
24 |
25 | ```bash
26 | uvicorn app:app --port 8000 --host 0.0.0.0
27 | ```
28 |
29 | After site is running log in as admin user on [localhost:8000/admin/](http://localhost:8000/admin/).
30 |
31 |
--------------------------------------------------------------------------------
/backend/apps/accounts/routes.py:
--------------------------------------------------------------------------------
1 | from starlette.routing import Route, Router
2 |
3 | from apps.accounts.views import dashboard, delete, login, logout, register
4 |
5 | routes = Router(
6 | [
7 | Route("/login", endpoint=login, methods=["GET", "POST"], name="login"),
8 | Route(
9 | "/register", endpoint=register, methods=["POST"], name="register"
10 | ),
11 | Route("/logout", endpoint=logout, methods=["GET"], name="logout"),
12 | Route(
13 | "/dashboard", endpoint=dashboard, methods=["GET"], name="dashboard"
14 | ),
15 | # delete user endpoint
16 | Route("/{id:int}", endpoint=delete, methods=["DELETE"], name="delete"),
17 | ]
18 | )
19 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Starlette | Vue
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | © Starlette | Vue 2019
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/frontend/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import { createBootstrap } from 'bootstrap-vue-next'
3 | import router from "./router"
4 | import store from "./store"
5 | import axios from 'axios'
6 | import App from './App.vue'
7 | import VueAwesomePaginate from "vue-awesome-paginate"
8 |
9 | // import the necessary css file
10 | import "vue-awesome-paginate/dist/style.css"
11 |
12 | // Add the necessary CSS
13 | import 'bootstrap/dist/css/bootstrap.css'
14 | import 'bootstrap-vue-next/dist/bootstrap-vue-next.css'
15 |
16 | axios.defaults.withCredentials = true
17 | axios.defaults.baseURL = "http://localhost:8000"
18 |
19 | const app = createApp(App)
20 | app.use(createBootstrap())
21 | app.use(VueAwesomePaginate)
22 | app.use(store)
23 | app.use(router)
24 | app.mount('#app')
25 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@vuelidate/core": "^2.0.3",
13 | "@vuelidate/validators": "^2.0.4",
14 | "axios": "^1.12.0",
15 | "bootstrap": "^5.3.3",
16 | "bootstrap-vue-next": "^0.25.10",
17 | "dayjs": "^1.11.13",
18 | "vue": "^3.5.12",
19 | "vue-awesome-paginate": "^1.2.0",
20 | "vue-router": "^4.4.5",
21 | "vuex": "^4.0.2",
22 | "vuex-persist": "^3.1.3"
23 | },
24 | "devDependencies": {
25 | "@vitejs/plugin-vue": "^5.1.4",
26 | "unplugin-vue-components": "^0.27.4",
27 | "vite": "^5.4.20"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 sinisaos
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/frontend/src/components/questions/Categories.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ tag[0].name }} ({{ tag[1] }})
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
48 |
49 |
--------------------------------------------------------------------------------
/backend/apps/accounts/models.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import bcrypt
4 | import jwt
5 | from marshmallow import Schema
6 | from marshmallow import fields as flds
7 | from starlette.authentication import (
8 | AuthCredentials,
9 | AuthenticationBackend,
10 | AuthenticationError,
11 | SimpleUser,
12 | )
13 | from tortoise import fields
14 | from tortoise.models import Model
15 |
16 |
17 | class BaseUser(Model):
18 | id = fields.IntField(pk=True)
19 | username = fields.CharField(max_length=255)
20 | email = fields.CharField(max_length=255)
21 | password = fields.CharField(max_length=255)
22 | joined = fields.DatetimeField(auto_now_add=True)
23 | last_login = fields.DatetimeField(auto_now=True)
24 | login_count = fields.IntField(default=0)
25 | is_superuser = fields.BooleanField(default=False)
26 |
27 | def __str__(self):
28 | return self.username
29 |
30 |
31 | class BaseUserSchema(Schema):
32 | id = flds.Int()
33 | username = flds.Str()
34 | email = flds.Str()
35 | joined = flds.DateTime()
36 | last_login = flds.DateTime()
37 | login_count = flds.Int()
38 | is_superuser = flds.Boolean()
39 |
40 |
41 | # model schemas
42 | users_schema = BaseUserSchema(many=True)
43 | user_schema = BaseUserSchema()
44 |
45 |
46 | class UserAuthentication(AuthenticationBackend):
47 | async def authenticate(self, request):
48 | jwt_cookie = request.cookies.get("jwt")
49 | if jwt_cookie:
50 | try:
51 | payload = jwt.decode(
52 | jwt_cookie.encode("utf8"),
53 | str(os.environ["SECRET_KEY"]),
54 | algorithms=["HS256"],
55 | )
56 | return (
57 | AuthCredentials(["authenticated"]),
58 | SimpleUser(payload["user_id"]),
59 | )
60 | except AuthenticationError:
61 | raise AuthenticationError("Invalid auth credentials")
62 | else:
63 | # unauthenticated
64 | return
65 |
66 |
67 | def hash_password(password: str):
68 | return bcrypt.hashpw(password, bcrypt.gensalt())
69 |
70 |
71 | def check_password(password: str, hashed_password):
72 | return bcrypt.checkpw(password, hashed_password)
73 |
74 |
75 | def generate_jwt(user_id):
76 | payload = {"user_id": user_id}
77 | token = jwt.encode(
78 | payload,
79 | str(os.environ["SECRET_KEY"]),
80 | algorithm="HS256",
81 | )
82 | return token
83 |
--------------------------------------------------------------------------------
/frontend/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Heading
9 |
10 | Donec id elit non mi porta gravida at eget metus. Fusce
11 | dapibus, tellus ac cursus commodo, tortor mauris
12 | condimentum nibh, ut fermentum massa justo sit amet
13 | risus. Etiam porta sem malesuada magna mollis euismod.
14 | Donec sed odio dui.
15 |
16 |
17 | View details »
20 |
21 |
22 |
23 |
Heading
24 |
25 | Donec id elit non mi porta gravida at eget metus. Fusce
26 | dapibus, tellus ac cursus commodo, tortor mauris
27 | condimentum nibh, ut fermentum massa justo sit amet
28 | risus. Etiam porta sem malesuada magna mollis euismod.
29 | Donec sed odio dui.
30 |
31 |
32 | View details »
35 |
36 |
37 |
38 |
Heading
39 |
40 | Donec id elit non mi porta gravida at eget metus. Fusce
41 | dapibus, tellus ac cursus commodo, tortor mauris
42 | condimentum nibh, ut fermentum massa justo sit amet
43 | risus. Etiam porta sem malesuada magna mollis euismod.
44 | Donec sed odio dui.
45 |
46 |
47 | View details »
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
61 |
62 |
67 |
68 |
--------------------------------------------------------------------------------
/frontend/src/components/questions/QuestionsByTag.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Result(s) for tag: "{{ this.$route.params.name }}"
5 |
6 |
7 |
12 |
13 |
19 | {{ item.title }}
20 |
21 |
22 |
23 | asked on
24 | {{ formatDate(item.created) }} by
25 | {{ item.user.username }}
26 |
27 |
28 |
29 |
30 |
31 |
40 |
41 |
42 |
43 |
44 |
45 |
87 |
88 |
--------------------------------------------------------------------------------
/backend/apps/questions/models.py:
--------------------------------------------------------------------------------
1 | from marshmallow import Schema
2 | from marshmallow import fields as flds
3 | from tortoise import fields
4 | from tortoise.models import Model
5 |
6 |
7 | class Question(Model):
8 | id = fields.IntField(pk=True)
9 | title = fields.CharField(max_length=255)
10 | slug = fields.CharField(max_length=255)
11 | content = fields.TextField()
12 | created = fields.DatetimeField(auto_now_add=True)
13 | view = fields.IntField(default=0)
14 | question_like = fields.IntField(default=0)
15 | answer_count = fields.IntField()
16 | accepted_answer = fields.BooleanField(default=False)
17 | tags = fields.ManyToManyField(
18 | "models.Tag", related_name="tags", through="question_tag"
19 | )
20 | user = fields.ForeignKeyField(
21 | "models.BaseUser",
22 | related_name="user",
23 | on_delete=fields.CASCADE,
24 | )
25 |
26 | def __str__(self):
27 | return self.title
28 |
29 |
30 | class Answer(Model):
31 | id = fields.IntField(pk=True)
32 | content = fields.TextField()
33 | created = fields.DatetimeField(auto_now_add=True)
34 | answer_like = fields.IntField(default=0)
35 | is_accepted_answer = fields.BooleanField(default=False)
36 | ans_user = fields.ForeignKeyField(
37 | "models.BaseUser",
38 | related_name="ans_user",
39 | on_delete=fields.CASCADE,
40 | )
41 | question = fields.ForeignKeyField(
42 | "models.Question",
43 | related_name="question",
44 | on_delete=fields.CASCADE,
45 | )
46 |
47 |
48 | class Tag(Model):
49 | id = fields.IntField(pk=True)
50 | name = fields.CharField(max_length=255)
51 |
52 | def __str__(self):
53 | return self.name
54 |
55 |
56 | class UserSchema(Schema):
57 | id = flds.Int()
58 | username = flds.Str()
59 | email = flds.Str()
60 | joined = flds.DateTime()
61 | last_login = flds.DateTime()
62 | login_count = flds.Int()
63 | password = flds.Str()
64 |
65 |
66 | class TagSchema(Schema):
67 | id = flds.Int()
68 | name = flds.Str()
69 |
70 |
71 | class QuestionSchema(Schema):
72 | id = flds.Int()
73 | title = flds.Str()
74 | slug = flds.Str()
75 | content = flds.Str()
76 | created = flds.DateTime()
77 | view = flds.Int()
78 | question_like = flds.Int()
79 | answer_count = flds.Int()
80 | accepted_answer = flds.Bool()
81 | tags = flds.Nested(TagSchema, many=True, dump_only=True)
82 | user = flds.Nested(UserSchema, dump_only=True, only=["username"])
83 |
84 |
85 | class AnswerSchema(Schema):
86 | id = flds.Int()
87 | content = flds.Str()
88 | created = flds.DateTime()
89 | answer_like = flds.Int()
90 | is_accepted_answer = flds.Bool()
91 | ans_user = flds.Nested(UserSchema, dump_only=True, only=["username"])
92 | question = flds.Nested(QuestionSchema, dump_only=True, only=["id"])
93 |
94 |
95 | # model schemas
96 | questions_schema = QuestionSchema(many=True)
97 | answers_schema = AnswerSchema(many=True)
98 | question_schema = QuestionSchema()
99 |
--------------------------------------------------------------------------------
/backend/app.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from dotenv import find_dotenv, load_dotenv
4 | from secure import Secure
5 | from starlette.applications import Starlette
6 | from starlette.middleware.authentication import AuthenticationMiddleware
7 | from starlette.middleware.base import BaseHTTPMiddleware
8 | from starlette.middleware.cors import CORSMiddleware
9 | from starlette.middleware.sessions import SessionMiddleware
10 | from starlette.responses import JSONResponse
11 | from tortoise.contrib.starlette import register_tortoise
12 | from tortoise.exceptions import DoesNotExist
13 |
14 | from apps.accounts.models import (
15 | BaseUser,
16 | UserAuthentication,
17 | hash_password,
18 | user_schema,
19 | )
20 | from apps.accounts.routes import routes
21 | from apps.admin.config import admin_app
22 | from apps.questions.routes import questions_routes
23 |
24 | load_dotenv(find_dotenv())
25 |
26 |
27 | # Security Headers are HTTP response headers that, when set,
28 | # can enhance the security of your web application
29 | # by enabling browser security policies.
30 | # more on https://secure.readthedocs.io/en/latest/headers.html
31 | secure_headers = Secure.with_default_headers()
32 |
33 | app = Starlette()
34 | app.mount("/accounts", routes)
35 | app.mount("/questions", questions_routes)
36 | app.mount("/admin", admin_app)
37 | app.add_middleware(AuthenticationMiddleware, backend=UserAuthentication())
38 | app.add_middleware(SessionMiddleware, secret_key=os.getenv("SECRET_KEY"))
39 | app.add_middleware(
40 | CORSMiddleware,
41 | allow_origins=["http://localhost:5173"],
42 | allow_methods=["*"],
43 | allow_headers=["*"],
44 | allow_credentials=True,
45 | )
46 |
47 |
48 | @app.route("/", methods=["GET"])
49 | async def index(request):
50 | if not await BaseUser.exists(email=os.getenv("ADMIN_EMAIL")):
51 | # create superuser
52 | user = BaseUser(
53 | username=os.getenv("ADMIN_USER"),
54 | email=os.getenv("ADMIN_EMAIL"),
55 | password=hash_password(os.getenv("ADMIN_PASSWORD")),
56 | is_superuser=True,
57 | )
58 | await user.save()
59 | auth_user = request.user.display_name
60 | try:
61 | user = await BaseUser.get(username=auth_user)
62 | result = user_schema.dump(user)
63 | return JSONResponse(
64 | {
65 | "auth_user": auth_user,
66 | "result": result,
67 | }
68 | )
69 | except DoesNotExist:
70 | response = JSONResponse(
71 | {
72 | "auth_user": "",
73 | }
74 | )
75 | return response
76 |
77 |
78 | # middleware for secure headers
79 | class SecurityHeadersMiddleware(BaseHTTPMiddleware):
80 | async def dispatch(self, request, call_next):
81 | response = await call_next(request)
82 | await secure_headers.set_headers_async(response)
83 | return response
84 |
85 |
86 | register_tortoise(
87 | app,
88 | db_url=os.environ["DB_URI"],
89 | modules={"models": ["apps.accounts.models", "apps.questions.models"]},
90 | generate_schemas=True,
91 | )
92 |
--------------------------------------------------------------------------------
/frontend/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Starlette | Vue
5 |
6 |
7 |
8 |
9 |
15 | Ask
16 | Question
17 |
18 |
24 | Ask
25 | Question
26 |
27 |
28 |
29 |
30 |
31 | Categories
32 | Questions
33 | Login
36 | Register
39 |
40 |
41 |
42 |
43 | Hello {{ authUser.username }}
44 |
45 | Profile
52 | Logout
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/backend/apps/admin/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import bcrypt
4 | from dotenv import find_dotenv, load_dotenv
5 | from fastadmin import TortoiseModelAdmin, register, settings
6 | from fastadmin import fastapi_app as admin_app
7 |
8 | from apps.accounts.models import BaseUser
9 | from apps.questions.models import Answer, Question, Tag
10 |
11 | load_dotenv(find_dotenv())
12 |
13 |
14 | # EXPORT to enviroment works, but this is the only way I find to
15 | # override the default settings with values from the .env
16 | settings.settings.ADMIN_SITE_NAME = os.getenv("ADMIN_SITE_NAME")
17 | settings.settings.ADMIN_USER_MODEL = os.getenv("ADMIN_USER_MODEL")
18 | settings.settings.ADMIN_USER_MODEL_USERNAME_FIELD = os.getenv(
19 | "ADMIN_USER_MODEL_USERNAME_FIELD"
20 | )
21 | settings.settings.ADMIN_SECRET_KEY = os.getenv("ADMIN_SECRET_KEY")
22 |
23 |
24 | @register(BaseUser)
25 | class BaseUserAdmin(TortoiseModelAdmin):
26 | list_display = (
27 | "id",
28 | "username",
29 | "email",
30 | "joined",
31 | "last_login",
32 | "login_count",
33 | )
34 | list_display_links = ("id",)
35 | search_fields = ("username", "email")
36 |
37 | async def authenticate(self, username: str, password: str) -> int | None:
38 | user = await BaseUser.filter(
39 | username=username, is_superuser=True
40 | ).first()
41 | if not user:
42 | return None
43 | if not bcrypt.checkpw(password.encode(), user.password.encode()):
44 | return None
45 | return user.id
46 |
47 |
48 | @register(Question)
49 | class QuestionAdmin(TortoiseModelAdmin):
50 | list_display = (
51 | "id",
52 | "title",
53 | "content",
54 | "created",
55 | "view",
56 | "question_like",
57 | "answer_count",
58 | "accepted_answer",
59 | "tags",
60 | "user",
61 | )
62 | list_display_links = (
63 | "id",
64 | "user",
65 | "tags",
66 | )
67 | search_fields = (
68 | "title",
69 | "content",
70 | )
71 | list_filter = (
72 | "title",
73 | "user",
74 | "view",
75 | "question_like",
76 | "answer_count",
77 | "accepted_answer",
78 | )
79 |
80 |
81 | @register(Answer)
82 | class AnswerAdmin(TortoiseModelAdmin):
83 | list_display = (
84 | "id",
85 | "content",
86 | "created",
87 | "answer_like",
88 | "is_accepted_answer",
89 | "ans_user",
90 | "question",
91 | )
92 | list_display_links = (
93 | "id",
94 | "question",
95 | )
96 | search_fields = ("content",)
97 | list_filter = (
98 | "content",
99 | "created",
100 | "answer_like",
101 | "is_accepted_answer",
102 | "ans_user",
103 | "question",
104 | )
105 |
106 |
107 | @register(Tag)
108 | class TagAdmin(TortoiseModelAdmin):
109 | list_display = (
110 | "id",
111 | "name",
112 | )
113 | list_display_links = ("id",)
114 | search_fields = ("name",)
115 | list_filter = ("name",)
116 |
117 |
118 | # re-export admin_app
119 | __all__ = ["admin_app"]
120 |
--------------------------------------------------------------------------------
/frontend/src/components/accounts/Profile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Profile
11 | Questions
16 | Answers
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Username
30 |
31 | {{ authUser.username }}
32 |
33 |
34 |
35 | Email
36 |
37 | {{ authUser.email }}
38 |
39 |
40 |
41 | Joined
42 |
43 | {{ authUser.joined }}
44 |
45 |
46 |
47 | Last login
48 |
49 | {{
50 | authUser.last_login
51 | }}
52 |
53 |
54 |
55 | Login count
56 |
57 | {{
58 | authUser.login_count
59 | }}
60 |
61 |
62 |
63 |
68 | Delete account
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/frontend/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createMemoryHistory, createRouter } from 'vue-router'
2 | import store from '../store/index.js'
3 | import Home from '../views/Home.vue'
4 | import Login from '../components/accounts/Login.vue'
5 | import Register from '../components/accounts/Register.vue'
6 | import Profile from '../components/accounts/Profile.vue'
7 | import Questions from '../components/questions/Questions.vue'
8 | import Question from '../components/questions/Question.vue'
9 | import CreateQuestion from '../components/questions/CreateQuestion.vue'
10 | import QuestionsByTag from '../components/questions/QuestionsByTag.vue'
11 | import Categories from '../components/questions/Categories.vue'
12 | import ProfileQuestions from '../components/questions/ProfileQuestions.vue'
13 | import ProfileQuestionsEdit from '../components/questions/ProfileQuestionsEdit.vue'
14 | import ProfileAnswers from '../components/questions/ProfileAnswers.vue'
15 | import ProfileAnswersEdit from '../components/questions/ProfileAnswersEdit.vue'
16 |
17 |
18 |
19 | const routes = [
20 | {
21 | path: '/',
22 | name: 'home',
23 | component: Home
24 | },
25 | {
26 | path: '/login',
27 | name: 'login',
28 | component: Login
29 | },
30 | {
31 | path: '/register',
32 | name: 'register',
33 | component: Register
34 | },
35 | {
36 | path: '/profile/:name',
37 | name: 'profile',
38 | component: Profile,
39 | meta: {
40 | requiresAuth: true
41 | }
42 | },
43 | {
44 | path: '/profile/:name/questions',
45 | name: 'profileQuestions',
46 | component: ProfileQuestions,
47 | meta: {
48 | requiresAuth: true
49 | }
50 | },
51 | {
52 | path: '/profile/:name/answers',
53 | name: 'profileAnswers',
54 | component: ProfileAnswers,
55 | meta: {
56 | requiresAuth: true
57 | }
58 | },
59 | {
60 | path: '/profile/:name/questions/question-edit/:id/:title/:content',
61 | name: 'profileQuestionsEdit',
62 | component: ProfileQuestionsEdit,
63 | meta: {
64 | requiresAuth: true
65 | }
66 | },
67 | {
68 | path: '/profile/:name/questions/answer-edit/:id/:content',
69 | name: 'profileAnswersEdit',
70 | component: ProfileAnswersEdit,
71 | meta: {
72 | requiresAuth: true
73 | }
74 | },
75 | {
76 | path: '/questions',
77 | name: 'questions',
78 | component: Questions
79 | },
80 | {
81 | path: '/questions/:id/:slug',
82 | name: 'question',
83 | component: Question
84 | },
85 | {
86 | path: '/create',
87 | name: 'createQuestion',
88 | component: CreateQuestion,
89 | meta: {
90 | requiresAuth: true
91 | }
92 | },
93 | {
94 | path: '/questions/tags/:name',
95 | name: 'questionsByTag',
96 | component: QuestionsByTag
97 | },
98 | {
99 | path: '/categories',
100 | name: 'categories',
101 | component: Categories
102 | },
103 | ]
104 |
105 |
106 | const router = createRouter({
107 | history: createMemoryHistory(),
108 | routes,
109 | })
110 |
111 |
112 | router.beforeEach((to, from, next) => {
113 | if (to.matched.some(record => record.meta.requiresAuth)) {
114 | if (store.getters.isLoggedIn) {
115 | next()
116 | return
117 | }
118 | next('/')
119 | } else {
120 | next()
121 | }
122 | })
123 |
124 | export default router
125 |
126 |
127 |
--------------------------------------------------------------------------------
/backend/apps/questions/routes.py:
--------------------------------------------------------------------------------
1 | from starlette.routing import Route, Router
2 |
3 | from apps.questions.views import (
4 | answer_accept,
5 | answer_create,
6 | answer_delete,
7 | answer_edit,
8 | answer_like,
9 | answers_user,
10 | question,
11 | question_create,
12 | question_delete,
13 | question_edit,
14 | question_like,
15 | questions_all,
16 | questions_solved,
17 | questions_unsolved,
18 | questions_user,
19 | tags,
20 | tags_categories,
21 | )
22 |
23 | questions_routes = Router(
24 | [
25 | # all questions endpoints
26 | Route(
27 | "/", endpoint=questions_all, methods=["GET"], name="questions_all"
28 | ),
29 | Route(
30 | "/unsolved",
31 | endpoint=questions_unsolved,
32 | methods=["GET"],
33 | name="questions_unsolved",
34 | ),
35 | Route(
36 | "/solved",
37 | endpoint=questions_solved,
38 | methods=["GET"],
39 | name="questions_solved",
40 | ),
41 | Route(
42 | "/{id:int}/{slug:str}",
43 | endpoint=question,
44 | methods=["GET", "POST"],
45 | name="question",
46 | ),
47 | # profile user questions and answers
48 | Route(
49 | "/user-questions/{id:str}",
50 | endpoint=questions_user,
51 | methods=["GET", "POST"],
52 | name="questions_user",
53 | ),
54 | Route(
55 | "/user-answers/{id:str}",
56 | endpoint=answers_user,
57 | methods=["GET", "POST"],
58 | name="answers_user",
59 | ),
60 | # question endpoints
61 | Route(
62 | "/create",
63 | endpoint=question_create,
64 | methods=["GET", "POST"],
65 | name="question_create",
66 | ),
67 | Route(
68 | "/{id:int}",
69 | endpoint=question_edit,
70 | methods=["PUT"],
71 | name="question_edit",
72 | ),
73 | Route(
74 | "/{id:int}/{slug:str}",
75 | endpoint=question_delete,
76 | methods=["DELETE"],
77 | name="question_delete",
78 | ),
79 | Route(
80 | "/question-like/{id:int}",
81 | endpoint=question_like,
82 | methods=["POST"],
83 | name="question_like",
84 | ),
85 | # answer endpoints
86 | Route(
87 | "/answer-create/{id:int}",
88 | endpoint=answer_create,
89 | methods=["POST"],
90 | name="answer_create",
91 | ),
92 | Route(
93 | "/answer-like/{id:int}",
94 | endpoint=answer_like,
95 | methods=["POST"],
96 | name="answer_like",
97 | ),
98 | Route(
99 | "/answer-accept/{id:int}",
100 | endpoint=answer_accept,
101 | methods=["POST"],
102 | name="answer_accept",
103 | ),
104 | Route(
105 | "/answer/{id:int}",
106 | endpoint=answer_edit,
107 | methods=["PUT"],
108 | name="answer_edit",
109 | ),
110 | Route(
111 | "/answer/{id:int}",
112 | endpoint=answer_delete,
113 | methods=["DELETE"],
114 | name="answer_delete",
115 | ),
116 | # tags
117 | Route("/tags/{tag:str}", endpoint=tags, methods=["GET"], name="tags"),
118 | Route(
119 | "/categories",
120 | endpoint=tags_categories,
121 | methods=["GET"],
122 | name="tags_categories",
123 | ),
124 | ]
125 | )
126 |
--------------------------------------------------------------------------------
/frontend/src/components/accounts/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ message }}
12 |
Login
13 |
14 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
110 |
--------------------------------------------------------------------------------
/frontend/src/components/questions/ProfileAnswersEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ message }}
12 |
Edit answer
13 |
14 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
119 |
--------------------------------------------------------------------------------
/frontend/src/components/questions/ProfileAnswers.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Profile
11 | Questions
16 | Answers
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Content
31 | Created
32 | Likes
33 | Action
34 |
35 |
36 |
37 |
38 |
39 | {{ item.content.slice(0, 50) }}...
40 |
41 | {{ formatDate(item.created) }}
42 | {{ item.answer_like }}
43 |
44 |
54 |
55 |
56 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
121 |
122 |
--------------------------------------------------------------------------------
/frontend/src/components/accounts/Dashboard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Users
11 | Questions
16 | Answers
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Username
30 | Email
31 | Joined
32 | Last login
33 | Login count
34 | Action
35 |
36 |
37 |
38 |
44 | {{ user.username }}
45 | {{ user.email }}
46 | {{ formatDate(user.joined) }}
47 | {{ formatDate(user.last_login) }}
48 | {{ user.login_count }}
49 |
50 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
70 |
71 |
72 |
73 |
74 |
75 |
121 |
--------------------------------------------------------------------------------
/frontend/src/components/questions/ProfileQuestions.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Profile
11 | Questions
16 | Answers
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Title
31 | Content
32 | Created
33 | Views
34 | Likes
35 | Action
36 |
37 |
38 |
39 |
40 | {{ item.title }}
41 |
42 | {{ item.content.slice(0, 50) }}...
43 |
44 | {{ formatDate(item.created) }}
45 | {{ item.view }}
46 | {{ item.question_like }}
47 |
48 |
59 |
60 |
61 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
130 |
--------------------------------------------------------------------------------
/frontend/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex'
2 | import VuexPersistence from 'vuex-persist'
3 | import axios from 'axios'
4 |
5 | const vuexLocal = new VuexPersistence({
6 | storage: window.localStorage
7 | })
8 |
9 |
10 | const store = createStore({
11 | state() {
12 | return {
13 | status: '',
14 | token: localStorage.getItem('token') || '',
15 | user: [],
16 | }
17 | },
18 | mutations: {
19 | AUTH_REQUEST(state) {
20 | state.status = 'loading'
21 | },
22 | AUTH_SUCCES(state, token, user) {
23 | state.status = 'success'
24 | state.token = token
25 | state.user = user
26 | },
27 | AUTH_ERROR(state) {
28 | state.status = 'error'
29 | },
30 | LOGOUT(state) {
31 | state.status = ''
32 | state.token = ''
33 | },
34 | DELETE_USER(state) {
35 | state.user = ''
36 | }
37 | },
38 | actions: {
39 | login({ commit }, user) {
40 | return new Promise((resolve, reject) => {
41 | commit('AUTH_REQUEST')
42 | axios({
43 | url: '/accounts/login',
44 | data: user,
45 | method: 'POST'
46 | })
47 | .then(resp => {
48 | const token = resp.data.auth_user
49 | const user = resp.data.result
50 | const users = resp.data.results
51 | localStorage.setItem('token', token)
52 | axios.defaults.headers.common['Authorization'] = token
53 | commit('AUTH_SUCCES', token)
54 | commit('AUTH_SUCCES', user)
55 | commit('AUTH_REQUEST', users)
56 | resolve(resp)
57 | })
58 | .catch(err => {
59 | commit('AUTH_ERROR')
60 | localStorage.removeItem('token')
61 | reject(err)
62 | })
63 | })
64 | },
65 | register({ commit }, user) {
66 | return new Promise((resolve, reject) => {
67 | commit('AUTH_REQUEST')
68 | axios({
69 | url: '/accounts/register',
70 | data: user,
71 | method: 'POST'
72 | })
73 | .then(resp => {
74 | const token = resp.data.auth_user
75 | const user = resp.data.result
76 | localStorage.setItem('token', token)
77 | axios.defaults.headers.common['Authorization'] = token
78 | commit('AUTH_SUCCES', token)
79 | commit('AUTH_SUCCES', user)
80 | resolve(resp)
81 | })
82 | .catch(err => {
83 | commit('AUTH_ERROR', err)
84 | localStorage.removeItem('token')
85 | reject(err)
86 | })
87 | })
88 | },
89 | logout({ commit }, user) {
90 | return new Promise((resolve, reject) => {
91 | axios({
92 | url: '/accounts/logout',
93 | method: 'GET'
94 | })
95 | .then(resp => {
96 | commit('LOGOUT')
97 | localStorage.removeItem('token')
98 | resolve(resp)
99 | })
100 | .catch(err => {
101 | reject(err)
102 | })
103 | })
104 | },
105 | delete_user({ commit, state, token, dispatch }) {
106 | const auth_token = localStorage.getItem('token', token)
107 | return new Promise((resolve, reject) => {
108 | if (confirm("Are you sure you want to delete the account!"))
109 | axios({
110 | url: '/accounts/' + state.token.id,
111 | data: token,
112 | headers: { Authorization: 'Token ' + auth_token },
113 | method: 'DELETE'
114 | }
115 | )
116 | .then(resp => {
117 | commit('DELETE_USER')
118 | commit('LOGOUT')
119 | const token = resp.data.token
120 | localStorage.removeItem('token', token)
121 | dispatch('logout')
122 | resolve(resp)
123 | })
124 | .catch(err => {
125 | console.log(auth_token);
126 | reject(err)
127 | })
128 | })
129 | }
130 | },
131 | getters: {
132 | authUser: state => state.token,
133 | isLoggedIn: state => !!state.token,
134 | authStatus: state => state.status,
135 | allUsers: state => state.users,
136 | },
137 | plugins: [vuexLocal.plugin]
138 | })
139 |
140 | export default store
141 |
142 |
--------------------------------------------------------------------------------
/frontend/src/components/questions/ProfileQuestionsEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ message }}
12 |
Edit question
13 |
14 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
141 |
--------------------------------------------------------------------------------
/frontend/src/components/questions/CreateQuestion.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ message }}
12 |
Create question
13 |
14 |
15 |
32 |
52 |
68 |
69 |
75 |
76 |
77 | Submit
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
145 |
--------------------------------------------------------------------------------
/frontend/src/components/accounts/DashboardAnswers.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Users
11 | Questions
16 | Answers
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | Content
32 | Created
33 | Likes
34 | Action
35 |
36 |
37 |
38 |
44 |
45 |
46 | {{ item.content.slice(0, 50) }}...
47 |
48 |
49 | {{ formatDate(item.created) }}
50 | {{ item.answer_like }}
51 |
52 |
62 |
63 |
64 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
85 |
86 |
87 |
88 |
89 |
90 |
141 |
--------------------------------------------------------------------------------
/backend/apps/accounts/views.py:
--------------------------------------------------------------------------------
1 | import datetime
2 |
3 | from starlette.authentication import requires
4 | from starlette.responses import JSONResponse, RedirectResponse, Response
5 | from tortoise.exceptions import DoesNotExist
6 | from tortoise.transactions import in_transaction
7 |
8 | from apps.accounts.models import (
9 | BaseUser,
10 | check_password,
11 | generate_jwt,
12 | hash_password,
13 | users_schema,
14 | )
15 | from apps.questions.models import (
16 | Answer,
17 | Question,
18 | answers_schema,
19 | questions_schema,
20 | )
21 |
22 |
23 | async def register(request):
24 | """
25 | Validate form, register and authenticate user with JWT token
26 | """
27 | results = await BaseUser.all()
28 | form = await request.json()
29 | username = form["username"]
30 | email = form["email"]
31 | password = form["password"]
32 | for result in results:
33 | if email == result.email or username == result.username:
34 | return Response(
35 | "User with that email or username already exists!",
36 | status_code=422,
37 | )
38 | query = BaseUser(
39 | username=username,
40 | email=email,
41 | joined=datetime.datetime.now(),
42 | last_login=datetime.datetime.now(),
43 | login_count=1,
44 | password=hash_password(password),
45 | )
46 | await query.save()
47 | user_query = await BaseUser.get(username=username)
48 | hashed_password = user_query.password
49 | valid_password = check_password(password, hashed_password)
50 | response = RedirectResponse(url="/", status_code=302)
51 | # generate httponly cookie with jwt token
52 | # than we don't have to store jwt in browser
53 | # localStorage for Vue auth
54 | if valid_password:
55 | response.set_cookie(
56 | "jwt", generate_jwt(user_query.username), httponly=True
57 | )
58 | return response
59 |
60 |
61 | async def login(request):
62 | """
63 | Validate form, login and authenticate user with JWT token
64 | """
65 | form = await request.json()
66 | username = form["username"]
67 | password = form["password"]
68 | try:
69 | results = await BaseUser.get(username=username)
70 | hashed_password = results.password
71 | valid_password = check_password(password, hashed_password)
72 | if not valid_password or results.username != username:
73 | response = Response(
74 | "Invalid username or password!", status_code=422
75 | )
76 | return response
77 | # update login counter and login time
78 | results.login_count += 1
79 | results.last_login = datetime.datetime.now()
80 | await results.save()
81 | response = RedirectResponse(url="/", status_code=302)
82 | # generate httponly cookie with jwt token
83 | # than we don't have to store jwt in browser
84 | # localStorage for Vue auth
85 | if valid_password:
86 | response.set_cookie(
87 | "jwt", generate_jwt(results.username), httponly=True
88 | )
89 | return response
90 | except DoesNotExist:
91 | response = Response(
92 | "Please register you don't have account!", status_code=422
93 | )
94 | return response
95 |
96 |
97 | @requires("authenticated")
98 | async def dashboard(request):
99 | if request.user.is_authenticated:
100 | users = await BaseUser.all()
101 | results = users_schema.dump(users)
102 | qus = (
103 | await Question.all()
104 | .prefetch_related("user", "tags")
105 | .order_by("-id")
106 | )
107 | questions = questions_schema.dump(qus)
108 | ans = (
109 | await Answer.all()
110 | .prefetch_related("ans_user", "question")
111 | .order_by("-id")
112 | )
113 | answers = answers_schema.dump(ans)
114 | return JSONResponse(
115 | {
116 | "results": results,
117 | "questions": questions,
118 | "answers": answers,
119 | }
120 | )
121 |
122 |
123 | @requires("authenticated")
124 | async def delete(request):
125 | """
126 | Delete user
127 | """
128 | id = request.path_params["id"]
129 | session_user = request.user.username
130 | results = await BaseUser.get(username=session_user)
131 | if request.method == "DELETE" and results.username == session_user:
132 | async with in_transaction() as conn:
133 | await conn.execute_query(
134 | f"DELETE FROM tag WHERE tag.id IN \
135 | (SELECT question_tag.tag_id FROM question \
136 | JOIN question_tag ON question_tag.question_id = question.id \
137 | JOIN user ON user.id = question.user_id WHERE user.id = {id})"
138 | )
139 | async with in_transaction() as conn:
140 | await conn.execute_query(
141 | f"UPDATE question \
142 | JOIN answer ON question.id = answer.question_id \
143 | JOIN user on user.id = answer.ans_user_id \
144 | SET question.accepted_answer = 0 \
145 | WHERE user.id = {id} AND answer.is_accepted_answer = 1"
146 | )
147 | await BaseUser.get(id=id).delete()
148 | # 303 status code for redirect after delete
149 | response = RedirectResponse(url="/", status_code=303)
150 | response.delete_cookie("jwt")
151 | return response
152 | else:
153 | return Response(status_code=403)
154 |
155 |
156 | async def logout(request):
157 | request.session.clear()
158 | response = RedirectResponse(url="/", status_code=302)
159 | response.delete_cookie("jwt")
160 | return response
161 |
--------------------------------------------------------------------------------
/frontend/src/components/accounts/DashboardQuestions.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Users
11 | Questions
16 | Answers
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | Title
32 | Content
33 | Created
34 | Views
35 | Likes
36 | Action
37 |
38 |
39 |
40 |
46 | {{ item.title }}
47 |
48 |
49 | {{ item.content.slice(0, 50) }}...
50 |
51 |
52 | {{ formatDate(item.created) }}
53 | {{ item.view }}
54 | {{ item.question_like }}
55 |
56 |
67 |
68 |
69 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
95 |
96 |
97 |
98 |
99 |
100 |
151 |
--------------------------------------------------------------------------------
/frontend/src/components/accounts/Register.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{ message }}
12 |
Register
13 |
14 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
170 |
--------------------------------------------------------------------------------
/frontend/src/components/questions/Questions.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 | All questions
16 |
17 |
18 | Open
19 |
20 |
21 | Solved
22 |
23 | Oldest
24 |
25 | Most viewed
26 |
27 |
28 | Most popular
29 |
30 |
31 |
32 |
33 |
38 |
39 |
45 | {{ item.title }}
46 |
47 |
48 |
49 | asked on
50 | {{ formatDate(item.created) }} by
51 | {{ item.user.username }}
52 |
53 |
54 |
55 |
{{ item.content }}
56 |
57 |
62 | {{ tag.name }}
70 |
71 |
Solved
76 |
77 |
78 |
{{ item.view }}
81 |
84 |
{{ item.question_like }}
87 |
88 |
89 |
90 |
91 |
92 |
101 |
102 |
103 |
104 |
No results.
105 |
106 |
107 |
108 |
109 |
110 |
205 |
206 |
--------------------------------------------------------------------------------
/frontend/src/components/questions/Question.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ question.title }}
6 |
7 |
8 | asked on
9 | {{ formatDate(question.created) }} by
10 | {{ question.user.username }}
11 |
12 |
13 |
14 |
{{ question.content }}
15 |
16 |
21 | {{ tag.name }}
26 |
27 |
33 | {{ question.question_like }}
39 |
40 |
54 |
55 |
56 |
57 |
58 |
*Must be logged in to post answer
59 |
60 |
77 |
78 |
79 | Submit
80 |
81 |
82 |
83 |
84 |
{{ answerCount }} answer(s)
85 |
86 |
87 |
88 |
89 | Answered on
90 | {{ formatDate(item.created) }} by
91 | {{ item.ans_user.username }}
92 |
93 |
94 |
95 |
{{ item.content }}
96 |
102 | {{ item.answer_like }}
108 |
109 |
122 |
123 |
130 |
136 |
137 |
138 |
144 | Accepted answer
147 |
148 |
149 | Accepted answer
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
271 |
272 |
--------------------------------------------------------------------------------
/backend/apps/questions/views.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import itertools as it
3 |
4 | from starlette.authentication import requires
5 | from starlette.responses import JSONResponse, RedirectResponse, Response
6 | from tortoise.transactions import in_transaction
7 |
8 | from apps.accounts.models import BaseUser
9 | from apps.questions.models import (
10 | Answer,
11 | Question,
12 | Tag,
13 | answers_schema,
14 | question_schema,
15 | questions_schema,
16 | )
17 |
18 |
19 | async def questions_all(request):
20 | """
21 | Marshmallow for serilization
22 | """
23 | results = (
24 | await Question.all().prefetch_related("user", "tags").order_by("-id")
25 | )
26 | # Serialize the queryset
27 | questions = questions_schema.dump(results)
28 | answer_count = [
29 | (
30 | await Answer.all()
31 | .prefetch_related("question")
32 | .filter(question__id=row.id)
33 | .count()
34 | )
35 | for row in results
36 | ]
37 | for item, x in zip(questions, answer_count):
38 | item["answer_count"] = x
39 | return JSONResponse({"questions": questions})
40 |
41 |
42 | async def questions_unsolved(request):
43 | results = (
44 | await Question.filter(accepted_answer=0)
45 | .prefetch_related("user", "tags")
46 | .order_by("-id")
47 | )
48 | # Serialize the queryset
49 | questions = questions_schema.dump(results)
50 | answer_count = [
51 | (
52 | await Answer.all()
53 | .prefetch_related("question")
54 | .filter(question__id=row.id)
55 | .count()
56 | )
57 | for row in results
58 | ]
59 | for item, x in zip(questions, answer_count):
60 | item["answer_count"] = x
61 | return JSONResponse({"questions": questions})
62 |
63 |
64 | async def questions_solved(request):
65 | results = (
66 | await Question.filter(accepted_answer=1)
67 | .prefetch_related("user", "tags")
68 | .order_by("-id")
69 | )
70 | # Serialize the queryset
71 | questions = questions_schema.dump(results)
72 | answer_count = [
73 | (
74 | await Answer.all()
75 | .prefetch_related("question")
76 | .filter(question__id=row.id)
77 | .count()
78 | )
79 | for row in results
80 | ]
81 | for item, x in zip(questions, answer_count):
82 | item["answer_count"] = x
83 | return JSONResponse({"questions": questions})
84 |
85 |
86 | async def question(request):
87 | """
88 | Single question
89 | """
90 | id = request.path_params["id"]
91 | result = await Question.get(id=id).prefetch_related("user", "tags")
92 | answers_result = (
93 | await Answer.all()
94 | .prefetch_related("ans_user")
95 | .filter(question__id=id)
96 | .order_by("-id")
97 | )
98 | # Serialize the queryset
99 | question = question_schema.dump(result)
100 | answers = answers_schema.dump(answers_result)
101 | # update question views
102 | result.view += 1
103 | await result.save()
104 | return JSONResponse(
105 | {
106 | "question": question,
107 | "answers": answers,
108 | "answer_count": len(answers),
109 | }
110 | )
111 |
112 |
113 | @requires("authenticated")
114 | async def question_like(request):
115 | id = request.path_params["id"]
116 | results = await Question.get(id=id)
117 | if request.user.is_authenticated:
118 | # update question likes and decrease question views
119 | # to avoid duplication of question views
120 | results.question_like += 1
121 | results.view -= 1
122 | await results.save()
123 | return RedirectResponse(url="/", status_code=303)
124 | else:
125 | return Response(status_code=403)
126 |
127 |
128 | async def question_create(request):
129 | """
130 | Question form
131 | """
132 | session_user = request.user.username
133 | results = await BaseUser.get(username=session_user)
134 | form = await request.json()
135 | title = form["title"]
136 | content = form["content"]
137 | if request.method == "POST":
138 | # possible to insert only one tag without error
139 | if "," in form["tags"] or len((form["tags"]).split()) == 1:
140 | query = Question(
141 | title=title,
142 | slug="-".join(title.lower().split()),
143 | content=content,
144 | created=datetime.datetime.now(),
145 | view=0,
146 | question_like=0,
147 | answer_count=0,
148 | user_id=results.id,
149 | )
150 | await query.save()
151 | tags = []
152 | # split tags and make sure that is valid tags list without empty
153 | # space and than insert in db
154 | valid_tags_list = [i for i in form["tags"].split(",") if i != ""]
155 | for idx, item in enumerate(valid_tags_list):
156 | tag = Tag(name=item.lower())
157 | await tag.save()
158 | tags.append(tag)
159 | await query.tags.add(tags[idx])
160 | return RedirectResponse(url="/questions", status_code=303)
161 | return Response("Tags must be comma-separated", status_code=422)
162 |
163 |
164 | @requires("authenticated")
165 | async def question_edit(request):
166 | """
167 | Question edit form
168 | """
169 | id = request.path_params["id"]
170 | session_user = request.user.username
171 | question = await Question.get(id=id).prefetch_related("user")
172 | if request.method == "PUT" and question.user.username == session_user:
173 | form = await request.json()
174 | title = form["title"]
175 | content = form["content"]
176 | await Question.filter(id=id).update(
177 | title=title,
178 | slug="-".join(title.lower().split()),
179 | content=content,
180 | created=question.created,
181 | view=question.view,
182 | question_like=question.question_like,
183 | answer_count=question.answer_count,
184 | accepted_answer=question.accepted_answer,
185 | user_id=question.user.id,
186 | )
187 | # 303 status code for redirect after update
188 | return RedirectResponse(url="/questions/", status_code=303)
189 | else:
190 | return Response(status_code=403)
191 |
192 |
193 | async def questions_user(request):
194 | id = request.path_params["id"]
195 | results = await BaseUser.get(username=id)
196 | result = (
197 | await Question.all()
198 | .prefetch_related("user", "tags")
199 | .filter(user_id=results.id)
200 | .order_by("-id")
201 | )
202 | # Serialize the queryset
203 | questions = questions_schema.dump(result)
204 | return JSONResponse({"questions": questions})
205 |
206 |
207 | @requires("authenticated")
208 | async def question_delete(request):
209 | """
210 | Delete question
211 | """
212 | id = request.path_params["id"]
213 | session_user = request.user.username
214 | results = await Question.get(id=id).prefetch_related("user")
215 | if request.method == "DELETE" and results.user.username == session_user:
216 | async with in_transaction() as conn:
217 | await conn.execute_query(
218 | f"DELETE FROM tag WHERE tag.id IN \
219 | (SELECT question_tag.tag_id FROM question \
220 | JOIN question_tag \
221 | ON question_tag.question_id = question.id \
222 | WHERE question.id={id})"
223 | )
224 | await Question.get(id=id).delete()
225 | # 303 status code for redirect after delete
226 | response = RedirectResponse(url="/", status_code=303)
227 | return response
228 |
229 |
230 | async def answer_create(request):
231 | """
232 | Answer form
233 | """
234 | id = request.path_params["id"]
235 | session_user = request.user.username
236 | form = await request.json()
237 | content = form["content"]
238 | result = await BaseUser.get(username=session_user)
239 | results = await Question.get(id=id)
240 | if request.method == "POST":
241 | query = Answer(
242 | content=content,
243 | created=datetime.datetime.now(),
244 | answer_like=0,
245 | is_accepted_answer=0,
246 | question_id=results.id,
247 | ans_user_id=result.id,
248 | )
249 | await query.save()
250 | results.answer_count += 1
251 | await results.save()
252 | return RedirectResponse(url="/questions/", status_code=303)
253 |
254 |
255 | @requires("authenticated")
256 | async def answer_like(request):
257 | id = request.path_params["id"]
258 | result = await Answer.get(id=id)
259 | question = await Question.get(id=result.question_id)
260 | if request.user.is_authenticated:
261 | # update answer likes and decrease question views
262 | # to avoid duplication of question views
263 | result.answer_like += 1
264 | await result.save()
265 | question.view -= 1
266 | await question.save()
267 | return RedirectResponse(url="/questions/", status_code=303)
268 | else:
269 | return Response(status_code=403)
270 |
271 |
272 | async def answer_accept(request):
273 | id = request.path_params["id"]
274 | result = await Answer.get(id=id)
275 | question = await Question.get(id=result.question_id)
276 | result.is_accepted_answer = 1
277 | await result.save()
278 | question.accepted_answer = 1
279 | await question.save()
280 | return RedirectResponse(url="/questions/", status_code=303)
281 |
282 |
283 | async def answers_user(request):
284 | id = request.path_params["id"]
285 | results = await BaseUser.get(username=id)
286 | result = await Answer.all().filter(ans_user_id=results.id)
287 | # Serialize the queryset
288 | answers = answers_schema.dump(result)
289 | return JSONResponse({"answers": answers})
290 |
291 |
292 | @requires("authenticated")
293 | async def answer_edit(request):
294 | """
295 | Answer edit form
296 | """
297 | id = request.path_params["id"]
298 | session_user = request.user.username
299 | answer = await Answer.get(id=id).prefetch_related("ans_user")
300 | if request.method == "PUT" and answer.ans_user.username == session_user:
301 | form = await request.json()
302 | content = form["content"]
303 | await Answer.filter(id=id).update(
304 | content=content,
305 | created=answer.created,
306 | answer_like=answer.answer_like,
307 | is_accepted_answer=answer.is_accepted_answer,
308 | question_id=answer.question_id,
309 | ans_user_id=answer.ans_user_id,
310 | )
311 | # 303 status code for redirect after update
312 | return RedirectResponse(url="/questions/", status_code=303)
313 | else:
314 | return Response(status_code=403)
315 |
316 |
317 | @requires("authenticated")
318 | async def answer_delete(request):
319 | """
320 | Delete answer
321 | """
322 | id = request.path_params["id"]
323 | session_user = request.user.username
324 | answer = (
325 | await Answer.get(id=id)
326 | .prefetch_related("ans_user")
327 | .filter(ans_user__username=session_user)
328 | )
329 | results = await Question.get(id=answer.question_id)
330 | if request.method == "DELETE" and answer.ans_user.username == session_user:
331 | # decrease question answer count
332 | results.answer_count -= 1
333 | if answer.is_accepted_answer:
334 | results.accepted_answer = False
335 | await results.save()
336 | await Answer.get(id=id).delete()
337 | # 303 status code for redirect after delete
338 | response = RedirectResponse(url="/", status_code=303)
339 | return response
340 |
341 |
342 | async def tags(request):
343 | """
344 | All tags
345 | """
346 | tag = request.path_params["tag"]
347 | result = (
348 | await Question.all()
349 | .prefetch_related("user", "tags")
350 | .filter(tags__name=tag)
351 | .order_by("-id")
352 | )
353 | # Serialize the queryset
354 | questions = questions_schema.dump(result)
355 | return JSONResponse({"questions": questions})
356 |
357 |
358 | async def tags_categories(request):
359 | """
360 | Tags categories
361 | """
362 | # use itertools.groupby to simulate SQL GROUP BY
363 | results = await Tag.all().order_by("name").values("name")
364 | categories_tags = [(k, sum(1 for i in g)) for k, g in it.groupby(results)]
365 | return JSONResponse({"categories_tags": categories_tags})
366 |
--------------------------------------------------------------------------------
/frontend/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "frontend",
9 | "version": "0.0.0",
10 | "dependencies": {
11 | "@vuelidate/core": "^2.0.3",
12 | "@vuelidate/validators": "^2.0.4",
13 | "axios": "^1.12.0",
14 | "bootstrap": "^5.3.3",
15 | "bootstrap-vue-next": "^0.25.10",
16 | "dayjs": "^1.11.13",
17 | "vue": "^3.5.12",
18 | "vue-awesome-paginate": "^1.2.0",
19 | "vue-router": "^4.4.5",
20 | "vuex": "^4.0.2",
21 | "vuex-persist": "^3.1.3"
22 | },
23 | "devDependencies": {
24 | "@vitejs/plugin-vue": "^5.1.4",
25 | "unplugin-vue-components": "^0.27.4",
26 | "vite": "^5.4.20"
27 | }
28 | },
29 | "node_modules/@antfu/utils": {
30 | "version": "0.7.10",
31 | "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz",
32 | "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==",
33 | "dev": true,
34 | "license": "MIT",
35 | "funding": {
36 | "url": "https://github.com/sponsors/antfu"
37 | }
38 | },
39 | "node_modules/@babel/helper-string-parser": {
40 | "version": "7.25.9",
41 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
42 | "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
43 | "license": "MIT",
44 | "engines": {
45 | "node": ">=6.9.0"
46 | }
47 | },
48 | "node_modules/@babel/helper-validator-identifier": {
49 | "version": "7.25.9",
50 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
51 | "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
52 | "license": "MIT",
53 | "engines": {
54 | "node": ">=6.9.0"
55 | }
56 | },
57 | "node_modules/@babel/parser": {
58 | "version": "7.26.1",
59 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.1.tgz",
60 | "integrity": "sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw==",
61 | "license": "MIT",
62 | "dependencies": {
63 | "@babel/types": "^7.26.0"
64 | },
65 | "bin": {
66 | "parser": "bin/babel-parser.js"
67 | },
68 | "engines": {
69 | "node": ">=6.0.0"
70 | }
71 | },
72 | "node_modules/@babel/types": {
73 | "version": "7.26.0",
74 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
75 | "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
76 | "license": "MIT",
77 | "dependencies": {
78 | "@babel/helper-string-parser": "^7.25.9",
79 | "@babel/helper-validator-identifier": "^7.25.9"
80 | },
81 | "engines": {
82 | "node": ">=6.9.0"
83 | }
84 | },
85 | "node_modules/@esbuild/aix-ppc64": {
86 | "version": "0.21.5",
87 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
88 | "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
89 | "cpu": [
90 | "ppc64"
91 | ],
92 | "dev": true,
93 | "license": "MIT",
94 | "optional": true,
95 | "os": [
96 | "aix"
97 | ],
98 | "engines": {
99 | "node": ">=12"
100 | }
101 | },
102 | "node_modules/@esbuild/android-arm": {
103 | "version": "0.21.5",
104 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
105 | "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
106 | "cpu": [
107 | "arm"
108 | ],
109 | "dev": true,
110 | "license": "MIT",
111 | "optional": true,
112 | "os": [
113 | "android"
114 | ],
115 | "engines": {
116 | "node": ">=12"
117 | }
118 | },
119 | "node_modules/@esbuild/android-arm64": {
120 | "version": "0.21.5",
121 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
122 | "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
123 | "cpu": [
124 | "arm64"
125 | ],
126 | "dev": true,
127 | "license": "MIT",
128 | "optional": true,
129 | "os": [
130 | "android"
131 | ],
132 | "engines": {
133 | "node": ">=12"
134 | }
135 | },
136 | "node_modules/@esbuild/android-x64": {
137 | "version": "0.21.5",
138 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
139 | "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
140 | "cpu": [
141 | "x64"
142 | ],
143 | "dev": true,
144 | "license": "MIT",
145 | "optional": true,
146 | "os": [
147 | "android"
148 | ],
149 | "engines": {
150 | "node": ">=12"
151 | }
152 | },
153 | "node_modules/@esbuild/darwin-arm64": {
154 | "version": "0.21.5",
155 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
156 | "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
157 | "cpu": [
158 | "arm64"
159 | ],
160 | "dev": true,
161 | "license": "MIT",
162 | "optional": true,
163 | "os": [
164 | "darwin"
165 | ],
166 | "engines": {
167 | "node": ">=12"
168 | }
169 | },
170 | "node_modules/@esbuild/darwin-x64": {
171 | "version": "0.21.5",
172 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
173 | "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
174 | "cpu": [
175 | "x64"
176 | ],
177 | "dev": true,
178 | "license": "MIT",
179 | "optional": true,
180 | "os": [
181 | "darwin"
182 | ],
183 | "engines": {
184 | "node": ">=12"
185 | }
186 | },
187 | "node_modules/@esbuild/freebsd-arm64": {
188 | "version": "0.21.5",
189 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
190 | "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
191 | "cpu": [
192 | "arm64"
193 | ],
194 | "dev": true,
195 | "license": "MIT",
196 | "optional": true,
197 | "os": [
198 | "freebsd"
199 | ],
200 | "engines": {
201 | "node": ">=12"
202 | }
203 | },
204 | "node_modules/@esbuild/freebsd-x64": {
205 | "version": "0.21.5",
206 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
207 | "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
208 | "cpu": [
209 | "x64"
210 | ],
211 | "dev": true,
212 | "license": "MIT",
213 | "optional": true,
214 | "os": [
215 | "freebsd"
216 | ],
217 | "engines": {
218 | "node": ">=12"
219 | }
220 | },
221 | "node_modules/@esbuild/linux-arm": {
222 | "version": "0.21.5",
223 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
224 | "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
225 | "cpu": [
226 | "arm"
227 | ],
228 | "dev": true,
229 | "license": "MIT",
230 | "optional": true,
231 | "os": [
232 | "linux"
233 | ],
234 | "engines": {
235 | "node": ">=12"
236 | }
237 | },
238 | "node_modules/@esbuild/linux-arm64": {
239 | "version": "0.21.5",
240 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
241 | "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
242 | "cpu": [
243 | "arm64"
244 | ],
245 | "dev": true,
246 | "license": "MIT",
247 | "optional": true,
248 | "os": [
249 | "linux"
250 | ],
251 | "engines": {
252 | "node": ">=12"
253 | }
254 | },
255 | "node_modules/@esbuild/linux-ia32": {
256 | "version": "0.21.5",
257 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
258 | "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
259 | "cpu": [
260 | "ia32"
261 | ],
262 | "dev": true,
263 | "license": "MIT",
264 | "optional": true,
265 | "os": [
266 | "linux"
267 | ],
268 | "engines": {
269 | "node": ">=12"
270 | }
271 | },
272 | "node_modules/@esbuild/linux-loong64": {
273 | "version": "0.21.5",
274 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
275 | "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
276 | "cpu": [
277 | "loong64"
278 | ],
279 | "dev": true,
280 | "license": "MIT",
281 | "optional": true,
282 | "os": [
283 | "linux"
284 | ],
285 | "engines": {
286 | "node": ">=12"
287 | }
288 | },
289 | "node_modules/@esbuild/linux-mips64el": {
290 | "version": "0.21.5",
291 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
292 | "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
293 | "cpu": [
294 | "mips64el"
295 | ],
296 | "dev": true,
297 | "license": "MIT",
298 | "optional": true,
299 | "os": [
300 | "linux"
301 | ],
302 | "engines": {
303 | "node": ">=12"
304 | }
305 | },
306 | "node_modules/@esbuild/linux-ppc64": {
307 | "version": "0.21.5",
308 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
309 | "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
310 | "cpu": [
311 | "ppc64"
312 | ],
313 | "dev": true,
314 | "license": "MIT",
315 | "optional": true,
316 | "os": [
317 | "linux"
318 | ],
319 | "engines": {
320 | "node": ">=12"
321 | }
322 | },
323 | "node_modules/@esbuild/linux-riscv64": {
324 | "version": "0.21.5",
325 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
326 | "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
327 | "cpu": [
328 | "riscv64"
329 | ],
330 | "dev": true,
331 | "license": "MIT",
332 | "optional": true,
333 | "os": [
334 | "linux"
335 | ],
336 | "engines": {
337 | "node": ">=12"
338 | }
339 | },
340 | "node_modules/@esbuild/linux-s390x": {
341 | "version": "0.21.5",
342 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
343 | "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
344 | "cpu": [
345 | "s390x"
346 | ],
347 | "dev": true,
348 | "license": "MIT",
349 | "optional": true,
350 | "os": [
351 | "linux"
352 | ],
353 | "engines": {
354 | "node": ">=12"
355 | }
356 | },
357 | "node_modules/@esbuild/linux-x64": {
358 | "version": "0.21.5",
359 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
360 | "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
361 | "cpu": [
362 | "x64"
363 | ],
364 | "dev": true,
365 | "license": "MIT",
366 | "optional": true,
367 | "os": [
368 | "linux"
369 | ],
370 | "engines": {
371 | "node": ">=12"
372 | }
373 | },
374 | "node_modules/@esbuild/netbsd-x64": {
375 | "version": "0.21.5",
376 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
377 | "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
378 | "cpu": [
379 | "x64"
380 | ],
381 | "dev": true,
382 | "license": "MIT",
383 | "optional": true,
384 | "os": [
385 | "netbsd"
386 | ],
387 | "engines": {
388 | "node": ">=12"
389 | }
390 | },
391 | "node_modules/@esbuild/openbsd-x64": {
392 | "version": "0.21.5",
393 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
394 | "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
395 | "cpu": [
396 | "x64"
397 | ],
398 | "dev": true,
399 | "license": "MIT",
400 | "optional": true,
401 | "os": [
402 | "openbsd"
403 | ],
404 | "engines": {
405 | "node": ">=12"
406 | }
407 | },
408 | "node_modules/@esbuild/sunos-x64": {
409 | "version": "0.21.5",
410 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
411 | "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
412 | "cpu": [
413 | "x64"
414 | ],
415 | "dev": true,
416 | "license": "MIT",
417 | "optional": true,
418 | "os": [
419 | "sunos"
420 | ],
421 | "engines": {
422 | "node": ">=12"
423 | }
424 | },
425 | "node_modules/@esbuild/win32-arm64": {
426 | "version": "0.21.5",
427 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
428 | "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
429 | "cpu": [
430 | "arm64"
431 | ],
432 | "dev": true,
433 | "license": "MIT",
434 | "optional": true,
435 | "os": [
436 | "win32"
437 | ],
438 | "engines": {
439 | "node": ">=12"
440 | }
441 | },
442 | "node_modules/@esbuild/win32-ia32": {
443 | "version": "0.21.5",
444 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
445 | "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
446 | "cpu": [
447 | "ia32"
448 | ],
449 | "dev": true,
450 | "license": "MIT",
451 | "optional": true,
452 | "os": [
453 | "win32"
454 | ],
455 | "engines": {
456 | "node": ">=12"
457 | }
458 | },
459 | "node_modules/@esbuild/win32-x64": {
460 | "version": "0.21.5",
461 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
462 | "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
463 | "cpu": [
464 | "x64"
465 | ],
466 | "dev": true,
467 | "license": "MIT",
468 | "optional": true,
469 | "os": [
470 | "win32"
471 | ],
472 | "engines": {
473 | "node": ">=12"
474 | }
475 | },
476 | "node_modules/@jridgewell/sourcemap-codec": {
477 | "version": "1.5.0",
478 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
479 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
480 | "license": "MIT"
481 | },
482 | "node_modules/@nodelib/fs.scandir": {
483 | "version": "2.1.5",
484 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
485 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
486 | "dev": true,
487 | "license": "MIT",
488 | "dependencies": {
489 | "@nodelib/fs.stat": "2.0.5",
490 | "run-parallel": "^1.1.9"
491 | },
492 | "engines": {
493 | "node": ">= 8"
494 | }
495 | },
496 | "node_modules/@nodelib/fs.stat": {
497 | "version": "2.0.5",
498 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
499 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
500 | "dev": true,
501 | "license": "MIT",
502 | "engines": {
503 | "node": ">= 8"
504 | }
505 | },
506 | "node_modules/@nodelib/fs.walk": {
507 | "version": "1.2.8",
508 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
509 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
510 | "dev": true,
511 | "license": "MIT",
512 | "dependencies": {
513 | "@nodelib/fs.scandir": "2.1.5",
514 | "fastq": "^1.6.0"
515 | },
516 | "engines": {
517 | "node": ">= 8"
518 | }
519 | },
520 | "node_modules/@popperjs/core": {
521 | "version": "2.11.8",
522 | "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
523 | "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
524 | "license": "MIT",
525 | "peer": true,
526 | "funding": {
527 | "type": "opencollective",
528 | "url": "https://opencollective.com/popperjs"
529 | }
530 | },
531 | "node_modules/@rollup/pluginutils": {
532 | "version": "5.1.3",
533 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz",
534 | "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==",
535 | "dev": true,
536 | "license": "MIT",
537 | "dependencies": {
538 | "@types/estree": "^1.0.0",
539 | "estree-walker": "^2.0.2",
540 | "picomatch": "^4.0.2"
541 | },
542 | "engines": {
543 | "node": ">=14.0.0"
544 | },
545 | "peerDependencies": {
546 | "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
547 | },
548 | "peerDependenciesMeta": {
549 | "rollup": {
550 | "optional": true
551 | }
552 | }
553 | },
554 | "node_modules/@rollup/rollup-android-arm-eabi": {
555 | "version": "4.24.2",
556 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.2.tgz",
557 | "integrity": "sha512-ufoveNTKDg9t/b7nqI3lwbCG/9IJMhADBNjjz/Jn6LxIZxD7T5L8l2uO/wD99945F1Oo8FvgbbZJRguyk/BdzA==",
558 | "cpu": [
559 | "arm"
560 | ],
561 | "dev": true,
562 | "license": "MIT",
563 | "optional": true,
564 | "os": [
565 | "android"
566 | ]
567 | },
568 | "node_modules/@rollup/rollup-android-arm64": {
569 | "version": "4.24.2",
570 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.2.tgz",
571 | "integrity": "sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA==",
572 | "cpu": [
573 | "arm64"
574 | ],
575 | "dev": true,
576 | "license": "MIT",
577 | "optional": true,
578 | "os": [
579 | "android"
580 | ]
581 | },
582 | "node_modules/@rollup/rollup-darwin-arm64": {
583 | "version": "4.24.2",
584 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.2.tgz",
585 | "integrity": "sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg==",
586 | "cpu": [
587 | "arm64"
588 | ],
589 | "dev": true,
590 | "license": "MIT",
591 | "optional": true,
592 | "os": [
593 | "darwin"
594 | ]
595 | },
596 | "node_modules/@rollup/rollup-darwin-x64": {
597 | "version": "4.24.2",
598 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.2.tgz",
599 | "integrity": "sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA==",
600 | "cpu": [
601 | "x64"
602 | ],
603 | "dev": true,
604 | "license": "MIT",
605 | "optional": true,
606 | "os": [
607 | "darwin"
608 | ]
609 | },
610 | "node_modules/@rollup/rollup-freebsd-arm64": {
611 | "version": "4.24.2",
612 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.2.tgz",
613 | "integrity": "sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw==",
614 | "cpu": [
615 | "arm64"
616 | ],
617 | "dev": true,
618 | "license": "MIT",
619 | "optional": true,
620 | "os": [
621 | "freebsd"
622 | ]
623 | },
624 | "node_modules/@rollup/rollup-freebsd-x64": {
625 | "version": "4.24.2",
626 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.2.tgz",
627 | "integrity": "sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg==",
628 | "cpu": [
629 | "x64"
630 | ],
631 | "dev": true,
632 | "license": "MIT",
633 | "optional": true,
634 | "os": [
635 | "freebsd"
636 | ]
637 | },
638 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
639 | "version": "4.24.2",
640 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.2.tgz",
641 | "integrity": "sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig==",
642 | "cpu": [
643 | "arm"
644 | ],
645 | "dev": true,
646 | "license": "MIT",
647 | "optional": true,
648 | "os": [
649 | "linux"
650 | ]
651 | },
652 | "node_modules/@rollup/rollup-linux-arm-musleabihf": {
653 | "version": "4.24.2",
654 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.2.tgz",
655 | "integrity": "sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw==",
656 | "cpu": [
657 | "arm"
658 | ],
659 | "dev": true,
660 | "license": "MIT",
661 | "optional": true,
662 | "os": [
663 | "linux"
664 | ]
665 | },
666 | "node_modules/@rollup/rollup-linux-arm64-gnu": {
667 | "version": "4.24.2",
668 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.2.tgz",
669 | "integrity": "sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ==",
670 | "cpu": [
671 | "arm64"
672 | ],
673 | "dev": true,
674 | "license": "MIT",
675 | "optional": true,
676 | "os": [
677 | "linux"
678 | ]
679 | },
680 | "node_modules/@rollup/rollup-linux-arm64-musl": {
681 | "version": "4.24.2",
682 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.2.tgz",
683 | "integrity": "sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA==",
684 | "cpu": [
685 | "arm64"
686 | ],
687 | "dev": true,
688 | "license": "MIT",
689 | "optional": true,
690 | "os": [
691 | "linux"
692 | ]
693 | },
694 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
695 | "version": "4.24.2",
696 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.2.tgz",
697 | "integrity": "sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw==",
698 | "cpu": [
699 | "ppc64"
700 | ],
701 | "dev": true,
702 | "license": "MIT",
703 | "optional": true,
704 | "os": [
705 | "linux"
706 | ]
707 | },
708 | "node_modules/@rollup/rollup-linux-riscv64-gnu": {
709 | "version": "4.24.2",
710 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.2.tgz",
711 | "integrity": "sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg==",
712 | "cpu": [
713 | "riscv64"
714 | ],
715 | "dev": true,
716 | "license": "MIT",
717 | "optional": true,
718 | "os": [
719 | "linux"
720 | ]
721 | },
722 | "node_modules/@rollup/rollup-linux-s390x-gnu": {
723 | "version": "4.24.2",
724 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.2.tgz",
725 | "integrity": "sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q==",
726 | "cpu": [
727 | "s390x"
728 | ],
729 | "dev": true,
730 | "license": "MIT",
731 | "optional": true,
732 | "os": [
733 | "linux"
734 | ]
735 | },
736 | "node_modules/@rollup/rollup-linux-x64-gnu": {
737 | "version": "4.24.2",
738 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.2.tgz",
739 | "integrity": "sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ==",
740 | "cpu": [
741 | "x64"
742 | ],
743 | "dev": true,
744 | "license": "MIT",
745 | "optional": true,
746 | "os": [
747 | "linux"
748 | ]
749 | },
750 | "node_modules/@rollup/rollup-linux-x64-musl": {
751 | "version": "4.24.2",
752 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.2.tgz",
753 | "integrity": "sha512-XAo7cJec80NWx9LlZFEJQxqKOMz/lX3geWs2iNT5CHIERLFfd90f3RYLLjiCBm1IMaQ4VOX/lTC9lWfzzQm14Q==",
754 | "cpu": [
755 | "x64"
756 | ],
757 | "dev": true,
758 | "license": "MIT",
759 | "optional": true,
760 | "os": [
761 | "linux"
762 | ]
763 | },
764 | "node_modules/@rollup/rollup-win32-arm64-msvc": {
765 | "version": "4.24.2",
766 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.2.tgz",
767 | "integrity": "sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw==",
768 | "cpu": [
769 | "arm64"
770 | ],
771 | "dev": true,
772 | "license": "MIT",
773 | "optional": true,
774 | "os": [
775 | "win32"
776 | ]
777 | },
778 | "node_modules/@rollup/rollup-win32-ia32-msvc": {
779 | "version": "4.24.2",
780 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.2.tgz",
781 | "integrity": "sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw==",
782 | "cpu": [
783 | "ia32"
784 | ],
785 | "dev": true,
786 | "license": "MIT",
787 | "optional": true,
788 | "os": [
789 | "win32"
790 | ]
791 | },
792 | "node_modules/@rollup/rollup-win32-x64-msvc": {
793 | "version": "4.24.2",
794 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.2.tgz",
795 | "integrity": "sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA==",
796 | "cpu": [
797 | "x64"
798 | ],
799 | "dev": true,
800 | "license": "MIT",
801 | "optional": true,
802 | "os": [
803 | "win32"
804 | ]
805 | },
806 | "node_modules/@types/estree": {
807 | "version": "1.0.6",
808 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
809 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
810 | "dev": true,
811 | "license": "MIT"
812 | },
813 | "node_modules/@vitejs/plugin-vue": {
814 | "version": "5.1.4",
815 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz",
816 | "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==",
817 | "dev": true,
818 | "license": "MIT",
819 | "engines": {
820 | "node": "^18.0.0 || >=20.0.0"
821 | },
822 | "peerDependencies": {
823 | "vite": "^5.0.0",
824 | "vue": "^3.2.25"
825 | }
826 | },
827 | "node_modules/@vue/compiler-core": {
828 | "version": "3.5.12",
829 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz",
830 | "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==",
831 | "license": "MIT",
832 | "dependencies": {
833 | "@babel/parser": "^7.25.3",
834 | "@vue/shared": "3.5.12",
835 | "entities": "^4.5.0",
836 | "estree-walker": "^2.0.2",
837 | "source-map-js": "^1.2.0"
838 | }
839 | },
840 | "node_modules/@vue/compiler-dom": {
841 | "version": "3.5.12",
842 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz",
843 | "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==",
844 | "license": "MIT",
845 | "dependencies": {
846 | "@vue/compiler-core": "3.5.12",
847 | "@vue/shared": "3.5.12"
848 | }
849 | },
850 | "node_modules/@vue/compiler-sfc": {
851 | "version": "3.5.12",
852 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz",
853 | "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==",
854 | "license": "MIT",
855 | "dependencies": {
856 | "@babel/parser": "^7.25.3",
857 | "@vue/compiler-core": "3.5.12",
858 | "@vue/compiler-dom": "3.5.12",
859 | "@vue/compiler-ssr": "3.5.12",
860 | "@vue/shared": "3.5.12",
861 | "estree-walker": "^2.0.2",
862 | "magic-string": "^0.30.11",
863 | "postcss": "^8.4.47",
864 | "source-map-js": "^1.2.0"
865 | }
866 | },
867 | "node_modules/@vue/compiler-ssr": {
868 | "version": "3.5.12",
869 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz",
870 | "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==",
871 | "license": "MIT",
872 | "dependencies": {
873 | "@vue/compiler-dom": "3.5.12",
874 | "@vue/shared": "3.5.12"
875 | }
876 | },
877 | "node_modules/@vue/devtools-api": {
878 | "version": "6.6.4",
879 | "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
880 | "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
881 | "license": "MIT"
882 | },
883 | "node_modules/@vue/reactivity": {
884 | "version": "3.5.12",
885 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz",
886 | "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==",
887 | "license": "MIT",
888 | "dependencies": {
889 | "@vue/shared": "3.5.12"
890 | }
891 | },
892 | "node_modules/@vue/runtime-core": {
893 | "version": "3.5.12",
894 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz",
895 | "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==",
896 | "license": "MIT",
897 | "dependencies": {
898 | "@vue/reactivity": "3.5.12",
899 | "@vue/shared": "3.5.12"
900 | }
901 | },
902 | "node_modules/@vue/runtime-dom": {
903 | "version": "3.5.12",
904 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz",
905 | "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==",
906 | "license": "MIT",
907 | "dependencies": {
908 | "@vue/reactivity": "3.5.12",
909 | "@vue/runtime-core": "3.5.12",
910 | "@vue/shared": "3.5.12",
911 | "csstype": "^3.1.3"
912 | }
913 | },
914 | "node_modules/@vue/server-renderer": {
915 | "version": "3.5.12",
916 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz",
917 | "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==",
918 | "license": "MIT",
919 | "dependencies": {
920 | "@vue/compiler-ssr": "3.5.12",
921 | "@vue/shared": "3.5.12"
922 | },
923 | "peerDependencies": {
924 | "vue": "3.5.12"
925 | }
926 | },
927 | "node_modules/@vue/shared": {
928 | "version": "3.5.12",
929 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz",
930 | "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==",
931 | "license": "MIT"
932 | },
933 | "node_modules/@vuelidate/core": {
934 | "version": "2.0.3",
935 | "resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.3.tgz",
936 | "integrity": "sha512-AN6l7KF7+mEfyWG0doT96z+47ljwPpZfi9/JrNMkOGLFv27XVZvKzRLXlmDPQjPl/wOB1GNnHuc54jlCLRNqGA==",
937 | "license": "MIT",
938 | "dependencies": {
939 | "vue-demi": "^0.13.11"
940 | },
941 | "peerDependencies": {
942 | "@vue/composition-api": "^1.0.0-rc.1",
943 | "vue": "^2.0.0 || >=3.0.0"
944 | },
945 | "peerDependenciesMeta": {
946 | "@vue/composition-api": {
947 | "optional": true
948 | }
949 | }
950 | },
951 | "node_modules/@vuelidate/core/node_modules/vue-demi": {
952 | "version": "0.13.11",
953 | "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
954 | "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
955 | "hasInstallScript": true,
956 | "license": "MIT",
957 | "bin": {
958 | "vue-demi-fix": "bin/vue-demi-fix.js",
959 | "vue-demi-switch": "bin/vue-demi-switch.js"
960 | },
961 | "engines": {
962 | "node": ">=12"
963 | },
964 | "funding": {
965 | "url": "https://github.com/sponsors/antfu"
966 | },
967 | "peerDependencies": {
968 | "@vue/composition-api": "^1.0.0-rc.1",
969 | "vue": "^3.0.0-0 || ^2.6.0"
970 | },
971 | "peerDependenciesMeta": {
972 | "@vue/composition-api": {
973 | "optional": true
974 | }
975 | }
976 | },
977 | "node_modules/@vuelidate/validators": {
978 | "version": "2.0.4",
979 | "resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.4.tgz",
980 | "integrity": "sha512-odTxtUZ2JpwwiQ10t0QWYJkkYrfd0SyFYhdHH44QQ1jDatlZgTh/KRzrWVmn/ib9Gq7H4hFD4e8ahoo5YlUlDw==",
981 | "license": "MIT",
982 | "dependencies": {
983 | "vue-demi": "^0.13.11"
984 | },
985 | "peerDependencies": {
986 | "@vue/composition-api": "^1.0.0-rc.1",
987 | "vue": "^2.0.0 || >=3.0.0"
988 | },
989 | "peerDependenciesMeta": {
990 | "@vue/composition-api": {
991 | "optional": true
992 | }
993 | }
994 | },
995 | "node_modules/@vuelidate/validators/node_modules/vue-demi": {
996 | "version": "0.13.11",
997 | "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
998 | "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
999 | "hasInstallScript": true,
1000 | "license": "MIT",
1001 | "bin": {
1002 | "vue-demi-fix": "bin/vue-demi-fix.js",
1003 | "vue-demi-switch": "bin/vue-demi-switch.js"
1004 | },
1005 | "engines": {
1006 | "node": ">=12"
1007 | },
1008 | "funding": {
1009 | "url": "https://github.com/sponsors/antfu"
1010 | },
1011 | "peerDependencies": {
1012 | "@vue/composition-api": "^1.0.0-rc.1",
1013 | "vue": "^3.0.0-0 || ^2.6.0"
1014 | },
1015 | "peerDependenciesMeta": {
1016 | "@vue/composition-api": {
1017 | "optional": true
1018 | }
1019 | }
1020 | },
1021 | "node_modules/acorn": {
1022 | "version": "8.14.0",
1023 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
1024 | "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
1025 | "dev": true,
1026 | "license": "MIT",
1027 | "bin": {
1028 | "acorn": "bin/acorn"
1029 | },
1030 | "engines": {
1031 | "node": ">=0.4.0"
1032 | }
1033 | },
1034 | "node_modules/anymatch": {
1035 | "version": "3.1.3",
1036 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
1037 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
1038 | "dev": true,
1039 | "license": "ISC",
1040 | "dependencies": {
1041 | "normalize-path": "^3.0.0",
1042 | "picomatch": "^2.0.4"
1043 | },
1044 | "engines": {
1045 | "node": ">= 8"
1046 | }
1047 | },
1048 | "node_modules/anymatch/node_modules/picomatch": {
1049 | "version": "2.3.1",
1050 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1051 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1052 | "dev": true,
1053 | "license": "MIT",
1054 | "engines": {
1055 | "node": ">=8.6"
1056 | },
1057 | "funding": {
1058 | "url": "https://github.com/sponsors/jonschlinkert"
1059 | }
1060 | },
1061 | "node_modules/asynckit": {
1062 | "version": "0.4.0",
1063 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
1064 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
1065 | "license": "MIT"
1066 | },
1067 | "node_modules/axios": {
1068 | "version": "1.12.0",
1069 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.0.tgz",
1070 | "integrity": "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==",
1071 | "license": "MIT",
1072 | "dependencies": {
1073 | "follow-redirects": "^1.15.6",
1074 | "form-data": "^4.0.4",
1075 | "proxy-from-env": "^1.1.0"
1076 | }
1077 | },
1078 | "node_modules/balanced-match": {
1079 | "version": "1.0.2",
1080 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
1081 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
1082 | "dev": true,
1083 | "license": "MIT"
1084 | },
1085 | "node_modules/binary-extensions": {
1086 | "version": "2.3.0",
1087 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
1088 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
1089 | "dev": true,
1090 | "license": "MIT",
1091 | "engines": {
1092 | "node": ">=8"
1093 | },
1094 | "funding": {
1095 | "url": "https://github.com/sponsors/sindresorhus"
1096 | }
1097 | },
1098 | "node_modules/bootstrap": {
1099 | "version": "5.3.3",
1100 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
1101 | "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
1102 | "funding": [
1103 | {
1104 | "type": "github",
1105 | "url": "https://github.com/sponsors/twbs"
1106 | },
1107 | {
1108 | "type": "opencollective",
1109 | "url": "https://opencollective.com/bootstrap"
1110 | }
1111 | ],
1112 | "license": "MIT",
1113 | "peerDependencies": {
1114 | "@popperjs/core": "^2.11.8"
1115 | }
1116 | },
1117 | "node_modules/bootstrap-vue-next": {
1118 | "version": "0.25.10",
1119 | "resolved": "https://registry.npmjs.org/bootstrap-vue-next/-/bootstrap-vue-next-0.25.10.tgz",
1120 | "integrity": "sha512-6Q/VPkc1y0l/Go8I0SUWl++4d9WNf5UxqMkyoFDBeETyAA0elMOlhug67WbJXhWAmRuOmNwHxCZW8B944hAq4A==",
1121 | "license": "MIT",
1122 | "funding": {
1123 | "type": "opencollective",
1124 | "url": "https://opencollective.com/bootstrap-vue-next"
1125 | },
1126 | "peerDependencies": {
1127 | "vue": "^3.5.3"
1128 | }
1129 | },
1130 | "node_modules/brace-expansion": {
1131 | "version": "2.0.1",
1132 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
1133 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
1134 | "dev": true,
1135 | "license": "MIT",
1136 | "dependencies": {
1137 | "balanced-match": "^1.0.0"
1138 | }
1139 | },
1140 | "node_modules/braces": {
1141 | "version": "3.0.3",
1142 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
1143 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
1144 | "dev": true,
1145 | "license": "MIT",
1146 | "dependencies": {
1147 | "fill-range": "^7.1.1"
1148 | },
1149 | "engines": {
1150 | "node": ">=8"
1151 | }
1152 | },
1153 | "node_modules/call-bind-apply-helpers": {
1154 | "version": "1.0.2",
1155 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
1156 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
1157 | "license": "MIT",
1158 | "dependencies": {
1159 | "es-errors": "^1.3.0",
1160 | "function-bind": "^1.1.2"
1161 | },
1162 | "engines": {
1163 | "node": ">= 0.4"
1164 | }
1165 | },
1166 | "node_modules/chokidar": {
1167 | "version": "3.6.0",
1168 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
1169 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
1170 | "dev": true,
1171 | "license": "MIT",
1172 | "dependencies": {
1173 | "anymatch": "~3.1.2",
1174 | "braces": "~3.0.2",
1175 | "glob-parent": "~5.1.2",
1176 | "is-binary-path": "~2.1.0",
1177 | "is-glob": "~4.0.1",
1178 | "normalize-path": "~3.0.0",
1179 | "readdirp": "~3.6.0"
1180 | },
1181 | "engines": {
1182 | "node": ">= 8.10.0"
1183 | },
1184 | "funding": {
1185 | "url": "https://paulmillr.com/funding/"
1186 | },
1187 | "optionalDependencies": {
1188 | "fsevents": "~2.3.2"
1189 | }
1190 | },
1191 | "node_modules/combined-stream": {
1192 | "version": "1.0.8",
1193 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
1194 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
1195 | "license": "MIT",
1196 | "dependencies": {
1197 | "delayed-stream": "~1.0.0"
1198 | },
1199 | "engines": {
1200 | "node": ">= 0.8"
1201 | }
1202 | },
1203 | "node_modules/confbox": {
1204 | "version": "0.1.8",
1205 | "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
1206 | "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
1207 | "dev": true,
1208 | "license": "MIT"
1209 | },
1210 | "node_modules/csstype": {
1211 | "version": "3.1.3",
1212 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
1213 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
1214 | "license": "MIT"
1215 | },
1216 | "node_modules/dayjs": {
1217 | "version": "1.11.13",
1218 | "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
1219 | "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
1220 | "license": "MIT"
1221 | },
1222 | "node_modules/debug": {
1223 | "version": "4.3.7",
1224 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
1225 | "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
1226 | "dev": true,
1227 | "license": "MIT",
1228 | "dependencies": {
1229 | "ms": "^2.1.3"
1230 | },
1231 | "engines": {
1232 | "node": ">=6.0"
1233 | },
1234 | "peerDependenciesMeta": {
1235 | "supports-color": {
1236 | "optional": true
1237 | }
1238 | }
1239 | },
1240 | "node_modules/deepmerge": {
1241 | "version": "4.3.1",
1242 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
1243 | "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
1244 | "license": "MIT",
1245 | "engines": {
1246 | "node": ">=0.10.0"
1247 | }
1248 | },
1249 | "node_modules/delayed-stream": {
1250 | "version": "1.0.0",
1251 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
1252 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
1253 | "license": "MIT",
1254 | "engines": {
1255 | "node": ">=0.4.0"
1256 | }
1257 | },
1258 | "node_modules/dunder-proto": {
1259 | "version": "1.0.1",
1260 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
1261 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
1262 | "license": "MIT",
1263 | "dependencies": {
1264 | "call-bind-apply-helpers": "^1.0.1",
1265 | "es-errors": "^1.3.0",
1266 | "gopd": "^1.2.0"
1267 | },
1268 | "engines": {
1269 | "node": ">= 0.4"
1270 | }
1271 | },
1272 | "node_modules/entities": {
1273 | "version": "4.5.0",
1274 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
1275 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
1276 | "license": "BSD-2-Clause",
1277 | "engines": {
1278 | "node": ">=0.12"
1279 | },
1280 | "funding": {
1281 | "url": "https://github.com/fb55/entities?sponsor=1"
1282 | }
1283 | },
1284 | "node_modules/es-define-property": {
1285 | "version": "1.0.1",
1286 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
1287 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
1288 | "license": "MIT",
1289 | "engines": {
1290 | "node": ">= 0.4"
1291 | }
1292 | },
1293 | "node_modules/es-errors": {
1294 | "version": "1.3.0",
1295 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
1296 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
1297 | "license": "MIT",
1298 | "engines": {
1299 | "node": ">= 0.4"
1300 | }
1301 | },
1302 | "node_modules/es-object-atoms": {
1303 | "version": "1.1.1",
1304 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
1305 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
1306 | "license": "MIT",
1307 | "dependencies": {
1308 | "es-errors": "^1.3.0"
1309 | },
1310 | "engines": {
1311 | "node": ">= 0.4"
1312 | }
1313 | },
1314 | "node_modules/es-set-tostringtag": {
1315 | "version": "2.1.0",
1316 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
1317 | "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
1318 | "license": "MIT",
1319 | "dependencies": {
1320 | "es-errors": "^1.3.0",
1321 | "get-intrinsic": "^1.2.6",
1322 | "has-tostringtag": "^1.0.2",
1323 | "hasown": "^2.0.2"
1324 | },
1325 | "engines": {
1326 | "node": ">= 0.4"
1327 | }
1328 | },
1329 | "node_modules/esbuild": {
1330 | "version": "0.21.5",
1331 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
1332 | "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
1333 | "dev": true,
1334 | "hasInstallScript": true,
1335 | "license": "MIT",
1336 | "bin": {
1337 | "esbuild": "bin/esbuild"
1338 | },
1339 | "engines": {
1340 | "node": ">=12"
1341 | },
1342 | "optionalDependencies": {
1343 | "@esbuild/aix-ppc64": "0.21.5",
1344 | "@esbuild/android-arm": "0.21.5",
1345 | "@esbuild/android-arm64": "0.21.5",
1346 | "@esbuild/android-x64": "0.21.5",
1347 | "@esbuild/darwin-arm64": "0.21.5",
1348 | "@esbuild/darwin-x64": "0.21.5",
1349 | "@esbuild/freebsd-arm64": "0.21.5",
1350 | "@esbuild/freebsd-x64": "0.21.5",
1351 | "@esbuild/linux-arm": "0.21.5",
1352 | "@esbuild/linux-arm64": "0.21.5",
1353 | "@esbuild/linux-ia32": "0.21.5",
1354 | "@esbuild/linux-loong64": "0.21.5",
1355 | "@esbuild/linux-mips64el": "0.21.5",
1356 | "@esbuild/linux-ppc64": "0.21.5",
1357 | "@esbuild/linux-riscv64": "0.21.5",
1358 | "@esbuild/linux-s390x": "0.21.5",
1359 | "@esbuild/linux-x64": "0.21.5",
1360 | "@esbuild/netbsd-x64": "0.21.5",
1361 | "@esbuild/openbsd-x64": "0.21.5",
1362 | "@esbuild/sunos-x64": "0.21.5",
1363 | "@esbuild/win32-arm64": "0.21.5",
1364 | "@esbuild/win32-ia32": "0.21.5",
1365 | "@esbuild/win32-x64": "0.21.5"
1366 | }
1367 | },
1368 | "node_modules/estree-walker": {
1369 | "version": "2.0.2",
1370 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
1371 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
1372 | "license": "MIT"
1373 | },
1374 | "node_modules/fast-glob": {
1375 | "version": "3.3.2",
1376 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
1377 | "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
1378 | "dev": true,
1379 | "license": "MIT",
1380 | "dependencies": {
1381 | "@nodelib/fs.stat": "^2.0.2",
1382 | "@nodelib/fs.walk": "^1.2.3",
1383 | "glob-parent": "^5.1.2",
1384 | "merge2": "^1.3.0",
1385 | "micromatch": "^4.0.4"
1386 | },
1387 | "engines": {
1388 | "node": ">=8.6.0"
1389 | }
1390 | },
1391 | "node_modules/fastq": {
1392 | "version": "1.17.1",
1393 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
1394 | "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
1395 | "dev": true,
1396 | "license": "ISC",
1397 | "dependencies": {
1398 | "reusify": "^1.0.4"
1399 | }
1400 | },
1401 | "node_modules/fill-range": {
1402 | "version": "7.1.1",
1403 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
1404 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
1405 | "dev": true,
1406 | "license": "MIT",
1407 | "dependencies": {
1408 | "to-regex-range": "^5.0.1"
1409 | },
1410 | "engines": {
1411 | "node": ">=8"
1412 | }
1413 | },
1414 | "node_modules/flatted": {
1415 | "version": "3.3.1",
1416 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
1417 | "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
1418 | "license": "ISC"
1419 | },
1420 | "node_modules/follow-redirects": {
1421 | "version": "1.15.9",
1422 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
1423 | "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
1424 | "funding": [
1425 | {
1426 | "type": "individual",
1427 | "url": "https://github.com/sponsors/RubenVerborgh"
1428 | }
1429 | ],
1430 | "license": "MIT",
1431 | "engines": {
1432 | "node": ">=4.0"
1433 | },
1434 | "peerDependenciesMeta": {
1435 | "debug": {
1436 | "optional": true
1437 | }
1438 | }
1439 | },
1440 | "node_modules/form-data": {
1441 | "version": "4.0.4",
1442 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
1443 | "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
1444 | "license": "MIT",
1445 | "dependencies": {
1446 | "asynckit": "^0.4.0",
1447 | "combined-stream": "^1.0.8",
1448 | "es-set-tostringtag": "^2.1.0",
1449 | "hasown": "^2.0.2",
1450 | "mime-types": "^2.1.12"
1451 | },
1452 | "engines": {
1453 | "node": ">= 6"
1454 | }
1455 | },
1456 | "node_modules/fsevents": {
1457 | "version": "2.3.3",
1458 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1459 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1460 | "dev": true,
1461 | "hasInstallScript": true,
1462 | "license": "MIT",
1463 | "optional": true,
1464 | "os": [
1465 | "darwin"
1466 | ],
1467 | "engines": {
1468 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1469 | }
1470 | },
1471 | "node_modules/function-bind": {
1472 | "version": "1.1.2",
1473 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
1474 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
1475 | "license": "MIT",
1476 | "funding": {
1477 | "url": "https://github.com/sponsors/ljharb"
1478 | }
1479 | },
1480 | "node_modules/get-intrinsic": {
1481 | "version": "1.3.0",
1482 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
1483 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
1484 | "license": "MIT",
1485 | "dependencies": {
1486 | "call-bind-apply-helpers": "^1.0.2",
1487 | "es-define-property": "^1.0.1",
1488 | "es-errors": "^1.3.0",
1489 | "es-object-atoms": "^1.1.1",
1490 | "function-bind": "^1.1.2",
1491 | "get-proto": "^1.0.1",
1492 | "gopd": "^1.2.0",
1493 | "has-symbols": "^1.1.0",
1494 | "hasown": "^2.0.2",
1495 | "math-intrinsics": "^1.1.0"
1496 | },
1497 | "engines": {
1498 | "node": ">= 0.4"
1499 | },
1500 | "funding": {
1501 | "url": "https://github.com/sponsors/ljharb"
1502 | }
1503 | },
1504 | "node_modules/get-proto": {
1505 | "version": "1.0.1",
1506 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
1507 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
1508 | "license": "MIT",
1509 | "dependencies": {
1510 | "dunder-proto": "^1.0.1",
1511 | "es-object-atoms": "^1.0.0"
1512 | },
1513 | "engines": {
1514 | "node": ">= 0.4"
1515 | }
1516 | },
1517 | "node_modules/glob-parent": {
1518 | "version": "5.1.2",
1519 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
1520 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
1521 | "dev": true,
1522 | "license": "ISC",
1523 | "dependencies": {
1524 | "is-glob": "^4.0.1"
1525 | },
1526 | "engines": {
1527 | "node": ">= 6"
1528 | }
1529 | },
1530 | "node_modules/gopd": {
1531 | "version": "1.2.0",
1532 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
1533 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
1534 | "license": "MIT",
1535 | "engines": {
1536 | "node": ">= 0.4"
1537 | },
1538 | "funding": {
1539 | "url": "https://github.com/sponsors/ljharb"
1540 | }
1541 | },
1542 | "node_modules/has-symbols": {
1543 | "version": "1.1.0",
1544 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
1545 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
1546 | "license": "MIT",
1547 | "engines": {
1548 | "node": ">= 0.4"
1549 | },
1550 | "funding": {
1551 | "url": "https://github.com/sponsors/ljharb"
1552 | }
1553 | },
1554 | "node_modules/has-tostringtag": {
1555 | "version": "1.0.2",
1556 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
1557 | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
1558 | "license": "MIT",
1559 | "dependencies": {
1560 | "has-symbols": "^1.0.3"
1561 | },
1562 | "engines": {
1563 | "node": ">= 0.4"
1564 | },
1565 | "funding": {
1566 | "url": "https://github.com/sponsors/ljharb"
1567 | }
1568 | },
1569 | "node_modules/hasown": {
1570 | "version": "2.0.2",
1571 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
1572 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
1573 | "license": "MIT",
1574 | "dependencies": {
1575 | "function-bind": "^1.1.2"
1576 | },
1577 | "engines": {
1578 | "node": ">= 0.4"
1579 | }
1580 | },
1581 | "node_modules/is-binary-path": {
1582 | "version": "2.1.0",
1583 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
1584 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
1585 | "dev": true,
1586 | "license": "MIT",
1587 | "dependencies": {
1588 | "binary-extensions": "^2.0.0"
1589 | },
1590 | "engines": {
1591 | "node": ">=8"
1592 | }
1593 | },
1594 | "node_modules/is-extglob": {
1595 | "version": "2.1.1",
1596 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
1597 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
1598 | "dev": true,
1599 | "license": "MIT",
1600 | "engines": {
1601 | "node": ">=0.10.0"
1602 | }
1603 | },
1604 | "node_modules/is-glob": {
1605 | "version": "4.0.3",
1606 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
1607 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
1608 | "dev": true,
1609 | "license": "MIT",
1610 | "dependencies": {
1611 | "is-extglob": "^2.1.1"
1612 | },
1613 | "engines": {
1614 | "node": ">=0.10.0"
1615 | }
1616 | },
1617 | "node_modules/is-number": {
1618 | "version": "7.0.0",
1619 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
1620 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
1621 | "dev": true,
1622 | "license": "MIT",
1623 | "engines": {
1624 | "node": ">=0.12.0"
1625 | }
1626 | },
1627 | "node_modules/local-pkg": {
1628 | "version": "0.5.0",
1629 | "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
1630 | "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==",
1631 | "dev": true,
1632 | "license": "MIT",
1633 | "dependencies": {
1634 | "mlly": "^1.4.2",
1635 | "pkg-types": "^1.0.3"
1636 | },
1637 | "engines": {
1638 | "node": ">=14"
1639 | },
1640 | "funding": {
1641 | "url": "https://github.com/sponsors/antfu"
1642 | }
1643 | },
1644 | "node_modules/magic-string": {
1645 | "version": "0.30.12",
1646 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
1647 | "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
1648 | "license": "MIT",
1649 | "dependencies": {
1650 | "@jridgewell/sourcemap-codec": "^1.5.0"
1651 | }
1652 | },
1653 | "node_modules/math-intrinsics": {
1654 | "version": "1.1.0",
1655 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
1656 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
1657 | "license": "MIT",
1658 | "engines": {
1659 | "node": ">= 0.4"
1660 | }
1661 | },
1662 | "node_modules/merge2": {
1663 | "version": "1.4.1",
1664 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
1665 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
1666 | "dev": true,
1667 | "license": "MIT",
1668 | "engines": {
1669 | "node": ">= 8"
1670 | }
1671 | },
1672 | "node_modules/micromatch": {
1673 | "version": "4.0.8",
1674 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
1675 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
1676 | "dev": true,
1677 | "license": "MIT",
1678 | "dependencies": {
1679 | "braces": "^3.0.3",
1680 | "picomatch": "^2.3.1"
1681 | },
1682 | "engines": {
1683 | "node": ">=8.6"
1684 | }
1685 | },
1686 | "node_modules/micromatch/node_modules/picomatch": {
1687 | "version": "2.3.1",
1688 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1689 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1690 | "dev": true,
1691 | "license": "MIT",
1692 | "engines": {
1693 | "node": ">=8.6"
1694 | },
1695 | "funding": {
1696 | "url": "https://github.com/sponsors/jonschlinkert"
1697 | }
1698 | },
1699 | "node_modules/mime-db": {
1700 | "version": "1.52.0",
1701 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1702 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1703 | "license": "MIT",
1704 | "engines": {
1705 | "node": ">= 0.6"
1706 | }
1707 | },
1708 | "node_modules/mime-types": {
1709 | "version": "2.1.35",
1710 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1711 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1712 | "license": "MIT",
1713 | "dependencies": {
1714 | "mime-db": "1.52.0"
1715 | },
1716 | "engines": {
1717 | "node": ">= 0.6"
1718 | }
1719 | },
1720 | "node_modules/minimatch": {
1721 | "version": "9.0.5",
1722 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
1723 | "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
1724 | "dev": true,
1725 | "license": "ISC",
1726 | "dependencies": {
1727 | "brace-expansion": "^2.0.1"
1728 | },
1729 | "engines": {
1730 | "node": ">=16 || 14 >=14.17"
1731 | },
1732 | "funding": {
1733 | "url": "https://github.com/sponsors/isaacs"
1734 | }
1735 | },
1736 | "node_modules/mlly": {
1737 | "version": "1.7.2",
1738 | "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.2.tgz",
1739 | "integrity": "sha512-tN3dvVHYVz4DhSXinXIk7u9syPYaJvio118uomkovAtWBT+RdbP6Lfh/5Lvo519YMmwBafwlh20IPTXIStscpA==",
1740 | "dev": true,
1741 | "license": "MIT",
1742 | "dependencies": {
1743 | "acorn": "^8.12.1",
1744 | "pathe": "^1.1.2",
1745 | "pkg-types": "^1.2.0",
1746 | "ufo": "^1.5.4"
1747 | }
1748 | },
1749 | "node_modules/ms": {
1750 | "version": "2.1.3",
1751 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1752 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1753 | "dev": true,
1754 | "license": "MIT"
1755 | },
1756 | "node_modules/nanoid": {
1757 | "version": "3.3.8",
1758 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
1759 | "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
1760 | "funding": [
1761 | {
1762 | "type": "github",
1763 | "url": "https://github.com/sponsors/ai"
1764 | }
1765 | ],
1766 | "bin": {
1767 | "nanoid": "bin/nanoid.cjs"
1768 | },
1769 | "engines": {
1770 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1771 | }
1772 | },
1773 | "node_modules/normalize-path": {
1774 | "version": "3.0.0",
1775 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1776 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1777 | "dev": true,
1778 | "license": "MIT",
1779 | "engines": {
1780 | "node": ">=0.10.0"
1781 | }
1782 | },
1783 | "node_modules/pathe": {
1784 | "version": "1.1.2",
1785 | "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
1786 | "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
1787 | "dev": true,
1788 | "license": "MIT"
1789 | },
1790 | "node_modules/picocolors": {
1791 | "version": "1.1.1",
1792 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1793 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1794 | "license": "ISC"
1795 | },
1796 | "node_modules/picomatch": {
1797 | "version": "4.0.2",
1798 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
1799 | "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
1800 | "dev": true,
1801 | "license": "MIT",
1802 | "engines": {
1803 | "node": ">=12"
1804 | },
1805 | "funding": {
1806 | "url": "https://github.com/sponsors/jonschlinkert"
1807 | }
1808 | },
1809 | "node_modules/pkg-types": {
1810 | "version": "1.2.1",
1811 | "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz",
1812 | "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==",
1813 | "dev": true,
1814 | "license": "MIT",
1815 | "dependencies": {
1816 | "confbox": "^0.1.8",
1817 | "mlly": "^1.7.2",
1818 | "pathe": "^1.1.2"
1819 | }
1820 | },
1821 | "node_modules/postcss": {
1822 | "version": "8.4.47",
1823 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
1824 | "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
1825 | "funding": [
1826 | {
1827 | "type": "opencollective",
1828 | "url": "https://opencollective.com/postcss/"
1829 | },
1830 | {
1831 | "type": "tidelift",
1832 | "url": "https://tidelift.com/funding/github/npm/postcss"
1833 | },
1834 | {
1835 | "type": "github",
1836 | "url": "https://github.com/sponsors/ai"
1837 | }
1838 | ],
1839 | "license": "MIT",
1840 | "dependencies": {
1841 | "nanoid": "^3.3.7",
1842 | "picocolors": "^1.1.0",
1843 | "source-map-js": "^1.2.1"
1844 | },
1845 | "engines": {
1846 | "node": "^10 || ^12 || >=14"
1847 | }
1848 | },
1849 | "node_modules/proxy-from-env": {
1850 | "version": "1.1.0",
1851 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
1852 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
1853 | "license": "MIT"
1854 | },
1855 | "node_modules/queue-microtask": {
1856 | "version": "1.2.3",
1857 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
1858 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
1859 | "dev": true,
1860 | "funding": [
1861 | {
1862 | "type": "github",
1863 | "url": "https://github.com/sponsors/feross"
1864 | },
1865 | {
1866 | "type": "patreon",
1867 | "url": "https://www.patreon.com/feross"
1868 | },
1869 | {
1870 | "type": "consulting",
1871 | "url": "https://feross.org/support"
1872 | }
1873 | ],
1874 | "license": "MIT"
1875 | },
1876 | "node_modules/readdirp": {
1877 | "version": "3.6.0",
1878 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1879 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1880 | "dev": true,
1881 | "license": "MIT",
1882 | "dependencies": {
1883 | "picomatch": "^2.2.1"
1884 | },
1885 | "engines": {
1886 | "node": ">=8.10.0"
1887 | }
1888 | },
1889 | "node_modules/readdirp/node_modules/picomatch": {
1890 | "version": "2.3.1",
1891 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1892 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1893 | "dev": true,
1894 | "license": "MIT",
1895 | "engines": {
1896 | "node": ">=8.6"
1897 | },
1898 | "funding": {
1899 | "url": "https://github.com/sponsors/jonschlinkert"
1900 | }
1901 | },
1902 | "node_modules/reusify": {
1903 | "version": "1.0.4",
1904 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
1905 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
1906 | "dev": true,
1907 | "license": "MIT",
1908 | "engines": {
1909 | "iojs": ">=1.0.0",
1910 | "node": ">=0.10.0"
1911 | }
1912 | },
1913 | "node_modules/rollup": {
1914 | "version": "4.24.2",
1915 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.2.tgz",
1916 | "integrity": "sha512-do/DFGq5g6rdDhdpPq5qb2ecoczeK6y+2UAjdJ5trjQJj5f1AiVdLRWRc9A9/fFukfvJRgM0UXzxBIYMovm5ww==",
1917 | "dev": true,
1918 | "license": "MIT",
1919 | "dependencies": {
1920 | "@types/estree": "1.0.6"
1921 | },
1922 | "bin": {
1923 | "rollup": "dist/bin/rollup"
1924 | },
1925 | "engines": {
1926 | "node": ">=18.0.0",
1927 | "npm": ">=8.0.0"
1928 | },
1929 | "optionalDependencies": {
1930 | "@rollup/rollup-android-arm-eabi": "4.24.2",
1931 | "@rollup/rollup-android-arm64": "4.24.2",
1932 | "@rollup/rollup-darwin-arm64": "4.24.2",
1933 | "@rollup/rollup-darwin-x64": "4.24.2",
1934 | "@rollup/rollup-freebsd-arm64": "4.24.2",
1935 | "@rollup/rollup-freebsd-x64": "4.24.2",
1936 | "@rollup/rollup-linux-arm-gnueabihf": "4.24.2",
1937 | "@rollup/rollup-linux-arm-musleabihf": "4.24.2",
1938 | "@rollup/rollup-linux-arm64-gnu": "4.24.2",
1939 | "@rollup/rollup-linux-arm64-musl": "4.24.2",
1940 | "@rollup/rollup-linux-powerpc64le-gnu": "4.24.2",
1941 | "@rollup/rollup-linux-riscv64-gnu": "4.24.2",
1942 | "@rollup/rollup-linux-s390x-gnu": "4.24.2",
1943 | "@rollup/rollup-linux-x64-gnu": "4.24.2",
1944 | "@rollup/rollup-linux-x64-musl": "4.24.2",
1945 | "@rollup/rollup-win32-arm64-msvc": "4.24.2",
1946 | "@rollup/rollup-win32-ia32-msvc": "4.24.2",
1947 | "@rollup/rollup-win32-x64-msvc": "4.24.2",
1948 | "fsevents": "~2.3.2"
1949 | }
1950 | },
1951 | "node_modules/run-parallel": {
1952 | "version": "1.2.0",
1953 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
1954 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
1955 | "dev": true,
1956 | "funding": [
1957 | {
1958 | "type": "github",
1959 | "url": "https://github.com/sponsors/feross"
1960 | },
1961 | {
1962 | "type": "patreon",
1963 | "url": "https://www.patreon.com/feross"
1964 | },
1965 | {
1966 | "type": "consulting",
1967 | "url": "https://feross.org/support"
1968 | }
1969 | ],
1970 | "license": "MIT",
1971 | "dependencies": {
1972 | "queue-microtask": "^1.2.2"
1973 | }
1974 | },
1975 | "node_modules/source-map-js": {
1976 | "version": "1.2.1",
1977 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1978 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1979 | "license": "BSD-3-Clause",
1980 | "engines": {
1981 | "node": ">=0.10.0"
1982 | }
1983 | },
1984 | "node_modules/to-regex-range": {
1985 | "version": "5.0.1",
1986 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1987 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1988 | "dev": true,
1989 | "license": "MIT",
1990 | "dependencies": {
1991 | "is-number": "^7.0.0"
1992 | },
1993 | "engines": {
1994 | "node": ">=8.0"
1995 | }
1996 | },
1997 | "node_modules/ufo": {
1998 | "version": "1.5.4",
1999 | "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
2000 | "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==",
2001 | "dev": true,
2002 | "license": "MIT"
2003 | },
2004 | "node_modules/unplugin": {
2005 | "version": "1.14.1",
2006 | "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.14.1.tgz",
2007 | "integrity": "sha512-lBlHbfSFPToDYp9pjXlUEFVxYLaue9f9T1HC+4OHlmj+HnMDdz9oZY+erXfoCe/5V/7gKUSY2jpXPb9S7f0f/w==",
2008 | "dev": true,
2009 | "license": "MIT",
2010 | "dependencies": {
2011 | "acorn": "^8.12.1",
2012 | "webpack-virtual-modules": "^0.6.2"
2013 | },
2014 | "engines": {
2015 | "node": ">=14.0.0"
2016 | },
2017 | "peerDependencies": {
2018 | "webpack-sources": "^3"
2019 | },
2020 | "peerDependenciesMeta": {
2021 | "webpack-sources": {
2022 | "optional": true
2023 | }
2024 | }
2025 | },
2026 | "node_modules/unplugin-vue-components": {
2027 | "version": "0.27.4",
2028 | "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.27.4.tgz",
2029 | "integrity": "sha512-1XVl5iXG7P1UrOMnaj2ogYa5YTq8aoh5jwDPQhemwO/OrXW+lPQKDXd1hMz15qxQPxgb/XXlbgo3HQ2rLEbmXQ==",
2030 | "dev": true,
2031 | "license": "MIT",
2032 | "dependencies": {
2033 | "@antfu/utils": "^0.7.10",
2034 | "@rollup/pluginutils": "^5.1.0",
2035 | "chokidar": "^3.6.0",
2036 | "debug": "^4.3.6",
2037 | "fast-glob": "^3.3.2",
2038 | "local-pkg": "^0.5.0",
2039 | "magic-string": "^0.30.11",
2040 | "minimatch": "^9.0.5",
2041 | "mlly": "^1.7.1",
2042 | "unplugin": "^1.12.1"
2043 | },
2044 | "engines": {
2045 | "node": ">=14"
2046 | },
2047 | "funding": {
2048 | "url": "https://github.com/sponsors/antfu"
2049 | },
2050 | "peerDependencies": {
2051 | "@babel/parser": "^7.15.8",
2052 | "@nuxt/kit": "^3.2.2",
2053 | "vue": "2 || 3"
2054 | },
2055 | "peerDependenciesMeta": {
2056 | "@babel/parser": {
2057 | "optional": true
2058 | },
2059 | "@nuxt/kit": {
2060 | "optional": true
2061 | }
2062 | }
2063 | },
2064 | "node_modules/vite": {
2065 | "version": "5.4.20",
2066 | "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
2067 | "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
2068 | "dev": true,
2069 | "license": "MIT",
2070 | "dependencies": {
2071 | "esbuild": "^0.21.3",
2072 | "postcss": "^8.4.43",
2073 | "rollup": "^4.20.0"
2074 | },
2075 | "bin": {
2076 | "vite": "bin/vite.js"
2077 | },
2078 | "engines": {
2079 | "node": "^18.0.0 || >=20.0.0"
2080 | },
2081 | "funding": {
2082 | "url": "https://github.com/vitejs/vite?sponsor=1"
2083 | },
2084 | "optionalDependencies": {
2085 | "fsevents": "~2.3.3"
2086 | },
2087 | "peerDependencies": {
2088 | "@types/node": "^18.0.0 || >=20.0.0",
2089 | "less": "*",
2090 | "lightningcss": "^1.21.0",
2091 | "sass": "*",
2092 | "sass-embedded": "*",
2093 | "stylus": "*",
2094 | "sugarss": "*",
2095 | "terser": "^5.4.0"
2096 | },
2097 | "peerDependenciesMeta": {
2098 | "@types/node": {
2099 | "optional": true
2100 | },
2101 | "less": {
2102 | "optional": true
2103 | },
2104 | "lightningcss": {
2105 | "optional": true
2106 | },
2107 | "sass": {
2108 | "optional": true
2109 | },
2110 | "sass-embedded": {
2111 | "optional": true
2112 | },
2113 | "stylus": {
2114 | "optional": true
2115 | },
2116 | "sugarss": {
2117 | "optional": true
2118 | },
2119 | "terser": {
2120 | "optional": true
2121 | }
2122 | }
2123 | },
2124 | "node_modules/vue": {
2125 | "version": "3.5.12",
2126 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz",
2127 | "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==",
2128 | "license": "MIT",
2129 | "dependencies": {
2130 | "@vue/compiler-dom": "3.5.12",
2131 | "@vue/compiler-sfc": "3.5.12",
2132 | "@vue/runtime-dom": "3.5.12",
2133 | "@vue/server-renderer": "3.5.12",
2134 | "@vue/shared": "3.5.12"
2135 | },
2136 | "peerDependencies": {
2137 | "typescript": "*"
2138 | },
2139 | "peerDependenciesMeta": {
2140 | "typescript": {
2141 | "optional": true
2142 | }
2143 | }
2144 | },
2145 | "node_modules/vue-awesome-paginate": {
2146 | "version": "1.2.0",
2147 | "resolved": "https://registry.npmjs.org/vue-awesome-paginate/-/vue-awesome-paginate-1.2.0.tgz",
2148 | "integrity": "sha512-+eYXo666E2n8NhpEPIdJcQEoJBDLo7wMGv45D2+i/VDntz2o422jRNcMQ7VESMgAcKyReWMwb9m99esCjsO/hw==",
2149 | "license": "MIT",
2150 | "dependencies": {
2151 | "vue": "^3.4.30"
2152 | }
2153 | },
2154 | "node_modules/vue-router": {
2155 | "version": "4.4.5",
2156 | "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.5.tgz",
2157 | "integrity": "sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==",
2158 | "license": "MIT",
2159 | "dependencies": {
2160 | "@vue/devtools-api": "^6.6.4"
2161 | },
2162 | "funding": {
2163 | "url": "https://github.com/sponsors/posva"
2164 | },
2165 | "peerDependencies": {
2166 | "vue": "^3.2.0"
2167 | }
2168 | },
2169 | "node_modules/vuex": {
2170 | "version": "4.0.2",
2171 | "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.0.2.tgz",
2172 | "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==",
2173 | "license": "MIT",
2174 | "dependencies": {
2175 | "@vue/devtools-api": "^6.0.0-beta.11"
2176 | },
2177 | "peerDependencies": {
2178 | "vue": "^3.0.2"
2179 | }
2180 | },
2181 | "node_modules/vuex-persist": {
2182 | "version": "3.1.3",
2183 | "resolved": "https://registry.npmjs.org/vuex-persist/-/vuex-persist-3.1.3.tgz",
2184 | "integrity": "sha512-QWOpP4SxmJDC5Y1+0+Yl/F4n7z27syd1St/oP+IYCGe0X0GFio0Zan6kngZFufdIhJm+5dFGDo3VG5kdkCGeRQ==",
2185 | "license": "MIT",
2186 | "dependencies": {
2187 | "deepmerge": "^4.2.2",
2188 | "flatted": "^3.0.5"
2189 | },
2190 | "peerDependencies": {
2191 | "vuex": ">=2.5"
2192 | }
2193 | },
2194 | "node_modules/webpack-virtual-modules": {
2195 | "version": "0.6.2",
2196 | "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
2197 | "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
2198 | "dev": true,
2199 | "license": "MIT"
2200 | }
2201 | }
2202 | }
2203 |
--------------------------------------------------------------------------------