├── src
├── graphql
│ ├── resolvers
│ │ ├── index.ts
│ │ ├── UserResolver.ts
│ │ └── AuthResolver.ts
│ ├── schema.gql
│ ├── index.ts
│ └── builder.ts
├── @types
│ └── sessions.d.ts
├── pages
│ ├── index.tsx
│ ├── _app.tsx
│ ├── authenticated.tsx
│ ├── __generated__
│ │ └── authenticated.generated.tsx
│ └── api
│ │ └── graphql.tsx
├── styles
│ └── globals.css
├── components
│ ├── Test.tsx
│ └── __generated__
│ │ └── Test.generated.tsx
├── utils
│ ├── apolloClient.ts
│ ├── prisma.ts
│ ├── unAuthenticatedRoute.ts
│ ├── authenticatedRoute.ts
│ ├── auth.ts
│ └── sessions.ts
└── __generated__
│ └── generated.d.ts
├── .prettierrc
├── next-env.d.ts
├── .babelrc
├── public
├── favicon.ico
└── vercel.svg
├── graphql.config.js
├── prisma
├── migrations
│ ├── migration_lock.toml
│ ├── 20210428094940_initial_migration
│ │ └── migration.sql
│ └── 20210429215940_new_user
│ │ └── migration.sql
└── schema.prisma
├── apollo.config.js
├── .gitignore
├── codegen.yml
├── .env
├── tsconfig.json
├── README.md
├── package.json
└── graphql.schema.json
/src/graphql/resolvers/index.ts:
--------------------------------------------------------------------------------
1 | import "./UserResolver";
2 | import "./AuthResolver";
3 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false,
4 | "printWidth": 80
5 | }
6 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"],
3 | "plugins": [
4 | "superjson-next" // 👈
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kush-daga/Next-Prisma-GraphQL-Template/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/graphql.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | schema: "./src/graphql/schema.gql",
3 | documents: "src/**/*.{graphql,js,ts,jsx,tsx}",
4 | };
5 |
--------------------------------------------------------------------------------
/src/@types/sessions.d.ts:
--------------------------------------------------------------------------------
1 | declare module "http" {
2 | interface IncomingMessage {
3 | session: import("next-iron-session").Session;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (i.e. Git)
3 | provider = "postgresql"
--------------------------------------------------------------------------------
/apollo.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | client: {
3 | includes: ["./src/**/*.tsx"],
4 | service: {
5 | name: "testing-prisma",
6 | localSchemaFile: "./src/graphql/schema.gql",
7 | },
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/prisma/migrations/20210428094940_initial_migration/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "User" (
3 | "id" TEXT NOT NULL,
4 | "firstName" TEXT NOT NULL,
5 | "lastName" TEXT NOT NULL,
6 | "email" TEXT NOT NULL,
7 | "photoUrl" TEXT NOT NULL,
8 |
9 | PRIMARY KEY ("id")
10 | );
11 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Head from "next/head";
2 |
3 | export default function Home() {
4 | return (
5 |
6 |
7 |
Create Next App
8 |
9 |
10 |
Hello world
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/src/graphql/resolvers/UserResolver.ts:
--------------------------------------------------------------------------------
1 | import { User } from "@prisma/client";
2 | import { builder } from "../builder";
3 |
4 | export const UserObject = builder.objectRef("User").implement({
5 | fields: t => ({
6 | id: t.exposeID("id", {}),
7 | name: t.exposeString("name", {}),
8 | email: t.exposeString("email", {})
9 | })
10 | });
11 |
--------------------------------------------------------------------------------
/src/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/src/components/Test.tsx:
--------------------------------------------------------------------------------
1 | //Just for testing need to change to apollo-client later..
2 | import { gql } from "apollo-server-core";
3 | import { UserQuery } from "./__generated__/Test.generated";
4 |
5 | export const Test = () => {
6 | const userQuery = gql`
7 | query User {
8 | me {
9 | id
10 | }
11 | }
12 | `;
13 |
14 | return Hello
;
15 | };
16 |
--------------------------------------------------------------------------------
/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { AppProps } from "next/dist/next-server/lib/router/router";
2 | import "../styles/globals.css";
3 | import { client } from "../utils/apolloClient";
4 | import { ApolloProvider } from "@apollo/client/react";
5 |
6 | function MyApp({ Component, pageProps }: AppProps) {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
14 | export default MyApp;
15 |
--------------------------------------------------------------------------------
/src/graphql/schema.gql:
--------------------------------------------------------------------------------
1 | scalar DateTime
2 |
3 | input LoginInput {
4 | email: String!
5 | password: String!
6 | }
7 |
8 | type Mutation {
9 | login(input: LoginInput!): User!
10 | logout: Boolean!
11 | signUp(input: SignUpInput!): User!
12 | }
13 |
14 | type Query {
15 | me: User
16 | }
17 |
18 | input SignUpInput {
19 | email: String!
20 | name: String!
21 | password: String!
22 | }
23 |
24 | type User {
25 | email: String!
26 | id: ID!
27 | name: String!
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/apolloClient.ts:
--------------------------------------------------------------------------------
1 | import { ApolloClient, InMemoryCache } from "@apollo/client";
2 |
3 | const cache = new InMemoryCache();
4 |
5 | export const client = new ApolloClient({
6 | // Provide required constructor fields
7 | cache: cache,
8 | uri: "/api/graphql",
9 | ssrMode: true,
10 | // Provide some optional constructor fields
11 | name: "testing-prisma",
12 | version: "1.0",
13 | defaultOptions: {
14 | watchQuery: {
15 | fetchPolicy: "cache-and-network"
16 | }
17 | }
18 | });
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/src/graphql/index.ts:
--------------------------------------------------------------------------------
1 | import { printSchema, lexicographicSortSchema } from "graphql";
2 | import fs from "fs";
3 | import path from "path";
4 | import { builder } from "../../src/graphql/builder";
5 | import "./resolvers";
6 |
7 | const schema = builder.toSchema({});
8 | const schemaAsString = printSchema(lexicographicSortSchema(schema));
9 |
10 | if (process.env.NODE_ENV === "development") {
11 | fs.writeFileSync(
12 | path.join(process.cwd(), "src/graphql/schema.gql"),
13 | schemaAsString
14 | );
15 | }
16 |
17 | export default schema;
18 |
--------------------------------------------------------------------------------
/codegen.yml:
--------------------------------------------------------------------------------
1 | overwrite: true
2 | schema: "src/graphql/schema.gql"
3 | documents: "./src/**/!(*.generated).{graphql,js,ts,jsx,tsx}"
4 | generates:
5 | src/__generated__/generated.d.ts:
6 | - "typescript"
7 | src/:
8 | preset: near-operation-file
9 | presetConfig:
10 | extension: .generated.tsx
11 | baseTypesPath: /__generated__/generated.d.ts
12 | folder: __generated__
13 | plugins:
14 | - "typescript"
15 | - "typescript-operations"
16 | ./graphql.schema.json:
17 | plugins:
18 | - "introspection"
19 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # Environment variables declared in this file are automatically made available to Prisma.
2 | # See the documentation for more detail: https://pris.ly/d/prisma-schema#using-environment-variables
3 |
4 | # Prisma supports the native connection string format for PostgreSQL, MySQL and SQLite.
5 | # See the documentation for all the connection string options: https://pris.ly/d/connection-strings
6 |
7 | DATABASE_URL="postgresql://postgres:password@localhost:5432/tryprisma?schema=public"
8 | COOKIE_SECRET="sthgsibetterthannthgsothisisarandomasspasswordwithtoomanycharacters"
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve"
16 | },
17 | "include": [
18 | "next-env.d.ts",
19 | "**/*.ts",
20 | "**/*.tsx",
21 | "src/pages/api/graphql.js"
22 | ],
23 | "exclude": ["node_modules"]
24 | }
25 |
--------------------------------------------------------------------------------
/src/utils/prisma.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client";
2 | interface CustomNodeJsGlobal extends NodeJS.Global {
3 | prisma: PrismaClient;
4 | }
5 | // PrismaClient is attached to the `global` object in development to prevent
6 | // exhausting your database connection limit.
7 | //
8 | // Learn more:
9 | // https://pris.ly/d/help/next-js-best-practices
10 |
11 | let prisma: PrismaClient;
12 | declare const global: CustomNodeJsGlobal;
13 |
14 | if (process.env.NODE_ENV === "production") {
15 | prisma = new PrismaClient();
16 | } else {
17 | if (!global.prisma) {
18 | global.prisma = new PrismaClient();
19 | }
20 | prisma = global.prisma;
21 | }
22 | export default prisma;
23 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | datasource db {
5 | provider = "postgresql"
6 | url = env("DATABASE_URL")
7 | }
8 |
9 | generator client {
10 | provider = "prisma-client-js"
11 | }
12 |
13 | model User {
14 | id String @id @default(cuid())
15 | name String
16 | email String @unique
17 | Session Session[]
18 | hashedPassword Bytes
19 | }
20 |
21 | model Session {
22 | id String @id @default(cuid())
23 | createdAt DateTime @default(now())
24 | updatedAt DateTime @updatedAt
25 | user User @relation(fields: [userId], references: [id])
26 | userId String
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/unAuthenticatedRoute.ts:
--------------------------------------------------------------------------------
1 | import { withIronSession } from "next-iron-session";
2 | import { sessionOptions } from "./sessions";
3 | import { GetServerSidePropsContext } from "next";
4 |
5 | import prisma from "./prisma";
6 |
7 | export const unAuthenticatedRoute = withIronSession(
8 | async ({ req, res }: GetServerSidePropsContext) => {
9 | const sessionID = req.session.get("sessionID");
10 |
11 | const session = await prisma.session.findUnique({
12 | where: { id: sessionID }
13 | });
14 |
15 | if (session) {
16 | res.setHeader("location", "/");
17 | res.statusCode = 302;
18 | res.end();
19 | return { props: { session } };
20 | }
21 |
22 | return {
23 | props: {}
24 | };
25 | },
26 | sessionOptions
27 | );
28 |
--------------------------------------------------------------------------------
/src/utils/authenticatedRoute.ts:
--------------------------------------------------------------------------------
1 | import { withIronSession } from "next-iron-session";
2 | import { sessionOptions } from "./sessions";
3 | import { GetServerSidePropsContext } from "next";
4 |
5 | import prisma from "./prisma";
6 |
7 | export const authenticatedRoute = withIronSession(
8 | async ({ req, res }: GetServerSidePropsContext) => {
9 | const sessionID = req.session.get("sessionID");
10 |
11 | const session = await prisma.session.findUnique({
12 | where: { id: sessionID }
13 | });
14 |
15 | if (!session) {
16 | res.setHeader("location", "/login");
17 | res.statusCode = 302;
18 | res.end();
19 | return { props: {} };
20 | }
21 |
22 | return {
23 | props: { session }
24 | };
25 | },
26 | sessionOptions
27 | );
28 |
--------------------------------------------------------------------------------
/src/pages/authenticated.tsx:
--------------------------------------------------------------------------------
1 | import { GetServerSideProps } from "next";
2 | import { authenticatedRoute } from "../utils/authenticatedRoute";
3 | import { Session } from "@prisma/client";
4 | import { gql, useQuery } from "@apollo/client";
5 | import { MeQuery } from "./__generated__/authenticated.generated";
6 |
7 | export const getServerSideProps: GetServerSideProps = authenticatedRoute;
8 |
9 | export default function Home({ session }: { session: Session }) {
10 | const query = gql`
11 | query Me {
12 | me {
13 | id
14 | email
15 | name
16 | }
17 | }
18 | `;
19 |
20 | const { loading, data, error } = useQuery(query);
21 |
22 | return (
23 |
24 | {loading && (
25 |
26 | Loadinggg
27 |
28 |
29 | )}
30 |
31 | {data && (
32 |
33 | Hello {data?.me?.name}, You are logged in as {data?.me?.email}
34 |
35 | )}
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Ive been working on nextjs for quite some time, and decided to make a template for a fullstack NextJs application, this template includes _Full GraphQL + TypeScript Support_ using _GiraphQL_ as the code first schema builder solution (https://giraphql.com) with _GraphQL CodeGen_ end to end setup for full type safety and uses Prisma as the ORM to talk to _PostgreSQL_ based db. It also has codegen plugins installed which will generate individual types for every file or component in your application (so queries written in the frontend will also have full type support). This is I believe quite scalable and I have set up session based auth using _next-iron-session_ which makes it super easy to manage sessions and also having a shared context between the requests. I hope this will be useful to you guys, and I will be updating it alot, so keep an eye for commits, will add more authentication and also add apollo-client in the frontend along with some demo pages styled with _TailwindCss_.
2 | Do star and fork if you find it helpful.
3 | https://github.com/kush-daga/Next-Prisma-GraphQL-Template
4 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/graphql/builder.ts:
--------------------------------------------------------------------------------
1 | import SchemaBuilder from "@giraphql/core";
2 | import { User, Session } from "@prisma/client";
3 | import { IncomingMessage, OutgoingMessage } from "http";
4 | import ScopeAuthPlugin from "@giraphql/plugin-scope-auth";
5 | import ValidationPlugin from "@giraphql/plugin-validation";
6 |
7 | export const builder = new SchemaBuilder<{
8 | DefaultInputFieldRequiredness: true;
9 | Context: {
10 | req: IncomingMessage;
11 | res: OutgoingMessage;
12 | user?: User | null;
13 | session?: Session | null;
14 | };
15 | Scalars: {
16 | DateTime: { Input: Date; Output: Date };
17 | };
18 | AuthScopes: {
19 | user: boolean;
20 | isUnauthenticated: boolean;
21 | };
22 | }>({
23 | defaultInputFieldRequiredness: true,
24 | plugins: [ScopeAuthPlugin, ValidationPlugin],
25 | authScopes: async context => ({
26 | user: !!context.user,
27 | isUnauthenticated: !context.user
28 | })
29 | });
30 |
31 | builder.queryType({});
32 | builder.mutationType({});
33 |
34 | builder.scalarType("DateTime", {
35 | serialize: date => date.toISOString(),
36 | parseValue: date => {
37 | return new Date(date);
38 | }
39 | });
40 |
--------------------------------------------------------------------------------
/src/__generated__/generated.d.ts:
--------------------------------------------------------------------------------
1 | export type Maybe = T | null;
2 | export type Exact = { [K in keyof T]: T[K] };
3 | export type MakeOptional = Omit & { [SubKey in K]?: Maybe };
4 | export type MakeMaybe = Omit & { [SubKey in K]: Maybe };
5 | /** All built-in and custom scalars, mapped to their actual values */
6 | export type Scalars = {
7 | ID: string;
8 | String: string;
9 | Boolean: boolean;
10 | Int: number;
11 | Float: number;
12 | DateTime: any;
13 | };
14 |
15 |
16 | export type LoginInput = {
17 | email: Scalars['String'];
18 | password: Scalars['String'];
19 | };
20 |
21 | export type Mutation = {
22 | __typename?: 'Mutation';
23 | login: User;
24 | logout: Scalars['Boolean'];
25 | signUp: User;
26 | };
27 |
28 |
29 | export type MutationLoginArgs = {
30 | input: LoginInput;
31 | };
32 |
33 |
34 | export type MutationSignUpArgs = {
35 | input: SignUpInput;
36 | };
37 |
38 | export type Query = {
39 | __typename?: 'Query';
40 | me?: Maybe;
41 | };
42 |
43 | export type SignUpInput = {
44 | email: Scalars['String'];
45 | name: Scalars['String'];
46 | password: Scalars['String'];
47 | };
48 |
49 | export type User = {
50 | __typename?: 'User';
51 | email: Scalars['String'];
52 | id: Scalars['ID'];
53 | name: Scalars['String'];
54 | };
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "testing-prisma",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "generate": "graphql-codegen --config codegen.yml --watch"
10 | },
11 | "dependencies": {
12 | "@apollo/client": "^3.3.16",
13 | "@giraphql/core": "^2.1.0",
14 | "@giraphql/plugin-scope-auth": "2.0.2",
15 | "@giraphql/plugin-validation": "1.2.0",
16 | "@prisma/client": "^2.21.2",
17 | "apollo-server-micro": "^2.23.0",
18 | "graphql": "^15.5.0",
19 | "graphql-helix": "^1.4.0",
20 | "next": "10.1.3",
21 | "next-iron-session": "^4.1.13",
22 | "react": "17.0.2",
23 | "react-dom": "17.0.2",
24 | "secure-password": "^4.0.0",
25 | "superjson": "^1.7.4",
26 | "zod": "^1.11.16"
27 | },
28 | "devDependencies": {
29 | "@graphql-codegen/cli": "1.21.4",
30 | "@graphql-codegen/introspection": "1.18.2",
31 | "@graphql-codegen/near-operation-file-preset": "^1.18.0",
32 | "@graphql-codegen/typescript": "1.22.0",
33 | "@graphql-codegen/typescript-document-nodes": "1.17.11",
34 | "@graphql-codegen/typescript-graphql-files-modules": "1.18.1",
35 | "@graphql-codegen/typescript-operations": "1.17.16",
36 | "@types/node": "^15.0.1",
37 | "@types/react": "^17.0.4",
38 | "@types/secure-password": "^3.1.0",
39 | "babel-plugin-superjson-next": "^0.2.3",
40 | "prisma": "^2.21.2",
41 | "typescript": "^4.2.4"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/prisma/migrations/20210429215940_new_user/migration.sql:
--------------------------------------------------------------------------------
1 | /*
2 | Warnings:
3 |
4 | - You are about to drop the column `firstName` on the `User` table. All the data in the column will be lost.
5 | - You are about to drop the column `lastName` on the `User` table. All the data in the column will be lost.
6 | - You are about to drop the column `photoUrl` on the `User` table. All the data in the column will be lost.
7 | - A unique constraint covering the columns `[email]` on the table `User` will be added. If there are existing duplicate values, this will fail.
8 | - Added the required column `name` to the `User` table without a default value. This is not possible if the table is not empty.
9 | - Added the required column `hashedPassword` to the `User` table without a default value. This is not possible if the table is not empty.
10 |
11 | */
12 | -- AlterTable
13 | ALTER TABLE "User" DROP COLUMN "firstName",
14 | DROP COLUMN "lastName",
15 | DROP COLUMN "photoUrl",
16 | ADD COLUMN "name" TEXT NOT NULL,
17 | ADD COLUMN "hashedPassword" BYTEA NOT NULL;
18 |
19 | -- CreateTable
20 | CREATE TABLE "Session" (
21 | "id" TEXT NOT NULL,
22 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
23 | "updatedAt" TIMESTAMP(3) NOT NULL,
24 | "userId" TEXT NOT NULL,
25 |
26 | PRIMARY KEY ("id")
27 | );
28 |
29 | -- CreateIndex
30 | CREATE UNIQUE INDEX "User.email_unique" ON "User"("email");
31 |
32 | -- AddForeignKey
33 | ALTER TABLE "Session" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
34 |
--------------------------------------------------------------------------------
/src/utils/auth.ts:
--------------------------------------------------------------------------------
1 | import prisma from "./prisma";
2 | import SecurePassword from "secure-password";
3 |
4 | const securePassword = new SecurePassword();
5 |
6 | export const hashPassword = async (password: string) => {
7 | try {
8 | return await securePassword.hash(Buffer.from(password));
9 | } catch (err) {
10 | throw new Error("Error in hash password" + err.message);
11 | }
12 | };
13 |
14 | export const verifyPassword = async (
15 | hashedPassword: Buffer,
16 | password: string
17 | ) => {
18 | try {
19 | return await securePassword.verify(Buffer.from(password), hashedPassword);
20 | } catch (error) {
21 | console.log("ERROR IN VERIFY PASS", error);
22 | return SecurePassword.INVALID;
23 | }
24 | };
25 |
26 | export const authenticateUser = async (email: string, password: string) => {
27 | const user = await prisma.user.findUnique({
28 | where: {
29 | email: email
30 | }
31 | });
32 |
33 | if (!user || !user.hashedPassword) {
34 | throw new Error("Email not found");
35 | }
36 | let isVerified;
37 | try {
38 | isVerified = await verifyPassword(user.hashedPassword, password);
39 | } catch (err) {
40 | console.error("ERROR" + err.message);
41 | }
42 |
43 | switch (isVerified) {
44 | case SecurePassword.VALID:
45 | break;
46 | case SecurePassword.VALID_NEEDS_REHASH:
47 | console.log("here");
48 | const newHash = await hashPassword(password);
49 | await prisma.user.update({
50 | where: { id: user.id },
51 | data: {
52 | hashedPassword: newHash
53 | }
54 | });
55 | break;
56 | default:
57 | throw new Error("Invalid Password");
58 | }
59 |
60 | return user;
61 | };
62 |
--------------------------------------------------------------------------------
/src/utils/sessions.ts:
--------------------------------------------------------------------------------
1 | import { Session, User } from "@prisma/client";
2 | import { applySession, SessionOptions } from "next-iron-session";
3 | import { IncomingMessage } from "http";
4 | import { GetServerSidePropsContext } from "next";
5 |
6 | import prisma from "./prisma";
7 |
8 | export const sessionOptions: SessionOptions = {
9 | password: [{ id: 1, password: process.env.COOKIE_SECRET as string }],
10 | cookieName: "template-repo",
11 | cookieOptions: {
12 | secure: process.env.NODE_ENV === "production",
13 | httpOnly: true,
14 | sameSite: "strict"
15 | }
16 | };
17 |
18 | export const createSession = async (req: any, user: User) => {
19 | const session = await prisma.session.create({
20 | data: {
21 | userId: user.id
22 | }
23 | });
24 |
25 | await req.session.set("sessionID", session.id);
26 | await req.session.save();
27 | return session;
28 | };
29 |
30 | interface PrismaSession extends Session {
31 | user: User;
32 | }
33 |
34 | const sessionCache = new WeakMap();
35 |
36 | export const resolveSession = async ({
37 | req,
38 | res
39 | }: Pick) => {
40 | if (sessionCache.has(req)) {
41 | console.log("in cache");
42 | return sessionCache.get(req);
43 | }
44 |
45 | await applySession(req, res, sessionOptions);
46 |
47 | let session: PrismaSession | null = null;
48 |
49 | const sessionID = req.session.get("sessionID");
50 |
51 | if (sessionID) {
52 | session = await prisma.session.findUnique({
53 | where: { id: sessionID },
54 | include: {
55 | user: true
56 | }
57 | });
58 | }
59 |
60 | sessionCache.set(req, session);
61 |
62 | return session;
63 | };
64 |
--------------------------------------------------------------------------------
/src/components/__generated__/Test.generated.tsx:
--------------------------------------------------------------------------------
1 | import * as Types from '../../__generated__/generated.d';
2 |
3 | export type Maybe = T | null;
4 | export type Exact = { [K in keyof T]: T[K] };
5 | export type MakeOptional = Omit & { [SubKey in K]?: Maybe };
6 | export type MakeMaybe = Omit & { [SubKey in K]: Maybe };
7 | /** All built-in and custom scalars, mapped to their actual values */
8 | export type Scalars = {
9 | ID: string;
10 | String: string;
11 | Boolean: boolean;
12 | Int: number;
13 | Float: number;
14 | DateTime: any;
15 | };
16 |
17 |
18 | export type LoginInput = {
19 | email: Scalars['String'];
20 | password: Scalars['String'];
21 | };
22 |
23 | export type Mutation = {
24 | __typename?: 'Mutation';
25 | login: User;
26 | logout: Scalars['Boolean'];
27 | signUp: User;
28 | };
29 |
30 |
31 | export type MutationLoginArgs = {
32 | input: LoginInput;
33 | };
34 |
35 |
36 | export type MutationSignUpArgs = {
37 | input: SignUpInput;
38 | };
39 |
40 | export type Query = {
41 | __typename?: 'Query';
42 | me?: Maybe;
43 | };
44 |
45 | export type SignUpInput = {
46 | email: Scalars['String'];
47 | name: Scalars['String'];
48 | password: Scalars['String'];
49 | };
50 |
51 | export type User = {
52 | __typename?: 'User';
53 | email: Scalars['String'];
54 | id: Scalars['ID'];
55 | name: Scalars['String'];
56 | };
57 |
58 | export type UserQueryVariables = Types.Exact<{ [key: string]: never; }>;
59 |
60 |
61 | export type UserQuery = (
62 | { __typename?: 'Query' }
63 | & { me?: Types.Maybe<(
64 | { __typename?: 'User' }
65 | & Pick
66 | )> }
67 | );
68 |
--------------------------------------------------------------------------------
/src/pages/__generated__/authenticated.generated.tsx:
--------------------------------------------------------------------------------
1 | import * as Types from '../../__generated__/generated.d';
2 |
3 | export type Maybe = T | null;
4 | export type Exact = { [K in keyof T]: T[K] };
5 | export type MakeOptional = Omit & { [SubKey in K]?: Maybe };
6 | export type MakeMaybe = Omit & { [SubKey in K]: Maybe };
7 | /** All built-in and custom scalars, mapped to their actual values */
8 | export type Scalars = {
9 | ID: string;
10 | String: string;
11 | Boolean: boolean;
12 | Int: number;
13 | Float: number;
14 | DateTime: any;
15 | };
16 |
17 |
18 | export type LoginInput = {
19 | email: Scalars['String'];
20 | password: Scalars['String'];
21 | };
22 |
23 | export type Mutation = {
24 | __typename?: 'Mutation';
25 | login: User;
26 | logout: Scalars['Boolean'];
27 | signUp: User;
28 | };
29 |
30 |
31 | export type MutationLoginArgs = {
32 | input: LoginInput;
33 | };
34 |
35 |
36 | export type MutationSignUpArgs = {
37 | input: SignUpInput;
38 | };
39 |
40 | export type Query = {
41 | __typename?: 'Query';
42 | me?: Maybe;
43 | };
44 |
45 | export type SignUpInput = {
46 | email: Scalars['String'];
47 | name: Scalars['String'];
48 | password: Scalars['String'];
49 | };
50 |
51 | export type User = {
52 | __typename?: 'User';
53 | email: Scalars['String'];
54 | id: Scalars['ID'];
55 | name: Scalars['String'];
56 | };
57 |
58 | export type MeQueryVariables = Types.Exact<{ [key: string]: never; }>;
59 |
60 |
61 | export type MeQuery = (
62 | { __typename?: 'Query' }
63 | & { me?: Types.Maybe<(
64 | { __typename?: 'User' }
65 | & Pick
66 | )> }
67 | );
68 |
--------------------------------------------------------------------------------
/src/graphql/resolvers/AuthResolver.ts:
--------------------------------------------------------------------------------
1 | import prisma from "../../utils/prisma";
2 |
3 | import { hashPassword, authenticateUser } from "../../utils/auth";
4 | import { createSession } from "../../utils/sessions";
5 | import { builder } from "../builder";
6 | import { UserObject } from "./UserResolver";
7 |
8 | builder.queryFields(t => ({
9 | me: t.field({
10 | type: UserObject,
11 | authScopes: {
12 | user: true
13 | },
14 | nullable: true,
15 | resolve: (_root, _args, { user, session }) => {
16 | return user;
17 | }
18 | })
19 | }));
20 |
21 | builder.mutationField("logout", t =>
22 | t.boolean({
23 | authScopes: {
24 | user: true
25 | },
26 | resolve: async (_root, _args, { session }) => {
27 | await prisma.session.delete({ where: { id: session!.id } });
28 | return true;
29 | }
30 | })
31 | );
32 |
33 | const SignUpInput = builder.inputType("SignUpInput", {
34 | fields: t => ({
35 | name: t.string({ required: true }),
36 | email: t.string({ required: true }),
37 | password: t.string({ required: true })
38 | })
39 | });
40 |
41 | builder.mutationField("signUp", t =>
42 | t.field({
43 | type: UserObject,
44 | args: {
45 | input: t.arg({ type: SignUpInput })
46 | },
47 | authScopes: {
48 | isUnauthenticated: true
49 | },
50 | resolve: async (_root, { input }, { req }) => {
51 | const user = await prisma.user.create({
52 | data: {
53 | email: input.email,
54 | name: input.name,
55 | hashedPassword: await hashPassword(input.password)
56 | }
57 | });
58 | await createSession(req, user);
59 | return user;
60 | }
61 | })
62 | );
63 |
64 | const LoginInput = builder.inputType("LoginInput", {
65 | fields: t => ({
66 | email: t.string({ required: true }),
67 | password: t.string({ required: true })
68 | })
69 | });
70 | builder.mutationField("login", t =>
71 | t.field({
72 | type: UserObject,
73 | args: {
74 | input: t.arg({ type: LoginInput })
75 | },
76 | authScopes: {
77 | isUnauthenticated: true
78 | },
79 | resolve: async (_root, { input }, { req }) => {
80 | console.log("CREATING USER");
81 | try {
82 | const user = await authenticateUser(input.email, input.password);
83 | await createSession(req, user);
84 | return user;
85 | } catch (err) {
86 | throw new Error(err);
87 | }
88 | }
89 | })
90 | );
91 |
--------------------------------------------------------------------------------
/src/pages/api/graphql.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | getGraphQLParameters,
3 | processRequest,
4 | renderGraphiQL,
5 | shouldRenderGraphiQL
6 | } from "graphql-helix";
7 | import { NextApiHandler } from "next/types";
8 | import { IncomingHttpHeaders } from "node:http";
9 | import schema from "../../graphql/index";
10 | import { resolveSession } from "../../utils/sessions";
11 |
12 | export default (async (req, res) => {
13 | const session = await resolveSession({ req, res });
14 |
15 | interface GraphQLRequest {
16 | body?: any;
17 | headers: IncomingHttpHeaders;
18 | method: string;
19 | query: any;
20 | }
21 | const request: GraphQLRequest = {
22 | body: req.body,
23 | headers: req.headers,
24 | method: req.method ?? "GET",
25 | query: req.query
26 | };
27 |
28 | if (shouldRenderGraphiQL(request)) {
29 | res.send(renderGraphiQL({ endpoint: "/api/graphql" }));
30 | } else {
31 | const { operationName, query, variables } = getGraphQLParameters(request);
32 |
33 | const result = await processRequest({
34 | operationName,
35 | query,
36 | variables,
37 | request,
38 | schema,
39 | contextFactory: () => {
40 | return {
41 | req,
42 | res,
43 | user: session?.user,
44 | session: session
45 | };
46 | }
47 | });
48 |
49 | if (result.type === "RESPONSE") {
50 | result.headers.forEach(({ name, value }) => res.setHeader(name, value));
51 | res.status(result.status);
52 | res.json(result.payload);
53 | } else if (result.type === "MULTIPART_RESPONSE") {
54 | res.writeHead(200, {
55 | Connection: "keep-alive",
56 | "Content-Type": 'multipart/mixed; boundary="-"',
57 | "Transfer-Encoding": "chunked"
58 | });
59 |
60 | req.on("close", () => {
61 | result.unsubscribe();
62 | });
63 |
64 | res.write("---");
65 |
66 | await result.subscribe(result => {
67 | const chunk = Buffer.from(JSON.stringify(result), "utf8");
68 | const data = [
69 | "",
70 | "Content-Type: application/json; charset=utf-8",
71 | "Content-Length: " + String(chunk.length),
72 | "",
73 | chunk
74 | ];
75 |
76 | if (result.hasNext) {
77 | data.push("---");
78 | }
79 |
80 | res.write(data.join("\r\n"));
81 | });
82 |
83 | res.write("\r\n-----\r\n");
84 | res.end();
85 | } else {
86 | res.writeHead(200, {
87 | "Content-Type": "text/event-stream",
88 | Connection: "keep-alive",
89 | "Cache-Control": "no-cache"
90 | });
91 |
92 | req.on("close", () => {
93 | result.unsubscribe();
94 | });
95 |
96 | await result.subscribe(result => {
97 | res.write(`data: ${JSON.stringify(result)}\n\n`);
98 | });
99 | }
100 | }
101 | }) as NextApiHandler;
102 |
--------------------------------------------------------------------------------
/graphql.schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "__schema": {
3 | "queryType": {
4 | "name": "Query"
5 | },
6 | "mutationType": {
7 | "name": "Mutation"
8 | },
9 | "subscriptionType": null,
10 | "types": [
11 | {
12 | "kind": "SCALAR",
13 | "name": "DateTime",
14 | "description": null,
15 | "fields": null,
16 | "inputFields": null,
17 | "interfaces": null,
18 | "enumValues": null,
19 | "possibleTypes": null
20 | },
21 | {
22 | "kind": "INPUT_OBJECT",
23 | "name": "LoginInput",
24 | "description": null,
25 | "fields": null,
26 | "inputFields": [
27 | {
28 | "name": "email",
29 | "description": null,
30 | "type": {
31 | "kind": "NON_NULL",
32 | "name": null,
33 | "ofType": {
34 | "kind": "SCALAR",
35 | "name": "String",
36 | "ofType": null
37 | }
38 | },
39 | "defaultValue": null,
40 | "isDeprecated": false,
41 | "deprecationReason": null
42 | },
43 | {
44 | "name": "password",
45 | "description": null,
46 | "type": {
47 | "kind": "NON_NULL",
48 | "name": null,
49 | "ofType": {
50 | "kind": "SCALAR",
51 | "name": "String",
52 | "ofType": null
53 | }
54 | },
55 | "defaultValue": null,
56 | "isDeprecated": false,
57 | "deprecationReason": null
58 | }
59 | ],
60 | "interfaces": null,
61 | "enumValues": null,
62 | "possibleTypes": null
63 | },
64 | {
65 | "kind": "SCALAR",
66 | "name": "String",
67 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
68 | "fields": null,
69 | "inputFields": null,
70 | "interfaces": null,
71 | "enumValues": null,
72 | "possibleTypes": null
73 | },
74 | {
75 | "kind": "OBJECT",
76 | "name": "Mutation",
77 | "description": null,
78 | "fields": [
79 | {
80 | "name": "login",
81 | "description": null,
82 | "args": [
83 | {
84 | "name": "input",
85 | "description": null,
86 | "type": {
87 | "kind": "NON_NULL",
88 | "name": null,
89 | "ofType": {
90 | "kind": "INPUT_OBJECT",
91 | "name": "LoginInput",
92 | "ofType": null
93 | }
94 | },
95 | "defaultValue": null,
96 | "isDeprecated": false,
97 | "deprecationReason": null
98 | }
99 | ],
100 | "type": {
101 | "kind": "NON_NULL",
102 | "name": null,
103 | "ofType": {
104 | "kind": "OBJECT",
105 | "name": "User",
106 | "ofType": null
107 | }
108 | },
109 | "isDeprecated": false,
110 | "deprecationReason": null
111 | },
112 | {
113 | "name": "logout",
114 | "description": null,
115 | "args": [],
116 | "type": {
117 | "kind": "NON_NULL",
118 | "name": null,
119 | "ofType": {
120 | "kind": "SCALAR",
121 | "name": "Boolean",
122 | "ofType": null
123 | }
124 | },
125 | "isDeprecated": false,
126 | "deprecationReason": null
127 | },
128 | {
129 | "name": "signUp",
130 | "description": null,
131 | "args": [
132 | {
133 | "name": "input",
134 | "description": null,
135 | "type": {
136 | "kind": "NON_NULL",
137 | "name": null,
138 | "ofType": {
139 | "kind": "INPUT_OBJECT",
140 | "name": "SignUpInput",
141 | "ofType": null
142 | }
143 | },
144 | "defaultValue": null,
145 | "isDeprecated": false,
146 | "deprecationReason": null
147 | }
148 | ],
149 | "type": {
150 | "kind": "NON_NULL",
151 | "name": null,
152 | "ofType": {
153 | "kind": "OBJECT",
154 | "name": "User",
155 | "ofType": null
156 | }
157 | },
158 | "isDeprecated": false,
159 | "deprecationReason": null
160 | }
161 | ],
162 | "inputFields": null,
163 | "interfaces": [],
164 | "enumValues": null,
165 | "possibleTypes": null
166 | },
167 | {
168 | "kind": "SCALAR",
169 | "name": "Boolean",
170 | "description": "The `Boolean` scalar type represents `true` or `false`.",
171 | "fields": null,
172 | "inputFields": null,
173 | "interfaces": null,
174 | "enumValues": null,
175 | "possibleTypes": null
176 | },
177 | {
178 | "kind": "OBJECT",
179 | "name": "Query",
180 | "description": null,
181 | "fields": [
182 | {
183 | "name": "me",
184 | "description": null,
185 | "args": [],
186 | "type": {
187 | "kind": "OBJECT",
188 | "name": "User",
189 | "ofType": null
190 | },
191 | "isDeprecated": false,
192 | "deprecationReason": null
193 | }
194 | ],
195 | "inputFields": null,
196 | "interfaces": [],
197 | "enumValues": null,
198 | "possibleTypes": null
199 | },
200 | {
201 | "kind": "INPUT_OBJECT",
202 | "name": "SignUpInput",
203 | "description": null,
204 | "fields": null,
205 | "inputFields": [
206 | {
207 | "name": "email",
208 | "description": null,
209 | "type": {
210 | "kind": "NON_NULL",
211 | "name": null,
212 | "ofType": {
213 | "kind": "SCALAR",
214 | "name": "String",
215 | "ofType": null
216 | }
217 | },
218 | "defaultValue": null,
219 | "isDeprecated": false,
220 | "deprecationReason": null
221 | },
222 | {
223 | "name": "name",
224 | "description": null,
225 | "type": {
226 | "kind": "NON_NULL",
227 | "name": null,
228 | "ofType": {
229 | "kind": "SCALAR",
230 | "name": "String",
231 | "ofType": null
232 | }
233 | },
234 | "defaultValue": null,
235 | "isDeprecated": false,
236 | "deprecationReason": null
237 | },
238 | {
239 | "name": "password",
240 | "description": null,
241 | "type": {
242 | "kind": "NON_NULL",
243 | "name": null,
244 | "ofType": {
245 | "kind": "SCALAR",
246 | "name": "String",
247 | "ofType": null
248 | }
249 | },
250 | "defaultValue": null,
251 | "isDeprecated": false,
252 | "deprecationReason": null
253 | }
254 | ],
255 | "interfaces": null,
256 | "enumValues": null,
257 | "possibleTypes": null
258 | },
259 | {
260 | "kind": "OBJECT",
261 | "name": "User",
262 | "description": null,
263 | "fields": [
264 | {
265 | "name": "email",
266 | "description": null,
267 | "args": [],
268 | "type": {
269 | "kind": "NON_NULL",
270 | "name": null,
271 | "ofType": {
272 | "kind": "SCALAR",
273 | "name": "String",
274 | "ofType": null
275 | }
276 | },
277 | "isDeprecated": false,
278 | "deprecationReason": null
279 | },
280 | {
281 | "name": "id",
282 | "description": null,
283 | "args": [],
284 | "type": {
285 | "kind": "NON_NULL",
286 | "name": null,
287 | "ofType": {
288 | "kind": "SCALAR",
289 | "name": "ID",
290 | "ofType": null
291 | }
292 | },
293 | "isDeprecated": false,
294 | "deprecationReason": null
295 | },
296 | {
297 | "name": "name",
298 | "description": null,
299 | "args": [],
300 | "type": {
301 | "kind": "NON_NULL",
302 | "name": null,
303 | "ofType": {
304 | "kind": "SCALAR",
305 | "name": "String",
306 | "ofType": null
307 | }
308 | },
309 | "isDeprecated": false,
310 | "deprecationReason": null
311 | }
312 | ],
313 | "inputFields": null,
314 | "interfaces": [],
315 | "enumValues": null,
316 | "possibleTypes": null
317 | },
318 | {
319 | "kind": "SCALAR",
320 | "name": "ID",
321 | "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.",
322 | "fields": null,
323 | "inputFields": null,
324 | "interfaces": null,
325 | "enumValues": null,
326 | "possibleTypes": null
327 | },
328 | {
329 | "kind": "OBJECT",
330 | "name": "__Schema",
331 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.",
332 | "fields": [
333 | {
334 | "name": "description",
335 | "description": null,
336 | "args": [],
337 | "type": {
338 | "kind": "SCALAR",
339 | "name": "String",
340 | "ofType": null
341 | },
342 | "isDeprecated": false,
343 | "deprecationReason": null
344 | },
345 | {
346 | "name": "types",
347 | "description": "A list of all types supported by this server.",
348 | "args": [],
349 | "type": {
350 | "kind": "NON_NULL",
351 | "name": null,
352 | "ofType": {
353 | "kind": "LIST",
354 | "name": null,
355 | "ofType": {
356 | "kind": "NON_NULL",
357 | "name": null,
358 | "ofType": {
359 | "kind": "OBJECT",
360 | "name": "__Type",
361 | "ofType": null
362 | }
363 | }
364 | }
365 | },
366 | "isDeprecated": false,
367 | "deprecationReason": null
368 | },
369 | {
370 | "name": "queryType",
371 | "description": "The type that query operations will be rooted at.",
372 | "args": [],
373 | "type": {
374 | "kind": "NON_NULL",
375 | "name": null,
376 | "ofType": {
377 | "kind": "OBJECT",
378 | "name": "__Type",
379 | "ofType": null
380 | }
381 | },
382 | "isDeprecated": false,
383 | "deprecationReason": null
384 | },
385 | {
386 | "name": "mutationType",
387 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.",
388 | "args": [],
389 | "type": {
390 | "kind": "OBJECT",
391 | "name": "__Type",
392 | "ofType": null
393 | },
394 | "isDeprecated": false,
395 | "deprecationReason": null
396 | },
397 | {
398 | "name": "subscriptionType",
399 | "description": "If this server support subscription, the type that subscription operations will be rooted at.",
400 | "args": [],
401 | "type": {
402 | "kind": "OBJECT",
403 | "name": "__Type",
404 | "ofType": null
405 | },
406 | "isDeprecated": false,
407 | "deprecationReason": null
408 | },
409 | {
410 | "name": "directives",
411 | "description": "A list of all directives supported by this server.",
412 | "args": [],
413 | "type": {
414 | "kind": "NON_NULL",
415 | "name": null,
416 | "ofType": {
417 | "kind": "LIST",
418 | "name": null,
419 | "ofType": {
420 | "kind": "NON_NULL",
421 | "name": null,
422 | "ofType": {
423 | "kind": "OBJECT",
424 | "name": "__Directive",
425 | "ofType": null
426 | }
427 | }
428 | }
429 | },
430 | "isDeprecated": false,
431 | "deprecationReason": null
432 | }
433 | ],
434 | "inputFields": null,
435 | "interfaces": [],
436 | "enumValues": null,
437 | "possibleTypes": null
438 | },
439 | {
440 | "kind": "OBJECT",
441 | "name": "__Type",
442 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByUrl`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.",
443 | "fields": [
444 | {
445 | "name": "kind",
446 | "description": null,
447 | "args": [],
448 | "type": {
449 | "kind": "NON_NULL",
450 | "name": null,
451 | "ofType": {
452 | "kind": "ENUM",
453 | "name": "__TypeKind",
454 | "ofType": null
455 | }
456 | },
457 | "isDeprecated": false,
458 | "deprecationReason": null
459 | },
460 | {
461 | "name": "name",
462 | "description": null,
463 | "args": [],
464 | "type": {
465 | "kind": "SCALAR",
466 | "name": "String",
467 | "ofType": null
468 | },
469 | "isDeprecated": false,
470 | "deprecationReason": null
471 | },
472 | {
473 | "name": "description",
474 | "description": null,
475 | "args": [],
476 | "type": {
477 | "kind": "SCALAR",
478 | "name": "String",
479 | "ofType": null
480 | },
481 | "isDeprecated": false,
482 | "deprecationReason": null
483 | },
484 | {
485 | "name": "specifiedByUrl",
486 | "description": null,
487 | "args": [],
488 | "type": {
489 | "kind": "SCALAR",
490 | "name": "String",
491 | "ofType": null
492 | },
493 | "isDeprecated": false,
494 | "deprecationReason": null
495 | },
496 | {
497 | "name": "fields",
498 | "description": null,
499 | "args": [
500 | {
501 | "name": "includeDeprecated",
502 | "description": null,
503 | "type": {
504 | "kind": "SCALAR",
505 | "name": "Boolean",
506 | "ofType": null
507 | },
508 | "defaultValue": "false",
509 | "isDeprecated": false,
510 | "deprecationReason": null
511 | }
512 | ],
513 | "type": {
514 | "kind": "LIST",
515 | "name": null,
516 | "ofType": {
517 | "kind": "NON_NULL",
518 | "name": null,
519 | "ofType": {
520 | "kind": "OBJECT",
521 | "name": "__Field",
522 | "ofType": null
523 | }
524 | }
525 | },
526 | "isDeprecated": false,
527 | "deprecationReason": null
528 | },
529 | {
530 | "name": "interfaces",
531 | "description": null,
532 | "args": [],
533 | "type": {
534 | "kind": "LIST",
535 | "name": null,
536 | "ofType": {
537 | "kind": "NON_NULL",
538 | "name": null,
539 | "ofType": {
540 | "kind": "OBJECT",
541 | "name": "__Type",
542 | "ofType": null
543 | }
544 | }
545 | },
546 | "isDeprecated": false,
547 | "deprecationReason": null
548 | },
549 | {
550 | "name": "possibleTypes",
551 | "description": null,
552 | "args": [],
553 | "type": {
554 | "kind": "LIST",
555 | "name": null,
556 | "ofType": {
557 | "kind": "NON_NULL",
558 | "name": null,
559 | "ofType": {
560 | "kind": "OBJECT",
561 | "name": "__Type",
562 | "ofType": null
563 | }
564 | }
565 | },
566 | "isDeprecated": false,
567 | "deprecationReason": null
568 | },
569 | {
570 | "name": "enumValues",
571 | "description": null,
572 | "args": [
573 | {
574 | "name": "includeDeprecated",
575 | "description": null,
576 | "type": {
577 | "kind": "SCALAR",
578 | "name": "Boolean",
579 | "ofType": null
580 | },
581 | "defaultValue": "false",
582 | "isDeprecated": false,
583 | "deprecationReason": null
584 | }
585 | ],
586 | "type": {
587 | "kind": "LIST",
588 | "name": null,
589 | "ofType": {
590 | "kind": "NON_NULL",
591 | "name": null,
592 | "ofType": {
593 | "kind": "OBJECT",
594 | "name": "__EnumValue",
595 | "ofType": null
596 | }
597 | }
598 | },
599 | "isDeprecated": false,
600 | "deprecationReason": null
601 | },
602 | {
603 | "name": "inputFields",
604 | "description": null,
605 | "args": [
606 | {
607 | "name": "includeDeprecated",
608 | "description": null,
609 | "type": {
610 | "kind": "SCALAR",
611 | "name": "Boolean",
612 | "ofType": null
613 | },
614 | "defaultValue": "false",
615 | "isDeprecated": false,
616 | "deprecationReason": null
617 | }
618 | ],
619 | "type": {
620 | "kind": "LIST",
621 | "name": null,
622 | "ofType": {
623 | "kind": "NON_NULL",
624 | "name": null,
625 | "ofType": {
626 | "kind": "OBJECT",
627 | "name": "__InputValue",
628 | "ofType": null
629 | }
630 | }
631 | },
632 | "isDeprecated": false,
633 | "deprecationReason": null
634 | },
635 | {
636 | "name": "ofType",
637 | "description": null,
638 | "args": [],
639 | "type": {
640 | "kind": "OBJECT",
641 | "name": "__Type",
642 | "ofType": null
643 | },
644 | "isDeprecated": false,
645 | "deprecationReason": null
646 | }
647 | ],
648 | "inputFields": null,
649 | "interfaces": [],
650 | "enumValues": null,
651 | "possibleTypes": null
652 | },
653 | {
654 | "kind": "ENUM",
655 | "name": "__TypeKind",
656 | "description": "An enum describing what kind of type a given `__Type` is.",
657 | "fields": null,
658 | "inputFields": null,
659 | "interfaces": null,
660 | "enumValues": [
661 | {
662 | "name": "SCALAR",
663 | "description": "Indicates this type is a scalar.",
664 | "isDeprecated": false,
665 | "deprecationReason": null
666 | },
667 | {
668 | "name": "OBJECT",
669 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.",
670 | "isDeprecated": false,
671 | "deprecationReason": null
672 | },
673 | {
674 | "name": "INTERFACE",
675 | "description": "Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields.",
676 | "isDeprecated": false,
677 | "deprecationReason": null
678 | },
679 | {
680 | "name": "UNION",
681 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.",
682 | "isDeprecated": false,
683 | "deprecationReason": null
684 | },
685 | {
686 | "name": "ENUM",
687 | "description": "Indicates this type is an enum. `enumValues` is a valid field.",
688 | "isDeprecated": false,
689 | "deprecationReason": null
690 | },
691 | {
692 | "name": "INPUT_OBJECT",
693 | "description": "Indicates this type is an input object. `inputFields` is a valid field.",
694 | "isDeprecated": false,
695 | "deprecationReason": null
696 | },
697 | {
698 | "name": "LIST",
699 | "description": "Indicates this type is a list. `ofType` is a valid field.",
700 | "isDeprecated": false,
701 | "deprecationReason": null
702 | },
703 | {
704 | "name": "NON_NULL",
705 | "description": "Indicates this type is a non-null. `ofType` is a valid field.",
706 | "isDeprecated": false,
707 | "deprecationReason": null
708 | }
709 | ],
710 | "possibleTypes": null
711 | },
712 | {
713 | "kind": "OBJECT",
714 | "name": "__Field",
715 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.",
716 | "fields": [
717 | {
718 | "name": "name",
719 | "description": null,
720 | "args": [],
721 | "type": {
722 | "kind": "NON_NULL",
723 | "name": null,
724 | "ofType": {
725 | "kind": "SCALAR",
726 | "name": "String",
727 | "ofType": null
728 | }
729 | },
730 | "isDeprecated": false,
731 | "deprecationReason": null
732 | },
733 | {
734 | "name": "description",
735 | "description": null,
736 | "args": [],
737 | "type": {
738 | "kind": "SCALAR",
739 | "name": "String",
740 | "ofType": null
741 | },
742 | "isDeprecated": false,
743 | "deprecationReason": null
744 | },
745 | {
746 | "name": "args",
747 | "description": null,
748 | "args": [
749 | {
750 | "name": "includeDeprecated",
751 | "description": null,
752 | "type": {
753 | "kind": "SCALAR",
754 | "name": "Boolean",
755 | "ofType": null
756 | },
757 | "defaultValue": "false",
758 | "isDeprecated": false,
759 | "deprecationReason": null
760 | }
761 | ],
762 | "type": {
763 | "kind": "NON_NULL",
764 | "name": null,
765 | "ofType": {
766 | "kind": "LIST",
767 | "name": null,
768 | "ofType": {
769 | "kind": "NON_NULL",
770 | "name": null,
771 | "ofType": {
772 | "kind": "OBJECT",
773 | "name": "__InputValue",
774 | "ofType": null
775 | }
776 | }
777 | }
778 | },
779 | "isDeprecated": false,
780 | "deprecationReason": null
781 | },
782 | {
783 | "name": "type",
784 | "description": null,
785 | "args": [],
786 | "type": {
787 | "kind": "NON_NULL",
788 | "name": null,
789 | "ofType": {
790 | "kind": "OBJECT",
791 | "name": "__Type",
792 | "ofType": null
793 | }
794 | },
795 | "isDeprecated": false,
796 | "deprecationReason": null
797 | },
798 | {
799 | "name": "isDeprecated",
800 | "description": null,
801 | "args": [],
802 | "type": {
803 | "kind": "NON_NULL",
804 | "name": null,
805 | "ofType": {
806 | "kind": "SCALAR",
807 | "name": "Boolean",
808 | "ofType": null
809 | }
810 | },
811 | "isDeprecated": false,
812 | "deprecationReason": null
813 | },
814 | {
815 | "name": "deprecationReason",
816 | "description": null,
817 | "args": [],
818 | "type": {
819 | "kind": "SCALAR",
820 | "name": "String",
821 | "ofType": null
822 | },
823 | "isDeprecated": false,
824 | "deprecationReason": null
825 | }
826 | ],
827 | "inputFields": null,
828 | "interfaces": [],
829 | "enumValues": null,
830 | "possibleTypes": null
831 | },
832 | {
833 | "kind": "OBJECT",
834 | "name": "__InputValue",
835 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.",
836 | "fields": [
837 | {
838 | "name": "name",
839 | "description": null,
840 | "args": [],
841 | "type": {
842 | "kind": "NON_NULL",
843 | "name": null,
844 | "ofType": {
845 | "kind": "SCALAR",
846 | "name": "String",
847 | "ofType": null
848 | }
849 | },
850 | "isDeprecated": false,
851 | "deprecationReason": null
852 | },
853 | {
854 | "name": "description",
855 | "description": null,
856 | "args": [],
857 | "type": {
858 | "kind": "SCALAR",
859 | "name": "String",
860 | "ofType": null
861 | },
862 | "isDeprecated": false,
863 | "deprecationReason": null
864 | },
865 | {
866 | "name": "type",
867 | "description": null,
868 | "args": [],
869 | "type": {
870 | "kind": "NON_NULL",
871 | "name": null,
872 | "ofType": {
873 | "kind": "OBJECT",
874 | "name": "__Type",
875 | "ofType": null
876 | }
877 | },
878 | "isDeprecated": false,
879 | "deprecationReason": null
880 | },
881 | {
882 | "name": "defaultValue",
883 | "description": "A GraphQL-formatted string representing the default value for this input value.",
884 | "args": [],
885 | "type": {
886 | "kind": "SCALAR",
887 | "name": "String",
888 | "ofType": null
889 | },
890 | "isDeprecated": false,
891 | "deprecationReason": null
892 | },
893 | {
894 | "name": "isDeprecated",
895 | "description": null,
896 | "args": [],
897 | "type": {
898 | "kind": "NON_NULL",
899 | "name": null,
900 | "ofType": {
901 | "kind": "SCALAR",
902 | "name": "Boolean",
903 | "ofType": null
904 | }
905 | },
906 | "isDeprecated": false,
907 | "deprecationReason": null
908 | },
909 | {
910 | "name": "deprecationReason",
911 | "description": null,
912 | "args": [],
913 | "type": {
914 | "kind": "SCALAR",
915 | "name": "String",
916 | "ofType": null
917 | },
918 | "isDeprecated": false,
919 | "deprecationReason": null
920 | }
921 | ],
922 | "inputFields": null,
923 | "interfaces": [],
924 | "enumValues": null,
925 | "possibleTypes": null
926 | },
927 | {
928 | "kind": "OBJECT",
929 | "name": "__EnumValue",
930 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.",
931 | "fields": [
932 | {
933 | "name": "name",
934 | "description": null,
935 | "args": [],
936 | "type": {
937 | "kind": "NON_NULL",
938 | "name": null,
939 | "ofType": {
940 | "kind": "SCALAR",
941 | "name": "String",
942 | "ofType": null
943 | }
944 | },
945 | "isDeprecated": false,
946 | "deprecationReason": null
947 | },
948 | {
949 | "name": "description",
950 | "description": null,
951 | "args": [],
952 | "type": {
953 | "kind": "SCALAR",
954 | "name": "String",
955 | "ofType": null
956 | },
957 | "isDeprecated": false,
958 | "deprecationReason": null
959 | },
960 | {
961 | "name": "isDeprecated",
962 | "description": null,
963 | "args": [],
964 | "type": {
965 | "kind": "NON_NULL",
966 | "name": null,
967 | "ofType": {
968 | "kind": "SCALAR",
969 | "name": "Boolean",
970 | "ofType": null
971 | }
972 | },
973 | "isDeprecated": false,
974 | "deprecationReason": null
975 | },
976 | {
977 | "name": "deprecationReason",
978 | "description": null,
979 | "args": [],
980 | "type": {
981 | "kind": "SCALAR",
982 | "name": "String",
983 | "ofType": null
984 | },
985 | "isDeprecated": false,
986 | "deprecationReason": null
987 | }
988 | ],
989 | "inputFields": null,
990 | "interfaces": [],
991 | "enumValues": null,
992 | "possibleTypes": null
993 | },
994 | {
995 | "kind": "OBJECT",
996 | "name": "__Directive",
997 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.",
998 | "fields": [
999 | {
1000 | "name": "name",
1001 | "description": null,
1002 | "args": [],
1003 | "type": {
1004 | "kind": "NON_NULL",
1005 | "name": null,
1006 | "ofType": {
1007 | "kind": "SCALAR",
1008 | "name": "String",
1009 | "ofType": null
1010 | }
1011 | },
1012 | "isDeprecated": false,
1013 | "deprecationReason": null
1014 | },
1015 | {
1016 | "name": "description",
1017 | "description": null,
1018 | "args": [],
1019 | "type": {
1020 | "kind": "SCALAR",
1021 | "name": "String",
1022 | "ofType": null
1023 | },
1024 | "isDeprecated": false,
1025 | "deprecationReason": null
1026 | },
1027 | {
1028 | "name": "isRepeatable",
1029 | "description": null,
1030 | "args": [],
1031 | "type": {
1032 | "kind": "NON_NULL",
1033 | "name": null,
1034 | "ofType": {
1035 | "kind": "SCALAR",
1036 | "name": "Boolean",
1037 | "ofType": null
1038 | }
1039 | },
1040 | "isDeprecated": false,
1041 | "deprecationReason": null
1042 | },
1043 | {
1044 | "name": "locations",
1045 | "description": null,
1046 | "args": [],
1047 | "type": {
1048 | "kind": "NON_NULL",
1049 | "name": null,
1050 | "ofType": {
1051 | "kind": "LIST",
1052 | "name": null,
1053 | "ofType": {
1054 | "kind": "NON_NULL",
1055 | "name": null,
1056 | "ofType": {
1057 | "kind": "ENUM",
1058 | "name": "__DirectiveLocation",
1059 | "ofType": null
1060 | }
1061 | }
1062 | }
1063 | },
1064 | "isDeprecated": false,
1065 | "deprecationReason": null
1066 | },
1067 | {
1068 | "name": "args",
1069 | "description": null,
1070 | "args": [],
1071 | "type": {
1072 | "kind": "NON_NULL",
1073 | "name": null,
1074 | "ofType": {
1075 | "kind": "LIST",
1076 | "name": null,
1077 | "ofType": {
1078 | "kind": "NON_NULL",
1079 | "name": null,
1080 | "ofType": {
1081 | "kind": "OBJECT",
1082 | "name": "__InputValue",
1083 | "ofType": null
1084 | }
1085 | }
1086 | }
1087 | },
1088 | "isDeprecated": false,
1089 | "deprecationReason": null
1090 | }
1091 | ],
1092 | "inputFields": null,
1093 | "interfaces": [],
1094 | "enumValues": null,
1095 | "possibleTypes": null
1096 | },
1097 | {
1098 | "kind": "ENUM",
1099 | "name": "__DirectiveLocation",
1100 | "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.",
1101 | "fields": null,
1102 | "inputFields": null,
1103 | "interfaces": null,
1104 | "enumValues": [
1105 | {
1106 | "name": "QUERY",
1107 | "description": "Location adjacent to a query operation.",
1108 | "isDeprecated": false,
1109 | "deprecationReason": null
1110 | },
1111 | {
1112 | "name": "MUTATION",
1113 | "description": "Location adjacent to a mutation operation.",
1114 | "isDeprecated": false,
1115 | "deprecationReason": null
1116 | },
1117 | {
1118 | "name": "SUBSCRIPTION",
1119 | "description": "Location adjacent to a subscription operation.",
1120 | "isDeprecated": false,
1121 | "deprecationReason": null
1122 | },
1123 | {
1124 | "name": "FIELD",
1125 | "description": "Location adjacent to a field.",
1126 | "isDeprecated": false,
1127 | "deprecationReason": null
1128 | },
1129 | {
1130 | "name": "FRAGMENT_DEFINITION",
1131 | "description": "Location adjacent to a fragment definition.",
1132 | "isDeprecated": false,
1133 | "deprecationReason": null
1134 | },
1135 | {
1136 | "name": "FRAGMENT_SPREAD",
1137 | "description": "Location adjacent to a fragment spread.",
1138 | "isDeprecated": false,
1139 | "deprecationReason": null
1140 | },
1141 | {
1142 | "name": "INLINE_FRAGMENT",
1143 | "description": "Location adjacent to an inline fragment.",
1144 | "isDeprecated": false,
1145 | "deprecationReason": null
1146 | },
1147 | {
1148 | "name": "VARIABLE_DEFINITION",
1149 | "description": "Location adjacent to a variable definition.",
1150 | "isDeprecated": false,
1151 | "deprecationReason": null
1152 | },
1153 | {
1154 | "name": "SCHEMA",
1155 | "description": "Location adjacent to a schema definition.",
1156 | "isDeprecated": false,
1157 | "deprecationReason": null
1158 | },
1159 | {
1160 | "name": "SCALAR",
1161 | "description": "Location adjacent to a scalar definition.",
1162 | "isDeprecated": false,
1163 | "deprecationReason": null
1164 | },
1165 | {
1166 | "name": "OBJECT",
1167 | "description": "Location adjacent to an object type definition.",
1168 | "isDeprecated": false,
1169 | "deprecationReason": null
1170 | },
1171 | {
1172 | "name": "FIELD_DEFINITION",
1173 | "description": "Location adjacent to a field definition.",
1174 | "isDeprecated": false,
1175 | "deprecationReason": null
1176 | },
1177 | {
1178 | "name": "ARGUMENT_DEFINITION",
1179 | "description": "Location adjacent to an argument definition.",
1180 | "isDeprecated": false,
1181 | "deprecationReason": null
1182 | },
1183 | {
1184 | "name": "INTERFACE",
1185 | "description": "Location adjacent to an interface definition.",
1186 | "isDeprecated": false,
1187 | "deprecationReason": null
1188 | },
1189 | {
1190 | "name": "UNION",
1191 | "description": "Location adjacent to a union definition.",
1192 | "isDeprecated": false,
1193 | "deprecationReason": null
1194 | },
1195 | {
1196 | "name": "ENUM",
1197 | "description": "Location adjacent to an enum definition.",
1198 | "isDeprecated": false,
1199 | "deprecationReason": null
1200 | },
1201 | {
1202 | "name": "ENUM_VALUE",
1203 | "description": "Location adjacent to an enum value definition.",
1204 | "isDeprecated": false,
1205 | "deprecationReason": null
1206 | },
1207 | {
1208 | "name": "INPUT_OBJECT",
1209 | "description": "Location adjacent to an input object type definition.",
1210 | "isDeprecated": false,
1211 | "deprecationReason": null
1212 | },
1213 | {
1214 | "name": "INPUT_FIELD_DEFINITION",
1215 | "description": "Location adjacent to an input object field definition.",
1216 | "isDeprecated": false,
1217 | "deprecationReason": null
1218 | }
1219 | ],
1220 | "possibleTypes": null
1221 | }
1222 | ],
1223 | "directives": [
1224 | {
1225 | "name": "include",
1226 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.",
1227 | "isRepeatable": false,
1228 | "locations": [
1229 | "FIELD",
1230 | "FRAGMENT_SPREAD",
1231 | "INLINE_FRAGMENT"
1232 | ],
1233 | "args": [
1234 | {
1235 | "name": "if",
1236 | "description": "Included when true.",
1237 | "type": {
1238 | "kind": "NON_NULL",
1239 | "name": null,
1240 | "ofType": {
1241 | "kind": "SCALAR",
1242 | "name": "Boolean",
1243 | "ofType": null
1244 | }
1245 | },
1246 | "defaultValue": null,
1247 | "isDeprecated": false,
1248 | "deprecationReason": null
1249 | }
1250 | ]
1251 | },
1252 | {
1253 | "name": "skip",
1254 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.",
1255 | "isRepeatable": false,
1256 | "locations": [
1257 | "FIELD",
1258 | "FRAGMENT_SPREAD",
1259 | "INLINE_FRAGMENT"
1260 | ],
1261 | "args": [
1262 | {
1263 | "name": "if",
1264 | "description": "Skipped when true.",
1265 | "type": {
1266 | "kind": "NON_NULL",
1267 | "name": null,
1268 | "ofType": {
1269 | "kind": "SCALAR",
1270 | "name": "Boolean",
1271 | "ofType": null
1272 | }
1273 | },
1274 | "defaultValue": null,
1275 | "isDeprecated": false,
1276 | "deprecationReason": null
1277 | }
1278 | ]
1279 | },
1280 | {
1281 | "name": "deprecated",
1282 | "description": "Marks an element of a GraphQL schema as no longer supported.",
1283 | "isRepeatable": false,
1284 | "locations": [
1285 | "FIELD_DEFINITION",
1286 | "ARGUMENT_DEFINITION",
1287 | "INPUT_FIELD_DEFINITION",
1288 | "ENUM_VALUE"
1289 | ],
1290 | "args": [
1291 | {
1292 | "name": "reason",
1293 | "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https://commonmark.org/).",
1294 | "type": {
1295 | "kind": "SCALAR",
1296 | "name": "String",
1297 | "ofType": null
1298 | },
1299 | "defaultValue": "\"No longer supported\"",
1300 | "isDeprecated": false,
1301 | "deprecationReason": null
1302 | }
1303 | ]
1304 | },
1305 | {
1306 | "name": "specifiedBy",
1307 | "description": "Exposes a URL that specifies the behaviour of this scalar.",
1308 | "isRepeatable": false,
1309 | "locations": [
1310 | "SCALAR"
1311 | ],
1312 | "args": [
1313 | {
1314 | "name": "url",
1315 | "description": "The URL that specifies the behaviour of this scalar.",
1316 | "type": {
1317 | "kind": "NON_NULL",
1318 | "name": null,
1319 | "ofType": {
1320 | "kind": "SCALAR",
1321 | "name": "String",
1322 | "ofType": null
1323 | }
1324 | },
1325 | "defaultValue": null,
1326 | "isDeprecated": false,
1327 | "deprecationReason": null
1328 | }
1329 | ]
1330 | }
1331 | ]
1332 | }
1333 | }
--------------------------------------------------------------------------------