From 673f42fd0f0ea4a866a5649c93d0fe033a11dc0c Mon Sep 17 00:00:00 2001 From: Dm1tr1y147 Date: Sun, 18 Oct 2020 22:09:40 +0500 Subject: [PATCH] Added forms property fetching to user query and some minor fixes --- .gitignore | 2 +- codegen.yml | 6 +-- nodemon.json | 2 +- package.json | 6 ++- src/controllers/auth.ts | 4 +- src/controllers/form.ts | 64 ++++++++++++++++-------- src/controllers/index.ts | 2 +- src/controllers/mailer.ts | 4 +- src/controllers/types.ts | 4 +- src/controllers/user.ts | 10 +++- src/db/index.ts | 100 +++++++++++++++++++++++--------------- src/index.ts | 13 ++--- src/resolvers/Form.ts | 26 +++++----- src/resolvers/User.ts | 12 +++-- src/resolvers/index.ts | 10 ++-- tsconfig.json | 4 +- 16 files changed, 167 insertions(+), 102 deletions(-) diff --git a/.gitignore b/.gitignore index cce557e..e2d1a56 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /node_modules -/build +/dist *.gen.ts diff --git a/codegen.yml b/codegen.yml index 8bb9da1..02f7569 100644 --- a/codegen.yml +++ b/codegen.yml @@ -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' diff --git a/nodemon.json b/nodemon.json index e7690ad..4583c27 100644 --- a/nodemon.json +++ b/nodemon.json @@ -3,4 +3,4 @@ "watch": ["src"], "exec": "yarn start", "ext": "ts" -} \ No newline at end of file +} diff --git a/package.json b/package.json index 2e85e8e..0ab3da6 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/controllers/auth.ts b/src/controllers/auth.ts index fbceb73..a523df0 100644 --- a/src/controllers/auth.ts +++ b/src/controllers/auth.ts @@ -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', }) } diff --git a/src/controllers/form.ts b/src/controllers/form.ts index 818160d..8f41278 100644 --- a/src/controllers/form.ts +++ b/src/controllers/form.ts @@ -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
=> { +): Promise => { 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 => { +const getForms = async ( + db: PrismaClient, + userId: number +): Promise => { 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( @@ -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 & { 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 } diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 5944089..3d8a16b 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -10,5 +10,5 @@ export { getForm, getFormAuthor, getForms, - submitAnswer + submitAnswer, } diff --git a/src/controllers/mailer.ts b/src/controllers/mailer.ts index 585a031..e1a2b15 100644 --- a/src/controllers/mailer.ts +++ b/src/controllers/mailer.ts @@ -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, }) } diff --git a/src/controllers/types.ts b/src/controllers/types.ts index a057db1..0c06fe5 100644 --- a/src/controllers/types.ts +++ b/src/controllers/types.ts @@ -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, } diff --git a/src/controllers/user.ts b/src/controllers/user.ts index d4b740c..9fc3522 100644 --- a/src/controllers/user.ts +++ b/src/controllers/user.ts @@ -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 => { 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) { diff --git a/src/db/index.ts b/src/db/index.ts index 69c8bc6..760d31b 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -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, } diff --git a/src/index.ts b/src/index.ts index 6922110..722b1ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 => { - const db = new PrismaClient() const user = req.user || null return { db, - user + user, } }, debug: false, schema: makeExecutableSchema({ resolvers, - typeDefs - }) + typeDefs, + }), }) server.applyMiddleware({ app }) diff --git a/src/resolvers/Form.ts b/src/resolvers/Form.ts index ba2f987..64b4c54 100644 --- a/src/resolvers/Form.ts +++ b/src/resolvers/Form.ts @@ -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 = async ( @@ -33,9 +33,9 @@ const formQuery: Resolver = async ( controller: getFormById, expected: { id: 0, - self: true + self: true, }, - user + user, }) } catch (err) { return err @@ -57,9 +57,9 @@ const formsQuery: Resolver = 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, } diff --git a/src/resolvers/User.ts b/src/resolvers/User.ts index 4c44200..253022a 100644 --- a/src/resolvers/User.ts +++ b/src/resolvers/User.ts @@ -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 = async ( controller: findUserById, expected: { id: id || 0, - self: true + self: true, }, - user + user, }) } catch (err) { return err diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts index 085a774..0b7ee2f 100644 --- a/src/resolvers/index.ts +++ b/src/resolvers/index.ts @@ -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 diff --git a/tsconfig.json b/tsconfig.json index ea8c600..dff5de9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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/**/*"] }