Added submission list filter based on authentificated user. Sorted object parameters and imports

This commit is contained in:
Dmitriy Shishkov 2020-10-10 17:13:51 +05:00
parent a07e0d752f
commit 4f82c80922
13 changed files with 225 additions and 169 deletions

View File

@ -1,17 +1,16 @@
import { PrismaClient } from '@prisma/client' import jwt from 'jsonwebtoken'
import { import {
ApolloError, ApolloError,
AuthenticationError, AuthenticationError,
ForbiddenError ForbiddenError
} from 'apollo-server-express' } from 'apollo-server-express'
import jwt from 'jsonwebtoken' import { CheckRightsAndResolve } from './types'
import { getDBFormAuthor } from '../db'
import { PrismaClient } from '@prisma/client'
import { sendToken } from '../mailer'
require('dotenv').config() require('dotenv').config()
import { getDBFormAuthor } from '../db'
import { sendToken } from '../mailer'
import { CheckRightsAndResolve } from './types'
const checkRightsAndResolve: CheckRightsAndResolve = async (params) => { const checkRightsAndResolve: CheckRightsAndResolve = async (params) => {
const { user, expected, controller } = params const { user, expected, controller } = params
@ -36,8 +35,8 @@ const getFormAuthor = async (db: PrismaClient, id: number) => {
const tokenGenerate = (email: string, id: number) => { const tokenGenerate = (email: string, id: number) => {
return jwt.sign({ email, id }, '' + process.env.JWT_SECRET, { return jwt.sign({ email, id }, '' + process.env.JWT_SECRET, {
expiresIn: '7 days', algorithm: 'HS256',
algorithm: 'HS256' expiresIn: '7 days'
}) })
} }
@ -52,4 +51,4 @@ const sendTokenEmail = async (
if (res[0].statusCode != 202) return new ApolloError("Couldn't send email") if (res[0].statusCode != 202) return new ApolloError("Couldn't send email")
} }
export { checkRightsAndResolve, getFormAuthor, tokenGenerate, sendTokenEmail } export { checkRightsAndResolve, getFormAuthor, sendTokenEmail, tokenGenerate }

View File

@ -1,8 +1,5 @@
import { PrismaClient, Answer } from '@prisma/client' import { Answer, PrismaClient } from '@prisma/client'
import { ApolloError } from 'apollo-server-express' import { ApolloError } from 'apollo-server-express'
import { createDBForm, getDBForm, getDBFormByUser, submitDBAnswer } from '../db'
import { FullForm } from '../db/types'
import { import {
ChoisesQuestion, ChoisesQuestion,
Form as GraphqlForm, Form as GraphqlForm,
@ -12,44 +9,55 @@ import {
MutationFormSubmitArgs, MutationFormSubmitArgs,
Question Question
} from '../typeDefs/typeDefs.gen' } from '../typeDefs/typeDefs.gen'
import { createChoises, newForm } from './types' import { createChoises, newForm } from './types'
import {
createDBForm,
getDBForm,
getDBFormsByUser,
submitDBAnswer
} from '../db'
const getForm = async (db: PrismaClient, id: number) => { const getForm = async (
const dbForm: FullForm = await getDBForm(db, id) db: PrismaClient,
id: number,
user: { requesterId: number; userId: number }
) => {
const dbForm = await getDBForm(db, id, user)
if (dbForm == null) throw new ApolloError('Not found') if (dbForm == null) throw new ApolloError('Not found')
const form: GraphqlForm = { const form: GraphqlForm = {
id: dbForm.id, author: dbForm.author,
title: dbForm.title,
questions: [...dbForm.choisesQuestions, ...dbForm.inputQuestions],
dateCreated: dbForm.dateCreated.toString(), dateCreated: dbForm.dateCreated.toString(),
submissions: dbForm.submissions.map<FormSubmission>((submission) => ({ id: dbForm.id,
questions: [...dbForm.choisesQuestions, ...dbForm.inputQuestions],
submissions: user.requesterId
? dbForm.submissions.map((submission) => ({
answers: submission.answers, answers: submission.answers,
date: submission.date.toString(), date: submission.date.toString(),
id: submission.id id: submission.id
})), }))
author: dbForm.author : undefined,
title: dbForm.title
} }
return form return form
} }
const getForms = async (db: PrismaClient, userId: number) => { const getForms = async (db: PrismaClient, userId: number) => {
const dbForms = await getDBFormByUser(db, userId) const dbForms = await getDBFormsByUser(db, userId)
const forms = [ const forms = [
...dbForms.map((form) => ({ ...dbForms.map((form) => ({
id: form.id,
title: form.title,
questions: [...form.choisesQuestions, ...form.inputQuestions],
dateCreated: form.dateCreated.toString(), dateCreated: form.dateCreated.toString(),
id: form.id,
questions: [...form.choisesQuestions, ...form.inputQuestions],
submissions: form.submissions.map<FormSubmission>((submission) => ({ submissions: form.submissions.map<FormSubmission>((submission) => ({
answers: submission.answers, answers: submission.answers,
date: submission.date.toString(), date: submission.date.toString(),
id: submission.id id: submission.id
})) })),
title: form.title
})) }))
] ]
@ -63,7 +71,28 @@ const createFormFrom = async (
) => { ) => {
const parsedQuestions = <Question[]>JSON.parse(params.questions) const parsedQuestions = <Question[]>JSON.parse(params.questions)
const newForm: newForm = { const newForm: newForm = {
title: params.title, choisesQuestions: {
create: parsedQuestions.flatMap<createChoises>(
(val: InputQuestion | ChoisesQuestion, index) => {
if ('type' in val) {
return [
{
number: index,
title: val.title,
type: val.type,
variants: {
create: val.variants
}
}
]
}
{
return []
}
}
)
},
inputQuestions: { inputQuestions: {
create: parsedQuestions.filter( create: parsedQuestions.filter(
(val: InputQuestion | ChoisesQuestion, index) => { (val: InputQuestion | ChoisesQuestion, index) => {
@ -75,25 +104,7 @@ const createFormFrom = async (
} }
) )
}, },
choisesQuestions: { title: params.title
create: parsedQuestions.flatMap<createChoises>(
(val: InputQuestion | ChoisesQuestion, index) => {
if ('type' in val) {
return [
{
number: index,
title: val.title,
type: val.type,
variants: { create: val.variants }
}
]
}
{
return []
}
}
)
}
} }
return createDBForm(db, newForm, id) return createDBForm(db, newForm, id)
@ -109,4 +120,4 @@ const submitAnswer = async (
return submitDBAnswer(db, userId, formId, parsedAnswers) return submitDBAnswer(db, userId, formId, parsedAnswers)
} }
export { getForm, getForms, createFormFrom, submitAnswer } export { createFormFrom, getForm, getForms, submitAnswer }

View File

@ -1,13 +1,14 @@
import { getForm, getForms, createFormFrom } from './form'
import { checkRightsAndResolve, getFormAuthor, sendTokenEmail } from './auth' import { checkRightsAndResolve, getFormAuthor, sendTokenEmail } from './auth'
import { createFormFrom, getForm, getForms, submitAnswer } from './form'
import { findUserBy } from './user' import { findUserBy } from './user'
export { export {
checkRightsAndResolve, checkRightsAndResolve,
createFormFrom,
findUserBy,
getForm, getForm,
getFormAuthor, getFormAuthor,
getForms, getForms,
createFormFrom,
sendTokenEmail, sendTokenEmail,
findUserBy submitAnswer
} }

View File

@ -1,8 +1,5 @@
import { AnswerType } from '@prisma/client'
import { import {
Answer,
ChoisesQuestion, ChoisesQuestion,
FormSubmission,
InputQuestion, InputQuestion,
Variant Variant
} from '../typeDefs/typeDefs.gen' } from '../typeDefs/typeDefs.gen'
@ -14,9 +11,9 @@ type expectedType = {
} }
interface ICheckRightsAndResolve<T> { interface ICheckRightsAndResolve<T> {
user: JwtPayloadType | null
expected: expectedType
controller: T controller: T
expected: expectedType
user: JwtPayloadType | null
} }
type CheckRightsAndResolve = <ReturnType, ControllerType extends Function>( type CheckRightsAndResolve = <ReturnType, ControllerType extends Function>(
@ -24,15 +21,15 @@ type CheckRightsAndResolve = <ReturnType, ControllerType extends Function>(
) => Promise<ReturnType> ) => Promise<ReturnType>
type newForm = { type newForm = {
title: string
choisesQuestions: { choisesQuestions: {
create: createChoises[] create: createChoises[]
} }
inputQuestions: { create: InputQuestion[] } inputQuestions: { create: InputQuestion[] }
title: string
} }
type createChoises = Omit<ChoisesQuestion, 'variants'> & { type createChoises = Omit<ChoisesQuestion, 'variants'> & {
variants: { create: Variant[] } variants: { create: Variant[] }
} }
export { CheckRightsAndResolve, newForm, createChoises } export { CheckRightsAndResolve, createChoises, newForm }

View File

@ -1,9 +1,8 @@
import { PrismaClient } from '@prisma/client'
import { UserInputError } from 'apollo-server-express'
import { createDBUser, findDBUserBy } from '../db' import { createDBUser, findDBUserBy } from '../db'
import { IFindUserParams } from '../db/types' import { IFindUserParams } from '../db/types'
import { MutationRegisterArgs, User } from '../typeDefs/typeDefs.gen' import { MutationRegisterArgs, User } from '../typeDefs/typeDefs.gen'
import { PrismaClient } from '@prisma/client'
import { UserInputError } from 'apollo-server-express'
const createUser = async ( const createUser = async (
db: PrismaClient, db: PrismaClient,

View File

@ -1,20 +1,34 @@
import { PrismaClient } from '@prisma/client'
import { UserInputError } from 'apollo-server-express'
import { newForm } from '../controllers/types'
import { Answer, MutationRegisterArgs } from '../typeDefs/typeDefs.gen' import { Answer, MutationRegisterArgs } from '../typeDefs/typeDefs.gen'
import { IFindUserParams } from './types' import { IFindUserParams } from './types'
import { newForm } from '../controllers/types'
import { PrismaClient } from '@prisma/client'
import { UserInputError } from 'apollo-server-express'
const getDBForm = async (db: PrismaClient, id: number) => { /**
* Get form from DataBase
*
* @async
* @param db {PrismaClient} Prisma client object
* @param formId {number} Form ID
* @param getSubmissions {boolean} Set to true if want to also get form submissions
* @example
* const form = await getDBForm(db, id, true)
*/
const getDBForm = async (
db: PrismaClient,
formId: number,
user?: {
requesterId: number
userId: number
}
) => {
return await db.form.findOne({ return await db.form.findOne({
where: {
id
},
include: { include: {
author: { author: {
select: { select: {
email: true,
id: true, id: true,
name: true, name: true
email: true
} }
}, },
choisesQuestions: { choisesQuestions: {
@ -26,19 +40,32 @@ const getDBForm = async (db: PrismaClient, id: number) => {
submissions: { submissions: {
include: { include: {
answers: true answers: true
},
where:
user?.requesterId != user?.userId
? {
user: {
id: user?.requesterId
} }
} }
: undefined
}
},
where: {
id: formId
} }
}) })
} }
const getDBFormByUser = async (db: PrismaClient, id: number) => { /**
* Get all forms of user
* @param db {PrismaClient} Prisma client object
* @param id {number} User ID
* @example
* const forms = await getDBFormsByUser(db, userId)
*/
const getDBFormsByUser = async (db: PrismaClient, id: number) => {
return await db.form.findMany({ return await db.form.findMany({
where: {
author: {
id
}
},
include: { include: {
choisesQuestions: { choisesQuestions: {
include: { include: {
@ -51,21 +78,26 @@ const getDBFormByUser = async (db: PrismaClient, id: number) => {
answers: true answers: true
} }
} }
},
where: {
author: {
id
}
} }
}) })
} }
const getDBFormAuthor = async (db: PrismaClient, id: number) => { const getDBFormAuthor = async (db: PrismaClient, id: number) => {
return await db.form.findOne({ return await db.form.findOne({
where: {
id
},
select: { select: {
author: { author: {
select: { select: {
id: true id: true
} }
} }
},
where: {
id
} }
}) })
} }
@ -97,10 +129,12 @@ const createDBForm = async (
) => { ) => {
return await db.form.create({ return await db.form.create({
data: { data: {
author: { connect: { id } }, author: {
title, connect: { id }
},
choisesQuestions, choisesQuestions,
inputQuestions inputQuestions,
title
} }
}) })
} }
@ -113,17 +147,19 @@ const submitDBAnswer = async (
) => { ) => {
const res = await db.formSubmission.create({ const res = await db.formSubmission.create({
data: { data: {
user: { answers: {
connect: { create: formAnswers
id: userId
}
}, },
Form: { Form: {
connect: { connect: {
id: formId id: formId
} }
}, },
answers: { create: formAnswers } user: {
connect: {
id: userId
}
}
} }
}) })
@ -133,11 +169,11 @@ const submitDBAnswer = async (
} }
export { export {
getDBForm, createDBForm,
getDBFormByUser,
getDBFormAuthor,
createDBUser, createDBUser,
findDBUserBy, findDBUserBy,
createDBForm, getDBForm,
getDBFormAuthor,
getDBFormsByUser,
submitDBAnswer submitDBAnswer
} }

View File

@ -1,5 +1,5 @@
import { PromiseReturnType } from '@prisma/client'
import { getDBForm } from '../db' import { getDBForm } from '../db'
import { PromiseReturnType } from '@prisma/client'
type FullForm = PromiseReturnType<typeof getDBForm> type FullForm = PromiseReturnType<typeof getDBForm>

View File

@ -1,40 +1,43 @@
import { ApolloServer, makeExecutableSchema } from 'apollo-server-express'
import express from 'express' import express from 'express'
import expressJwt from 'express-jwt' import expressJwt from 'express-jwt'
import resolvers from './resolvers'
import typeDefs from './typeDefs'
import { ApolloContextType, JwtPayloadType } from './types'
import { ApolloServer, makeExecutableSchema } from 'apollo-server-express'
import { PrismaClient } from '@prisma/client' import { PrismaClient } from '@prisma/client'
require('dotenv').config() require('dotenv').config()
import typeDefs from './typeDefs'
import resolvers from './resolvers'
import { ApolloContextType, JwtPayloadType } from './types'
const app = express() const app = express()
app.use( app.use(
expressJwt({ expressJwt({
secret: '' + process.env.JWT_SECRET, algorithms: ['HS256'],
credentialsRequired: false, credentialsRequired: false,
algorithms: ['HS256'] secret: '' + process.env.JWT_SECRET
}) })
) )
const server = new ApolloServer({ const server = new ApolloServer({
schema: makeExecutableSchema({
typeDefs,
resolvers
}),
context: async ({ context: async ({
req req
}: { }: {
req: Request & { user: JwtPayloadType } req: Request & {
user: JwtPayloadType
}
}): Promise<ApolloContextType> => { }): Promise<ApolloContextType> => {
const db = new PrismaClient() const db = new PrismaClient()
const user = req.user || null const user = req.user || null
return {
return { db, user } db,
user
}
}, },
debug: false debug: false,
schema: makeExecutableSchema({
resolvers,
typeDefs
})
}) })
server.applyMiddleware({ app }) server.applyMiddleware({ app })

View File

@ -1,20 +1,20 @@
import sgMail from "@sendgrid/mail" import sgMail from '@sendgrid/mail'
require("dotenv").config() require('dotenv').config()
sgMail.setApiKey(process.env.SENDGRID_API_KEY!) sgMail.setApiKey(process.env.SENDGRID_API_KEY!)
const sendToken = (username: string, email: string, token: string) => { const sendToken = (username: string, email: string, token: string) => {
return sgMail.send({ return sgMail.send({
from: "me@dmitriy.icu",
to: email,
subject: "Login link",
templateId: "d-a9275a4437bf4dd2b9e858f3a57f85d5",
dynamicTemplateData: { dynamicTemplateData: {
username: username,
siteUrl: process.env.SITE_URL, siteUrl: process.env.SITE_URL,
token: token, token: token,
username: username
}, },
from: 'me@dmitriy.icu',
subject: 'Login link',
templateId: 'd-a9275a4437bf4dd2b9e858f3a57f85d5',
to: email
}) })
} }

View File

@ -1,22 +1,22 @@
import { import {
checkRightsAndResolve, AnswerResolvers,
getForm,
getFormAuthor,
getForms,
createFormFrom
} from '../controllers'
import { submitAnswer } from '../controllers/form'
import {
Form, Form,
MutationCreateFormArgs,
MutationFormSubmitArgs,
QueryFormArgs, QueryFormArgs,
QuestionResolvers, QuestionResolvers,
Resolver, Resolver,
AnswerResolvers, ServerAnswer
MutationCreateFormArgs,
ServerAnswer,
MutationFormSubmitArgs
} from '../typeDefs/typeDefs.gen' } from '../typeDefs/typeDefs.gen'
import { ApolloContextType } from '../types' import { ApolloContextType } from '../types'
import {
checkRightsAndResolve,
createFormFrom,
getForm,
getFormAuthor,
getForms,
submitAnswer
} from '../controllers'
const formQuery: Resolver<Form, {}, ApolloContextType, QueryFormArgs> = async ( const formQuery: Resolver<Form, {}, ApolloContextType, QueryFormArgs> = async (
_, _,
@ -24,17 +24,18 @@ const formQuery: Resolver<Form, {}, ApolloContextType, QueryFormArgs> = async (
{ db, user } { db, user }
) => { ) => {
try { try {
const authorId = await getFormAuthor(db, id) const ownerId = await getFormAuthor(db, id)
const getFormById = () => getForm(db, id) const getFormById = (userId: number) =>
getForm(db, id, { requesterId: userId, userId: ownerId })
return await checkRightsAndResolve({ return await checkRightsAndResolve({
user, controller: getFormById,
expected: { expected: {
id: 0, id: 0,
self: false self: true
}, },
controller: getFormById user
}) })
} catch (err) { } catch (err) {
return err return err
@ -53,12 +54,12 @@ const formsQuery: Resolver<Form[], {}, ApolloContextType> = async (
Form[], Form[],
(userId: number) => Promise<Form[] | null> (userId: number) => Promise<Form[] | null>
>({ >({
user, controller: getFormsByUserId,
expected: { expected: {
id: 0, id: 0,
self: true self: true
}, },
controller: getFormsByUserId user
}) })
} catch (err) { } catch (err) {
return err return err
@ -74,9 +75,12 @@ const createForm: Resolver<
const createNewForm = (id: number) => createFormFrom(db, params, id) const createNewForm = (id: number) => createFormFrom(db, params, id)
return await checkRightsAndResolve({ return await checkRightsAndResolve({
user, controller: createNewForm,
expected: { id: 0, self: true }, expected: {
controller: createNewForm id: 0,
self: true
},
user
}) })
} }
@ -89,9 +93,12 @@ const formSubmit: Resolver<
const submitNewAnswer = (userId: number) => submitAnswer(db, params, userId) const submitNewAnswer = (userId: number) => submitAnswer(db, params, userId)
return await checkRightsAndResolve({ return await checkRightsAndResolve({
user, controller: submitNewAnswer,
expected: { id: 0, self: true }, expected: {
controller: submitNewAnswer id: 0,
self: true
},
user
}) })
} }
@ -114,10 +121,10 @@ const AnswerResolver: AnswerResolvers = {
} }
export { export {
formQuery,
formsQuery,
QuestionResolver,
AnswerResolver, AnswerResolver,
createForm, createForm,
formSubmit formQuery,
formsQuery,
formSubmit,
QuestionResolver
} }

View File

@ -58,9 +58,12 @@ const userResolver: Resolver<
try { try {
return await checkRightsAndResolve({ return await checkRightsAndResolve({
user, controller: findUserById,
expected: { id: id || 0, self: true }, expected: {
controller: findUserById id: id || 0,
self: true
},
user
}) })
} catch (err) { } catch (err) {
return err return err

View File

@ -1,5 +1,5 @@
import { gql } from 'apollo-server-express'
import fs from 'fs' import fs from 'fs'
import { gql } from 'apollo-server-express'
const typeDefs = gql( const typeDefs = gql(
fs.readFileSync(__dirname.concat('/typeDefs.gql'), { encoding: 'utf-8' }) fs.readFileSync(__dirname.concat('/typeDefs.gql'), { encoding: 'utf-8' })

View File

@ -1,35 +1,35 @@
type Query { type Query {
forms: [Form!]!
form(id: Int!): Form form(id: Int!): Form
forms: [Form!]!
user(id: Int): User user(id: Int): User
} }
type Mutation { type Mutation {
login(email: String!): serverAnswer
register(name: String!, email: String!): serverAnswer
createForm(title: String!, questions: String!): Form! createForm(title: String!, questions: String!): Form!
formSubmit(formId: Int!, answers: String!): serverAnswer formSubmit(formId: Int!, answers: String!): serverAnswer
login(email: String!): serverAnswer
register(name: String!, email: String!): serverAnswer
} }
type Form { type Form {
id: Int!
title: String!
questions: [Question!]!
submissions: [FormSubmission!]!
dateCreated: String!
author: User author: User
dateCreated: String!
id: Int!
questions: [Question!]!
submissions: [FormSubmission!]
title: String!
} }
interface Question { interface Question {
title: String!
number: Int! number: Int!
title: String!
} }
type ChoisesQuestion implements Question { type ChoisesQuestion implements Question {
title: String!
variants: [Variant!]!
type: ChoiseType!
number: Int! number: Int!
title: String!
type: ChoiseType!
variants: [Variant!]!
} }
type Variant { type Variant {
@ -37,14 +37,14 @@ type Variant {
} }
type InputQuestion implements Question { type InputQuestion implements Question {
title: String!
number: Int! number: Int!
title: String!
} }
type FormSubmission { type FormSubmission {
id: Int!
answers: [Answer!]! answers: [Answer!]!
date: String! date: String!
id: Int!
} }
interface Answer { interface Answer {
@ -62,21 +62,21 @@ type ChoiseAnswer implements Answer {
} }
enum ChoiseType { enum ChoiseType {
SELECT
CHECK CHECK
CHOOSE CHOOSE
SELECT
} }
enum AnswerType { enum AnswerType {
INPUT
CHOISE CHOISE
INPUT
} }
type User { type User {
name: String!
email: String! email: String!
id: Int!
forms: [Form!] forms: [Form!]
id: Int!
name: String!
} }
type serverAnswer { type serverAnswer {