Compare commits

...

12 Commits
v1.0.0 ... main

25 changed files with 584 additions and 128 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
node_modules/

4
.gitignore vendored
View File

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

17
Dockerfile Normal file
View File

@ -0,0 +1,17 @@
FROM node:alpine AS builder
WORKDIR /backend
COPY package.json /backend/package.json
RUN yarn
COPY . /backend
RUN yarn prisma generate && yarn codegen && yarn build
FROM node:alpine
WORKDIR /backend
COPY --from=builder /backend/dist /backend
COPY package.json /backend/package.json
RUN yarn install --prod
COPY --from=builder /backend/node_modules/@prisma/client /backend/node_modules/@prisma/client
COPY --from=builder /backend/node_modules/.prisma/client/ /backend/node_modules/.prisma/client/
COPY --from=builder /backend/prisma /backend/prisma
USER node
CMD [ "node", "./index.js" ]

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

@ -2,31 +2,36 @@
"name": "backend",
"version": "1.0.0",
"main": "src/index.ts",
"private": "true",
"license": "MIT",
"dependencies": {
"@prisma/client": "^2.7.1",
"@sendgrid/mail": "^7.2.6",
"@types/jsonwebtoken": "^8.5.0",
"apollo-server-express": "^2.18.2",
"express-jwt": "^6.0.0",
"graphql": "^15.3.0",
"jsonwebtoken": "^8.5.1",
"jwks-rsa": "^1.10.1",
"nodemon": "^2.0.4"
"jwks-rsa": "^1.10.1"
},
"scripts": {
"dev": "nodemon",
"start": "ts-node src/index.ts",
"copy-assets": "cp src/typeDefs/typeDefs.gql dist/typeDefs/typeDefs.gql",
"build": "tsc && yarn 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",
"@graphql-codegen/introspection": "1.18.0",
"@graphql-codegen/typescript": "1.17.10",
"@graphql-codegen/typescript-resolvers": "1.17.10",
"@prisma/cli": "2.8.1",
"@types/dotenv": "^8.2.0",
"@types/jsonwebtoken": "^8.5.0",
"dotenv": "^8.2.0",
"nodemon": "^2.0.4",
"ts-node": "^9.0.0",
"typescript": "^4.0.3"
}

View File

@ -0,0 +1,41 @@
# Migration `20201104091229-renamed-user-form-submissions-name`
This migration has been generated by Dm1tr1y147 at 11/4/2020, 2:12:29 PM.
You can check out the [state of the schema](./schema.prisma) after the migration.
## Database Steps
```sql
```
## Changes
```diff
diff --git schema.prisma schema.prisma
migration 20201009145620-add-user-email..20201104091229-renamed-user-form-submissions-name
--- datamodel.dml
+++ datamodel.dml
@@ -2,9 +2,9 @@
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "postgres"
- url = "***"
+ url = "***"
}
generator client {
provider = "prisma-client-js"
@@ -60,10 +60,10 @@
name String
email String @unique @default("test@mail.com")
forms Form[]
- id Int @id @default(autoincrement())
- formsSubmissions FormSubmission[]
+ id Int @id @default(autoincrement())
+ formSubmissions FormSubmission[]
}
model FormSubmission {
answers Answer[]
```

View File

@ -0,0 +1,92 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "postgres"
url = "***"
}
generator client {
provider = "prisma-client-js"
}
model Form {
title String
choisesQuestions ChoisesQuestion[]
inputQuestions InputQuestion[]
submissions FormSubmission[]
dateCreated DateTime @default(now())
author User @relation(fields: [userId], references: [id])
id Int @id @default(autoincrement())
userId Int
}
model ChoisesQuestion {
title String
variants Variant[]
type ChoiseType
number Int
id Int @id @default(autoincrement())
Form Form? @relation(fields: [formId], references: [id])
formId Int?
}
model Variant {
text String
id Int @id @default(autoincrement())
ChoisesQuestion ChoisesQuestion? @relation(fields: [choisesQuestionId], references: [id])
choisesQuestionId Int?
}
model InputQuestion {
title String
number Int
id Int @id @default(autoincrement())
Form Form? @relation(fields: [formId], references: [id])
formId Int?
}
enum ChoiseType {
SELECT
CHECK
CHOOSE
}
model User {
name String
email String @unique @default("test@mail.com")
forms Form[]
id Int @id @default(autoincrement())
formSubmissions FormSubmission[]
}
model FormSubmission {
answers Answer[]
date DateTime @default(now())
user User @relation(fields: [userId], references: [id])
id Int @id @default(autoincrement())
userId Int
Form Form? @relation(fields: [formId], references: [id])
formId Int?
}
model Answer {
userInput String?
userChoise Int?
type AnswerType
id Int @id @default(autoincrement())
FormSubmission FormSubmission? @relation(fields: [formSubmissionId], references: [id])
formSubmissionId Int?
}
enum AnswerType {
INPUT
CHOISE
}

View File

@ -0,0 +1,17 @@
{
"version": "0.3.14-fixed",
"steps": [
{
"tag": "CreateField",
"model": "User",
"field": "formSubmissions",
"type": "FormSubmission",
"arity": "List"
},
{
"tag": "DeleteField",
"model": "User",
"field": "formsSubmissions"
}
]
}

View File

@ -4,3 +4,4 @@
20201006185953-improved-schema-structure
20201007134933-fix-optional-values
20201009145620-add-user-email
20201104091229-renamed-user-form-submissions-name

View File

@ -62,7 +62,7 @@ model User {
forms Form[]
id Int @id @default(autoincrement())
formsSubmissions FormSubmission[]
formSubmissions FormSubmission[]
}
model FormSubmission {

View File

@ -2,16 +2,16 @@ import jwt from 'jsonwebtoken'
import {
ApolloError,
AuthenticationError,
ForbiddenError
ForbiddenError,
} from 'apollo-server-express'
import { PrismaClient } from '@prisma/client'
require('dotenv').config()
import { CheckRightsAndResolve } from './types'
import { getDBFormAuthor } from '../db'
import { sendToken } from './mailer'
if (process.env.NODE_ENV === 'development') require('dotenv').config()
const checkRightsAndResolve: CheckRightsAndResolve = async (params) => {
const { user, expected, controller } = params
@ -35,10 +35,12 @@ const getFormAuthor = async (db: PrismaClient, id: number) => {
}
const tokenGenerate = (email: string, id: number) => {
return jwt.sign({ email, id }, '' + process.env.JWT_SECRET, {
const token = jwt.sign({ email, id }, '' + process.env.JWT_SECRET, {
algorithm: 'HS256',
expiresIn: '7 days'
expiresIn: '7 days',
})
return token
}
const genAndSendToken = async (

View File

@ -1,33 +1,48 @@
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,
getDBFormToSubmit,
} from '../db'
import {
validateCreateFormParameters,
validateSubmitAnswerParameters,
} from './validate'
const formatQuestions = (
choisesQuestions: (ChoisesQuestion & {
variants: Variant[]
})[],
inputQuestions: InputQuestion[]
) =>
[...choisesQuestions, ...inputQuestions].sort((a, b) => a.number - b.number)
const getForm = async (
db: PrismaClient,
id: number,
user: { requesterId: number; userId: number }
): Promise<Form> => {
user: { requesterId: number; ownerId: number }
): Promise<GraphqlForm> => {
try {
const dbForm = await getDBForm(db, id, user)
@ -37,13 +52,20 @@ const getForm = async (
author: dbForm.author,
dateCreated: dbForm.dateCreated.toString(),
id: dbForm.id,
questions: [...dbForm.choisesQuestions, ...dbForm.inputQuestions],
submissions: dbForm.submissions.map((submission) => ({
questions: formatQuestions(
dbForm.choisesQuestions,
dbForm.inputQuestions
),
submissions:
user.ownerId == user.requesterId || !(dbForm.submissions.length == 0)
? dbForm.submissions.map((submission) => ({
user: submission.user,
answers: submission.answers,
date: submission.date.toString(),
id: submission.id
})),
title: dbForm.title
id: submission.id,
}))
: null,
title: dbForm.title,
}
return form
@ -52,22 +74,26 @@ 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) => ({
user: submission.user,
answers: submission.answers,
date: submission.date.toString(),
id: submission.id
id: submission.id,
})),
title: form.title
title: form.title,
}))
return forms
@ -84,6 +110,8 @@ const createFormFrom = async (
try {
const parsedQuestions = <UploadedQuestion[]>JSON.parse(params.questions)
await validateCreateFormParameters(params.title, parsedQuestions)
const newForm: FormConstructor = {
choisesQuestions: {
create: parsedQuestions.flatMap<CreateChoises>(
@ -95,12 +123,12 @@ const createFormFrom = async (
title: uQuestion.title,
type: uQuestion.type,
variants: {
create: uQuestion.variants
}
}
create: uQuestion.variants,
},
},
]
: []
)
),
},
inputQuestions: {
create: parsedQuestions.flatMap<InputQuestion>(
@ -108,9 +136,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)
@ -130,8 +158,21 @@ const submitAnswer = async (
userId: number
): Promise<ServerAnswer> => {
try {
const form = await getDBFormToSubmit(db, formId)
if (!form) throw new UserInputError("Can't submit form")
form.submissions.forEach((submission) => {
if (submission.userId === userId)
throw new UserInputError("Can't submit same form more than once")
})
const parsedAnswers = <DbAnswer[]>JSON.parse(answers)
await validateSubmitAnswerParameters(
parsedAnswers,
formatQuestions(form.choisesQuestions, form.inputQuestions)
)
const res = await submitDBAnswer(db, userId, formId, parsedAnswers)
if (!res) throw new UserInputError("Can't submit form")
@ -142,4 +183,26 @@ const submitAnswer = async (
}
}
export { createFormFrom, getForm, getForms, submitAnswer }
const formatForms = (
forms: (Form & {
choisesQuestions: (ChoisesQuestion & {
variants: Variant[]
})[]
inputQuestions: InputQuestion[]
submissions: (Omit<FormSubmission, 'date'> & { date: Date })[]
})[]
): GraphqlForm[] =>
forms.map<GraphqlForm>((form) => ({
dateCreated: form.dateCreated.toString(),
id: form.id,
questions: formatQuestions(form.choisesQuestions, form.inputQuestions),
submissions: form.submissions.map((submission) => ({
answers: submission.answers,
date: submission.date.toString(),
id: submission.id,
user: submission.user,
})),
title: form.title,
}))
export { createFormFrom, getForm, getForms, submitAnswer, formatForms }

View File

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

View File

@ -1,6 +1,6 @@
import sgMail from '@sendgrid/mail'
require('dotenv').config()
if (process.env.NODE_ENV === 'development') require('dotenv').config()
sgMail.setApiKey('' + process.env.SENDGRID_API_KEY)
@ -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,8 @@ 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'
import { formSubmitMutation, formsQuery } from 'resolvers/Form'
const createUser = async (
db: PrismaClient,
@ -31,9 +33,22 @@ 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: User = {
...dbUser,
forms: formatForms(dbUser.forms),
formSubmissions: dbUser.formSubmissions.map((formSubmission) => ({
...formSubmission,
date: formSubmission.date.toString(),
form: formSubmission.Form && {
...formSubmission.Form,
dateCreated: formSubmission.Form?.dateCreated.toString(),
},
})),
}
return user
} catch (err) {

127
src/controllers/validate.ts Normal file
View File

@ -0,0 +1,127 @@
'use strict'
import { UserInputError } from 'apollo-server-express'
import { Answer } from '@prisma/client'
import {
UploadedChoisesQuestion,
UploadedInputQuestion,
UploadedQuestion,
} from './types'
import { ChoisesQuestion, InputQuestion, Variant } from 'typeDefs/typeDefs.gen'
const choisesVariants = ['CHECK', 'CHOOSE', 'SELECT']
const validateCreateFormParameters = async (
title: string,
questions: UploadedQuestion[]
) => {
if (!title)
throw new UserInputError("Form title can't be empty", {
invalidArgs: ['title'],
})
questions.forEach(
(question: UploadedChoisesQuestion | UploadedInputQuestion) => {
if (!question.title)
throw new UserInputError("Question title can't be empty", {
invalidArgs: ['questions'],
})
if ('type' in question) {
if (!question.variants || question.variants.length < 1)
throw new UserInputError(
'Question with choises must have at least one answer variant',
{ invalidArgs: ['questions'] }
)
question.variants.forEach((variant) => {
if (!variant.text || variant.text.length < 1)
throw new UserInputError("Choises variant text can't be empty", {
invalidArgs: ['questions'],
})
})
if (!choisesVariants.includes(question.type))
throw new UserInputError(
'Question with choises must be of one of supported types',
{ invalidArgs: ['questions'] }
)
}
}
)
}
const validateSubmitAnswerParameters = async (
answers: Answer[],
questions: (
| (ChoisesQuestion & {
variants: Variant[]
})
| InputQuestion
)[]
) => {
questions.forEach((question, questionIndex) => {
const answer = answers[questionIndex]
if (!answer)
throw new UserInputError('Every required question must have answer', {
invalidArgs: ['answers'],
})
if (!answer.type)
throw new UserInputError('Type must be specified for answer', {
invalidArgs: ['answers'],
})
if (answer.type !== 'CHOISE' && answer.type !== 'INPUT')
throw new UserInputError('Answer must have supported type', {
invalidArgs: ['answers'],
})
if (answer.type === 'CHOISE' && !('type' in question))
throw new UserInputError(
`Answer ${questionIndex + 1} must be of 'INPUT' type`,
{
invalidArgs: ['answers'],
}
)
if (answer.type === 'INPUT' && 'type' in question)
throw new UserInputError(
`Answer ${questionIndex + 1} must be of 'CHOISE' type`,
{
invalidArgs: ['answers'],
}
)
if (answer.type === 'CHOISE' && answer.userChoise === null)
throw new UserInputError(
"Question of type 'CHOISE' must have choise number set",
{
invalidArgs: ['answers'],
}
)
if (answer.type === 'INPUT' && answer.userInput === null)
throw new UserInputError(
"Question of type 'INPUT' must have input string",
{
invalidArgs: ['answers'],
}
)
if (
answer.userChoise !== null &&
(question as ChoisesQuestion).variants &&
answer.userChoise > (question as ChoisesQuestion).variants.length - 1
)
throw new UserInputError(
"Can't have chosen number bigger than amount of variants: " +
(question as ChoisesQuestion).variants.length,
{
invalidArgs: ['answers'],
}
)
})
}
export { validateCreateFormParameters, validateSubmitAnswerParameters }

View File

@ -19,10 +19,10 @@ const getDBForm = (
formId: number,
{
requesterId,
userId
ownerId: ownerId,
}: {
requesterId: number
userId: number
ownerId: number
}
) =>
db.form.findOne({
@ -31,32 +31,33 @@ const getDBForm = (
select: {
email: true,
id: true,
name: true
}
name: true,
},
},
choisesQuestions: {
include: {
variants: true
}
variants: true,
},
},
inputQuestions: true,
submissions: {
include: {
answers: true
user: true,
answers: true,
},
where:
requesterId != userId
requesterId != ownerId
? {
user: {
id: requesterId
}
}
: undefined
id: requesterId,
},
}
: undefined,
},
},
where: {
id: formId
}
id: formId,
},
})
/**
@ -71,21 +72,22 @@ const getDBFormsByUser = (db: PrismaClient, id: number) =>
include: {
choisesQuestions: {
include: {
variants: true
}
variants: true,
},
},
inputQuestions: true,
submissions: {
include: {
answers: true
}
}
user: true,
answers: true,
},
},
},
where: {
author: {
id
}
}
id,
},
},
})
const getDBFormAuthor = (db: PrismaClient, id: number) =>
@ -93,13 +95,13 @@ const getDBFormAuthor = (db: PrismaClient, id: number) =>
select: {
author: {
select: {
id: true
}
}
id: true,
},
},
},
where: {
id
}
id,
},
})
const createDBUser = (
@ -107,24 +109,48 @@ 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: {
user: true,
answers: true,
},
},
},
},
formSubmissions: {
include: {
answers: true,
Form: true,
},
},
},
})
const createDBForm = (db: PrismaClient, form: FormConstructor, id: number) =>
db.form.create({
data: {
author: {
connect: { id }
connect: { id },
},
...form,
},
...form
}
})
const submitDBAnswer = (
@ -136,19 +162,39 @@ const submitDBAnswer = (
db.formSubmission.create({
data: {
answers: {
create: formAnswers
create: formAnswers,
},
Form: {
connect: {
id: formId
}
id: formId,
},
},
user: {
connect: {
id: userId
}
}
}
id: userId,
},
},
},
})
const getDBFormToSubmit = async (db: PrismaClient, formId: number) =>
db.form.findOne({
where: {
id: formId,
},
select: {
choisesQuestions: {
include: {
variants: true,
},
},
inputQuestions: true,
submissions: {
select: {
userId: true,
},
},
},
})
export {
@ -158,5 +204,6 @@ export {
getDBForm,
getDBFormAuthor,
getDBFormsByUser,
submitDBAnswer
submitDBAnswer,
getDBFormToSubmit,
}

View File

@ -6,7 +6,7 @@ import { ApolloContextType, JwtPayloadType } from './types'
import { ApolloServer, makeExecutableSchema } from 'apollo-server-express'
import { PrismaClient } from '@prisma/client'
require('dotenv').config()
if (process.env.NODE_ENV === 'development') require('dotenv').config()
const app = express()
@ -14,34 +14,45 @@ app.use(
expressJwt({
algorithms: ['HS256'],
credentialsRequired: false,
secret: '' + process.env.JWT_SECRET
secret: '' + process.env.JWT_SECRET,
})
)
const errorHandler: express.ErrorRequestHandler = (err, _, res, __) => {
if (err.name === 'UnauthorizedError') {
res.status(401).send('Invalid token')
}
}
app.use(errorHandler)
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 })
app.listen(4000, () => {
console.log('Server ready at http://localhost:4000')
const port = process.env.BACKEND_PORT || 4000
app.listen(port, () => {
console.log(`Server ready at http://localhost:${port}`)
})

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 (
@ -26,16 +26,16 @@ const formQuery: Resolver<Form, {}, ApolloContextType, QueryFormArgs> = async (
try {
const ownerId = await getFormAuthor(db, id)
const getFormById = (userId: number) =>
getForm(db, id, { requesterId: userId, userId: ownerId })
const getFormById = (requesterId: number) =>
getForm(db, id, { requesterId, ownerId })
return await checkRightsAndResolve({
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
@ -74,13 +74,16 @@ const createFormMutation: Resolver<
> = async (_, params, { db, user }) => {
const createNewForm = (id: number) => createFormFrom(db, params, id)
return await checkRightsAndResolve({
return await checkRightsAndResolve<
ServerAnswer,
(id: number) => Promise<ServerAnswer>
>({
controller: createNewForm,
expected: {
id: 0,
self: true
self: true,
},
user
user,
})
}
@ -92,13 +95,16 @@ const formSubmitMutation: Resolver<
> = async (_, params, { db, user }) => {
const submitNewAnswer = (userId: number) => submitAnswer(db, params, userId)
return await checkRightsAndResolve({
return await checkRightsAndResolve<
ServerAnswer,
(userId: number) => Promise<ServerAnswer>
>({
controller: submitNewAnswer,
expected: {
id: 0,
self: true
self: true,
},
user
user,
})
}
@ -108,7 +114,7 @@ const QuestionResolver: QuestionResolvers = {
return 'ChoisesQuestion'
}
return 'InputQuestion'
}
},
}
const AnswerResolver: AnswerResolvers = {
@ -117,7 +123,7 @@ const AnswerResolver: AnswerResolvers = {
if (obj.type == 'INPUT') return 'InputAnswer'
return null
}
},
}
export {
@ -126,5 +132,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 fix 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 fix 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

@ -15,7 +15,7 @@ type Form {
author: User
dateCreated: String!
id: Int!
questions: [Question!]!
questions: [Question!]
submissions: [FormSubmission!]
title: String!
}
@ -42,9 +42,11 @@ type InputQuestion implements Question {
}
type FormSubmission {
user: User
answers: [Answer!]!
date: String!
id: Int!
form: Form
}
interface Answer {
@ -77,6 +79,7 @@ type User {
forms: [Form!]
id: Int!
name: String!
formSubmissions: [FormSubmission!]
}
type serverAnswer {

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": false,
"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/**/*"]
}