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 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.data.user import create_user
from src.app.models.user import User, UserCreate
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.security import hash_password, create_access_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:
user = authenticate_user(form_data.username, form_data.password)
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})
return Token(access_token=access_token, token_type="bearer")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/token")
@router.get("/user")
@router.get("/me")
async def user(current_user: Annotated[str, Depends(get_current_user)]):
return current_user
@router.post("/user")
async def create(username, password):
hashed_password = await hash_password(password)
user = User(username = username, hashed_password = hashed_password)
@router.post("/register")
async def create(user_data: UserCreate):
if get_user_by_username(user_data.username):
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)
return created_user

View File

@@ -1,48 +1,47 @@
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.data.knowledge import create_knowledge, read_knowledges, read_knowledge, update_knowledge, delete_knowledge
from src.app.data.question import read_questions as read_questions_crud, create_question
from src.app.data.knowledge import create_knowledge, get_knowledges_by_user, get_knowledge_by_id
#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
#Added in __ini__
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/")
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)
# if created_knowledge is None:
# raise NotFoundException("Failed to create 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")
def create_questions(id: int):
knowledge = read_knowledge(id)
def generate_questions(id: int, current_user: Annotated[str, Depends(get_current_user)]):
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)
for q in questions_raw:
question = Question(question = q, knowledge=knowledge)
@@ -50,9 +49,24 @@ def create_questions(id: int):
return questions_raw
@router.get("/knowledges/{id}/questions")
def read_questions(id: int):
knowledge: Knowledge = read_knowledge(id)
#questions = knowledge.questions
#TODO : refacto ?
questions = read_questions_crud(knowledge)
def read_questions(id: int, current_user: Annotated[str, Depends(get_current_user)]):
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 = get_questions(knowledge)
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.auth.dependancies import get_current_user
router = APIRouter(tags=["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)
return created_metric

View File

@@ -1,23 +1,19 @@
from src.app.config import settings
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
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.data.user import get_user
from src.app.data.user import get_user_by_username
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()
def authenticate_user(username: str, password: str):
user: User = get_user(username)
user: User = get_user_by_username(username)
if not user:
# Add timing to prevent attack
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",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
payload = verify_token(token, token_type="access")
if payload is None:
raise credentials_exception
username = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except InvalidTokenError:
raise credentials_exception
user = get_user(username=token_data.username)
user = get_user_by_username(username=token_data.username)
if user is None:
raise credentials_exception
return user

View File

@@ -1,4 +1,5 @@
from src.app.config import settings
from typing import Optional
from datetime import timedelta, datetime, timezone
from argon2 import PasswordHasher
from argon2.exceptions import (
@@ -10,6 +11,7 @@ import jwt
from jwt.exceptions import InvalidTokenError
password_hasher = PasswordHasher()
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)
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
ORIGIN: str = Field('http://localhost:5173', env='ORIGIN')
SECRET_KEY : str = Field('random_string', env='SECRET_KEY')
ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
ACCESS_TOKEN_EXPIRE_MINUTES: int = 240
ALGORITHM: str = "HS256"
class Config:

View File

@@ -1,9 +1,36 @@
from fastapi import Depends
from sqlmodel import Session, select
from src.app.models.knowledge import Knowledge
from src.app.models.user import User
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):
with Session(engine) as session:
session.add(knowledge)
@@ -11,32 +38,23 @@ def create_knowledge(knowledge: Knowledge):
session.refresh(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
def update_knowledge(knowledge_id: int, content: str, uri: str):
with Session(engine) as session:
knowledge = session.get(Knowledge, knowledge_id)
knowledge.content = content if content else knowledge.content
knowledge.uri = uri if uri else knowledge.uri
# No filter by user
# Transform to update_knowledge(knowledge: Knowledge)
# def update_knowledge(knowledge_id: int, content: str, uri: str):
# with Session(engine) as session:
# 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.commit()
session.refresh(knowledge)
return knowledge
# session.add(knowledge)
# session.commit()
# session.refresh(knowledge)
# return knowledge
def delete_knowledge(knowledge_id: int):
with Session(engine) as session:
knowledge = session.get(Knowledge, knowledge_id)
session.delete(knowledge)
session.commit()
# No filter by user
# def delete_knowledge(knowledge_id: int):
# with Session(engine) as session:
# knowledge = session.get(Knowledge, knowledge_id)
# 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.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):
with Session(engine) as session:
session.add(metric)
@@ -11,18 +23,6 @@ def create_metric(metric: Metric):
session.refresh(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):
with Session(engine) as session:
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.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):
with Session(engine) as session:
session.add(question)
@@ -10,18 +22,6 @@ def create_question(question: Question):
session.refresh(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):
with Session(engine) as session:
question = session.get(Question, question_id)

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,15 @@
<script setup lang="ts">
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 type { Knowledge } from "@/types/types";
import type { Knowledge, KnowledgeCreate } from "@/types/types";
import { useStepStore } from '@/stores/step'
import { useItemStore } from '@/stores/item'
const stepStore = useStepStore()
const itemStore = useItemStore()
@@ -16,16 +19,16 @@
const Toast = useToast()
async function postKnowledge(){
const knowledge: Knowledge = {
id: null,
const knowledge: KnowledgeCreate = {
content: knowledgeModel.value,
uri: uriModel.value
}
if(validation(knowledge)){
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"})
itemStore.$patch({ knowledge:response })
itemStore.$patch({ knowledge: knowledge_data })
stepStore.nextStep()
}
catch {
@@ -34,7 +37,7 @@
}
}
function validation(knowledge: Knowledge){
function validation(knowledge: KnowledgeCreate){
return knowledge.content && knowledge.uri
}

View File

@@ -1,20 +1,19 @@
<script setup lang="ts">
import { ref } from "vue";
import { onBeforeMount } from 'vue'
import type { Knowledge } from "@/types/types";
import type { Question } from "@/types/types";
import type { Metric } from "@/types/types";
import type { Knowledge, Question, MetricCreate } from "@/types/types"
//import { useStepStore } from '@/stores/step'
import { useItemStore } from '@/stores/item'
import { apiClient } from "@/services/api";
import api from "@/services/apiAxios"
//const stepStore = useStepStore()
const itemStore = useItemStore()
const questions = ref<Question[]>()
const metrics = ref<Metric[]>([])
const metrics = ref<MetricCreate[]>([])
onBeforeMount(async () => {
if(itemStore.knowledge != undefined){
@@ -33,13 +32,14 @@
})
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[]){
questions.forEach((q)=>{
const metric: Metric = {
//id: null,
const metric: MetricCreate = {
question_id: q.id!,
need_index: -1
}
@@ -52,14 +52,15 @@
return metrics.value.findIndex((metric) => metric.question_id === question.id)
}
else{
throw new Error("The is no metrics element");
throw new Error("The is no metrics element")
}
}
async function postMetrics(){
console.log(metrics.value)
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>

View File

@@ -3,7 +3,7 @@
import { BProgress } from "buefy";
import type { Knowledge } from "@/types/types";
import { apiClient } from "@/services/api";
import api from "@/services/apiAxios";
import { useItemStore } from '@/stores/item'
import { useStepStore } from '@/stores/step'
@@ -17,7 +17,7 @@
})
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()
}
</script>

View File

@@ -9,6 +9,12 @@ const router = createRouter({
name: 'app',
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 {
id: number | null,
content: string,
uri: string,
user: User
}
interface Question {
@@ -9,12 +15,17 @@ interface Question {
question: string,
knowledgeId: number,
metric: Metric | null
user: User
}
interface Metric {
//id: number | null,
interface MetricCreate {
question_id: 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>