add user restriction

This commit is contained in:
Robin COuret
2026-03-06 16:31:40 +01:00
parent a243149bf1
commit a06e9c3633
37 changed files with 359 additions and 215 deletions

View File

@@ -1,18 +1,18 @@
from typing import Annotated from typing import Annotated
from fastapi import Depends, APIRouter, HTTPException, status from fastapi import Depends, APIRouter, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer from fastapi.security import OAuth2PasswordRequestForm
from src.app.models.user import User from src.app.models.user import User, UserCreate
from src.app.data.user import create_user from src.app.data.user import create_user, get_user_by_username
from src.app.auth.dependancies import get_current_user, authenticate_user from src.app.auth.dependancies import get_current_user, authenticate_user
from src.app.auth.security import hash_password, create_access_token from src.app.auth.security import hash_password, create_access_token
from src.app.auth.schemas import Token from src.app.auth.schemas import Token
router = APIRouter(tags=["users"]) router = APIRouter(prefix="/auth", tags=["auth"])
@router.post("/token") @router.post("/login")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> Token: async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> Token:
user = authenticate_user(form_data.username, form_data.password) user = authenticate_user(form_data.username, form_data.password)
if not user: if not user:
@@ -24,15 +24,19 @@ async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> T
access_token = create_access_token(data={"sub": user.username}) access_token = create_access_token(data={"sub": user.username})
return Token(access_token=access_token, token_type="bearer") return Token(access_token=access_token, token_type="bearer")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/token") @router.get("/me")
@router.get("/user")
async def user(current_user: Annotated[str, Depends(get_current_user)]): async def user(current_user: Annotated[str, Depends(get_current_user)]):
return current_user return current_user
@router.post("/user") @router.post("/register")
async def create(username, password): async def create(user_data: UserCreate):
hashed_password = await hash_password(password) if get_user_by_username(user_data.username):
user = User(username = username, hashed_password = hashed_password) raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Username already registered"
)
hashed_password = hash_password(user_data.plain_password)
user = User(username = user_data.username, hashed_password = hashed_password)
created_user = create_user(user) created_user = create_user(user)
return created_user return created_user

View File

@@ -1,48 +1,47 @@
from typing import Annotated from typing import Annotated
from fastapi import APIRouter from fastapi import APIRouter, Depends, HTTPException, status
from src.app.models.knowledge import Knowledge from src.app.auth.dependancies import get_current_user
from src.app.models.knowledge import Knowledge, KnowledgeCreate
from src.app.models.question import Question from src.app.models.question import Question
from src.app.data.knowledge import create_knowledge, read_knowledges, read_knowledge, update_knowledge, delete_knowledge from src.app.data.knowledge import create_knowledge, get_knowledges_by_user, get_knowledge_by_id
from src.app.data.question import read_questions as read_questions_crud, create_question #from src.app.data.knowledge update_knowledge, delete_knowledge
from src.app.data.question import get_questions, create_question
from src.app.services.language_generation import questions_generation from src.app.services.language_generation import questions_generation
#Added in __ini__ #Added in __ini__
router = APIRouter(tags=["knowledges"]) router = APIRouter(tags=["knowledges"])
@router.get("/knowledges/")
def read(current_user: Annotated[str, Depends(get_current_user)]):
knowledges = get_knowledges_by_user(current_user)
return knowledges
@router.get("/knowledges/{id}")
def read(id: int, current_user: Annotated[str, Depends(get_current_user)]):
knowledge = get_knowledge_by_id(id, current_user)
return knowledge
@router.post("/knowledges/") @router.post("/knowledges/")
def create(knowledge: Knowledge): def create(knowledge_data: KnowledgeCreate, current_user: Annotated[str, Depends(get_current_user)]):
knowledge = Knowledge(content=knowledge_data.content, uri=knowledge_data.uri, user=current_user)
created_knowledge = create_knowledge(knowledge) created_knowledge = create_knowledge(knowledge)
# if created_knowledge is None: # if created_knowledge is None:
# raise NotFoundException("Failed to create knowledge") # raise NotFoundException("Failed to create knowledge")
return created_knowledge return created_knowledge
@router.get("/knowledges/")
def read():
knowledges = read_knowledges()
return knowledges
@router.get("/knowledges/{id}")
def read(id: int):
knowledge = read_knowledge(id)
return knowledge
#TODO: adapt with correct pattern
@router.post("/knowledges/{id}")
def update(id: int, content: str, uri: str):
knowledge = update_knowledge(id, content, uri)
return knowledge
@router.delete("/knowledges/{id}")
def delete(id: int):
knowledge = delete_knowledge(id)
return knowledge
@router.post("/knowledges/{id}/questions") @router.post("/knowledges/{id}/questions")
def create_questions(id: int): def generate_questions(id: int, current_user: Annotated[str, Depends(get_current_user)]):
knowledge = read_knowledge(id) knowledge: Knowledge = get_knowledge_by_id(id, current_user)
if not knowledge:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Forbidden. The requested knowledge is not available for the provided ID.",
headers={"WWW-Authenticate": "Bearer"},
)
questions_raw = questions_generation(knowledge) questions_raw = questions_generation(knowledge)
for q in questions_raw: for q in questions_raw:
question = Question(question = q, knowledge=knowledge) question = Question(question = q, knowledge=knowledge)
@@ -50,9 +49,24 @@ def create_questions(id: int):
return questions_raw return questions_raw
@router.get("/knowledges/{id}/questions") @router.get("/knowledges/{id}/questions")
def read_questions(id: int): def read_questions(id: int, current_user: Annotated[str, Depends(get_current_user)]):
knowledge: Knowledge = read_knowledge(id) knowledge: Knowledge = get_knowledge_by_id(id, current_user)
#questions = knowledge.questions if not knowledge:
#TODO : refacto ? raise HTTPException(
questions = read_questions_crud(knowledge) status_code=status.HTTP_403_FORBIDDEN,
detail="Forbidden. The requested knowledge is not available for the provided ID.",
headers={"WWW-Authenticate": "Bearer"},
)
questions = get_questions(knowledge)
return questions return questions
#TODO: adapt with correct pattern
# @router.post("/knowledges/{id}")
# def update(id: int, content: str, uri: str):
# knowledge = update_knowledge(id, content, uri)
# return knowledge
# @router.delete("/knowledges/{id}")
# def delete(id: int):
# knowledge = delete_knowledge(id)
# return knowledge

View File

@@ -1,11 +1,15 @@
from fastapi import APIRouter from typing import Annotated
from fastapi import APIRouter, Depends
from src.app.models.metric import Metric from src.app.models.metric import Metric, MetricCreate
from src.app.data.metric import create_metric from src.app.data.metric import create_metric
from src.app.auth.dependancies import get_current_user
router = APIRouter(tags=["metrics"]) router = APIRouter(tags=["metrics"])
@router.post("/metrics/") @router.post("/metrics/")
def create(metric: Metric): def create(metric_data: MetricCreate, current_user: Annotated[str, Depends(get_current_user)]):
metric: Metric = Metric(question_id = metric_data.question_id, need_index = metric_data.need_index, user = current_user)
created_metric: Metric = create_metric(metric) created_metric: Metric = create_metric(metric)
return created_metric return created_metric

View File

@@ -1,23 +1,19 @@
from src.app.config import settings
from typing import Annotated from typing import Annotated
from fastapi import Depends, HTTPException, status from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from argon2 import PasswordHasher from argon2 import PasswordHasher
from argon2 import PasswordHasher from argon2 import PasswordHasher
import jwt
from jwt.exceptions import InvalidTokenError
from src.app.models.user import User from src.app.models.user import User
from src.app.data.user import get_user from src.app.data.user import get_user_by_username
from .schemas import TokenData from .schemas import TokenData
from .security import verify_password from .security import verify_password, verify_token
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/token") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
password_hasher = PasswordHasher() password_hasher = PasswordHasher()
def authenticate_user(username: str, password: str): def authenticate_user(username: str, password: str):
user: User = get_user(username) user: User = get_user_by_username(username)
if not user: if not user:
# Add timing to prevent attack # Add timing to prevent attack
password_hasher.hash(password) password_hasher.hash(password)
@@ -32,17 +28,19 @@ async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> Use
detail="Could not validate credentials", detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
) )
try: payload = verify_token(token, token_type="access")
payload = jwt.decode(token, settings.SECRET_KEY, algorithm=settings.ALGORITHM) if payload is None:
raise credentials_exception
username = payload.get("sub") username = payload.get("sub")
if username is None: if username is None:
raise credentials_exception raise credentials_exception
token_data = TokenData(username=username) token_data = TokenData(username=username)
except InvalidTokenError:
raise credentials_exception user = get_user_by_username(username=token_data.username)
user = get_user(username=token_data.username)
if user is None: if user is None:
raise credentials_exception raise credentials_exception
return user return user

View File

@@ -1,4 +1,5 @@
from src.app.config import settings from src.app.config import settings
from typing import Optional
from datetime import timedelta, datetime, timezone from datetime import timedelta, datetime, timezone
from argon2 import PasswordHasher from argon2 import PasswordHasher
from argon2.exceptions import ( from argon2.exceptions import (
@@ -10,6 +11,7 @@ import jwt
from jwt.exceptions import InvalidTokenError from jwt.exceptions import InvalidTokenError
password_hasher = PasswordHasher() password_hasher = PasswordHasher()
def verify_password(plain_password: str, hashed_password: str) -> bool: def verify_password(plain_password: str, hashed_password: str) -> bool:
@@ -28,6 +30,11 @@ def create_access_token(data: dict):
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return encoded_jwt return encoded_jwt
#def create_refresh_token(data: dict) -> str: def verify_token(token: str, token_type: str = "access") -> Optional[dict]:
try:
payload = jwt.decode(jwt = token, key = settings.SECRET_KEY, algorithms = [settings.ALGORITHM])
return payload
except InvalidTokenError:
return None
#def verify_token(token: str, token_type: str = "access") -> Optional[dict]: #def create_refresh_token(data: dict) -> str:

View File

@@ -12,7 +12,7 @@ class Settings(BaseSettings):
# Security # Security
ORIGIN: str = Field('http://localhost:5173', env='ORIGIN') ORIGIN: str = Field('http://localhost:5173', env='ORIGIN')
SECRET_KEY : str = Field('random_string', env='SECRET_KEY') SECRET_KEY : str = Field('random_string', env='SECRET_KEY')
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 ACCESS_TOKEN_EXPIRE_MINUTES: int = 240
ALGORITHM: str = "HS256" ALGORITHM: str = "HS256"
class Config: class Config:

View File

@@ -1,9 +1,36 @@
from fastapi import Depends
from sqlmodel import Session, select from sqlmodel import Session, select
from src.app.models.knowledge import Knowledge from src.app.models.knowledge import Knowledge
from src.app.models.user import User
from src.app.database import engine from src.app.database import engine
def get_knowledge_by_id(knowledge_id: int, user: User):
with Session(engine) as session:
statement = select(Knowledge).where(Knowledge.id == knowledge_id, Knowledge.user_id == user.id)
results = session.exec(statement)
knowledge = results.first()
return knowledge
# def get_knowledge_by_id(knowledge_id: int):
# with Session(engine) as session:
# knowledge = session.get(Knowledge, knowledge_id)
# return knowledge
# No filter by user
def get_knowledges():
with Session(engine) as session:
statement = select(Knowledge)
results = session.exec(statement)
knowledges = results.all()
return knowledges
def get_knowledges_by_user(user: User):
with Session(engine) as session:
statement = select(Knowledge).where(Knowledge.user_id == user.id)
results = session.exec(statement)
knowledges = results.all()
return knowledges
def create_knowledge(knowledge: Knowledge): def create_knowledge(knowledge: Knowledge):
with Session(engine) as session: with Session(engine) as session:
session.add(knowledge) session.add(knowledge)
@@ -11,32 +38,23 @@ def create_knowledge(knowledge: Knowledge):
session.refresh(knowledge) session.refresh(knowledge)
return knowledge return knowledge
def read_knowledges():
with Session(engine) as session:
statement = select(Knowledge)
results = session.exec(statement)
knowledges = results.all()
return knowledges
def read_knowledge(knowledge_id: int):
with Session(engine) as session:
knowledge = session.get(Knowledge, knowledge_id)
return knowledge
#TODO adapt logic with args #TODO adapt logic with args
def update_knowledge(knowledge_id: int, content: str, uri: str): # No filter by user
with Session(engine) as session: # Transform to update_knowledge(knowledge: Knowledge)
knowledge = session.get(Knowledge, knowledge_id) # def update_knowledge(knowledge_id: int, content: str, uri: str):
knowledge.content = content if content else knowledge.content # with Session(engine) as session:
knowledge.uri = uri if uri else knowledge.uri # knowledge = session.get(Knowledge, knowledge_id)
# knowledge.content = content if content else knowledge.content
# knowledge.uri = uri if uri else knowledge.uri
session.add(knowledge) # session.add(knowledge)
session.commit() # session.commit()
session.refresh(knowledge) # session.refresh(knowledge)
return knowledge # return knowledge
def delete_knowledge(knowledge_id: int): # No filter by user
with Session(engine) as session: # def delete_knowledge(knowledge_id: int):
knowledge = session.get(Knowledge, knowledge_id) # with Session(engine) as session:
session.delete(knowledge) # knowledge = session.get(Knowledge, knowledge_id)
session.commit() # session.delete(knowledge)
# session.commit()

View File

@@ -4,6 +4,18 @@ from src.app.models.metric import Metric
from src.app.models.question import Question from src.app.models.question import Question
from src.app.database import engine from src.app.database import engine
def get_metric_by_id(metric_id: int):
with Session(engine) as session:
metric = session.get(Metric, metric_id)
return metric
def get_metrics(question):
with Session(engine) as session:
statement = select(Metric).where(Metric.question_id == question.id)
results = session.exec(statement)
metrics = results.all()
return metrics
def create_metric(metric: Metric): def create_metric(metric: Metric):
with Session(engine) as session: with Session(engine) as session:
session.add(metric) session.add(metric)
@@ -11,18 +23,6 @@ def create_metric(metric: Metric):
session.refresh(metric) session.refresh(metric)
return metric return metric
def read_metrics(question):
with Session(engine) as session:
statement = select(Metric).where(Metric.question_id == question.id)
results = session.exec(statement)
metrics = results.all()
return metrics
def read_metric(metric_id: int):
with Session(engine) as session:
metric = session.get(Metric, metric_id)
return metric
def delete_metric(metric_id: int): def delete_metric(metric_id: int):
with Session(engine) as session: with Session(engine) as session:
metric = session.get(Metric, metric_id) metric = session.get(Metric, metric_id)

View File

@@ -3,6 +3,18 @@ from sqlmodel import Session, select
from src.app.models.question import Question from src.app.models.question import Question
from src.app.database import engine from src.app.database import engine
def get_question_by_id(question_id: int):
with Session(engine) as session:
question = session.get(Question, question_id)
return question
def get_questions(knowledge):
with Session(engine) as session:
statement = select(Question).where(Question.knowledge_id == knowledge.id)
results = session.exec(statement)
questions = results.all()
return questions
def create_question(question: Question): def create_question(question: Question):
with Session(engine) as session: with Session(engine) as session:
session.add(question) session.add(question)
@@ -10,18 +22,6 @@ def create_question(question: Question):
session.refresh(question) session.refresh(question)
return question return question
def read_questions(knowledge):
with Session(engine) as session:
statement = select(Question).where(Question.knowledge_id == knowledge.id)
results = session.exec(statement)
questions = results.all()
return questions
def read_question(question_id: int):
with Session(engine) as session:
question = session.get(Question, question_id)
return question
def delete_question(question_id: int): def delete_question(question_id: int):
with Session(engine) as session: with Session(engine) as session:
question = session.get(Question, question_id) question = session.get(Question, question_id)

View File

@@ -10,14 +10,14 @@ def create_user(user: User):
session.refresh(user) session.refresh(user)
return user return user
def get_user(username: str): def get_user_by_username(username: str):
with Session(engine) as session: with Session(engine) as session:
statement = select(User).where(User.username == username) statement = select(User).where(User.username == username)
results = session.exec(statement) results = session.exec(statement)
result = results.first() result = results.first()
return result return result
def read_users(): def get_users():
with Session(engine) as session: with Session(engine) as session:
statement = select(User) statement = select(User)
results = session.exec(statement) results = session.exec(statement)

View File

@@ -1,4 +1,6 @@
from sqlmodel import Field, SQLModel, Relationship from sqlmodel import Field, SQLModel, Relationship
from pydantic import BaseModel
from src.app.models.user import User
class Knowledge(SQLModel, table=True): class Knowledge(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True) id: int | None = Field(default=None, primary_key=True)
@@ -6,3 +8,10 @@ class Knowledge(SQLModel, table=True):
uri: str = Field(index=True) uri: str = Field(index=True)
questions: list["Question"] = Relationship(back_populates="knowledge", cascade_delete=True) # type: ignore questions: list["Question"] = Relationship(back_populates="knowledge", cascade_delete=True) # type: ignore
user_id: int | None = Field(default=None, foreign_key="user.id", ondelete="CASCADE")
user: User | None = Relationship(back_populates="knowledges")
class KnowledgeCreate(BaseModel):
content: str
uri:str

View File

@@ -1,5 +1,7 @@
from sqlmodel import Field, SQLModel, Relationship from sqlmodel import Field, SQLModel, Relationship
from pydantic import BaseModel
from src.app.models.question import Question from src.app.models.question import Question
from src.app.models.user import User
class Metric(SQLModel, table=True): class Metric(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True) id: int | None = Field(default=None, primary_key=True)
@@ -8,3 +10,10 @@ class Metric(SQLModel, table=True):
question: Question | None = Relationship(back_populates="metrics") question: Question | None = Relationship(back_populates="metrics")
need_index: int need_index: int
user_id: int | None = Field(default=None, foreign_key="user.id", ondelete="CASCADE")
user: User | None = Relationship(back_populates="metrics")
class MetricCreate(BaseModel):
question_id: int
need_index: int

View File

@@ -1,5 +1,6 @@
from sqlmodel import Field, SQLModel, Relationship from sqlmodel import Field, SQLModel, Relationship
from src.app.models.knowledge import Knowledge from src.app.models.knowledge import Knowledge
from src.app.models.user import User
class Question(SQLModel, table=True): class Question(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True) id: int | None = Field(default=None, primary_key=True)
@@ -9,3 +10,6 @@ class Question(SQLModel, table=True):
knowledge: Knowledge | None = Relationship(back_populates="questions") knowledge: Knowledge | None = Relationship(back_populates="questions")
metrics: list["Metric"] = Relationship(back_populates="question", cascade_delete=True) # type: ignore metrics: list["Metric"] = Relationship(back_populates="question", cascade_delete=True) # type: ignore
user_id: int | None = Field(default=None, foreign_key="user.id", ondelete="CASCADE")
user: User | None = Relationship(back_populates="questions")

View File

@@ -1,7 +1,16 @@
from sqlmodel import Field, SQLModel, Relationship from sqlmodel import Field, SQLModel, Relationship
from pydantic import BaseModel
class User(SQLModel, table=True): class User(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True) id: int | None = Field(default=None, primary_key=True)
username: str username: str
hashed_password: str hashed_password: str
#is_active: bool #is_active: bool
knowledges: list["Knowledge"] = Relationship(back_populates="user", cascade_delete=True) # type: ignore
questions: list["Question"] = Relationship(back_populates="user", cascade_delete=True) # type: ignore
metrics: list["Metric"] = Relationship(back_populates="user", cascade_delete=True) # type: ignore
class UserCreate(BaseModel):
username: str
plain_password: str

View File

@@ -8,6 +8,7 @@
"name": "user-interface", "name": "user-interface",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"axios": "^1.13.6",
"buefy": "^3.0.4", "buefy": "^3.0.4",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"vue": "^3.5.27", "vue": "^3.5.27",
@@ -1250,9 +1251,9 @@
} }
}, },
"node_modules/@eslint/config-array/node_modules/minimatch": { "node_modules/@eslint/config-array/node_modules/minimatch": {
"version": "10.2.2", "version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true, "dev": true,
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
@@ -3278,7 +3279,6 @@
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/at-least-node": { "node_modules/at-least-node": {
@@ -3309,10 +3309,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.13.5", "version": "1.13.6",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.11", "follow-redirects": "^1.15.11",
@@ -3324,7 +3323,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/balanced-match": { "node_modules/balanced-match": {
@@ -3564,7 +3562,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -3798,7 +3795,6 @@
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"delayed-stream": "~1.0.0" "delayed-stream": "~1.0.0"
@@ -4125,7 +4121,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.4.0" "node": ">=0.4.0"
@@ -4135,7 +4130,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.1", "call-bind-apply-helpers": "^1.0.1",
@@ -4172,15 +4166,15 @@
} }
}, },
"node_modules/editorconfig": { "node_modules/editorconfig": {
"version": "1.0.4", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz",
"integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@one-ini/wasm": "0.1.1", "@one-ini/wasm": "0.1.1",
"commander": "^10.0.0", "commander": "^10.0.0",
"minimatch": "9.0.1", "minimatch": "^9.0.1",
"semver": "^7.5.3" "semver": "^7.5.3"
}, },
"bin": { "bin": {
@@ -4190,23 +4184,6 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/editorconfig/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/editorconfig/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/editorconfig/node_modules/commander": { "node_modules/editorconfig/node_modules/commander": {
"version": "10.0.1", "version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
@@ -4217,22 +4194,6 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/editorconfig/node_modules/minimatch": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
"integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.302", "version": "1.5.302",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz",
@@ -4298,7 +4259,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -4308,7 +4268,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -4325,7 +4284,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0" "es-errors": "^1.3.0"
@@ -4338,7 +4296,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@@ -4588,9 +4545,9 @@
} }
}, },
"node_modules/eslint/node_modules/minimatch": { "node_modules/eslint/node_modules/minimatch": {
"version": "10.2.2", "version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true, "dev": true,
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
@@ -4965,7 +4922,6 @@
"version": "1.15.11", "version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -5026,7 +4982,6 @@
"version": "4.0.5", "version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
@@ -5081,7 +5036,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@@ -5101,7 +5055,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
@@ -5126,7 +5079,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"dunder-proto": "^1.0.1", "dunder-proto": "^1.0.1",
@@ -5230,7 +5182,6 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -5260,7 +5211,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -5273,7 +5223,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"has-symbols": "^1.0.3" "has-symbols": "^1.0.3"
@@ -5306,7 +5255,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@@ -6174,7 +6122,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -6231,7 +6178,6 @@
"version": "1.52.0", "version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
@@ -6241,7 +6187,6 @@
"version": "2.1.35", "version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"mime-db": "1.52.0" "mime-db": "1.52.0"
@@ -6261,13 +6206,13 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "9.0.6", "version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^5.0.2" "brace-expansion": "^2.0.2"
}, },
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
@@ -6276,6 +6221,23 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/minimatch/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/minimatch/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/minimist": { "node_modules/minimist": {
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",

View File

@@ -19,6 +19,7 @@
"format": "prettier --write --experimental-cli src/" "format": "prettier --write --experimental-cli src/"
}, },
"dependencies": { "dependencies": {
"axios": "^1.13.6",
"buefy": "^3.0.4", "buefy": "^3.0.4",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"vue": "^3.5.27", "vue": "^3.5.27",

View File

@@ -1,12 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { BField, BInput, BButton, useToast } from "buefy"; import { BField, BInput, BButton, useToast } from "buefy";
import { apiClient } from "@/services/api"; //import { apiClient } from "@/services/api";
import api from "@/services/apiAxios"
import { AxiosResponse } from "axios";
import { ref } from "vue"; import { ref } from "vue";
import type { Knowledge } from "@/types/types"; import type { Knowledge, KnowledgeCreate } from "@/types/types";
import { useStepStore } from '@/stores/step' import { useStepStore } from '@/stores/step'
import { useItemStore } from '@/stores/item' import { useItemStore } from '@/stores/item'
const stepStore = useStepStore() const stepStore = useStepStore()
const itemStore = useItemStore() const itemStore = useItemStore()
@@ -16,16 +19,16 @@
const Toast = useToast() const Toast = useToast()
async function postKnowledge(){ async function postKnowledge(){
const knowledge: Knowledge = { const knowledge: KnowledgeCreate = {
id: null,
content: knowledgeModel.value, content: knowledgeModel.value,
uri: uriModel.value uri: uriModel.value
} }
if(validation(knowledge)){ if(validation(knowledge)){
try { try {
const response: Knowledge = await apiClient.post("api/v1/knowledges/", knowledge) const response: AxiosResponse = await api.post("api/v1/knowledges/", knowledge)
const knowledge_data: Knowledge = response.data
Toast.open({message: "Knowledge collected", type: "is-success"}) Toast.open({message: "Knowledge collected", type: "is-success"})
itemStore.$patch({ knowledge:response }) itemStore.$patch({ knowledge: knowledge_data })
stepStore.nextStep() stepStore.nextStep()
} }
catch { catch {
@@ -34,7 +37,7 @@
} }
} }
function validation(knowledge: Knowledge){ function validation(knowledge: KnowledgeCreate){
return knowledge.content && knowledge.uri return knowledge.content && knowledge.uri
} }

View File

@@ -1,20 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { onBeforeMount } from 'vue' import { onBeforeMount } from 'vue'
import type { Knowledge } from "@/types/types";
import type { Question } from "@/types/types"; import type { Knowledge, Question, MetricCreate } from "@/types/types"
import type { Metric } from "@/types/types";
//import { useStepStore } from '@/stores/step' //import { useStepStore } from '@/stores/step'
import { useItemStore } from '@/stores/item' import { useItemStore } from '@/stores/item'
import { apiClient } from "@/services/api"; import api from "@/services/apiAxios"
//const stepStore = useStepStore() //const stepStore = useStepStore()
const itemStore = useItemStore() const itemStore = useItemStore()
const questions = ref<Question[]>() const questions = ref<Question[]>()
const metrics = ref<Metric[]>([]) const metrics = ref<MetricCreate[]>([])
onBeforeMount(async () => { onBeforeMount(async () => {
if(itemStore.knowledge != undefined){ if(itemStore.knowledge != undefined){
@@ -33,13 +32,14 @@
}) })
async function getQuestions(knowledge: Knowledge): Promise<Question[]>{ async function getQuestions(knowledge: Knowledge): Promise<Question[]>{
return apiClient.get(`api/v1/knowledges/${knowledge.id}/questions`) const response = await api.get(`api/v1/knowledges/${knowledge.id}/questions`)
const questions: Question[] = response.data
return questions
} }
function initializeMetrics(questions: Question[]){ function initializeMetrics(questions: Question[]){
questions.forEach((q)=>{ questions.forEach((q)=>{
const metric: Metric = { const metric: MetricCreate = {
//id: null,
question_id: q.id!, question_id: q.id!,
need_index: -1 need_index: -1
} }
@@ -52,14 +52,15 @@
return metrics.value.findIndex((metric) => metric.question_id === question.id) return metrics.value.findIndex((metric) => metric.question_id === question.id)
} }
else{ else{
throw new Error("The is no metrics element"); throw new Error("The is no metrics element")
} }
} }
async function postMetrics(){ async function postMetrics(){
console.log(metrics.value)
metrics.value?.forEach(async (metric) => { metrics.value?.forEach(async (metric) => {
await apiClient.post(`api/v1/metrics/`, metric) const response = await api.post(`api/v1/metrics/`, metric)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const metric_data = response.data
}) })
} }
</script> </script>

View File

@@ -3,7 +3,7 @@
import { BProgress } from "buefy"; import { BProgress } from "buefy";
import type { Knowledge } from "@/types/types"; import type { Knowledge } from "@/types/types";
import { apiClient } from "@/services/api"; import api from "@/services/apiAxios";
import { useItemStore } from '@/stores/item' import { useItemStore } from '@/stores/item'
import { useStepStore } from '@/stores/step' import { useStepStore } from '@/stores/step'
@@ -17,7 +17,7 @@
}) })
async function generateQuestions (knowledge: Knowledge) { async function generateQuestions (knowledge: Knowledge) {
await apiClient.post(`api/v1/knowledges/${knowledge.id}/questions`, null) await api.post(`api/v1/knowledges/${knowledge.id}/questions`)
stepStore.nextStep() stepStore.nextStep()
} }
</script> </script>

View File

@@ -9,6 +9,12 @@ const router = createRouter({
name: 'app', name: 'app',
component: () => import('@/views/ExperimentView.vue'), component: () => import('@/views/ExperimentView.vue'),
}, },
{
path: '/login',
alias: '/login',
name: 'login',
component: () => import('@/views/AuthView.vue'),
}
], ],
}) })

View File

@@ -0,0 +1,26 @@
import axios from "axios"
const api = axios.create({
baseURL: import.meta.env.VITE_API_URL
});
api.interceptors.request.use((config) => {
const token = localStorage.getItem('access_token')
if (token){
config.headers.Authorization = `Bearer ${token}`
}
return config
})
export const authAPI = {
register: (data: unknown) => api.post('/api/v1/auth/register', data),
login: (username: string, password: string) =>
api.post(
'/api/v1/auth/login',
new URLSearchParams({ username, password }),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' }}
),
getMe: () => api.get('/auth/me')
}
export default api;

View File

@@ -1,7 +1,13 @@
interface KnowledgeCreate{
content: string,
uri: string,
}
interface Knowledge { interface Knowledge {
id: number | null, id: number | null,
content: string, content: string,
uri: string, uri: string,
user: User
} }
interface Question { interface Question {
@@ -9,12 +15,17 @@ interface Question {
question: string, question: string,
knowledgeId: number, knowledgeId: number,
metric: Metric | null metric: Metric | null
user: User
} }
interface Metric { interface MetricCreate {
//id: number | null,
question_id: number, question_id: number,
need_index: number need_index: number
} }
export type {Knowledge, Question, Metric} interface User {
username: string,
token: string
}
export type {KnowledgeCreate, Knowledge, Question, MetricCreate}

View File

@@ -0,0 +1,58 @@
<script setup lang="ts">
import { BField, BInput, BButton, useToast } from "buefy";
import { ref } from "vue"
import { authAPI } from '@/services/apiAxios'
const username = ref<string>("")
const password = ref<string>("")
const Toast = useToast()
async function login() {
try {
const response = await authAPI.login(username.value, password.value)
const { access_token, refresh_token } = response.data
localStorage.setItem('access_token', access_token)
localStorage.setItem('refresh_token', refresh_token)
} catch (err){
console.log(err)
Toast.open({message: "Login failed", type: "is-danger"})
}
}
</script>
<template>
<div class="container">
<b-field label="Username">
<!-- @vue-ignore -->
<b-input
v-model="username"
placeholder="Alice"
maxlength="20"
required
></b-input>
</b-field>
<b-field label="Password">
<!-- @vue-ignore -->
<b-input
v-model="password"
maxlength="50"
type="password"
required
></b-input>
</b-field>
<b-button type="is-primary" @click="login" >Login</b-button>
</div>
</template>
<style scoped>
.container{
width: 20%;
background-color: #ffffff;
border-radius: 16px;
padding: 34px;
}
</style>