diff --git a/src/controllers/form.ts b/src/controllers/form.ts index 10fe5ac..d568272 100644 --- a/src/controllers/form.ts +++ b/src/controllers/form.ts @@ -23,7 +23,20 @@ import { getDBForm, getDBFormsByUser, submitDBAnswer, + getDBFormQuestions, } from '../db' +import { + validateCreateFormParameters, + validateSubmitAnswerParameters, +} from './validate' + +const formatQuestions = ( + choisesQuestions: (ChoisesQuestion & { + variants: Variant[] + })[], + inputQuestions: InputQuestion[] +) => + [...choisesQuestions, ...inputQuestions].sort((a, b) => a.number - b.number) const getForm = async ( db: PrismaClient, @@ -39,15 +52,19 @@ const getForm = async ( author: dbForm.author, dateCreated: dbForm.dateCreated.toString(), id: dbForm.id, - questions: [...dbForm.choisesQuestions, ...dbForm.inputQuestions].sort( - (a, b) => a.number - b.number + questions: formatQuestions( + dbForm.choisesQuestions, + dbForm.inputQuestions ), - submissions: dbForm.submissions.map((submission) => ({ - user: submission.user, - answers: submission.answers, - date: submission.date.toString(), - id: submission.id, - })), + submissions: + dbForm.submissions.length == 0 + ? null + : dbForm.submissions.map((submission) => ({ + user: submission.user, + answers: submission.answers, + date: submission.date.toString(), + id: submission.id, + })), title: dbForm.title, } @@ -93,6 +110,8 @@ const createFormFrom = async ( try { const parsedQuestions = JSON.parse(params.questions) + await validateCreateFormParameters(params.title, parsedQuestions) + const newForm: FormConstructor = { choisesQuestions: { create: parsedQuestions.flatMap( @@ -139,9 +158,22 @@ const submitAnswer = async ( userId: number ): Promise => { try { + const form = await getDBFormQuestions(db, formId) + if (!form) throw new UserInputError("Can't submit form") + + console.log(formatQuestions(form.choisesQuestions, form.inputQuestions)) + + form.submissions.forEach((submission) => { + if (submission.userId === userId) + throw new UserInputError("Can't submit same form more than once") + }) + const parsedAnswers = JSON.parse(answers) - console.log(parsedAnswers) + await validateSubmitAnswerParameters( + parsedAnswers, + formatQuestions(form.choisesQuestions, form.inputQuestions) + ) const res = await submitDBAnswer(db, userId, formId, parsedAnswers) @@ -165,9 +197,7 @@ const formatForms = ( forms.map((form) => ({ dateCreated: form.dateCreated.toString(), id: form.id, - questions: [...form.choisesQuestions, ...form.inputQuestions].sort( - (a, b) => a.number - b.number - ), + questions: formatQuestions(form.choisesQuestions, form.inputQuestions), submissions: form.submissions.map((submission) => ({ answers: submission.answers, date: submission.date.toString(), diff --git a/src/controllers/validate.ts b/src/controllers/validate.ts new file mode 100644 index 0000000..b65ed5a --- /dev/null +++ b/src/controllers/validate.ts @@ -0,0 +1,127 @@ +'use strict' +import { UserInputError } from 'apollo-server-express' +import { Answer } from '@prisma/client' +import { + UploadedChoisesQuestion, + UploadedInputQuestion, + UploadedQuestion, +} from './types' +import { ChoisesQuestion, InputQuestion, Variant } from 'typeDefs/typeDefs.gen' + +const choisesVariants = ['CHECK', 'CHOOSE', 'SELECT'] + +const validateCreateFormParameters = async ( + title: string, + questions: UploadedQuestion[] +) => { + if (!title) + throw new UserInputError("Form title can't be empty", { + invalidArgs: ['title'], + }) + + questions.forEach( + (question: UploadedChoisesQuestion | UploadedInputQuestion) => { + if (!question.title) + throw new UserInputError("Question title can't be empty", { + invalidArgs: ['questions'], + }) + + if ('type' in question) { + if (!question.variants || question.variants.length < 1) + throw new UserInputError( + 'Question with choises must have at least one answer variant', + { invalidArgs: ['questions'] } + ) + + question.variants.forEach((variant) => { + if (!variant.text || variant.text.length < 1) + throw new UserInputError("Choises variant text can't be empty", { + invalidArgs: ['questions'], + }) + }) + + if (!choisesVariants.includes(question.type)) + throw new UserInputError( + 'Question with choises must be of one of supported types', + { invalidArgs: ['questions'] } + ) + } + } + ) +} + +const validateSubmitAnswerParameters = async ( + answers: Answer[], + questions: ( + | (ChoisesQuestion & { + variants: Variant[] + }) + | InputQuestion + )[] +) => { + questions.forEach((question, questionIndex) => { + const answer = answers[questionIndex] + + if (!answer) + throw new UserInputError('Every required question must have answer', { + invalidArgs: ['answers'], + }) + + if (!answer.type) + throw new UserInputError('Type must be specified for answer', { + invalidArgs: ['answers'], + }) + + if (answer.type !== 'CHOISE' && answer.type !== 'INPUT') + throw new UserInputError('Answer must have supported type', { + invalidArgs: ['answers'], + }) + + if (answer.type === 'CHOISE' && !('type' in question)) + throw new UserInputError( + `Answer ${questionIndex + 1} must be of 'INPUT' type`, + { + invalidArgs: ['answers'], + } + ) + + if (answer.type === 'INPUT' && 'type' in question) + throw new UserInputError( + `Answer ${questionIndex + 1} must be of 'CHOISE' type`, + { + invalidArgs: ['answers'], + } + ) + + if (answer.type === 'CHOISE' && answer.userChoise === null) + throw new UserInputError( + "Question of type 'CHOISE' must have choise number set", + { + invalidArgs: ['answers'], + } + ) + + if (answer.type === 'INPUT' && answer.userInput === null) + throw new UserInputError( + "Question of type 'INPUT' must have input string", + { + invalidArgs: ['answers'], + } + ) + + if ( + answer.userChoise !== null && + (question as ChoisesQuestion).variants && + answer.userChoise > (question as ChoisesQuestion).variants.length - 1 + ) + throw new UserInputError( + "Can't have chosen number bigger than amount of variants: " + + (question as ChoisesQuestion).variants.length, + { + invalidArgs: ['answers'], + } + ) + }) +} + +export { validateCreateFormParameters, validateSubmitAnswerParameters } diff --git a/src/db/index.ts b/src/db/index.ts index 5be26b7..a7644c4 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -176,6 +176,26 @@ const submitDBAnswer = ( }, }) +const getDBFormQuestions = async (db: PrismaClient, formId: number) => + db.form.findOne({ + where: { + id: formId, + }, + select: { + choisesQuestions: { + include: { + variants: true, + }, + }, + inputQuestions: true, + submissions: { + select: { + userId: true, + }, + }, + }, + }) + export { createDBForm, createDBUser, @@ -184,4 +204,5 @@ export { getDBFormAuthor, getDBFormsByUser, submitDBAnswer, + getDBFormQuestions, }