├── 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 | 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 | 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 | 56 | 57 | 61 | 62 | 67 | 68 | -------------------------------------------------------------------------------- /frontend/src/components/questions/QuestionsByTag.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 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 | 64 | 65 | 110 | -------------------------------------------------------------------------------- /frontend/src/components/questions/ProfileAnswersEdit.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 119 | -------------------------------------------------------------------------------- /frontend/src/components/questions/ProfileAnswers.vue: -------------------------------------------------------------------------------- 1 | 72 | 73 | 121 | 122 | -------------------------------------------------------------------------------- /frontend/src/components/accounts/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 121 | -------------------------------------------------------------------------------- /frontend/src/components/questions/ProfileQuestions.vue: -------------------------------------------------------------------------------- 1 | 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 | 69 | 70 | 141 | -------------------------------------------------------------------------------- /frontend/src/components/questions/CreateQuestion.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 145 | -------------------------------------------------------------------------------- /frontend/src/components/accounts/DashboardAnswers.vue: -------------------------------------------------------------------------------- 1 | 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 | 99 | 100 | 151 | -------------------------------------------------------------------------------- /frontend/src/components/accounts/Register.vue: -------------------------------------------------------------------------------- 1 | 115 | 116 | 170 | -------------------------------------------------------------------------------- /frontend/src/components/questions/Questions.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 205 | 206 | -------------------------------------------------------------------------------- /frontend/src/components/questions/Question.vue: -------------------------------------------------------------------------------- 1 | 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 | --------------------------------------------------------------------------------