Compare commits
23 Commits
express-mo
...
main
Author | SHA1 | Date | |
---|---|---|---|
a7aac23011 | |||
a8fa574f66 | |||
aa73cc9a83 | |||
c09a66c6dd | |||
10978a42b2 | |||
e86e0d6027 | |||
bc06090437 | |||
220dcf18ab | |||
a554b91e53 | |||
3367930690 | |||
c640602e99 | |||
673f42fd0f | |||
01676c59e4 | |||
4f82c80922 | |||
a07e0d752f | |||
9439730f4c | |||
301281b7fc | |||
ec2c5feb69 | |||
db8b3eea51 | |||
aa8fdda13d | |||
09662877ea | |||
898b17510b | |||
ebca575f46 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
3
.env.example
Normal file
3
.env.example
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
SENDGRID_API_KEY=
|
||||||
|
JWT_SECRET=
|
||||||
|
SITE_URL=test.com
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,8 +1,12 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
|
|
||||||
/build
|
/dist
|
||||||
|
|
||||||
.env.local
|
.env
|
||||||
|
|
||||||
|
*.gen.ts
|
||||||
|
|
||||||
|
*.local*
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
|
17
Dockerfile
Normal file
17
Dockerfile
Normal 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" ]
|
21
LICENCE
Normal file
21
LICENCE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Ditriy Shishkov <me@dmitriy.com> (https://dmitriy.icu)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
22
README.md
22
README.md
@ -1,9 +1,27 @@
|
|||||||
# QuestionForm Backend
|
# QuestionForm Backend
|
||||||
|
|
||||||
Backend used with QuestionForm application.
|
Backend for QuestionForm application.
|
||||||
|
|
||||||
# Built with:
|
# Built with:
|
||||||
|
|
||||||
- Prisma
|
- Prisma
|
||||||
- Graphql
|
- Graphql
|
||||||
- Graphql-yoga
|
- Apollo Server
|
||||||
|
- Graphql code generator
|
||||||
|
- SendGrid
|
||||||
|
|
||||||
|
# Setting up development environment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git clone https://github.com/Dm1tr1y147/questionForm_backend
|
||||||
|
$ yarn
|
||||||
|
$ mv .env.example .env && vim .env
|
||||||
|
$ mv prisma/.env.example prisma/.env && vim .env
|
||||||
|
$ prisma migrate save --experimental && prisma migrate up --experimental
|
||||||
|
$ prisma generate
|
||||||
|
$ yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
_...coming soon..._
|
||||||
|
12
codegen.yml
Normal file
12
codegen.yml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
overwrite: true
|
||||||
|
schema: 'src/typeDefs/typeDefs.gql'
|
||||||
|
documents: null
|
||||||
|
generates:
|
||||||
|
src/typeDefs/typeDefs.gen.ts:
|
||||||
|
config:
|
||||||
|
useIndexSignature: true
|
||||||
|
wrapFieldDefinitions: true
|
||||||
|
enumsAsTypes: true
|
||||||
|
plugins:
|
||||||
|
- 'typescript'
|
||||||
|
- 'typescript-resolvers'
|
6
nodemon.json
Normal file
6
nodemon.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"ignore": ["**/*.test.ts", "**/*.spec.ts", "node_modules"],
|
||||||
|
"watch": ["src"],
|
||||||
|
"exec": "yarn start",
|
||||||
|
"ext": "ts"
|
||||||
|
}
|
73
package-lock.json
generated
73
package-lock.json
generated
@ -1,73 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "backend",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@prisma/cli": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-0uA+gWkNQ35DveVHDPltiTCTr4wcXtEhnPs463IEM+Xn8dTv9x0gtZiYHSuQM3t7uwlOxj1rurBsqSbiljynfQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"@prisma/client": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-IEWDCuvIaQTira8/jAyf+uY+AuPPUFDIXMSN4zEA/gvoJv2woq7RmkaubS+NQVgDbbyOR6F3UcXLiFTYQDzZkQ==",
|
|
||||||
"requires": {
|
|
||||||
"pkg-up": "^3.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"find-up": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
|
|
||||||
"requires": {
|
|
||||||
"locate-path": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"locate-path": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
|
|
||||||
"requires": {
|
|
||||||
"p-locate": "^3.0.0",
|
|
||||||
"path-exists": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-limit": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
|
||||||
"requires": {
|
|
||||||
"p-try": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-locate": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
|
|
||||||
"requires": {
|
|
||||||
"p-limit": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-try": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
|
|
||||||
},
|
|
||||||
"path-exists": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
|
|
||||||
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
|
|
||||||
},
|
|
||||||
"pkg-up": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
|
|
||||||
"requires": {
|
|
||||||
"find-up": "^3.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
26
package.json
26
package.json
@ -2,16 +2,36 @@
|
|||||||
"name": "backend",
|
"name": "backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
"private": "true",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/client": "^2.7.1",
|
"@prisma/client": "^2.7.1",
|
||||||
"graphql-yoga": "^1.18.3"
|
"@sendgrid/mail": "^7.2.6",
|
||||||
|
"apollo-server-express": "^2.18.2",
|
||||||
|
"express-jwt": "^6.0.0",
|
||||||
|
"graphql": "^15.3.0",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"jwks-rsa": "^1.10.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "ts-node src/index.ts"
|
"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",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@prisma/cli": "^2.7.1",
|
"@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",
|
"ts-node": "^9.0.0",
|
||||||
"typescript": "^4.0.3"
|
"typescript": "^4.0.3"
|
||||||
}
|
}
|
||||||
|
1
prisma/.env.example
Normal file
1
prisma/.env.example
Normal file
@ -0,0 +1 @@
|
|||||||
|
DATABASE_URL="postgres://"
|
BIN
prisma/dev.db
BIN
prisma/dev.db
Binary file not shown.
@ -1,103 +0,0 @@
|
|||||||
# Migration `20201003131039-initial-structure`
|
|
||||||
|
|
||||||
This migration has been generated by Dm1tr1y147 at 10/3/2020, 6:10:39 PM.
|
|
||||||
You can check out the [state of the schema](./schema.prisma) after the migration.
|
|
||||||
|
|
||||||
## Database Steps
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE TABLE "User" (
|
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
"name" TEXT NOT NULL,
|
|
||||||
"email" TEXT,
|
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
|
|
||||||
CREATE TABLE "Poll" (
|
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
"userId" INTEGER,
|
|
||||||
"title" TEXT NOT NULL,
|
|
||||||
"description" TEXT,
|
|
||||||
"slug" TEXT NOT NULL,
|
|
||||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE
|
|
||||||
)
|
|
||||||
|
|
||||||
CREATE TABLE "Question" (
|
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
"pollId" INTEGER,
|
|
||||||
"title" TEXT NOT NULL,
|
|
||||||
|
|
||||||
FOREIGN KEY ("pollId") REFERENCES "Poll"("id") ON DELETE SET NULL ON UPDATE CASCADE
|
|
||||||
)
|
|
||||||
|
|
||||||
CREATE TABLE "Variant" (
|
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
"questionId" INTEGER,
|
|
||||||
"text" TEXT NOT NULL,
|
|
||||||
"count" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
|
|
||||||
FOREIGN KEY ("questionId") REFERENCES "Question"("id") ON DELETE SET NULL ON UPDATE CASCADE
|
|
||||||
)
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "Poll.slug_unique" ON "Poll"("slug")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
|
|
||||||
```diff
|
|
||||||
diff --git schema.prisma schema.prisma
|
|
||||||
migration ..20201003131039-initial-structure
|
|
||||||
--- datamodel.dml
|
|
||||||
+++ datamodel.dml
|
|
||||||
@@ -1,0 +1,46 @@
|
|
||||||
+// This is your Prisma schema file,
|
|
||||||
+// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
||||||
+
|
|
||||||
+datasource db {
|
|
||||||
+ provider = "sqlite"
|
|
||||||
+ url = "***"
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+generator client {
|
|
||||||
+ provider = "prisma-client-js"
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+model User {
|
|
||||||
+ id Int @id @default(autoincrement())
|
|
||||||
+ name String
|
|
||||||
+ email String?
|
|
||||||
+ createdAt DateTime @default(now())
|
|
||||||
+ polls Poll[]
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+model Poll {
|
|
||||||
+ id Int @id @default(autoincrement())
|
|
||||||
+ User User? @relation(fields: [userId], references: [id])
|
|
||||||
+ userId Int?
|
|
||||||
+ title String
|
|
||||||
+ description String?
|
|
||||||
+ slug String @unique
|
|
||||||
+ questions Question[]
|
|
||||||
+ createdAt DateTime @default(now())
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+model Question {
|
|
||||||
+ id Int @id @default(autoincrement())
|
|
||||||
+ Poll Poll? @relation(fields: [pollId], references: [id])
|
|
||||||
+ pollId Int?
|
|
||||||
+ title String
|
|
||||||
+ variants Variant[]
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+model Variant {
|
|
||||||
+ id Int @id @default(autoincrement())
|
|
||||||
+ Question Question? @relation(fields: [questionId], references: [id])
|
|
||||||
+ questionId Int?
|
|
||||||
+ text String
|
|
||||||
+ count Int @default(0)
|
|
||||||
+}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
|||||||
// This is your Prisma schema file,
|
|
||||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
||||||
|
|
||||||
datasource db {
|
|
||||||
provider = "sqlite"
|
|
||||||
url = "***"
|
|
||||||
}
|
|
||||||
|
|
||||||
generator client {
|
|
||||||
provider = "prisma-client-js"
|
|
||||||
}
|
|
||||||
|
|
||||||
model User {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
name String
|
|
||||||
email String?
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
polls Poll[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model Poll {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
User User? @relation(fields: [userId], references: [id])
|
|
||||||
userId Int?
|
|
||||||
title String
|
|
||||||
description String?
|
|
||||||
slug String @unique
|
|
||||||
questions Question[]
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
}
|
|
||||||
|
|
||||||
model Question {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
Poll Poll? @relation(fields: [pollId], references: [id])
|
|
||||||
pollId Int?
|
|
||||||
title String
|
|
||||||
variants Variant[]
|
|
||||||
}
|
|
||||||
|
|
||||||
model Variant {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
Question Question? @relation(fields: [questionId], references: [id])
|
|
||||||
questionId Int?
|
|
||||||
text String
|
|
||||||
count Int @default(0)
|
|
||||||
}
|
|
219
prisma/migrations/20201006125838-initial-migration/README.md
Normal file
219
prisma/migrations/20201006125838-initial-migration/README.md
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
# Migration `20201006125838-initial-migration`
|
||||||
|
|
||||||
|
This migration has been generated by Dm1tr1y147 at 10/6/2020, 5:58:38 PM.
|
||||||
|
You can check out the [state of the schema](./schema.prisma) after the migration.
|
||||||
|
|
||||||
|
## Database Steps
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TYPE "public"."ChoiseType" AS ENUM ('SELECT', 'CHECK', 'CHOOSE')
|
||||||
|
|
||||||
|
CREATE TYPE "public"."AnswerType" AS ENUM ('INPUT', 'CHOISE')
|
||||||
|
|
||||||
|
CREATE TABLE "public"."Form" (
|
||||||
|
"title" text NOT NULL ,
|
||||||
|
"dateCreated" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"id" integer NOT NULL ,
|
||||||
|
"userId" integer NOT NULL ,
|
||||||
|
PRIMARY KEY ("id")
|
||||||
|
)
|
||||||
|
|
||||||
|
CREATE TABLE "public"."ChoisesQuestion" (
|
||||||
|
"title" text NOT NULL ,
|
||||||
|
"type" "ChoiseType" NOT NULL ,
|
||||||
|
"number" integer NOT NULL ,
|
||||||
|
"id" integer NOT NULL ,
|
||||||
|
"formId" integer ,
|
||||||
|
PRIMARY KEY ("id")
|
||||||
|
)
|
||||||
|
|
||||||
|
CREATE TABLE "public"."Variant" (
|
||||||
|
"text" text NOT NULL ,
|
||||||
|
"id" integer NOT NULL ,
|
||||||
|
"choisesQuestionId" integer ,
|
||||||
|
PRIMARY KEY ("id")
|
||||||
|
)
|
||||||
|
|
||||||
|
CREATE TABLE "public"."InputQuestion" (
|
||||||
|
"title" text NOT NULL ,
|
||||||
|
"number" integer NOT NULL ,
|
||||||
|
"id" integer NOT NULL ,
|
||||||
|
"formId" integer ,
|
||||||
|
PRIMARY KEY ("id")
|
||||||
|
)
|
||||||
|
|
||||||
|
CREATE TABLE "public"."FormSubmission" (
|
||||||
|
"date" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"id" integer NOT NULL ,
|
||||||
|
"formId" integer ,
|
||||||
|
"userId" integer NOT NULL ,
|
||||||
|
PRIMARY KEY ("id")
|
||||||
|
)
|
||||||
|
|
||||||
|
CREATE TABLE "public"."Answer" (
|
||||||
|
"id" integer NOT NULL ,
|
||||||
|
"userInput" text NOT NULL ,
|
||||||
|
"userChoise" integer NOT NULL ,
|
||||||
|
"type" "AnswerType" NOT NULL ,
|
||||||
|
"formSubmissionId" integer ,
|
||||||
|
PRIMARY KEY ("id")
|
||||||
|
)
|
||||||
|
|
||||||
|
CREATE TABLE "public"."User" (
|
||||||
|
"id" integer NOT NULL ,
|
||||||
|
"name" text NOT NULL ,
|
||||||
|
PRIMARY KEY ("id")
|
||||||
|
)
|
||||||
|
|
||||||
|
ALTER TABLE "public"."Form" ADD FOREIGN KEY ("userId")REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
|
||||||
|
ALTER TABLE "public"."ChoisesQuestion" ADD FOREIGN KEY ("formId")REFERENCES "public"."Form"("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
|
||||||
|
ALTER TABLE "public"."Variant" ADD FOREIGN KEY ("choisesQuestionId")REFERENCES "public"."ChoisesQuestion"("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
|
||||||
|
ALTER TABLE "public"."InputQuestion" ADD FOREIGN KEY ("formId")REFERENCES "public"."Form"("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
|
||||||
|
ALTER TABLE "public"."FormSubmission" ADD FOREIGN KEY ("userId")REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
|
||||||
|
ALTER TABLE "public"."FormSubmission" ADD FOREIGN KEY ("formId")REFERENCES "public"."Form"("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
|
||||||
|
ALTER TABLE "public"."Answer" ADD FOREIGN KEY ("formSubmissionId")REFERENCES "public"."FormSubmission"("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
```diff
|
||||||
|
diff --git schema.prisma schema.prisma
|
||||||
|
migration ..20201006125838-initial-migration
|
||||||
|
--- datamodel.dml
|
||||||
|
+++ datamodel.dml
|
||||||
|
@@ -1,0 +1,126 @@
|
||||||
|
+// 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 User {
|
||||||
|
+// id Int @id @default(autoincrement())
|
||||||
|
+// name String
|
||||||
|
+// email String?
|
||||||
|
+// createdAt DateTime @default(now())
|
||||||
|
+// polls Poll[]
|
||||||
|
+// }
|
||||||
|
+
|
||||||
|
+// model Poll {
|
||||||
|
+// id Int @id @default(autoincrement())
|
||||||
|
+// User User? @relation(fields: [userId], references: [id])
|
||||||
|
+// userId Int?
|
||||||
|
+// title String
|
||||||
|
+// description String?
|
||||||
|
+// slug String @unique @default(cuid())
|
||||||
|
+// questions Question[]
|
||||||
|
+// createdAt DateTime @default(now())
|
||||||
|
+// }
|
||||||
|
+
|
||||||
|
+// model Question {
|
||||||
|
+// id Int @id @default(autoincrement())
|
||||||
|
+// Poll Poll? @relation(fields: [pollId], references: [id])
|
||||||
|
+// pollId Int?
|
||||||
|
+// title String
|
||||||
|
+// variants Variant[]
|
||||||
|
+// }
|
||||||
|
+
|
||||||
|
+// model Variant {
|
||||||
|
+// id Int @id @default(autoincrement())
|
||||||
|
+// Question Question? @relation(fields: [questionId], references: [id])
|
||||||
|
+// questionId Int?
|
||||||
|
+// text String
|
||||||
|
+// count Int @default(0)
|
||||||
|
+// }
|
||||||
|
+
|
||||||
|
+model Form {
|
||||||
|
+ title String
|
||||||
|
+ choisesQuestions ChoisesQuestion[]
|
||||||
|
+ inputQuestions InputQuestion[]
|
||||||
|
+ submissions FormSubmission[]
|
||||||
|
+ dateCreated DateTime @default(now())
|
||||||
|
+
|
||||||
|
+ id Int @id
|
||||||
|
+ userId Int
|
||||||
|
+ author User @relation(fields: [userId], references: [id])
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+model ChoisesQuestion {
|
||||||
|
+ title String
|
||||||
|
+ variants Variant[]
|
||||||
|
+ type ChoiseType
|
||||||
|
+ number Int
|
||||||
|
+
|
||||||
|
+ id Int @id
|
||||||
|
+ Form Form? @relation(fields: [formId], references: [id])
|
||||||
|
+ formId Int?
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+model Variant {
|
||||||
|
+ text String
|
||||||
|
+
|
||||||
|
+ id Int @id
|
||||||
|
+ ChoisesQuestion ChoisesQuestion? @relation(fields: [choisesQuestionId], references: [id])
|
||||||
|
+ choisesQuestionId Int?
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+model InputQuestion {
|
||||||
|
+ title String
|
||||||
|
+ number Int
|
||||||
|
+
|
||||||
|
+ id Int @id
|
||||||
|
+ Form Form? @relation(fields: [formId], references: [id])
|
||||||
|
+ formId Int?
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+model FormSubmission {
|
||||||
|
+ answers Answer[]
|
||||||
|
+ date DateTime @default(now())
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+ id Int @id
|
||||||
|
+ user User @relation(fields: [userId], references: [id])
|
||||||
|
+ Form Form? @relation(fields: [formId], references: [id])
|
||||||
|
+ formId Int?
|
||||||
|
+ userId Int
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+model Answer {
|
||||||
|
+ id Int @id
|
||||||
|
+ userInput String
|
||||||
|
+ userChoise Int
|
||||||
|
+ type AnswerType
|
||||||
|
+ FormSubmission FormSubmission? @relation(fields: [formSubmissionId], references: [id])
|
||||||
|
+ formSubmissionId Int?
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+enum ChoiseType {
|
||||||
|
+ SELECT
|
||||||
|
+ CHECK
|
||||||
|
+ CHOOSE
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+enum AnswerType {
|
||||||
|
+ INPUT
|
||||||
|
+ CHOISE
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+model User {
|
||||||
|
+ id Int @id
|
||||||
|
+ name String
|
||||||
|
+
|
||||||
|
+ forms Form[]
|
||||||
|
+ formsSubmissions FormSubmission[]
|
||||||
|
+}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
126
prisma/migrations/20201006125838-initial-migration/schema.prisma
Normal file
126
prisma/migrations/20201006125838-initial-migration/schema.prisma
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// 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 User {
|
||||||
|
// id Int @id @default(autoincrement())
|
||||||
|
// name String
|
||||||
|
// email String?
|
||||||
|
// createdAt DateTime @default(now())
|
||||||
|
// polls Poll[]
|
||||||
|
// }
|
||||||
|
|
||||||
|
// model Poll {
|
||||||
|
// id Int @id @default(autoincrement())
|
||||||
|
// User User? @relation(fields: [userId], references: [id])
|
||||||
|
// userId Int?
|
||||||
|
// title String
|
||||||
|
// description String?
|
||||||
|
// slug String @unique @default(cuid())
|
||||||
|
// questions Question[]
|
||||||
|
// createdAt DateTime @default(now())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// model Question {
|
||||||
|
// id Int @id @default(autoincrement())
|
||||||
|
// Poll Poll? @relation(fields: [pollId], references: [id])
|
||||||
|
// pollId Int?
|
||||||
|
// title String
|
||||||
|
// variants Variant[]
|
||||||
|
// }
|
||||||
|
|
||||||
|
// model Variant {
|
||||||
|
// id Int @id @default(autoincrement())
|
||||||
|
// Question Question? @relation(fields: [questionId], references: [id])
|
||||||
|
// questionId Int?
|
||||||
|
// text String
|
||||||
|
// count Int @default(0)
|
||||||
|
// }
|
||||||
|
|
||||||
|
model Form {
|
||||||
|
title String
|
||||||
|
choisesQuestions ChoisesQuestion[]
|
||||||
|
inputQuestions InputQuestion[]
|
||||||
|
submissions FormSubmission[]
|
||||||
|
dateCreated DateTime @default(now())
|
||||||
|
|
||||||
|
id Int @id
|
||||||
|
userId Int
|
||||||
|
author User @relation(fields: [userId], references: [id])
|
||||||
|
}
|
||||||
|
|
||||||
|
model ChoisesQuestion {
|
||||||
|
title String
|
||||||
|
variants Variant[]
|
||||||
|
type ChoiseType
|
||||||
|
number Int
|
||||||
|
|
||||||
|
id Int @id
|
||||||
|
Form Form? @relation(fields: [formId], references: [id])
|
||||||
|
formId Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
model Variant {
|
||||||
|
text String
|
||||||
|
|
||||||
|
id Int @id
|
||||||
|
ChoisesQuestion ChoisesQuestion? @relation(fields: [choisesQuestionId], references: [id])
|
||||||
|
choisesQuestionId Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
model InputQuestion {
|
||||||
|
title String
|
||||||
|
number Int
|
||||||
|
|
||||||
|
id Int @id
|
||||||
|
Form Form? @relation(fields: [formId], references: [id])
|
||||||
|
formId Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
model FormSubmission {
|
||||||
|
answers Answer[]
|
||||||
|
date DateTime @default(now())
|
||||||
|
|
||||||
|
|
||||||
|
id Int @id
|
||||||
|
user User @relation(fields: [userId], references: [id])
|
||||||
|
Form Form? @relation(fields: [formId], references: [id])
|
||||||
|
formId Int?
|
||||||
|
userId Int
|
||||||
|
}
|
||||||
|
|
||||||
|
model Answer {
|
||||||
|
id Int @id
|
||||||
|
userInput String
|
||||||
|
userChoise Int
|
||||||
|
type AnswerType
|
||||||
|
FormSubmission FormSubmission? @relation(fields: [formSubmissionId], references: [id])
|
||||||
|
formSubmissionId Int?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ChoiseType {
|
||||||
|
SELECT
|
||||||
|
CHECK
|
||||||
|
CHOOSE
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AnswerType {
|
||||||
|
INPUT
|
||||||
|
CHOISE
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id Int @id
|
||||||
|
name String
|
||||||
|
|
||||||
|
forms Form[]
|
||||||
|
formsSubmissions FormSubmission[]
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,187 @@
|
|||||||
|
# Migration `20201006185953-improved-schema-structure`
|
||||||
|
|
||||||
|
This migration has been generated by Dm1tr1y147 at 10/6/2020, 11:59:53 PM.
|
||||||
|
You can check out the [state of the schema](./schema.prisma) after the migration.
|
||||||
|
|
||||||
|
## Database Steps
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE SEQUENCE "answer_id_seq";
|
||||||
|
ALTER TABLE "public"."Answer" ALTER COLUMN "id" SET DEFAULT nextval('answer_id_seq');
|
||||||
|
ALTER SEQUENCE "answer_id_seq" OWNED BY "public"."Answer"."id"
|
||||||
|
|
||||||
|
CREATE SEQUENCE "choisesquestion_id_seq";
|
||||||
|
ALTER TABLE "public"."ChoisesQuestion" ALTER COLUMN "id" SET DEFAULT nextval('choisesquestion_id_seq');
|
||||||
|
ALTER SEQUENCE "choisesquestion_id_seq" OWNED BY "public"."ChoisesQuestion"."id"
|
||||||
|
|
||||||
|
CREATE SEQUENCE "form_id_seq";
|
||||||
|
ALTER TABLE "public"."Form" ALTER COLUMN "id" SET DEFAULT nextval('form_id_seq');
|
||||||
|
ALTER SEQUENCE "form_id_seq" OWNED BY "public"."Form"."id"
|
||||||
|
|
||||||
|
CREATE SEQUENCE "formsubmission_id_seq";
|
||||||
|
ALTER TABLE "public"."FormSubmission" ALTER COLUMN "id" SET DEFAULT nextval('formsubmission_id_seq');
|
||||||
|
ALTER SEQUENCE "formsubmission_id_seq" OWNED BY "public"."FormSubmission"."id"
|
||||||
|
|
||||||
|
CREATE SEQUENCE "inputquestion_id_seq";
|
||||||
|
ALTER TABLE "public"."InputQuestion" ALTER COLUMN "id" SET DEFAULT nextval('inputquestion_id_seq');
|
||||||
|
ALTER SEQUENCE "inputquestion_id_seq" OWNED BY "public"."InputQuestion"."id"
|
||||||
|
|
||||||
|
CREATE SEQUENCE "user_id_seq";
|
||||||
|
ALTER TABLE "public"."User" ALTER COLUMN "id" SET DEFAULT nextval('user_id_seq');
|
||||||
|
ALTER SEQUENCE "user_id_seq" OWNED BY "public"."User"."id"
|
||||||
|
|
||||||
|
CREATE SEQUENCE "variant_id_seq";
|
||||||
|
ALTER TABLE "public"."Variant" ALTER COLUMN "id" SET DEFAULT nextval('variant_id_seq');
|
||||||
|
ALTER SEQUENCE "variant_id_seq" OWNED BY "public"."Variant"."id"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
```diff
|
||||||
|
diff --git schema.prisma schema.prisma
|
||||||
|
migration 20201006125838-initial-migration..20201006185953-improved-schema-structure
|
||||||
|
--- datamodel.dml
|
||||||
|
+++ datamodel.dml
|
||||||
|
@@ -2,125 +2,90 @@
|
||||||
|
// 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"
|
||||||
|
}
|
||||||
|
-// model User {
|
||||||
|
-// id Int @id @default(autoincrement())
|
||||||
|
-// name String
|
||||||
|
-// email String?
|
||||||
|
-// createdAt DateTime @default(now())
|
||||||
|
-// polls Poll[]
|
||||||
|
-// }
|
||||||
|
-
|
||||||
|
-// model Poll {
|
||||||
|
-// id Int @id @default(autoincrement())
|
||||||
|
-// User User? @relation(fields: [userId], references: [id])
|
||||||
|
-// userId Int?
|
||||||
|
-// title String
|
||||||
|
-// description String?
|
||||||
|
-// slug String @unique @default(cuid())
|
||||||
|
-// questions Question[]
|
||||||
|
-// createdAt DateTime @default(now())
|
||||||
|
-// }
|
||||||
|
-
|
||||||
|
-// model Question {
|
||||||
|
-// id Int @id @default(autoincrement())
|
||||||
|
-// Poll Poll? @relation(fields: [pollId], references: [id])
|
||||||
|
-// pollId Int?
|
||||||
|
-// title String
|
||||||
|
-// variants Variant[]
|
||||||
|
-// }
|
||||||
|
-
|
||||||
|
-// model Variant {
|
||||||
|
-// id Int @id @default(autoincrement())
|
||||||
|
-// Question Question? @relation(fields: [questionId], references: [id])
|
||||||
|
-// questionId Int?
|
||||||
|
-// text String
|
||||||
|
-// count Int @default(0)
|
||||||
|
-// }
|
||||||
|
-
|
||||||
|
model Form {
|
||||||
|
title String
|
||||||
|
choisesQuestions ChoisesQuestion[]
|
||||||
|
inputQuestions InputQuestion[]
|
||||||
|
submissions FormSubmission[]
|
||||||
|
dateCreated DateTime @default(now())
|
||||||
|
+ author User @relation(fields: [userId], references: [id])
|
||||||
|
- id Int @id
|
||||||
|
+ id Int @id @default(autoincrement())
|
||||||
|
userId Int
|
||||||
|
- author User @relation(fields: [userId], references: [id])
|
||||||
|
}
|
||||||
|
model ChoisesQuestion {
|
||||||
|
title String
|
||||||
|
variants Variant[]
|
||||||
|
type ChoiseType
|
||||||
|
number Int
|
||||||
|
- id Int @id
|
||||||
|
+ id Int @id @default(autoincrement())
|
||||||
|
Form Form? @relation(fields: [formId], references: [id])
|
||||||
|
formId Int?
|
||||||
|
}
|
||||||
|
model Variant {
|
||||||
|
text String
|
||||||
|
- id Int @id
|
||||||
|
+ id Int @id @default(autoincrement())
|
||||||
|
ChoisesQuestion ChoisesQuestion? @relation(fields: [choisesQuestionId], references: [id])
|
||||||
|
choisesQuestionId Int?
|
||||||
|
}
|
||||||
|
model InputQuestion {
|
||||||
|
title String
|
||||||
|
number Int
|
||||||
|
- id Int @id
|
||||||
|
+ id Int @id @default(autoincrement())
|
||||||
|
Form Form? @relation(fields: [formId], references: [id])
|
||||||
|
formId Int?
|
||||||
|
}
|
||||||
|
+enum ChoiseType {
|
||||||
|
+ SELECT
|
||||||
|
+ CHECK
|
||||||
|
+ CHOOSE
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+model User {
|
||||||
|
+ name String
|
||||||
|
+ forms Form[]
|
||||||
|
+
|
||||||
|
+ id Int @id @default(autoincrement())
|
||||||
|
+ formsSubmissions FormSubmission[]
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
model FormSubmission {
|
||||||
|
answers Answer[]
|
||||||
|
date DateTime @default(now())
|
||||||
|
+ user User @relation(fields: [userId], references: [id])
|
||||||
|
-
|
||||||
|
- id Int @id
|
||||||
|
- user User @relation(fields: [userId], references: [id])
|
||||||
|
+ id Int @id @default(autoincrement())
|
||||||
|
+ userId Int
|
||||||
|
Form Form? @relation(fields: [formId], references: [id])
|
||||||
|
formId Int?
|
||||||
|
- userId Int
|
||||||
|
}
|
||||||
|
model Answer {
|
||||||
|
- id Int @id
|
||||||
|
- userInput String
|
||||||
|
- userChoise Int
|
||||||
|
- type AnswerType
|
||||||
|
+ userInput String
|
||||||
|
+ userChoise Int
|
||||||
|
+ type AnswerType
|
||||||
|
+
|
||||||
|
+ id Int @id @default(autoincrement())
|
||||||
|
FormSubmission FormSubmission? @relation(fields: [formSubmissionId], references: [id])
|
||||||
|
formSubmissionId Int?
|
||||||
|
}
|
||||||
|
-enum ChoiseType {
|
||||||
|
- SELECT
|
||||||
|
- CHECK
|
||||||
|
- CHOOSE
|
||||||
|
-}
|
||||||
|
-
|
||||||
|
enum AnswerType {
|
||||||
|
INPUT
|
||||||
|
CHOISE
|
||||||
|
}
|
||||||
|
-
|
||||||
|
-model User {
|
||||||
|
- id Int @id
|
||||||
|
- name String
|
||||||
|
-
|
||||||
|
- forms Form[]
|
||||||
|
- formsSubmissions FormSubmission[]
|
||||||
|
-}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
|||||||
|
// 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
|
||||||
|
forms Form[]
|
||||||
|
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
formsSubmissions 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
|
||||||
|
}
|
@ -0,0 +1,180 @@
|
|||||||
|
{
|
||||||
|
"version": "0.3.14-fixed",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"tag": "CreateDirective",
|
||||||
|
"location": {
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "Form",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateArgument",
|
||||||
|
"location": {
|
||||||
|
"tag": "Directive",
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "Form",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
},
|
||||||
|
"argument": "",
|
||||||
|
"value": "autoincrement()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateDirective",
|
||||||
|
"location": {
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "ChoisesQuestion",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateArgument",
|
||||||
|
"location": {
|
||||||
|
"tag": "Directive",
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "ChoisesQuestion",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
},
|
||||||
|
"argument": "",
|
||||||
|
"value": "autoincrement()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateDirective",
|
||||||
|
"location": {
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "Variant",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateArgument",
|
||||||
|
"location": {
|
||||||
|
"tag": "Directive",
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "Variant",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
},
|
||||||
|
"argument": "",
|
||||||
|
"value": "autoincrement()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateDirective",
|
||||||
|
"location": {
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "InputQuestion",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateArgument",
|
||||||
|
"location": {
|
||||||
|
"tag": "Directive",
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "InputQuestion",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
},
|
||||||
|
"argument": "",
|
||||||
|
"value": "autoincrement()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateDirective",
|
||||||
|
"location": {
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "FormSubmission",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateArgument",
|
||||||
|
"location": {
|
||||||
|
"tag": "Directive",
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "FormSubmission",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
},
|
||||||
|
"argument": "",
|
||||||
|
"value": "autoincrement()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateDirective",
|
||||||
|
"location": {
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "Answer",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateArgument",
|
||||||
|
"location": {
|
||||||
|
"tag": "Directive",
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "Answer",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
},
|
||||||
|
"argument": "",
|
||||||
|
"value": "autoincrement()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateDirective",
|
||||||
|
"location": {
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "User",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateArgument",
|
||||||
|
"location": {
|
||||||
|
"tag": "Directive",
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "User",
|
||||||
|
"field": "id"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
},
|
||||||
|
"argument": "",
|
||||||
|
"value": "autoincrement()"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
# Migration `20201007134933-fix-optional-values`
|
||||||
|
|
||||||
|
This migration has been generated by Dm1tr1y147 at 10/7/2020, 6:49:33 PM.
|
||||||
|
You can check out the [state of the schema](./schema.prisma) after the migration.
|
||||||
|
|
||||||
|
## Database Steps
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE "public"."Answer" ALTER COLUMN "userInput" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "userChoise" DROP NOT NULL
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
```diff
|
||||||
|
diff --git schema.prisma schema.prisma
|
||||||
|
migration 20201006185953-improved-schema-structure..20201007134933-fix-optional-values
|
||||||
|
--- 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"
|
||||||
|
@@ -75,10 +75,10 @@
|
||||||
|
formId Int?
|
||||||
|
}
|
||||||
|
model Answer {
|
||||||
|
- userInput String
|
||||||
|
- userChoise Int
|
||||||
|
+ userInput String?
|
||||||
|
+ userChoise Int?
|
||||||
|
type AnswerType
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
FormSubmission FormSubmission? @relation(fields: [formSubmissionId], references: [id])
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
|||||||
|
// 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
|
||||||
|
forms Form[]
|
||||||
|
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
formsSubmissions 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
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"version": "0.3.14-fixed",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"tag": "UpdateField",
|
||||||
|
"model": "Answer",
|
||||||
|
"field": "userInput",
|
||||||
|
"arity": "Optional"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "UpdateField",
|
||||||
|
"model": "Answer",
|
||||||
|
"field": "userChoise",
|
||||||
|
"arity": "Optional"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
40
prisma/migrations/20201009145620-add-user-email/README.md
Normal file
40
prisma/migrations/20201009145620-add-user-email/README.md
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Migration `20201009145620-add-user-email`
|
||||||
|
|
||||||
|
This migration has been generated by Dm1tr1y147 at 10/9/2020, 7:56:20 PM.
|
||||||
|
You can check out the [state of the schema](./schema.prisma) after the migration.
|
||||||
|
|
||||||
|
## Database Steps
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE "public"."User" ADD COLUMN "email" text NOT NULL DEFAULT E'test@mail.com'
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "User.email_unique" ON "public"."User"("email")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
```diff
|
||||||
|
diff --git schema.prisma schema.prisma
|
||||||
|
migration 20201007134933-fix-optional-values..20201009145620-add-user-email
|
||||||
|
--- 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"
|
||||||
|
@@ -57,8 +57,9 @@
|
||||||
|
}
|
||||||
|
model User {
|
||||||
|
name String
|
||||||
|
+ email String @unique @default("test@mail.com")
|
||||||
|
forms Form[]
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
formsSubmissions FormSubmission[]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -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())
|
||||||
|
formsSubmissions 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
|
||||||
|
}
|
48
prisma/migrations/20201009145620-add-user-email/steps.json
Normal file
48
prisma/migrations/20201009145620-add-user-email/steps.json
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"version": "0.3.14-fixed",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"tag": "CreateField",
|
||||||
|
"model": "User",
|
||||||
|
"field": "email",
|
||||||
|
"type": "String",
|
||||||
|
"arity": "Required"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateDirective",
|
||||||
|
"location": {
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "User",
|
||||||
|
"field": "email"
|
||||||
|
},
|
||||||
|
"directive": "unique"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateDirective",
|
||||||
|
"location": {
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "User",
|
||||||
|
"field": "email"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "CreateArgument",
|
||||||
|
"location": {
|
||||||
|
"tag": "Directive",
|
||||||
|
"path": {
|
||||||
|
"tag": "Field",
|
||||||
|
"model": "User",
|
||||||
|
"field": "email"
|
||||||
|
},
|
||||||
|
"directive": "default"
|
||||||
|
},
|
||||||
|
"argument": "",
|
||||||
|
"value": "\"test@mail.com\""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -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[]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,3 +1,7 @@
|
|||||||
# Prisma Migrate lockfile v1
|
# Prisma Migrate lockfile v1
|
||||||
|
|
||||||
20201003131039-initial-structure
|
20201006125838-initial-migration
|
||||||
|
20201006185953-improved-schema-structure
|
||||||
|
20201007134933-fix-optional-values
|
||||||
|
20201009145620-add-user-email
|
||||||
|
20201104091229-renamed-user-form-submissions-name
|
@ -2,45 +2,91 @@
|
|||||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "sqlite"
|
provider = "postgres"
|
||||||
url = "file:./dev.db"
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model Form {
|
||||||
id Int @id @default(autoincrement())
|
title String
|
||||||
name String
|
choisesQuestions ChoisesQuestion[]
|
||||||
email String?
|
inputQuestions InputQuestion[]
|
||||||
createdAt DateTime @default(now())
|
submissions FormSubmission[]
|
||||||
polls Poll[]
|
dateCreated DateTime @default(now())
|
||||||
|
author User @relation(fields: [userId], references: [id])
|
||||||
|
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
userId Int
|
||||||
}
|
}
|
||||||
|
|
||||||
model Poll {
|
model ChoisesQuestion {
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
User User? @relation(fields: [userId], references: [id])
|
|
||||||
userId Int?
|
|
||||||
title String
|
|
||||||
description String?
|
|
||||||
slug String @unique @default(cuid())
|
|
||||||
questions Question[]
|
|
||||||
createdAt DateTime @default(now())
|
|
||||||
}
|
|
||||||
|
|
||||||
model Question {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
Poll Poll? @relation(fields: [pollId], references: [id])
|
|
||||||
pollId Int?
|
|
||||||
title String
|
title String
|
||||||
variants Variant[]
|
variants Variant[]
|
||||||
|
type ChoiseType
|
||||||
|
number Int
|
||||||
|
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
Form Form? @relation(fields: [formId], references: [id])
|
||||||
|
formId Int?
|
||||||
}
|
}
|
||||||
|
|
||||||
model Variant {
|
model Variant {
|
||||||
id Int @id @default(autoincrement())
|
text String
|
||||||
Question Question? @relation(fields: [questionId], references: [id])
|
|
||||||
questionId Int?
|
id Int @id @default(autoincrement())
|
||||||
text String
|
ChoisesQuestion ChoisesQuestion? @relation(fields: [choisesQuestionId], references: [id])
|
||||||
count Int @default(0)
|
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
|
||||||
}
|
}
|
||||||
|
58
src/controllers/auth.ts
Normal file
58
src/controllers/auth.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import jwt from 'jsonwebtoken'
|
||||||
|
import {
|
||||||
|
ApolloError,
|
||||||
|
AuthenticationError,
|
||||||
|
ForbiddenError,
|
||||||
|
} from 'apollo-server-express'
|
||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if (!user) throw new AuthenticationError('Authorization required')
|
||||||
|
|
||||||
|
if (expected.id && expected.self) return controller(expected.id || user.id)
|
||||||
|
if (expected.self) return controller(user.id)
|
||||||
|
if (!expected.id || user.id == expected.id) return controller()
|
||||||
|
|
||||||
|
throw new ForbiddenError('Access denied')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFormAuthor = async (db: PrismaClient, id: number) => {
|
||||||
|
const author = await getDBFormAuthor(db, id)
|
||||||
|
|
||||||
|
if (!author) throw new ApolloError('Not found', 'NOTFOUND')
|
||||||
|
|
||||||
|
const authorId = author.author.id
|
||||||
|
|
||||||
|
return authorId
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenGenerate = (email: string, id: number) => {
|
||||||
|
const token = jwt.sign({ email, id }, '' + process.env.JWT_SECRET, {
|
||||||
|
algorithm: 'HS256',
|
||||||
|
expiresIn: '7 days',
|
||||||
|
})
|
||||||
|
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
const genAndSendToken = async (
|
||||||
|
email: string,
|
||||||
|
user: { id: number; name: string }
|
||||||
|
) => {
|
||||||
|
const token = tokenGenerate(email, user.id)
|
||||||
|
|
||||||
|
const res = await sendToken(user.name, email, token)
|
||||||
|
|
||||||
|
if (res[0].statusCode != 202)
|
||||||
|
return new ApolloError("Couldn't send email", 'EMAILSENDERROR')
|
||||||
|
}
|
||||||
|
|
||||||
|
export { checkRightsAndResolve, getFormAuthor, genAndSendToken }
|
208
src/controllers/form.ts
Normal file
208
src/controllers/form.ts
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import { Answer as DbAnswer, PrismaClient, Form } from '@prisma/client'
|
||||||
|
import { ApolloError, UserInputError } from 'apollo-server-express'
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChoisesQuestion,
|
||||||
|
Form as GraphqlForm,
|
||||||
|
FormSubmission,
|
||||||
|
InputQuestion,
|
||||||
|
MutationCreateFormArgs,
|
||||||
|
MutationFormSubmitArgs,
|
||||||
|
ServerAnswer,
|
||||||
|
Variant,
|
||||||
|
} from '../typeDefs/typeDefs.gen'
|
||||||
|
import {
|
||||||
|
CreateChoises,
|
||||||
|
FormConstructor,
|
||||||
|
UploadedChoisesQuestion,
|
||||||
|
UploadedInputQuestion,
|
||||||
|
UploadedQuestion,
|
||||||
|
} from './types'
|
||||||
|
import {
|
||||||
|
createDBForm,
|
||||||
|
getDBForm,
|
||||||
|
getDBFormsByUser,
|
||||||
|
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; ownerId: number }
|
||||||
|
): Promise<GraphqlForm> => {
|
||||||
|
try {
|
||||||
|
const dbForm = await getDBForm(db, id, user)
|
||||||
|
|
||||||
|
if (!dbForm) throw new ApolloError('Not found', 'NOTFOUND')
|
||||||
|
|
||||||
|
const form: GraphqlForm = {
|
||||||
|
author: dbForm.author,
|
||||||
|
dateCreated: dbForm.dateCreated.toString(),
|
||||||
|
id: dbForm.id,
|
||||||
|
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,
|
||||||
|
}))
|
||||||
|
: null,
|
||||||
|
title: dbForm.title,
|
||||||
|
}
|
||||||
|
|
||||||
|
return form
|
||||||
|
} catch (err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: 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,
|
||||||
|
})),
|
||||||
|
title: form.title,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return forms
|
||||||
|
} catch (err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createFormFrom = async (
|
||||||
|
db: PrismaClient,
|
||||||
|
params: MutationCreateFormArgs,
|
||||||
|
id: number
|
||||||
|
): Promise<ServerAnswer> => {
|
||||||
|
try {
|
||||||
|
const parsedQuestions = <UploadedQuestion[]>JSON.parse(params.questions)
|
||||||
|
|
||||||
|
await validateCreateFormParameters(params.title, parsedQuestions)
|
||||||
|
|
||||||
|
const newForm: FormConstructor = {
|
||||||
|
choisesQuestions: {
|
||||||
|
create: parsedQuestions.flatMap<CreateChoises>(
|
||||||
|
(uQuestion: UploadedChoisesQuestion | UploadedInputQuestion, index) =>
|
||||||
|
'type' in uQuestion
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
number: index,
|
||||||
|
title: uQuestion.title,
|
||||||
|
type: uQuestion.type,
|
||||||
|
variants: {
|
||||||
|
create: uQuestion.variants,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
},
|
||||||
|
inputQuestions: {
|
||||||
|
create: parsedQuestions.flatMap<InputQuestion>(
|
||||||
|
(uQuestion: UploadedChoisesQuestion | UploadedInputQuestion, index) =>
|
||||||
|
!('type' in uQuestion)
|
||||||
|
? [{ number: index, title: uQuestion.title }]
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
},
|
||||||
|
title: params.title,
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await createDBForm(db, newForm, id)
|
||||||
|
|
||||||
|
if (!res)
|
||||||
|
throw new ApolloError("Couldn't create new form", 'FORMCREATIONERROR')
|
||||||
|
|
||||||
|
return { success: true }
|
||||||
|
} catch (err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitAnswer = async (
|
||||||
|
db: PrismaClient,
|
||||||
|
{ answers, formId }: MutationFormSubmitArgs,
|
||||||
|
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")
|
||||||
|
|
||||||
|
return { success: true }
|
||||||
|
} catch (err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 }
|
14
src/controllers/index.ts
Normal file
14
src/controllers/index.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { checkRightsAndResolve, getFormAuthor, genAndSendToken } from './auth'
|
||||||
|
import { createFormFrom, getForm, getForms, submitAnswer } from './form'
|
||||||
|
import { findUserBy } from './user'
|
||||||
|
|
||||||
|
export {
|
||||||
|
checkRightsAndResolve,
|
||||||
|
createFormFrom,
|
||||||
|
findUserBy,
|
||||||
|
genAndSendToken,
|
||||||
|
getForm,
|
||||||
|
getFormAuthor,
|
||||||
|
getForms,
|
||||||
|
submitAnswer,
|
||||||
|
}
|
21
src/controllers/mailer.ts
Normal file
21
src/controllers/mailer.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import sgMail from '@sendgrid/mail'
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') require('dotenv').config()
|
||||||
|
|
||||||
|
sgMail.setApiKey('' + process.env.SENDGRID_API_KEY)
|
||||||
|
|
||||||
|
const sendToken = (username: string, email: string, token: string) => {
|
||||||
|
return sgMail.send({
|
||||||
|
dynamicTemplateData: {
|
||||||
|
siteUrl: process.env.SITE_URL,
|
||||||
|
token: token,
|
||||||
|
username: username,
|
||||||
|
},
|
||||||
|
from: 'me@dmitriy.icu',
|
||||||
|
subject: 'Login link',
|
||||||
|
templateId: 'd-a9275a4437bf4dd2b9e858f3a57f85d5',
|
||||||
|
to: email,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export { sendToken }
|
52
src/controllers/types.ts
Normal file
52
src/controllers/types.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { ChoiseType } from '@prisma/client'
|
||||||
|
import {
|
||||||
|
ChoisesQuestion,
|
||||||
|
InputQuestion,
|
||||||
|
Variant,
|
||||||
|
} from '../typeDefs/typeDefs.gen'
|
||||||
|
import { JwtPayloadType } from '../types'
|
||||||
|
|
||||||
|
type ExpectedType = {
|
||||||
|
id: number
|
||||||
|
self: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICheckRightsAndResolve<T> {
|
||||||
|
controller: T
|
||||||
|
expected: ExpectedType
|
||||||
|
user: JwtPayloadType | null
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckRightsAndResolve = <ReturnType, ControllerType extends Function>(
|
||||||
|
params: ICheckRightsAndResolve<ControllerType>
|
||||||
|
) => Promise<ReturnType>
|
||||||
|
|
||||||
|
type FormConstructor = {
|
||||||
|
choisesQuestions: { create: CreateChoises[] }
|
||||||
|
inputQuestions: { create: InputQuestion[] }
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateChoises = Omit<ChoisesQuestion, 'variants'> & {
|
||||||
|
variants: { create: Variant[] }
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadedQuestion = {
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadedChoisesQuestion = UploadedQuestion & {
|
||||||
|
type: ChoiseType
|
||||||
|
variants: Variant[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadedInputQuestion = UploadedQuestion
|
||||||
|
|
||||||
|
export {
|
||||||
|
CheckRightsAndResolve,
|
||||||
|
CreateChoises,
|
||||||
|
FormConstructor,
|
||||||
|
UploadedChoisesQuestion,
|
||||||
|
UploadedInputQuestion,
|
||||||
|
UploadedQuestion,
|
||||||
|
}
|
59
src/controllers/user.ts
Normal file
59
src/controllers/user.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { createDBUser, findDBUserBy } from '../db'
|
||||||
|
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,
|
||||||
|
{ email, name }: MutationRegisterArgs
|
||||||
|
): Promise<User> => {
|
||||||
|
try {
|
||||||
|
if (!email || !name)
|
||||||
|
throw new UserInputError(
|
||||||
|
'Provide full user information',
|
||||||
|
[!email ? [email] : [], !name ? [name] : []].flat()
|
||||||
|
)
|
||||||
|
|
||||||
|
const newUser = await createDBUser(db, { email, name })
|
||||||
|
|
||||||
|
if (!newUser)
|
||||||
|
throw new ApolloError("Couldn't create user", 'USERCREATIONERROR')
|
||||||
|
|
||||||
|
return newUser
|
||||||
|
} catch (err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const findUserBy = async (
|
||||||
|
db: PrismaClient,
|
||||||
|
params: IFindUserParams
|
||||||
|
): Promise<User> => {
|
||||||
|
try {
|
||||||
|
const dbUser = await findDBUserBy(db, params)
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createUser, findUserBy }
|
127
src/controllers/validate.ts
Normal file
127
src/controllers/validate.ts
Normal 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 }
|
209
src/db/index.ts
Normal file
209
src/db/index.ts
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
|
import { Answer, MutationRegisterArgs } from '../typeDefs/typeDefs.gen'
|
||||||
|
import { IFindUserParams } from './types'
|
||||||
|
import { FormConstructor } from '../controllers/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = (
|
||||||
|
db: PrismaClient,
|
||||||
|
formId: number,
|
||||||
|
{
|
||||||
|
requesterId,
|
||||||
|
ownerId: ownerId,
|
||||||
|
}: {
|
||||||
|
requesterId: number
|
||||||
|
ownerId: number
|
||||||
|
}
|
||||||
|
) =>
|
||||||
|
db.form.findOne({
|
||||||
|
include: {
|
||||||
|
author: {
|
||||||
|
select: {
|
||||||
|
email: true,
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
choisesQuestions: {
|
||||||
|
include: {
|
||||||
|
variants: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputQuestions: true,
|
||||||
|
submissions: {
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
answers: true,
|
||||||
|
},
|
||||||
|
where:
|
||||||
|
requesterId != ownerId
|
||||||
|
? {
|
||||||
|
user: {
|
||||||
|
id: requesterId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: formId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = (db: PrismaClient, id: number) =>
|
||||||
|
db.form.findMany({
|
||||||
|
include: {
|
||||||
|
choisesQuestions: {
|
||||||
|
include: {
|
||||||
|
variants: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputQuestions: true,
|
||||||
|
submissions: {
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
answers: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
author: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const getDBFormAuthor = (db: PrismaClient, id: number) =>
|
||||||
|
db.form.findOne({
|
||||||
|
select: {
|
||||||
|
author: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const createDBUser = (
|
||||||
|
db: PrismaClient,
|
||||||
|
{ email, name }: MutationRegisterArgs
|
||||||
|
) =>
|
||||||
|
db.user.create({
|
||||||
|
data: { email, name },
|
||||||
|
})
|
||||||
|
|
||||||
|
const findDBUserBy = (db: PrismaClient, params: IFindUserParams) =>
|
||||||
|
db.user.findOne({
|
||||||
|
where: {
|
||||||
|
...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 },
|
||||||
|
},
|
||||||
|
...form,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const submitDBAnswer = (
|
||||||
|
db: PrismaClient,
|
||||||
|
userId: number,
|
||||||
|
formId: number,
|
||||||
|
formAnswers: Answer[]
|
||||||
|
) =>
|
||||||
|
db.formSubmission.create({
|
||||||
|
data: {
|
||||||
|
answers: {
|
||||||
|
create: formAnswers,
|
||||||
|
},
|
||||||
|
Form: {
|
||||||
|
connect: {
|
||||||
|
id: formId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
connect: {
|
||||||
|
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 {
|
||||||
|
createDBForm,
|
||||||
|
createDBUser,
|
||||||
|
findDBUserBy,
|
||||||
|
getDBForm,
|
||||||
|
getDBFormAuthor,
|
||||||
|
getDBFormsByUser,
|
||||||
|
submitDBAnswer,
|
||||||
|
getDBFormToSubmit,
|
||||||
|
}
|
12
src/db/types.ts
Normal file
12
src/db/types.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { PromiseReturnType } from '@prisma/client'
|
||||||
|
|
||||||
|
import { getDBForm } from '../db'
|
||||||
|
|
||||||
|
type FullForm = PromiseReturnType<typeof getDBForm>
|
||||||
|
|
||||||
|
interface IFindUserParams {
|
||||||
|
email?: string
|
||||||
|
id?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export { FullForm, IFindUserParams }
|
62
src/index.ts
62
src/index.ts
@ -1,14 +1,58 @@
|
|||||||
import { GraphQLServer } from "graphql-yoga"
|
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'
|
||||||
|
|
||||||
const typeDefs = ``
|
if (process.env.NODE_ENV === 'development') require('dotenv').config()
|
||||||
|
|
||||||
const resolvers = {}
|
const app = express()
|
||||||
|
|
||||||
const server = new GraphQLServer({
|
app.use(
|
||||||
typeDefs,
|
expressJwt({
|
||||||
resolvers,
|
algorithms: ['HS256'],
|
||||||
|
credentialsRequired: false,
|
||||||
|
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: Request & {
|
||||||
|
user: JwtPayloadType
|
||||||
|
}
|
||||||
|
}): Promise<ApolloContextType> => {
|
||||||
|
const user = req.user || null
|
||||||
|
return {
|
||||||
|
db,
|
||||||
|
user,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
debug: false,
|
||||||
|
schema: makeExecutableSchema({
|
||||||
|
resolvers,
|
||||||
|
typeDefs,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
server.start(({ port }) =>
|
server.applyMiddleware({ app })
|
||||||
console.log(`Server is running on http://localhost:${port}`)
|
|
||||||
)
|
const port = process.env.BACKEND_PORT || 4000
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server ready at http://localhost:${port}`)
|
||||||
|
})
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
136
src/resolvers/Form.ts
Normal file
136
src/resolvers/Form.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import {
|
||||||
|
AnswerResolvers,
|
||||||
|
Form,
|
||||||
|
MutationCreateFormArgs,
|
||||||
|
MutationFormSubmitArgs,
|
||||||
|
QueryFormArgs,
|
||||||
|
QuestionResolvers,
|
||||||
|
Resolver,
|
||||||
|
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 (
|
||||||
|
_,
|
||||||
|
{ id },
|
||||||
|
{ db, user }
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const ownerId = await getFormAuthor(db, id)
|
||||||
|
|
||||||
|
const getFormById = (requesterId: number) =>
|
||||||
|
getForm(db, id, { requesterId, ownerId })
|
||||||
|
|
||||||
|
return await checkRightsAndResolve({
|
||||||
|
controller: getFormById,
|
||||||
|
expected: {
|
||||||
|
id: 0,
|
||||||
|
self: true,
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formsQuery: Resolver<Form[], {}, ApolloContextType> = async (
|
||||||
|
_,
|
||||||
|
__,
|
||||||
|
{ db, user }
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const getFormsByUserId = (userId: number) => getForms(db, userId)
|
||||||
|
|
||||||
|
return await checkRightsAndResolve<
|
||||||
|
Form[],
|
||||||
|
(userId: number) => Promise<Form[] | null>
|
||||||
|
>({
|
||||||
|
controller: getFormsByUserId,
|
||||||
|
expected: {
|
||||||
|
id: 0,
|
||||||
|
self: true,
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createFormMutation: Resolver<
|
||||||
|
ServerAnswer,
|
||||||
|
{},
|
||||||
|
ApolloContextType,
|
||||||
|
MutationCreateFormArgs
|
||||||
|
> = async (_, params, { db, user }) => {
|
||||||
|
const createNewForm = (id: number) => createFormFrom(db, params, id)
|
||||||
|
|
||||||
|
return await checkRightsAndResolve<
|
||||||
|
ServerAnswer,
|
||||||
|
(id: number) => Promise<ServerAnswer>
|
||||||
|
>({
|
||||||
|
controller: createNewForm,
|
||||||
|
expected: {
|
||||||
|
id: 0,
|
||||||
|
self: true,
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const formSubmitMutation: Resolver<
|
||||||
|
ServerAnswer,
|
||||||
|
{},
|
||||||
|
ApolloContextType,
|
||||||
|
MutationFormSubmitArgs
|
||||||
|
> = async (_, params, { db, user }) => {
|
||||||
|
const submitNewAnswer = (userId: number) => submitAnswer(db, params, userId)
|
||||||
|
|
||||||
|
return await checkRightsAndResolve<
|
||||||
|
ServerAnswer,
|
||||||
|
(userId: number) => Promise<ServerAnswer>
|
||||||
|
>({
|
||||||
|
controller: submitNewAnswer,
|
||||||
|
expected: {
|
||||||
|
id: 0,
|
||||||
|
self: true,
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const QuestionResolver: QuestionResolvers = {
|
||||||
|
__resolveType(obj: any) {
|
||||||
|
if (obj.type) {
|
||||||
|
return 'ChoisesQuestion'
|
||||||
|
}
|
||||||
|
return 'InputQuestion'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnswerResolver: AnswerResolvers = {
|
||||||
|
__resolveType(obj) {
|
||||||
|
if (obj.type == 'CHOISE') return 'ChoiseAnswer'
|
||||||
|
if (obj.type == 'INPUT') return 'InputAnswer'
|
||||||
|
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
AnswerResolver,
|
||||||
|
createFormMutation,
|
||||||
|
formQuery,
|
||||||
|
formsQuery,
|
||||||
|
formSubmitMutation,
|
||||||
|
QuestionResolver,
|
||||||
|
}
|
76
src/resolvers/User.ts
Normal file
76
src/resolvers/User.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import {
|
||||||
|
checkRightsAndResolve,
|
||||||
|
findUserBy,
|
||||||
|
genAndSendToken,
|
||||||
|
} from '../controllers'
|
||||||
|
import { createUser } from '../controllers/user'
|
||||||
|
import {
|
||||||
|
MutationLoginArgs,
|
||||||
|
MutationRegisterArgs,
|
||||||
|
Resolver,
|
||||||
|
ServerAnswer,
|
||||||
|
User,
|
||||||
|
QueryUserArgs,
|
||||||
|
} from '../typeDefs/typeDefs.gen'
|
||||||
|
import { ApolloContextType } from '../types'
|
||||||
|
|
||||||
|
const loginMutation: Resolver<
|
||||||
|
ServerAnswer,
|
||||||
|
{},
|
||||||
|
ApolloContextType,
|
||||||
|
MutationLoginArgs
|
||||||
|
> = async (_, { email }, { db }) => {
|
||||||
|
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 }
|
||||||
|
} catch (err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerMutation: Resolver<
|
||||||
|
ServerAnswer,
|
||||||
|
{},
|
||||||
|
ApolloContextType,
|
||||||
|
MutationRegisterArgs
|
||||||
|
> = async (_, { email, name }, { db }) => {
|
||||||
|
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 }
|
||||||
|
} catch (err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userQuery: Resolver<User, {}, ApolloContextType, QueryUserArgs> = async (
|
||||||
|
_,
|
||||||
|
{ id },
|
||||||
|
{ db, user }
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const findUserById = (id: number) => findUserBy(db, { id })
|
||||||
|
|
||||||
|
return await checkRightsAndResolve({
|
||||||
|
controller: findUserById,
|
||||||
|
expected: {
|
||||||
|
id: id || 0,
|
||||||
|
self: true,
|
||||||
|
},
|
||||||
|
user,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { loginMutation, registerMutation, userQuery }
|
32
src/resolvers/index.ts
Normal file
32
src/resolvers/index.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Resolvers } from '../typeDefs/typeDefs.gen'
|
||||||
|
import {
|
||||||
|
formQuery as form,
|
||||||
|
QuestionResolver as Question,
|
||||||
|
AnswerResolver as Answer,
|
||||||
|
formsQuery as forms,
|
||||||
|
createFormMutation as createForm,
|
||||||
|
formSubmitMutation as formSubmit,
|
||||||
|
} from './Form'
|
||||||
|
import {
|
||||||
|
loginMutation as login,
|
||||||
|
registerMutation as register,
|
||||||
|
userQuery as user,
|
||||||
|
} from './User'
|
||||||
|
|
||||||
|
const resolvers: Resolvers = {
|
||||||
|
Query: {
|
||||||
|
form,
|
||||||
|
forms,
|
||||||
|
user,
|
||||||
|
},
|
||||||
|
Mutation: {
|
||||||
|
login,
|
||||||
|
register,
|
||||||
|
createForm,
|
||||||
|
formSubmit,
|
||||||
|
},
|
||||||
|
Question,
|
||||||
|
Answer,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default resolvers
|
8
src/typeDefs/index.ts
Normal file
8
src/typeDefs/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import fs from 'fs'
|
||||||
|
import { gql } from 'apollo-server-express'
|
||||||
|
|
||||||
|
const typeDefs = gql(
|
||||||
|
fs.readFileSync(__dirname.concat('/typeDefs.gql'), { encoding: 'utf-8' })
|
||||||
|
)
|
||||||
|
|
||||||
|
export default typeDefs
|
87
src/typeDefs/typeDefs.gql
Normal file
87
src/typeDefs/typeDefs.gql
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
type Query {
|
||||||
|
form(id: Int!): Form
|
||||||
|
forms: [Form!]!
|
||||||
|
user(id: Int): User
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
createForm(title: String!, questions: String!): serverAnswer
|
||||||
|
formSubmit(formId: Int!, answers: String!): serverAnswer
|
||||||
|
login(email: String!): serverAnswer
|
||||||
|
register(name: String!, email: String!): serverAnswer
|
||||||
|
}
|
||||||
|
|
||||||
|
type Form {
|
||||||
|
author: User
|
||||||
|
dateCreated: String!
|
||||||
|
id: Int!
|
||||||
|
questions: [Question!]
|
||||||
|
submissions: [FormSubmission!]
|
||||||
|
title: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Question {
|
||||||
|
number: Int!
|
||||||
|
title: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChoisesQuestion implements Question {
|
||||||
|
number: Int!
|
||||||
|
title: String!
|
||||||
|
type: ChoiseType!
|
||||||
|
variants: [Variant!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Variant {
|
||||||
|
text: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type InputQuestion implements Question {
|
||||||
|
number: Int!
|
||||||
|
title: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormSubmission {
|
||||||
|
user: User
|
||||||
|
answers: [Answer!]!
|
||||||
|
date: String!
|
||||||
|
id: Int!
|
||||||
|
form: Form
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Answer {
|
||||||
|
type: AnswerType!
|
||||||
|
}
|
||||||
|
|
||||||
|
type InputAnswer implements Answer {
|
||||||
|
type: AnswerType!
|
||||||
|
userInput: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChoiseAnswer implements Answer {
|
||||||
|
type: AnswerType!
|
||||||
|
userChoise: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ChoiseType {
|
||||||
|
CHECK
|
||||||
|
CHOOSE
|
||||||
|
SELECT
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AnswerType {
|
||||||
|
CHOISE
|
||||||
|
INPUT
|
||||||
|
}
|
||||||
|
|
||||||
|
type User {
|
||||||
|
email: String!
|
||||||
|
forms: [Form!]
|
||||||
|
id: Int!
|
||||||
|
name: String!
|
||||||
|
formSubmissions: [FormSubmission!]
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverAnswer {
|
||||||
|
success: Boolean!
|
||||||
|
}
|
11
src/types.ts
Normal file
11
src/types.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
|
export type ApolloContextType = {
|
||||||
|
db: PrismaClient
|
||||||
|
user: JwtPayloadType | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JwtPayloadType = {
|
||||||
|
id: number
|
||||||
|
email: string
|
||||||
|
}
|
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||||
|
"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/**/*"]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user