mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: init quizblock
This commit is contained in:
parent
eebaef6679
commit
23412ce19a
10 changed files with 263 additions and 6 deletions
|
|
@ -22,6 +22,7 @@ import { Save } from "lucide-react";
|
||||||
import MathEquationBlock from "./Extensions/MathEquation/MathEquationBlock";
|
import MathEquationBlock from "./Extensions/MathEquation/MathEquationBlock";
|
||||||
import PDFBlockComponent from "./Extensions/PDF/PDFBlockComponent";
|
import PDFBlockComponent from "./Extensions/PDF/PDFBlockComponent";
|
||||||
import PDFBlock from "./Extensions/PDF/PDFBlock";
|
import PDFBlock from "./Extensions/PDF/PDFBlock";
|
||||||
|
import QuizBlock from "./Extensions/Quiz/QuizBlock";
|
||||||
|
|
||||||
interface Editor {
|
interface Editor {
|
||||||
content: string;
|
content: string;
|
||||||
|
|
@ -65,6 +66,10 @@ function Editor(props: Editor) {
|
||||||
editable: true,
|
editable: true,
|
||||||
lecture: props.lecture,
|
lecture: props.lecture,
|
||||||
}),
|
}),
|
||||||
|
QuizBlock.configure({
|
||||||
|
editable: true,
|
||||||
|
lecture: props.lecture,
|
||||||
|
}),
|
||||||
Youtube.configure({
|
Youtube.configure({
|
||||||
controls: true,
|
controls: true,
|
||||||
modestBranding: true,
|
modestBranding: true,
|
||||||
|
|
@ -279,8 +284,8 @@ export const EditorContentWrapper = styled.div`
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
|
||||||
padding-top: 1px;
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
outline-style: none !important;
|
outline-style: none !important;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ function ImageBlockComponent(props: any) {
|
||||||
{fileObject && (
|
{fileObject && (
|
||||||
<BlockImage>
|
<BlockImage>
|
||||||
<img
|
<img
|
||||||
src={`${getBackendUrl()}content/uploads/files/pictures/${props.extension.options.lecture.lecture_id}/${fileObject.file_id}.${
|
src={`${getBackendUrl()}content/uploads/files/images/${props.extension.options.lecture.lecture_id}/${fileObject.file_id}.${
|
||||||
fileObject.file_format
|
fileObject.file_format
|
||||||
}`}
|
}`}
|
||||||
alt=""
|
alt=""
|
||||||
|
|
|
||||||
35
front/components/Editor/Extensions/Quiz/QuizBlock.ts
Normal file
35
front/components/Editor/Extensions/Quiz/QuizBlock.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { mergeAttributes, Node } from "@tiptap/core";
|
||||||
|
import { ReactNodeViewRenderer } from "@tiptap/react";
|
||||||
|
|
||||||
|
import QuizBlockComponent from "./QuizBlockComponent";
|
||||||
|
|
||||||
|
export default Node.create({
|
||||||
|
name: "blockQuiz",
|
||||||
|
group: "block",
|
||||||
|
|
||||||
|
atom: true,
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
quizId: {
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
tag: "block-quiz",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return ["block-quiz", mergeAttributes(HTMLAttributes), 0];
|
||||||
|
},
|
||||||
|
|
||||||
|
addNodeView() {
|
||||||
|
return ReactNodeViewRenderer(QuizBlockComponent);
|
||||||
|
},
|
||||||
|
});
|
||||||
164
front/components/Editor/Extensions/Quiz/QuizBlockComponent.tsx
Normal file
164
front/components/Editor/Extensions/Quiz/QuizBlockComponent.tsx
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
import { NodeViewWrapper } from "@tiptap/react";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { submitQuizBlock } from "@services/blocks/Quiz/quiz";
|
||||||
|
|
||||||
|
function ImageBlockComponent(props: any) {
|
||||||
|
const [questions, setQuestions] = React.useState([]) as any;
|
||||||
|
const [answers, setAnswers] = React.useState([]) as any;
|
||||||
|
|
||||||
|
function addSampleQuestion() {
|
||||||
|
setQuestions([
|
||||||
|
...questions,
|
||||||
|
{
|
||||||
|
question_id: "question_" + uuidv4(),
|
||||||
|
question_value: "",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
option_id: "option_" + uuidv4(),
|
||||||
|
option_data: "",
|
||||||
|
option_type: "text",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteQuestion = (index: number) => {
|
||||||
|
let modifiedQuestions = [...questions];
|
||||||
|
modifiedQuestions.splice(index, 1);
|
||||||
|
setQuestions(modifiedQuestions);
|
||||||
|
console.log(questions);
|
||||||
|
|
||||||
|
// remove the answers from the answers array
|
||||||
|
let modifiedAnswers = [...answers];
|
||||||
|
modifiedAnswers = modifiedAnswers.filter((answer: any) => answer.question_id !== questions[index].question_id);
|
||||||
|
setAnswers(modifiedAnswers);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onQuestionChange = (e: any, index: number) => {
|
||||||
|
let modifiedQuestions = [...questions];
|
||||||
|
modifiedQuestions[index].question_value = e.target.value;
|
||||||
|
setQuestions(modifiedQuestions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addOption = (question_id: string) => {
|
||||||
|
// find the question index from the question_id and add the option to that question index
|
||||||
|
let modifiedQuestions = [...questions];
|
||||||
|
let questionIndex = modifiedQuestions.findIndex((question: any) => question.question_id === question_id);
|
||||||
|
modifiedQuestions[questionIndex].options.push({
|
||||||
|
option_id: "option_" + uuidv4(),
|
||||||
|
option_data: "",
|
||||||
|
option_type: "text",
|
||||||
|
});
|
||||||
|
setQuestions(modifiedQuestions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteOption = (question_id: string, option_id: string) => {
|
||||||
|
// find the option index from the option_id and delete the option from that option index
|
||||||
|
let modifiedQuestions = [...questions];
|
||||||
|
let questionIndex = modifiedQuestions.findIndex((question: any) => question.question_id === question_id);
|
||||||
|
let optionIndex = modifiedQuestions[questionIndex].options.findIndex((option: any) => option.option_id === option_id);
|
||||||
|
modifiedQuestions[questionIndex].options.splice(optionIndex, 1);
|
||||||
|
setQuestions(modifiedQuestions);
|
||||||
|
|
||||||
|
// remove the answer from the answers array
|
||||||
|
let answerIndex = answers.findIndex((answer: any) => answer.option_id === option_id);
|
||||||
|
if (answerIndex !== -1) {
|
||||||
|
let modifiedAnswers = [...answers];
|
||||||
|
modifiedAnswers.splice(answerIndex, 1);
|
||||||
|
setAnswers(modifiedAnswers);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const markOptionAsCorrect = (question_id: string, option_id: string) => {
|
||||||
|
// find the option index from the option_id and mark the option as correct
|
||||||
|
let answer = {
|
||||||
|
question_id: question_id,
|
||||||
|
option_id: option_id,
|
||||||
|
};
|
||||||
|
setAnswers([...answers, answer]);
|
||||||
|
console.log(answers);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveQuiz = async () => {
|
||||||
|
// save the questions and answers to the backend
|
||||||
|
console.log("saving quiz");
|
||||||
|
console.log(questions);
|
||||||
|
console.log(answers);
|
||||||
|
try {
|
||||||
|
let res = await submitQuizBlock(props.extension.options.lecture.lecture_id, {questions : questions , answers : answers})
|
||||||
|
console.log(res.block_id);
|
||||||
|
props.updateAttributes({
|
||||||
|
quizId: {
|
||||||
|
value : res.block_id
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOptionChange = (e: any, questionIndex: number, optionIndex: number) => {
|
||||||
|
let modifiedQuestions = [...questions];
|
||||||
|
modifiedQuestions[questionIndex].options[optionIndex].option_data = e.target.value;
|
||||||
|
setQuestions(modifiedQuestions);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
// fetch the questions and options from the backend
|
||||||
|
console.log("fetching questions");
|
||||||
|
console.log(questions);
|
||||||
|
console.log(answers);
|
||||||
|
}, [questions, answers]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NodeViewWrapper className="block-quiz">
|
||||||
|
<QuizBlockWrapper>
|
||||||
|
Questions <button onClick={addSampleQuestion}>Add Question</button> <button onClick={() => saveQuiz()}>Save</button>
|
||||||
|
<hr />
|
||||||
|
{questions.map((question: any, qIndex: number) => (
|
||||||
|
<>
|
||||||
|
<div key={qIndex} style={{ marginTop: "10px" }}>
|
||||||
|
Question : <input type="text" value={question.question} onChange={(e) => onQuestionChange(e, qIndex)} />
|
||||||
|
<button onClick={() => deleteQuestion(qIndex)}>Delete</button>
|
||||||
|
</div>
|
||||||
|
Answers : <br />
|
||||||
|
{question.options.map((option: any, oIndex: number) => (
|
||||||
|
<>
|
||||||
|
<div key={oIndex}>
|
||||||
|
<input type="text" value={option.option_data} onChange={(e) => onOptionChange(e, qIndex, oIndex)} />
|
||||||
|
|
||||||
|
<button onClick={() => deleteOption(question.question_id, option.option_id)}>Delete</button>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
onChange={(e) =>
|
||||||
|
// check if checkbox is checked or not
|
||||||
|
// if checked then add the answer to the answers array
|
||||||
|
// if unchecked then remove the answer from the answers array
|
||||||
|
e.target.checked ? markOptionAsCorrect(question.question_id, option.option_id) : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
<button onClick={() => addOption(question.question_id)}>Add Option</button>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</QuizBlockWrapper>
|
||||||
|
</NodeViewWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QuizBlockWrapper = styled.div`
|
||||||
|
background-color: #0000001d;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 20px;
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
export default ImageBlockComponent;
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { FontBoldIcon, FontItalicIcon, StrikethroughIcon, ArrowLeftIcon, ArrowRightIcon, OpacityIcon } from "@radix-ui/react-icons";
|
import { FontBoldIcon, FontItalicIcon, StrikethroughIcon, ArrowLeftIcon, ArrowRightIcon, OpacityIcon } from "@radix-ui/react-icons";
|
||||||
import { AlertCircle, AlertTriangle, FileText, ImagePlus, Info, Sigma, Video, Youtube } from "lucide-react";
|
import { AlertCircle, AlertTriangle, FileText, GraduationCap, ImagePlus, Info, Sigma, Video, Youtube } from "lucide-react";
|
||||||
|
|
||||||
export const ToolbarButtons = ({ editor }: any) => {
|
export const ToolbarButtons = ({ editor, props }: any) => {
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -116,6 +116,19 @@ export const ToolbarButtons = ({ editor }: any) => {
|
||||||
>
|
>
|
||||||
<FileText size={15} />
|
<FileText size={15} />
|
||||||
</ToolBtn>
|
</ToolBtn>
|
||||||
|
<ToolBtn
|
||||||
|
onClick={() =>
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.focus()
|
||||||
|
.insertContent({
|
||||||
|
type: "blockQuiz",
|
||||||
|
})
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<GraduationCap size={15} />
|
||||||
|
</ToolBtn>
|
||||||
</ToolButtonsWrapper>
|
</ToolButtonsWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
27
front/package-lock.json
generated
27
front/package-lock.json
generated
|
|
@ -26,6 +26,7 @@
|
||||||
"react-katex": "^3.0.1",
|
"react-katex": "^3.0.1",
|
||||||
"styled-components": "^6.0.0-beta.9",
|
"styled-components": "^6.0.0-beta.9",
|
||||||
"swr": "^2.0.1",
|
"swr": "^2.0.1",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
"y-indexeddb": "^9.0.9",
|
"y-indexeddb": "^9.0.9",
|
||||||
"y-webrtc": "^10.2.3",
|
"y-webrtc": "^10.2.3",
|
||||||
"yjs": "^13.5.42"
|
"yjs": "^13.5.42"
|
||||||
|
|
@ -37,6 +38,7 @@
|
||||||
"@types/react-dom": "18.0.6",
|
"@types/react-dom": "18.0.6",
|
||||||
"@types/react-katex": "^3.0.0",
|
"@types/react-katex": "^3.0.0",
|
||||||
"@types/styled-components": "^5.1.26",
|
"@types/styled-components": "^5.1.26",
|
||||||
|
"@types/uuid": "^9.0.0",
|
||||||
"autoprefixer": "^10.4.12",
|
"autoprefixer": "^10.4.12",
|
||||||
"eslint": "8.23.1",
|
"eslint": "8.23.1",
|
||||||
"eslint-config-next": "^13.0.6",
|
"eslint-config-next": "^13.0.6",
|
||||||
|
|
@ -3058,6 +3060,12 @@
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/uuid": {
|
||||||
|
"version": "9.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz",
|
||||||
|
"integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "5.46.1",
|
"version": "5.46.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz",
|
||||||
|
|
@ -7088,6 +7096,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "9.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||||
|
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/w3c-keyname": {
|
"node_modules/w3c-keyname": {
|
||||||
"version": "2.2.6",
|
"version": "2.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
|
||||||
|
|
@ -9309,6 +9325,12 @@
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/uuid": {
|
||||||
|
"version": "9.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz",
|
||||||
|
"integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@typescript-eslint/parser": {
|
"@typescript-eslint/parser": {
|
||||||
"version": "5.46.1",
|
"version": "5.46.1",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.46.1.tgz",
|
||||||
|
|
@ -12128,6 +12150,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||||
},
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "9.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||||
|
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
||||||
|
},
|
||||||
"w3c-keyname": {
|
"w3c-keyname": {
|
||||||
"version": "2.2.6",
|
"version": "2.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
"react-katex": "^3.0.1",
|
"react-katex": "^3.0.1",
|
||||||
"styled-components": "^6.0.0-beta.9",
|
"styled-components": "^6.0.0-beta.9",
|
||||||
"swr": "^2.0.1",
|
"swr": "^2.0.1",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
"y-indexeddb": "^9.0.9",
|
"y-indexeddb": "^9.0.9",
|
||||||
"y-webrtc": "^10.2.3",
|
"y-webrtc": "^10.2.3",
|
||||||
"yjs": "^13.5.42"
|
"yjs": "^13.5.42"
|
||||||
|
|
@ -38,6 +39,7 @@
|
||||||
"@types/react-dom": "18.0.6",
|
"@types/react-dom": "18.0.6",
|
||||||
"@types/react-katex": "^3.0.0",
|
"@types/react-katex": "^3.0.0",
|
||||||
"@types/styled-components": "^5.1.26",
|
"@types/styled-components": "^5.1.26",
|
||||||
|
"@types/uuid": "^9.0.0",
|
||||||
"autoprefixer": "^10.4.12",
|
"autoprefixer": "^10.4.12",
|
||||||
"eslint": "8.23.1",
|
"eslint": "8.23.1",
|
||||||
"eslint-config-next": "^13.0.6",
|
"eslint-config-next": "^13.0.6",
|
||||||
|
|
|
||||||
10
front/services/blocks/Quiz/quiz.ts
Normal file
10
front/services/blocks/Quiz/quiz.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { getAPIUrl } from "@services/config";
|
||||||
|
import { RequestBody, RequestBodyForm } from "@services/utils/requests";
|
||||||
|
|
||||||
|
|
||||||
|
export async function submitQuizBlock(lecture_id: string, data: any) {
|
||||||
|
const result: any = await fetch(`${getAPIUrl()}blocks/quiz/${lecture_id}"`, RequestBody("POST", data))
|
||||||
|
.then((result) => result.json())
|
||||||
|
.catch((error) => console.log("error", error));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
@ -56,7 +56,7 @@ async def api_get_document_file_block(request: Request, file_id: str, current_us
|
||||||
return await get_document_file(request, file_id, current_user)
|
return await get_document_file(request, file_id, current_user)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/quiz")
|
@router.post("/quiz/{lecture_id}")
|
||||||
async def api_create_quiz_block(request: Request, quiz_block: quizBlock, lecture_id: str, current_user: PublicUser = Depends(get_current_user)):
|
async def api_create_quiz_block(request: Request, quiz_block: quizBlock, lecture_id: str, current_user: PublicUser = Depends(get_current_user)):
|
||||||
"""
|
"""
|
||||||
Create new document file
|
Create new document file
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class answer(BaseModel):
|
||||||
|
|
||||||
class question(BaseModel):
|
class question(BaseModel):
|
||||||
question_id: str
|
question_id: str
|
||||||
|
question_value:str
|
||||||
options: List[option]
|
options: List[option]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue