Added forms property fetching to user query and some minor fixes

This commit is contained in:
Dmitriy Shishkov 2020-10-18 22:09:40 +05:00
parent 01676c59e4
commit 673f42fd0f
16 changed files with 167 additions and 102 deletions

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
/node_modules
/build
/dist
*.gen.ts

View File

@ -1,5 +1,5 @@
overwrite: true
schema: "src/typeDefs/typeDefs.gql"
schema: 'src/typeDefs/typeDefs.gql'
documents: null
generates:
src/typeDefs/typeDefs.gen.ts:
@ -8,5 +8,5 @@ generates:
wrapFieldDefinitions: true
enumsAsTypes: true
plugins:
- "typescript"
- "typescript-resolvers"
- 'typescript'
- 'typescript-resolvers'

View File

@ -3,4 +3,4 @@
"watch": ["src"],
"exec": "yarn start",
"ext": "ts"
}
}

View File

@ -2,6 +2,7 @@
"name": "backend",
"version": "1.0.0",
"main": "src/index.ts",
"private": "true",
"license": "MIT",
"dependencies": {
"@prisma/client": "^2.7.1",
@ -17,8 +18,11 @@
"scripts": {
"dev": "nodemon",
"start": "ts-node src/index.ts",
"copy-assets": "cp src/typeDefs/typeDefs.gql dist/typeDefs/typeDefs.gql && cp .env.example dist/.env && vi dist/.env",
"build": "tsc && npm copy-assets",
"codegen": "graphql-codegen --config codegen.yml",
"lint": "eslint"
"lint": "eslint",
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"@graphql-codegen/cli": "1.17.10",

View File

@ -2,7 +2,7 @@ import jwt from 'jsonwebtoken'
import {
ApolloError,
AuthenticationError,
ForbiddenError
ForbiddenError,
} from 'apollo-server-express'
import { PrismaClient } from '@prisma/client'
@ -37,7 +37,7 @@ const getFormAuthor = async (db: PrismaClient, id: number) => {
const tokenGenerate = (email: string, id: number) => {
return jwt.sign({ email, id }, '' + process.env.JWT_SECRET, {
algorithm: 'HS256',
expiresIn: '7 days'
expiresIn: '7 days',
})
}

View File

@ -1,33 +1,35 @@
import { Answer as DbAnswer, PrismaClient } from '@prisma/client'
import { Answer as DbAnswer, PrismaClient, Form } from '@prisma/client'
import { ApolloError, UserInputError } from 'apollo-server-express'
import {
Form,
ChoisesQuestion,
Form as GraphqlForm,
FormSubmission,
InputQuestion,
MutationCreateFormArgs,
MutationFormSubmitArgs,
ServerAnswer
ServerAnswer,
Variant,
} from '../typeDefs/typeDefs.gen'
import {
CreateChoises,
FormConstructor,
UploadedChoisesQuestion,
UploadedInputQuestion,
UploadedQuestion
UploadedQuestion,
} from './types'
import {
createDBForm,
getDBForm,
getDBFormsByUser,
submitDBAnswer
submitDBAnswer,
} from '../db'
const getForm = async (
db: PrismaClient,
id: number,
user: { requesterId: number; userId: number }
): Promise<Form> => {
): Promise<GraphqlForm> => {
try {
const dbForm = await getDBForm(db, id, user)
@ -41,9 +43,9 @@ const getForm = async (
submissions: dbForm.submissions.map((submission) => ({
answers: submission.answers,
date: submission.date.toString(),
id: submission.id
id: submission.id,
})),
title: dbForm.title
title: dbForm.title,
}
return form
@ -52,22 +54,25 @@ const getForm = async (
}
}
const getForms = async (db: PrismaClient, userId: number): Promise<Form[]> => {
const getForms = async (
db: PrismaClient,
userId: number
): Promise<GraphqlForm[]> => {
try {
const dbForms = await getDBFormsByUser(db, userId)
if (!dbForms) throw new ApolloError("Couldn't load forms", 'FETCHINGERROR')
const forms: Form[] = dbForms.map((form) => ({
const forms: GraphqlForm[] = dbForms.map((form) => ({
dateCreated: form.dateCreated.toString(),
id: form.id,
questions: [...form.choisesQuestions, ...form.inputQuestions],
submissions: form.submissions.map((submission) => ({
answers: submission.answers,
date: submission.date.toString(),
id: submission.id
id: submission.id,
})),
title: form.title
title: form.title,
}))
return forms
@ -95,12 +100,12 @@ const createFormFrom = async (
title: uQuestion.title,
type: uQuestion.type,
variants: {
create: uQuestion.variants
}
}
create: uQuestion.variants,
},
},
]
: []
)
),
},
inputQuestions: {
create: parsedQuestions.flatMap<InputQuestion>(
@ -108,9 +113,9 @@ const createFormFrom = async (
!('type' in uQuestion)
? [{ number: index, title: uQuestion.title }]
: []
)
),
},
title: params.title
title: params.title,
}
const res = await createDBForm(db, newForm, id)
@ -142,4 +147,25 @@ const submitAnswer = async (
}
}
export { createFormFrom, getForm, getForms, submitAnswer }
const formatForms = (
forms: (Form & {
choisesQuestions: (ChoisesQuestion & {
variants: Variant[]
})[]
inputQuestions: InputQuestion[]
submissions: (Omit<FormSubmission, 'date'> & { date: Date })[]
})[]
) =>
forms.map((form) => ({
dateCreated: form.dateCreated.toString(),
id: form.id,
questions: [...form.choisesQuestions, ...form.inputQuestions],
submissions: form.submissions.map((submission) => ({
answers: submission.answers,
date: submission.date.toString(),
id: submission.id,
})),
title: form.title,
}))
export { createFormFrom, getForm, getForms, submitAnswer, formatForms }

View File

@ -10,5 +10,5 @@ export {
getForm,
getFormAuthor,
getForms,
submitAnswer
submitAnswer,
}

View File

@ -9,12 +9,12 @@ const sendToken = (username: string, email: string, token: string) => {
dynamicTemplateData: {
siteUrl: process.env.SITE_URL,
token: token,
username: username
username: username,
},
from: 'me@dmitriy.icu',
subject: 'Login link',
templateId: 'd-a9275a4437bf4dd2b9e858f3a57f85d5',
to: email
to: email,
})
}

View File

@ -2,7 +2,7 @@ import { ChoiseType } from '@prisma/client'
import {
ChoisesQuestion,
InputQuestion,
Variant
Variant,
} from '../typeDefs/typeDefs.gen'
import { JwtPayloadType } from '../types'
@ -48,5 +48,5 @@ export {
FormConstructor,
UploadedChoisesQuestion,
UploadedInputQuestion,
UploadedQuestion
UploadedQuestion,
}

View File

@ -3,6 +3,7 @@ import { IFindUserParams } from '../db/types'
import { MutationRegisterArgs, User } from '../typeDefs/typeDefs.gen'
import { PrismaClient } from '@prisma/client'
import { ApolloError, UserInputError } from 'apollo-server-express'
import { formatForms } from './form'
const createUser = async (
db: PrismaClient,
@ -31,9 +32,14 @@ const findUserBy = async (
params: IFindUserParams
): Promise<User> => {
try {
const user = await findDBUserBy(db, params)
const dbUser = await findDBUserBy(db, params)
if (!user) throw new UserInputError('No such user')
if (!dbUser) throw new UserInputError('No such user')
const user = {
...dbUser,
forms: formatForms(dbUser.forms),
}
return user
} catch (err) {

View File

@ -19,7 +19,7 @@ const getDBForm = (
formId: number,
{
requesterId,
userId
userId,
}: {
requesterId: number
userId: number
@ -31,32 +31,32 @@ const getDBForm = (
select: {
email: true,
id: true,
name: true
}
name: true,
},
},
choisesQuestions: {
include: {
variants: true
}
variants: true,
},
},
inputQuestions: true,
submissions: {
include: {
answers: true
answers: true,
},
where:
requesterId != userId
? {
user: {
id: requesterId
}
id: requesterId,
},
}
: undefined
}
: undefined,
},
},
where: {
id: formId
}
id: formId,
},
})
/**
@ -71,21 +71,21 @@ const getDBFormsByUser = (db: PrismaClient, id: number) =>
include: {
choisesQuestions: {
include: {
variants: true
}
variants: true,
},
},
inputQuestions: true,
submissions: {
include: {
answers: true
}
}
answers: true,
},
},
},
where: {
author: {
id
}
}
id,
},
},
})
const getDBFormAuthor = (db: PrismaClient, id: number) =>
@ -93,13 +93,13 @@ const getDBFormAuthor = (db: PrismaClient, id: number) =>
select: {
author: {
select: {
id: true
}
}
id: true,
},
},
},
where: {
id
}
id,
},
})
const createDBUser = (
@ -107,24 +107,46 @@ const createDBUser = (
{ email, name }: MutationRegisterArgs
) =>
db.user.create({
data: { email, name }
data: { email, name },
})
const findDBUserBy = (db: PrismaClient, params: IFindUserParams) =>
db.user.findOne({
where: {
...params
}
...params,
},
include: {
forms: {
include: {
choisesQuestions: {
include: {
variants: true,
},
},
inputQuestions: true,
submissions: {
include: {
answers: true,
},
},
},
},
formsSubmissions: {
include: {
answers: true,
},
},
},
})
const createDBForm = (db: PrismaClient, form: FormConstructor, id: number) =>
db.form.create({
data: {
author: {
connect: { id }
connect: { id },
},
...form
}
...form,
},
})
const submitDBAnswer = (
@ -136,19 +158,19 @@ const submitDBAnswer = (
db.formSubmission.create({
data: {
answers: {
create: formAnswers
create: formAnswers,
},
Form: {
connect: {
id: formId
}
id: formId,
},
},
user: {
connect: {
id: userId
}
}
}
id: userId,
},
},
},
})
export {
@ -158,5 +180,5 @@ export {
getDBForm,
getDBFormAuthor,
getDBFormsByUser,
submitDBAnswer
submitDBAnswer,
}

View File

@ -14,30 +14,31 @@ app.use(
expressJwt({
algorithms: ['HS256'],
credentialsRequired: false,
secret: '' + process.env.JWT_SECRET
secret: '' + process.env.JWT_SECRET,
})
)
const db = new PrismaClient()
const server = new ApolloServer({
context: async ({
req
req,
}: {
req: Request & {
user: JwtPayloadType
}
}): Promise<ApolloContextType> => {
const db = new PrismaClient()
const user = req.user || null
return {
db,
user
user,
}
},
debug: false,
schema: makeExecutableSchema({
resolvers,
typeDefs
})
typeDefs,
}),
})
server.applyMiddleware({ app })

View File

@ -6,7 +6,7 @@ import {
QueryFormArgs,
QuestionResolvers,
Resolver,
ServerAnswer
ServerAnswer,
} from '../typeDefs/typeDefs.gen'
import { ApolloContextType } from '../types'
import {
@ -15,7 +15,7 @@ import {
getForm,
getFormAuthor,
getForms,
submitAnswer
submitAnswer,
} from '../controllers'
const formQuery: Resolver<Form, {}, ApolloContextType, QueryFormArgs> = async (
@ -33,9 +33,9 @@ const formQuery: Resolver<Form, {}, ApolloContextType, QueryFormArgs> = async (
controller: getFormById,
expected: {
id: 0,
self: true
self: true,
},
user
user,
})
} catch (err) {
return err
@ -57,9 +57,9 @@ const formsQuery: Resolver<Form[], {}, ApolloContextType> = async (
controller: getFormsByUserId,
expected: {
id: 0,
self: true
self: true,
},
user
user,
})
} catch (err) {
return err
@ -78,9 +78,9 @@ const createFormMutation: Resolver<
controller: createNewForm,
expected: {
id: 0,
self: true
self: true,
},
user
user,
})
}
@ -96,9 +96,9 @@ const formSubmitMutation: Resolver<
controller: submitNewAnswer,
expected: {
id: 0,
self: true
self: true,
},
user
user,
})
}
@ -108,7 +108,7 @@ const QuestionResolver: QuestionResolvers = {
return 'ChoisesQuestion'
}
return 'InputQuestion'
}
},
}
const AnswerResolver: AnswerResolvers = {
@ -117,7 +117,7 @@ const AnswerResolver: AnswerResolvers = {
if (obj.type == 'INPUT') return 'InputAnswer'
return null
}
},
}
export {
@ -126,5 +126,5 @@ export {
formQuery,
formsQuery,
formSubmitMutation,
QuestionResolver
QuestionResolver,
}

View File

@ -1,7 +1,7 @@
import {
checkRightsAndResolve,
findUserBy,
genAndSendToken
genAndSendToken,
} from '../controllers'
import { createUser } from '../controllers/user'
import {
@ -10,7 +10,7 @@ import {
Resolver,
ServerAnswer,
User,
QueryUserArgs
QueryUserArgs,
} from '../typeDefs/typeDefs.gen'
import { ApolloContextType } from '../types'
@ -23,6 +23,8 @@ const loginMutation: Resolver<
try {
const user = await findUserBy(db, { email })
if (user instanceof Error) throw user // Needed to a strange error
await genAndSendToken(email, user)
return { success: true }
@ -40,6 +42,8 @@ const registerMutation: Resolver<
try {
const user = await createUser(db, { email, name })
if (user instanceof Error) throw user // Needed to a strange error
await genAndSendToken(email, user)
return { success: true }
@ -60,9 +64,9 @@ const userQuery: Resolver<User, {}, ApolloContextType, QueryUserArgs> = async (
controller: findUserById,
expected: {
id: id || 0,
self: true
self: true,
},
user
user,
})
} catch (err) {
return err

View File

@ -5,28 +5,28 @@ import {
AnswerResolver as Answer,
formsQuery as forms,
createFormMutation as createForm,
formSubmitMutation as formSubmit
formSubmitMutation as formSubmit,
} from './Form'
import {
loginMutation as login,
registerMutation as register,
userQuery as user
userQuery as user,
} from './User'
const resolvers: Resolvers = {
Query: {
form,
forms,
user
user,
},
Mutation: {
login,
register,
createForm,
formSubmit
formSubmit,
},
Question,
Answer
Answer,
}
export default resolvers

View File

@ -4,11 +4,13 @@
"module": "CommonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"strict": true /* Enable all strict type-checking options. */,
"outDir": "dist",
"noImplicitAny": true,
"moduleResolution": "Node",
"baseUrl": "src",
"incremental": true,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": ["src"]
"include": ["src/**/*"]
}