diff --git a/server/src/app/api/v1/__init__.py b/server/src/app/api/v1/__init__.py index 37b1ddf..9713036 100644 --- a/server/src/app/api/v1/__init__.py +++ b/server/src/app/api/v1/__init__.py @@ -3,8 +3,10 @@ from fastapi import APIRouter from .knowledges import router as knowledge_router from .metrics import router as metric_router from .auth import router as auth_router +from .questions import router as question_router router = APIRouter(prefix="/v1") router.include_router(knowledge_router) +router.include_router(question_router) router.include_router(metric_router) router.include_router(auth_router) \ No newline at end of file diff --git a/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc b/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc index e682e78..51b78f3 100644 Binary files a/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc and b/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc differ diff --git a/server/src/app/api/v1/__pycache__/knowledges.cpython-311.pyc b/server/src/app/api/v1/__pycache__/knowledges.cpython-311.pyc index 5d042c4..c6e6378 100644 Binary files a/server/src/app/api/v1/__pycache__/knowledges.cpython-311.pyc and b/server/src/app/api/v1/__pycache__/knowledges.cpython-311.pyc differ diff --git a/server/src/app/api/v1/__pycache__/metrics.cpython-311.pyc b/server/src/app/api/v1/__pycache__/metrics.cpython-311.pyc index 06ec38f..702e857 100644 Binary files a/server/src/app/api/v1/__pycache__/metrics.cpython-311.pyc and b/server/src/app/api/v1/__pycache__/metrics.cpython-311.pyc differ diff --git a/server/src/app/api/v1/__pycache__/questions.cpython-311.pyc b/server/src/app/api/v1/__pycache__/questions.cpython-311.pyc new file mode 100644 index 0000000..2c3d0f4 Binary files /dev/null and b/server/src/app/api/v1/__pycache__/questions.cpython-311.pyc differ diff --git a/server/src/app/api/v1/knowledges.py b/server/src/app/api/v1/knowledges.py index 3ba5824..4649c69 100644 --- a/server/src/app/api/v1/knowledges.py +++ b/server/src/app/api/v1/knowledges.py @@ -44,7 +44,7 @@ def generate_questions(id: int, current_user: Annotated[str, Depends(get_current ) questions_raw = questions_generation(knowledge) for q in questions_raw: - question = Question(question = q, knowledge=knowledge) + question = Question(question = q, knowledge=knowledge, user=current_user) create_question(question) return questions_raw diff --git a/server/src/app/api/v1/metrics.py b/server/src/app/api/v1/metrics.py index de06eee..7a4ff9f 100644 --- a/server/src/app/api/v1/metrics.py +++ b/server/src/app/api/v1/metrics.py @@ -8,8 +8,8 @@ from src.app.auth.dependancies import get_current_user router = APIRouter(tags=["metrics"]) -@router.post("/metrics/") -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 +# @router.post("/metrics/") +# 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 diff --git a/server/src/app/api/v1/questions.py b/server/src/app/api/v1/questions.py new file mode 100644 index 0000000..a420f58 --- /dev/null +++ b/server/src/app/api/v1/questions.py @@ -0,0 +1,38 @@ +from typing import Annotated +from fastapi import APIRouter, Depends, HTTPException, status + +from src.app.auth.dependancies import get_current_user + +from src.app.models.question import Question +from src.app.models.metric import Metric, MetricCreate + +from src.app.data.question import get_question_by_id +from src.app.data.metric import get_metrics, create_metric + +#Added in __ini__ +router = APIRouter(tags=["questions"]) + +@router.get("/questions/{id}/metrics") +def read_questions(id: int, current_user: Annotated[str, Depends(get_current_user)]): + question: Question = get_question_by_id(id, current_user) + if not question: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Forbidden. The requested knowledge is not available for the provided ID.", + headers={"WWW-Authenticate": "Bearer"}, + ) + metrics = get_metrics(question) + return metrics + +@router.post("/questions/{id}/metrics") +def create(id: int, metric_data: MetricCreate, current_user: Annotated[str, Depends(get_current_user)]): + question: Question = get_question_by_id(id, current_user) + if not question: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Forbidden. The requested knowledge is not available for the provided ID.", + headers={"WWW-Authenticate": "Bearer"}, + ) + metric: Metric = Metric(question_id = id, need_index = metric_data.need_index, user = current_user) + created_metric: Metric = create_metric(metric) + return created_metric \ No newline at end of file diff --git a/server/src/app/data/__pycache__/question.cpython-311.pyc b/server/src/app/data/__pycache__/question.cpython-311.pyc index d424742..909e1be 100644 Binary files a/server/src/app/data/__pycache__/question.cpython-311.pyc and b/server/src/app/data/__pycache__/question.cpython-311.pyc differ diff --git a/server/src/app/data/question.py b/server/src/app/data/question.py index 382d12e..29a3d6c 100644 --- a/server/src/app/data/question.py +++ b/server/src/app/data/question.py @@ -1,13 +1,21 @@ from sqlmodel import Session, select from src.app.models.question import Question +from src.app.models.user import User from src.app.database import engine -def get_question_by_id(question_id: int): +def get_question_by_id(question_id: int, user: User): with Session(engine) as session: - question = session.get(Question, question_id) + statement = select(Question).where(Question.id == question_id, Question.user_id == user.id) + results = session.exec(statement) + question = results.first() return question +# 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) diff --git a/server/src/app/models/__pycache__/metric.cpython-311.pyc b/server/src/app/models/__pycache__/metric.cpython-311.pyc index 5ec8f65..455cd1f 100644 Binary files a/server/src/app/models/__pycache__/metric.cpython-311.pyc and b/server/src/app/models/__pycache__/metric.cpython-311.pyc differ diff --git a/server/src/app/models/metric.py b/server/src/app/models/metric.py index 42ba569..54289a6 100644 --- a/server/src/app/models/metric.py +++ b/server/src/app/models/metric.py @@ -15,5 +15,4 @@ class Metric(SQLModel, table=True): user: User | None = Relationship(back_populates="metrics") class MetricCreate(BaseModel): - question_id: int need_index: int \ No newline at end of file diff --git a/user-interface/src/App.vue b/user-interface/src/App.vue index f9f122b..8a3f7fc 100644 --- a/user-interface/src/App.vue +++ b/user-interface/src/App.vue @@ -21,14 +21,4 @@ import AppTopbar from '@/components/AppTopbar.vue' padding-inline: 5%; height: 100vh; } - -/* main { - height: 100%; -} - -@media screen and (min-width: 768px) { - #app { - height: 100vh; - } -} */ diff --git a/user-interface/src/components/CollectKnowledge.vue b/user-interface/src/components/CollectKnowledge.vue index 9ff87f6..a1c3976 100644 --- a/user-interface/src/components/CollectKnowledge.vue +++ b/user-interface/src/components/CollectKnowledge.vue @@ -1,11 +1,10 @@ @@ -80,7 +91,7 @@
- Share + Partager
diff --git a/user-interface/src/components/GenerateQuestion.vue b/user-interface/src/components/GenerateQuestion.vue index ca74258..58dddd8 100644 --- a/user-interface/src/components/GenerateQuestion.vue +++ b/user-interface/src/components/GenerateQuestion.vue @@ -3,10 +3,12 @@ import { BProgress } from "buefy"; import type { Knowledge } from "@/types/types"; - import api from "@/services/apiAxios"; + import api from "@/services/api"; import { useItemStore } from '@/stores/item' import { useStepStore } from '@/stores/step' + import { ProcessStep } from '@/services/knowledgeWorklow' + const stepStore = useStepStore() const itemStore = useItemStore() @@ -18,7 +20,8 @@ async function generateQuestions (knowledge: Knowledge) { await api.post(`api/v1/knowledges/${knowledge.id}/questions`) - stepStore.nextStep() + if(stepStore.indexStep == ProcessStep.WaitGeneration) + stepStore.nextStep() } diff --git a/user-interface/src/components/ReadKnowledges.vue b/user-interface/src/components/ReadKnowledges.vue index 99bc9ee..e9d1088 100644 --- a/user-interface/src/components/ReadKnowledges.vue +++ b/user-interface/src/components/ReadKnowledges.vue @@ -1,17 +1,148 @@ \ No newline at end of file diff --git a/user-interface/src/router/index.ts b/user-interface/src/router/index.ts index 852944f..0b88ba3 100644 --- a/user-interface/src/router/index.ts +++ b/user-interface/src/router/index.ts @@ -1,5 +1,5 @@ import { createRouter, createWebHistory } from 'vue-router' -import { isAuthenticated } from '@/services/apiAxios' +import { isAuthenticated } from '@/services/api' const pagesWithoutGuard = ['login', 'app', 'register'] @@ -33,9 +33,10 @@ const router = createRouter({ ], }) +// Guard system router.beforeEach(async (to, from) => { const isAuth = await isAuthenticated() - if (!isAuth && pagesWithoutGuard.includes(to.name!.toString())) { + if (!isAuth && !pagesWithoutGuard.includes(to.name!.toString())) { return { name: 'login' } } }) diff --git a/user-interface/src/services/api.ts b/user-interface/src/services/api.ts index f6045f2..4250a94 100644 --- a/user-interface/src/services/api.ts +++ b/user-interface/src/services/api.ts @@ -1,74 +1,45 @@ -class ApiClient { - private baseURL: string - private defaultHeaders: Record +import axios from "axios" +import type { AxiosResponse } from "axios"; - constructor(baseURL: string) { - this.baseURL = baseURL - this.defaultHeaders = { - 'Content-Type': 'application/json', +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 +}) - private async request(endpoint: string, options: RequestInit = {}): Promise { - const url = `${this.baseURL}${endpoint}` - const config: RequestInit = { - headers: { ...this.defaultHeaders, ...options.headers }, - ...options, - } - - let response +export const authAPI = { + register: (username: string, password: string) => + api.post( + '/api/v1/auth/register', + { "username":username, "plain_password":password } + ), + 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('/api/v1/auth/me') +} +export const isAuthenticated = async () => { try { - response = await fetch(url, config) - } catch (error) { - if (error instanceof Error) { - throw new HTTPError(`Network error: ${error.message}`) - } - throw new HTTPError('Unknown network error') + const response: AxiosResponse = await authAPI.getMe() + if (response.status==200) + return true + else + return false } - - if (response?.ok) { - return response.json() + catch{ + return false } - - const errorData = await response.json().catch(() => ({})) - throw new HTTPError( - errorData.message || `HTTP ${response.status}: ${response.statusText}`, - response.status, - response, - ) - } - - /** - * HTTP Get - * @param endpoint - * @returns - */ - - async get(endpoint: string): Promise { - return this.request(endpoint, { method: 'GET' }) - } - - /** - * HTTP Post - * @param endpoint - * @param data - * @returns - */ - async post(endpoint: string, data: unknown): Promise { - return this.request(endpoint, { method: 'POST', body: JSON.stringify(data) }) - } + } -class HTTPError extends Error { - constructor( - message: string, - private status: number | null = null, - private reponse: Response | null = null, - ) { - super(message) - this.name = 'HTTPError' - } -} - -export const apiClient = new ApiClient(import.meta.env.VITE_API_URL) \ No newline at end of file +export default api; \ No newline at end of file diff --git a/user-interface/src/services/apiAxios.ts b/user-interface/src/services/apiAxios.ts deleted file mode 100644 index 828d996..0000000 --- a/user-interface/src/services/apiAxios.ts +++ /dev/null @@ -1,45 +0,0 @@ -import axios from "axios" -import type { AxiosResponse } 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: (username: string, password: string) => - api.post( - '/api/v1/auth/register', - { "username":username, "plain_password":password } - ), - 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('/api/v1/auth/me') -} - -export const isAuthenticated = async () => { - try { - const response: AxiosResponse = await authAPI.getMe() - if (response.status==200) - return true - else - return false - } - catch{ - return false - } - -} - -export default api; \ No newline at end of file diff --git a/user-interface/src/services/knowledgeWorklow.ts b/user-interface/src/services/knowledgeWorklow.ts new file mode 100644 index 0000000..28aecb1 --- /dev/null +++ b/user-interface/src/services/knowledgeWorklow.ts @@ -0,0 +1,54 @@ +import type { Knowledge, Metric, Question } from "@/types/types" +import api from "@/services/api" +import type { AxiosResponse } from "axios" + +const processFlow = ["CollectKnowledge", "GenerateQuestion", "EvaluateQuestion", "Done"] + +//From user action in worlfow +export enum ProcessStep { + CollectKnowledge, + WaitGeneration, + EvaluateQuestion, + ProcessDone +} + +export async function identifyProcessStep(knowledge: Knowledge | null): Promise{ + if (knowledge == null) + return ProcessStep.CollectKnowledge + + //Check questions exist + const apiGetQuestions: AxiosResponse = await api.get( + `api/v1/knowledges/${knowledge.id}/questions/`, + { + validateStatus: function (status) { + return status < 500 + }, + }) + if (apiGetQuestions.status >= 400 && apiGetQuestions.status < 500) + return ProcessStep.WaitGeneration + + const questions: Question[] = apiGetQuestions.data + if (questions.length == 0) + return ProcessStep.WaitGeneration + + //Check metrics exist + const apiGetMetrics: AxiosResponse = await api.get( + `api/v1/questions/${questions[0]!.id}/metrics/`, + { + validateStatus: function (status) { + return status < 500 + }, + }) + if (apiGetMetrics.status >= 400 && apiGetMetrics.status < 500) + return ProcessStep.EvaluateQuestion + + const metrics: Metric[] = apiGetMetrics.data + if (metrics.length == 0) + return ProcessStep.EvaluateQuestion + + //Data is complete + return ProcessStep.ProcessDone + } + + + \ No newline at end of file diff --git a/user-interface/src/stores/step.ts b/user-interface/src/stores/step.ts index 503f363..e5aa3d4 100644 --- a/user-interface/src/stores/step.ts +++ b/user-interface/src/stores/step.ts @@ -5,6 +5,8 @@ import CollectKnowledge from '@/components/CollectKnowledge.vue' import EvaluateQuestion from '@/components/EvaluateQuestion.vue' import GenerateQuestion from '@/components/GenerateQuestion.vue' +import { ProcessStep } from '@/services/knowledgeWorklow' + const steps: Component = [ CollectKnowledge, GenerateQuestion, @@ -17,8 +19,15 @@ export const useStepStore = defineStore('step', () => { const getCurrentComponent = computed(() => steps[indexStep.value]) function nextStep() { - indexStep.value++ + if(indexStep.value + 1 < steps.length) + indexStep.value++ + else + indexStep.value = 0 } - return { steps, getCurrentComponent, nextStep } + function goToStep(processStep: ProcessStep){ + indexStep.value = processStep + } + + return { indexStep, steps, getCurrentComponent, nextStep, goToStep } }) diff --git a/user-interface/src/types/types.d.ts b/user-interface/src/types/types.d.ts index b0e2d65..dbdd002 100644 --- a/user-interface/src/types/types.d.ts +++ b/user-interface/src/types/types.d.ts @@ -18,9 +18,15 @@ interface Question { user: User } -interface MetricCreate { +interface Metric { + //id question_id: number, need_index: number + //user +} + +interface MetricCreate { + need_index: number } interface User { @@ -28,4 +34,4 @@ interface User { token: string } -export type {KnowledgeCreate, Knowledge, Question, MetricCreate} \ No newline at end of file +export type {KnowledgeCreate, Knowledge, Question, MetricCreate, Metric} \ No newline at end of file diff --git a/user-interface/src/views/LoginView.vue b/user-interface/src/views/LoginView.vue index fa380e4..8eeb529 100644 --- a/user-interface/src/views/LoginView.vue +++ b/user-interface/src/views/LoginView.vue @@ -2,7 +2,7 @@ import router from '@/router/index' import { BField, BInput, BButton, useToast } from "buefy"; import { ref } from "vue" - import { authAPI } from '@/services/apiAxios' + import { authAPI } from '@/services/api' const username = ref("") const password = ref("") diff --git a/user-interface/src/views/RegisterView.vue b/user-interface/src/views/RegisterView.vue index 57c593f..1b1d68e 100644 --- a/user-interface/src/views/RegisterView.vue +++ b/user-interface/src/views/RegisterView.vue @@ -2,7 +2,7 @@ import router from '@/router/index' import { BField, BInput, BButton, useToast } from "buefy"; import { ref } from "vue" - import { authAPI } from '@/services/apiAxios' + import { authAPI } from '@/services/api' const username = ref("") const password = ref("")