diff --git a/src/client/src/quiz/EditProblem.jsx b/src/client/src/quiz/EditProblem.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..c1b2258c4849129dfe3ac1877283aa15d202cc75
--- /dev/null
+++ b/src/client/src/quiz/EditProblem.jsx
@@ -0,0 +1,11 @@
+import React from 'react'
+
+function EditProblem() {
+ return (
+
+
+
+ )
+}
+
+export default EditProblem
diff --git a/src/client/src/quiz/NewProblem.jsx b/src/client/src/quiz/NewProblem.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..185516904f18f8372057d2e0fecf618850021ed7
--- /dev/null
+++ b/src/client/src/quiz/NewProblem.jsx
@@ -0,0 +1,76 @@
+import React, { useState } from "react";
+import Button from "react-bootstrap/Button";
+import Form from "react-bootstrap/Form";
+import Col from "react-bootstrap/Col";
+
+function NewProblem({ addProblem }) {
+ const [answers, setAnswers] = useState([""]);
+ const [question, setQuestion] = useState("");
+
+ const addAnswer = () => {
+ setAnswers([...answers, ""]);
+ };
+
+ const removeAnswer = (index) => {
+ const list = [...answers];
+ list.splice(index, 1);
+ setAnswers(list);
+ };
+
+ const handleAnswer = (event, index) => {
+ const { value } = event.target;
+ const list = [...answers];
+ list[index] = value;
+ setAnswers(list);
+ };
+
+ const handleQuestion = (event) => {
+ setQuestion(event.target.value);
+ };
+
+ const clickAdd = (event) => {
+ event.preventDefault();
+ addProblem({ question, answers });
+ };
+
+ return (
+
+
+ Question
+
+
+ Answers
+ {answers.map((answer, index) => {
+ return (
+
+
+ handleAnswer(event, index)}
+ />
+
+
+ {answers.length !== 1 && (
+
+ )}
+ {answers.length - 1 === index && (
+
+ )}
+
+
+ );
+ })}
+
+
+
+ );
+}
+
+export default NewProblem;
diff --git a/src/client/src/quiz/NewQuiz.jsx b/src/client/src/quiz/NewQuiz.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..e924138849264f276f63fb3de26dbae7889ea8d8
--- /dev/null
+++ b/src/client/src/quiz/NewQuiz.jsx
@@ -0,0 +1,53 @@
+import React, { useState } from "react";
+import Button from "react-bootstrap/Button";
+import authHelpers from "../auth/auth-helpers";
+import { create } from "./api-quiz";
+import NewProblem from "./NewProblem";
+import Problem from "./Problem";
+
+function NewQuiz() {
+
+ const [problems, setProblems] = useState([])
+
+ const jwt = authHelpers.isAuthenticated();
+
+ const addProblem = (problem) => {
+ console.log(problem)
+ setProblems([...problems, problem])
+ }
+
+ const clickSubmit = (event) => {
+ event.preventDefault();
+
+ const quizData = {
+ problems
+ }
+
+ create({ userId: jwt.user._id }, { t: jwt.token }, quizData).then(
+ (data) => {
+ if (data.error) {
+ console.log(data.error);
+ } else {
+ console.log(data);
+ }
+ }
+ );
+ };
+
+ return (
+
+
+ Quiz List
+
+ {
+ problems.map((problem, index) => {
+ return
+ })
+ }
+
+
+
+ );
+}
+
+export default NewQuiz;
diff --git a/src/client/src/quiz/Problem.jsx b/src/client/src/quiz/Problem.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..761106b70080d88cc97f4b7d8f570fef7e7db6a7
--- /dev/null
+++ b/src/client/src/quiz/Problem.jsx
@@ -0,0 +1,23 @@
+import React from "react";
+import Card from "react-bootstrap/Card";
+import Button from "react-bootstrap/Button";
+
+function Problem({ problem, number }) {
+ return (
+
+
+
+ {number}번. {problem.question}
+
+ Answers
+ {problem.answers.map((answer, index) => {
+ return {answer};
+ })}
+
+
+
+
+ );
+}
+
+export default Problem;
diff --git a/src/client/src/quiz/Problems.jsx b/src/client/src/quiz/Problems.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..8cf0cc4c05ba5e34b70bd0710ac69b95351b6c93
--- /dev/null
+++ b/src/client/src/quiz/Problems.jsx
@@ -0,0 +1,11 @@
+import React from 'react'
+
+function Problems() {
+ return (
+
+
+
+ )
+}
+
+export default Problems
diff --git a/src/client/src/quiz/Quiz.jsx b/src/client/src/quiz/Quiz.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..be938346f8798ca86de270bd4c086813db00f643
--- /dev/null
+++ b/src/client/src/quiz/Quiz.jsx
@@ -0,0 +1,38 @@
+import React, { useState, useEffect } from "react";
+import { useParams } from "react-router-dom";
+import { read } from "./api-quiz";
+import auth from "../auth/auth-helpers";
+import Problem from './Problem'
+
+function Quiz() {
+ const { quizId } = useParams();
+ const [quiz, setQuiz] = useState({});
+
+ const jwt = auth.isAuthenticated();
+
+ useEffect(() => {
+ const abortController = new AbortController();
+ const signal = abortController.signal;
+
+ read({ quizId: quizId }, { t: jwt.token }, signal).then((data) => {
+ if (data.error) {
+ console.log(data.error);
+ } else {
+ setQuiz(data);
+ }
+ });
+ return () => {
+ abortController.abort();
+ };
+ }, [quizId]);
+
+ return (
+
+ {quiz.problems?.map((problem, i) => {
+ return
;
+ })}
+
+ );
+}
+
+export default Quiz;
diff --git a/src/client/src/quiz/api-quiz.js b/src/client/src/quiz/api-quiz.js
new file mode 100644
index 0000000000000000000000000000000000000000..1074aea411f32a80ae171d015578512b0d5ff31b
--- /dev/null
+++ b/src/client/src/quiz/api-quiz.js
@@ -0,0 +1,39 @@
+
+const create = async (params, credentials, quiz) => {
+ try {
+ let response = await fetch('/api/quiz/' + params.userId, {
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + credentials.t,
+ },
+ body: JSON.stringify(quiz),
+ })
+ return await response.json()
+ } catch (error) {
+ console.log(error)
+ }
+}
+
+const read = async (params, credentials, signal) => {
+ try {
+ let response = await fetch('/api/quiz/' + params.quizId, {
+ method: 'GET',
+ signal: signal,
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': 'Bearer ' + credentials.t,
+ },
+ })
+ return await response.json()
+ } catch (error) {
+ console.log(error)
+ }
+}
+
+export {
+ create,
+ read,
+}
\ No newline at end of file
diff --git a/src/server/quiz/answer.model.js b/src/server/quiz/answer.model.js
index 42708a6921e41013fe1c8abf2e45b21ab50f4fe9..0d68365db54a27f35b0aa308fe96e0edcf33e219 100644
--- a/src/server/quiz/answer.model.js
+++ b/src/server/quiz/answer.model.js
@@ -1,13 +1,13 @@
import mongoose from 'mongoose'
const AnswerSchema = new mongoose.Schema({
- questionId: {
+ problemId: {
type: mongoose.Schema.Types.ObjectId,
- ref: 'Question',
+ ref: 'Problem',
},
type: String, // single/multiple choice?
score: Number, // 맞힌 점수
- points: Number, // Question.score와 자동 연결 필요
+ points: Number, // Problem.score와 자동 연결 필요
content: String, // 기록한 답
})
diff --git a/src/server/quiz/question.model.js b/src/server/quiz/problem.model.js
similarity index 68%
rename from src/server/quiz/question.model.js
rename to src/server/quiz/problem.model.js
index c96d25ecb0c07743661a3a14cf540924627725ee..b9b4ea517469585ff9dc8ac226c28c2a039cdb02 100644
--- a/src/server/quiz/question.model.js
+++ b/src/server/quiz/problem.model.js
@@ -1,6 +1,6 @@
import mongoose from 'mongoose'
-const QuestionSchema = new mongoose.Schema({
+const ProblemSchema = new mongoose.Schema({
type: String, // 객관식, 주관식 single/multiple choice
created: {
type: Date,
@@ -10,12 +10,12 @@ const QuestionSchema = new mongoose.Schema({
level: String,
category: [String],
score: Number, //문제당 할당 점수
- content: String, //질문
- choices: [String], // 선택형 항목
+ question: String, //질문
+ answers: [String], // 선택형 항목
correct: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Answer'
}, // 정답; Answer Model 객체
})
-export default mongoose.model('Question', QuestionSchema)
\ No newline at end of file
+export default mongoose.model('Problem', ProblemSchema)
\ No newline at end of file
diff --git a/src/server/quiz/quiz.controller.js b/src/server/quiz/quiz.controller.js
index 90c968b42fb96d7fb6e9d147633f6d8329db39f5..8603f3c23da388cd6bb97be0cd3272565378a538 100644
--- a/src/server/quiz/quiz.controller.js
+++ b/src/server/quiz/quiz.controller.js
@@ -1,35 +1,75 @@
import formidable from 'formidable'
import fs from 'fs'
-import quizModel from './quiz.model.js'
+import Problem from './problem.model.js'
+import Quiz from './quiz.model.js'
const create = async (req, res) => {
- const form = new formidable.IncomingForm()
- form.keepExtensions = true
- form.parse(req, async (err, fields, files) => {
- if (err) {
- return res.status(400).json({
- error: 'Image could not be uploaded'
- })
+ try {
+ const { problems } = req.body
+
+ const quiz = new Quiz()
+ // console.log('quiz in quiz.controller:', quiz);
+
+ for await (let problem of problems) {
+ // console.log('problem in quiz.controller:', problem);
+ const p = new Problem(problem)
+ // console.log('problem in quiz.controller:', p);
+ await p.save()
+ quiz.problems.push(p._id)
}
- const quiz = new quizModel(fields)
quiz.author = req.profile
- if (files.image) {
- quiz.image.data = fs.readFileSync(files.image.path)
- quiz.image.contentType = files.image.type
- }
- try {
- const result = await quiz.save()
- res.json(result)
- } catch (error) {
+ // console.log('quiz in quiz.controller:', quiz);
+ await quiz.save()
+ quiz.author.hashedPassword = undefined
+ quiz.author.salt = undefined
+ res.json(quiz)
+ } catch (error) {
+ return res.status(400).json({
+ error: 'Quiz save DB error' + error
+ })
+ }
+}
+
+const isAuthor = (req, res, next) => {
+ const isAuthor = req.auth && req.quiz && req.auth._id == req.quiz.author._id
+ if (!isAuthor) {
+ return res.status(403).json({
+ error: 'User is not an author'
+ })
+ }
+ next()
+}
+
+const read = async (req, res) => {
+ let quiz = req.quiz
+ res.json(quiz)
+}
+
+const quizById = async (req, res, next, id) => {
+ try {
+ const quiz = await Quiz.findById(id)
+ .populate('author', '_id name')
+ .populate('problems')
+ .exec()
+ if (!quiz) {
return res.status(400).json({
- error: 'Quiz save db error'
+ error: 'Quiz not found'
})
}
- })
+ req.quiz = quiz
+ next()
+ } catch (error) {
+ return res.status(400).json({
+ error: 'Quiz by id query db error: ' + error
+ })
+ }
}
export default {
create,
+ read,
+ isAuthor,
+ quizById,
}
\ No newline at end of file
diff --git a/src/server/quiz/quiz.model.js b/src/server/quiz/quiz.model.js
index f766dec78024617ee5bd87090b86221d0c3c4a23..903970dbb38721c343040b61a9998e84d96a71d2 100644
--- a/src/server/quiz/quiz.model.js
+++ b/src/server/quiz/quiz.model.js
@@ -16,7 +16,10 @@ const QuizSchema = new mongoose.Schema({
publishedAt: Date,
startAt: Date,
endAt: Date,
- questions: [], // Question Schemas
+ problems: [{
+ type: mongoose.SchemaTypes.ObjectId,
+ ref: 'Problem'
+ }], // Problem Schemas
image: {
type: Buffer,
contentType: String,
diff --git a/src/server/quiz/quiz.routes.js b/src/server/quiz/quiz.routes.js
index 61dbcd141d2eba2a021836baae69019c960e3df5..8a6b1b820827af681a25703804a1678513de5988 100644
--- a/src/server/quiz/quiz.routes.js
+++ b/src/server/quiz/quiz.routes.js
@@ -7,7 +7,11 @@ const router = express.Router()
router.route('/api/quiz/:userId')
.post(authCtrl.requireSignin, authCtrl.hasAuthorization, userCtrl.isInstructor, quizCtrl.create)
+
+router.route('/api/quiz/:quizId')
+ .get(authCtrl.requireSignin, quizCtrl.isAuthor, quizCtrl.read)
router.param('userId', userCtrl.userById)
+router.param('quizId', quizCtrl.quizById)
export default router
\ No newline at end of file
diff --git a/src/server/user/user.controller.js b/src/server/user/user.controller.js
index 8f319230cdf52154b74ca7fb680005a60d84f134..4982f2123e0a8a34c4283737720c2ffae62f1928 100644
--- a/src/server/user/user.controller.js
+++ b/src/server/user/user.controller.js
@@ -89,6 +89,7 @@ const isInstructor = (req, res, next) => {
}
const userById = async (req, res, next, id) => {
+ // console.log('req.body in userById', req.body);
try {
let user = await User.findById(id)
.exec()