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 {
ApolloError,
AuthenticationError,
ForbiddenError
} 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()
import { getDBFormAuthor } from '../db'
import { sendToken } from '../mailer'
import { CheckRightsAndResolve } from './types'
const checkRightsAndResolve: CheckRightsAndResolve = async (params) => {
const { user, expected, controller } = params
@ -36,8 +35,8 @@ const getFormAuthor = async (db: PrismaClient, id: number) => {
const tokenGenerate = (email: string, id: number) => {
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")
}
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 { createDBForm, getDBForm, getDBFormByUser, submitDBAnswer } from '../db'
import { FullForm } from '../db/types'
import {
ChoisesQuestion,
Form as GraphqlForm,
@ -12,44 +9,55 @@ import {
MutationFormSubmitArgs,
Question
} from '../typeDefs/typeDefs.gen'
import { createChoises, newForm } from './types'
import {
createDBForm,
getDBForm,
getDBFormsByUser,
submitDBAnswer
} from '../db'
const getForm = async (db: PrismaClient, id: number) => {
const dbForm: FullForm = await getDBForm(db, id)
const getForm = async (
db: PrismaClient,
id: number,
user: { requesterId: number; userId: number }
) => {
const dbForm = await getDBForm(db, id, user)
if (dbForm == null) throw new ApolloError('Not found')
const form: GraphqlForm = {
id: dbForm.id,
title: dbForm.title,
questions: [...dbForm.choisesQuestions, ...dbForm.inputQuestions],
author: dbForm.author,
dateCreated: dbForm.dateCreated.toString(),
submissions: dbForm.submissions.map<FormSubmission>((submission) => ({
answers: submission.answers,
date: submission.date.toString(),
id: submission.id
})),
author: dbForm.author
id: dbForm.id,
questions: [...dbForm.choisesQuestions, ...dbForm.inputQuestions],
submissions: user.requesterId
? dbForm.submissions.map((submission) => ({
answers: submission.answers,
date: submission.date.toString(),
id: submission.id
}))
: undefined,
title: dbForm.title
}
return form
}
const getForms = async (db: PrismaClient, userId: number) => {
const dbForms = await getDBFormByUser(db, userId)
const dbForms = await getDBFormsByUser(db, userId)
const forms = [
...dbForms.map((form) => ({
id: form.id,
title: form.title,
questions: [...form.choisesQuestions, ...form.inputQuestions],
dateCreated: form.dateCreated.toString(),
id: form.id,
questions: [...form.choisesQuestions, ...form.inputQuestions],
submissions: form.submissions.map<FormSubmission>((submission) => ({
answers: submission.answers,
date: submission.date.toString(),
id: submission.id
}))
})),
title: form.title
}))
]
@ -63,7 +71,28 @@ const createFormFrom = async (
) => {
const parsedQuestions = <Question[]>JSON.parse(params.questions)
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: {
create: parsedQuestions.filter(
(val: InputQuestion | ChoisesQuestion, index) => {
@ -75,25 +104,7 @@ const createFormFrom = async (
}
)
},
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 []
}
}
)
}
title: params.title
}
return createDBForm(db, newForm, id)
@ -109,4 +120,4 @@ const submitAnswer = async (
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 { createFormFrom, getForm, getForms, submitAnswer } from './form'
import { findUserBy } from './user'
export {
checkRightsAndResolve,
createFormFrom,
findUserBy,
getForm,
getFormAuthor,
getForms,
createFormFrom,
sendTokenEmail,
findUserBy
submitAnswer
}

View File

@ -1,8 +1,5 @@
import { AnswerType } from '@prisma/client'
import {
Answer,
ChoisesQuestion,
FormSubmission,
InputQuestion,
Variant
} from '../typeDefs/typeDefs.gen'
@ -14,9 +11,9 @@ type expectedType = {
}
interface ICheckRightsAndResolve<T> {
user: JwtPayloadType | null
expected: expectedType
controller: T
expected: expectedType
user: JwtPayloadType | null
}
type CheckRightsAndResolve = <ReturnType, ControllerType extends Function>(
@ -24,15 +21,15 @@ type CheckRightsAndResolve = <ReturnType, ControllerType extends Function>(
) => Promise<ReturnType>
type newForm = {
title: string
choisesQuestions: {
create: createChoises[]
}
inputQuestions: { create: InputQuestion[] }
title: string
}
type createChoises = Omit<ChoisesQuestion, 'variants'> & {
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 { IFindUserParams } from '../db/types'
import { MutationRegisterArgs, User } from '../typeDefs/typeDefs.gen'
import { PrismaClient } from '@prisma/client'
import { UserInputError } from 'apollo-server-express'
const createUser = async (
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 { 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({
where: {
id
},
include: {
author: {
select: {
email: true,
id: true,
name: true,
email: true
name: true
}
},
choisesQuestions: {
@ -26,19 +40,32 @@ const getDBForm = async (db: PrismaClient, id: number) => {
submissions: {
include: {
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({
where: {
author: {
id
}
},
include: {
choisesQuestions: {
include: {
@ -51,21 +78,26 @@ const getDBFormByUser = async (db: PrismaClient, id: number) => {
answers: true
}
}
},
where: {
author: {
id
}
}
})
}
const getDBFormAuthor = async (db: PrismaClient, id: number) => {
return await db.form.findOne({
where: {
id
},
select: {
author: {
select: {
id: true
}
}
},
where: {
id
}
})
}
@ -97,10 +129,12 @@ const createDBForm = async (
) => {
return await db.form.create({
data: {
author: { connect: { id } },
title,
author: {
connect: { id }
},
choisesQuestions,
inputQuestions
inputQuestions,
title
}
})
}
@ -113,17 +147,19 @@ const submitDBAnswer = async (
) => {
const res = await db.formSubmission.create({
data: {
user: {
connect: {
id: userId
}
answers: {
create: formAnswers
},
Form: {
connect: {
id: formId
}
},
answers: { create: formAnswers }
user: {
connect: {
id: userId
}
}
}
})
@ -133,11 +169,11 @@ const submitDBAnswer = async (
}
export {
getDBForm,
getDBFormByUser,
getDBFormAuthor,
createDBForm,
createDBUser,
findDBUserBy,
createDBForm,
getDBForm,
getDBFormAuthor,
getDBFormsByUser,
submitDBAnswer
}

View File

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

View File

@ -1,40 +1,43 @@
import { ApolloServer, makeExecutableSchema } from 'apollo-server-express'
import express from 'express'
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'
require('dotenv').config()
import typeDefs from './typeDefs'
import resolvers from './resolvers'
import { ApolloContextType, JwtPayloadType } from './types'
const app = express()
app.use(
expressJwt({
secret: '' + process.env.JWT_SECRET,
algorithms: ['HS256'],
credentialsRequired: false,
algorithms: ['HS256']
secret: '' + process.env.JWT_SECRET
})
)
const server = new ApolloServer({
schema: makeExecutableSchema({
typeDefs,
resolvers
}),
context: async ({
req
}: {
req: Request & { user: JwtPayloadType }
req: Request & {
user: JwtPayloadType
}
}): Promise<ApolloContextType> => {
const db = new PrismaClient()
const user = req.user || null
return { db, user }
return {
db,
user
}
},
debug: false
debug: false,
schema: makeExecutableSchema({
resolvers,
typeDefs
})
})
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!)
const sendToken = (username: string, email: string, token: string) => {
return sgMail.send({
from: "me@dmitriy.icu",
to: email,
subject: "Login link",
templateId: "d-a9275a4437bf4dd2b9e858f3a57f85d5",
dynamicTemplateData: {
username: username,
siteUrl: process.env.SITE_URL,
token: token,
username: username
},
from: 'me@dmitriy.icu',
subject: 'Login link',
templateId: 'd-a9275a4437bf4dd2b9e858f3a57f85d5',
to: email
})
}

View File

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

View File

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

View File

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

View File

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