From 4f82c809227c996f0fb4af13e9631d9cd03cf178 Mon Sep 17 00:00:00 2001 From: Dm1tr1y147 Date: Sat, 10 Oct 2020 17:13:51 +0500 Subject: [PATCH] Added submission list filter based on authentificated user. Sorted object parameters and imports --- src/controllers/auth.ts | 17 ++++--- src/controllers/form.ts | 95 ++++++++++++++++++++----------------- src/controllers/index.ts | 7 +-- src/controllers/types.ts | 11 ++--- src/controllers/user.ts | 5 +- src/db/index.ts | 98 ++++++++++++++++++++++++++------------- src/db/types.ts | 2 +- src/index.ts | 33 +++++++------ src/mailer/index.ts | 14 +++--- src/resolvers/Form.ts | 65 ++++++++++++++------------ src/resolvers/User.ts | 9 ++-- src/typeDefs/index.ts | 2 +- src/typeDefs/typeDefs.gql | 36 +++++++------- 13 files changed, 225 insertions(+), 169 deletions(-) diff --git a/src/controllers/auth.ts b/src/controllers/auth.ts index 49f8dcb..5980a2b 100644 --- a/src/controllers/auth.ts +++ b/src/controllers/auth.ts @@ -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 } diff --git a/src/controllers/form.ts b/src/controllers/form.ts index 56ecd3e..6b5f81f 100644 --- a/src/controllers/form.ts +++ b/src/controllers/form.ts @@ -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((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((submission) => ({ answers: submission.answers, date: submission.date.toString(), id: submission.id - })) + })), + title: form.title })) ] @@ -63,7 +71,28 @@ const createFormFrom = async ( ) => { const parsedQuestions = JSON.parse(params.questions) const newForm: newForm = { - title: params.title, + choisesQuestions: { + create: parsedQuestions.flatMap( + (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( - (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 } diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 3fc4770..b3d9698 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -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 } diff --git a/src/controllers/types.ts b/src/controllers/types.ts index c31013f..bc009f4 100644 --- a/src/controllers/types.ts +++ b/src/controllers/types.ts @@ -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 { - user: JwtPayloadType | null - expected: expectedType controller: T + expected: expectedType + user: JwtPayloadType | null } type CheckRightsAndResolve = ( @@ -24,15 +21,15 @@ type CheckRightsAndResolve = ( ) => Promise type newForm = { - title: string choisesQuestions: { create: createChoises[] } inputQuestions: { create: InputQuestion[] } + title: string } type createChoises = Omit & { variants: { create: Variant[] } } -export { CheckRightsAndResolve, newForm, createChoises } +export { CheckRightsAndResolve, createChoises, newForm } diff --git a/src/controllers/user.ts b/src/controllers/user.ts index 696960c..6f1d688 100644 --- a/src/controllers/user.ts +++ b/src/controllers/user.ts @@ -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, diff --git a/src/db/index.ts b/src/db/index.ts index 8d4da1a..82bf47c 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -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 } diff --git a/src/db/types.ts b/src/db/types.ts index 24527ae..f7f987c 100644 --- a/src/db/types.ts +++ b/src/db/types.ts @@ -1,5 +1,5 @@ -import { PromiseReturnType } from '@prisma/client' import { getDBForm } from '../db' +import { PromiseReturnType } from '@prisma/client' type FullForm = PromiseReturnType diff --git a/src/index.ts b/src/index.ts index 12dff63..6922110 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 => { 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 }) diff --git a/src/mailer/index.ts b/src/mailer/index.ts index 151bed4..bb717e8 100644 --- a/src/mailer/index.ts +++ b/src/mailer/index.ts @@ -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 }) } diff --git a/src/resolvers/Form.ts b/src/resolvers/Form.ts index 56c1b45..93a84ce 100644 --- a/src/resolvers/Form.ts +++ b/src/resolvers/Form.ts @@ -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 = async ( _, @@ -24,17 +24,18 @@ const formQuery: Resolver = 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 = async ( Form[], (userId: number) => Promise >({ - 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 } diff --git a/src/resolvers/User.ts b/src/resolvers/User.ts index ccb5e44..846160f 100644 --- a/src/resolvers/User.ts +++ b/src/resolvers/User.ts @@ -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 diff --git a/src/typeDefs/index.ts b/src/typeDefs/index.ts index 88dfa57..7b3a2b8 100644 --- a/src/typeDefs/index.ts +++ b/src/typeDefs/index.ts @@ -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' }) diff --git a/src/typeDefs/typeDefs.gql b/src/typeDefs/typeDefs.gql index a52d579..12c6f06 100644 --- a/src/typeDefs/typeDefs.gql +++ b/src/typeDefs/typeDefs.gql @@ -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 {