From 9c8f8083465917495e23a413327d48ce9a386055 Mon Sep 17 00:00:00 2001 From: Jonathan Weinreich Date: Fri, 8 Oct 2021 08:00:23 +0100 Subject: [PATCH 1/3] Pr commit --- LICENSE | 21 - README.md | 73 +- keystone.ts | 20 +- .../migration.sql | 2 + next-env.d.ts | 9 +- next.config.js | 26 +- package.json | 47 +- pages/_app.tsx | 31 +- pages/index.tsx | 217 +- pages/post/[slug].tsx | 47 +- pages/signin.tsx | 12 +- pages/signup.tsx | 6 +- schema.graphql | 1036 ++--- schema.prisma | 4 + schema.ts | 2 +- schema/access.ts | 19 +- schema/content.ts | 70 +- schema/fields/content/components.tsx | 2 +- schema/fields/content/renderers.tsx | 14 +- schema/fields/githubRepos/components.tsx | 4 +- schema/fields/githubRepos/field.ts | 3 +- schema/mutations.ts | 16 +- schema/polls.ts | 19 +- schema/users.ts | 67 +- tailwind.config.js | 1 - tsconfig.json | 7 +- yarn.lock | 3394 ++++++++++++----- 27 files changed, 3179 insertions(+), 1990 deletions(-) delete mode 100644 LICENSE create mode 100644 migrations/20211008052137_initial_migrate/migration.sql diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8c4c8ef5..00000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 KeystoneJS - -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. diff --git a/README.md b/README.md index 1f9d5f14..6c02af79 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,52 @@ -# Prisma Day 2021 Workshop +# Keystone Project Starter -This is the sample app built for Jed's Prisma Day 2021 Workshop. +Welcome to Keystone! -It's both the front and back-end for a Blog built with Prisma, KeystoneJS, GraphQL, Next.js and Tailwind. +Run -The App includes public auth and signup, role-based access control, and custom design-system based components in the Content field. Content authors can embed Polls in post content, and authenticated visitors can vote on responses. +``` +yarn dev +``` -## About KeystoneJS +To view the config for your new app, look at [./keystone.ts](./keystone.ts) -**Keystone 6** is the next-gen CMS for Node.js built with Prisma, Apollo Server, and Next.js. +This project starter is designed to give you a sense of the power Keystone can offer you, and show off some of its main features. It's also a pretty simple setup if you want to build out from it. -Fully open source, it's not just a great headless CMS, it's also a powerful API server and app back-end. +We recommend you use this alongside our [getting started walkthrough](https://keystonejs.com/docs/walkthroughs/getting-started-with-create-keystone-app) which will walk you through what you get as part of this starter. -Learn more at [keystonejs.com](https://keystonejs.com) +If you want an overview of all the features Keystone offers, check out our [features](https://keystonejs.com/why-keystone#features) page. -## Running this app +## Some Quick Notes On Getting Started -Make sure you have: +### Changing the database -- Node v12 or v14 -- Yarn -- Postgres +We've set you up with an [SQLite database](https://keystonejs.com/docs/apis/config#sqlite) for ease-of-use. If you're wanting to use postgres, you can! -Then, clone this repo and run `yarn` to install the dependencies. +Just change the `db` property on line 16 of the Keystone file [./keystone.ts](./keystone.ts) to -### Starting the API +```typescript +db: { + adapter: 'prisma_postgresql', + url: process.env.DATABASE_URL || 'DATABASE_URL_TO_REPLACE', +} +``` -Start the API Server first by running `yarn api:dev`. This will start Keystone's GraphQL API and Admin UI on `localhost:3000` +And provide your database url from postgres. -The first time you open that link you will be prompted to create a new user. +For more on database configuration, check out or [db api docs](https://keystonejs.com/docs/apis/config#db) -### Starting the Site +### Auth -With the API running, in a separate terminal run `yarn site:dev`. This will start the front-end Next.js app on `localhost:8000` +We've put auth into its own file to make this humble starter easier to navigate. To explore it without auth turned on, comment out the `isAccessAllowed` on line 21 of the keystone file [./keystone.ts](./keystone.ts). -## About the codebase +For more on auth, check out our [authentication API docs](https://keystonejs.com/docs/apis/auth#authentication-api) -The Keystone and Next.js app are colocated in the same repo for ease of demonstration, but you'd often separate them into different packages in a monorepo or even separate repositories. +### Adding a frontend -The back-end files include: +As a Headless CMS, Keystone can be used with any frontend that uses GraphQL. It provides a GraphQL endpoint you can write queries against at `/api/graphql` (by default [http://localhost:3000/api/graphql](http://localhost:3000/api/graphql)). At Thinkmill, we tend to use [Next.js](https://nextjs.org/) and [Apollo graphql](https://www.apollographql.com/docs/react/get-started/) as our frontend and way to write queries, but if you have your own favourite, feel free to use it. -``` -keystone.ts -schema.graphql (generated) -schema.prisma (generated) -schema.ts -schema/* -``` - -The front-end files include: - -``` -components/* -pages/* -next-env.d.ts -next.config.js -postcss.config.js -tailwind.config.js -utils.js -``` +A walkthrough on how to do this is forthcoming, but in the meantime our [todo example](https://github.com/keystonejs/keystone-react-todo-demo) shows a Keystone set up with a frontend. For a more full example, you can also look at an example app we built for [Prisma Day 2021](https://github.com/keystonejs/prisma-day-2021-workshop) -## License +### Embedding Keystone in a Next.js frontend -Copyright (c) 2021 Thinkmill Labs Pty Ltd. Licensed under the MIT License. +While Keystone works as a standalone app, you can embed your Keystone app into a [Next.js](https://nextjs.org/) app. This is quite a different setup to the starter, and we recommend checking out our walkthrough for that [here](https://keystonejs.com/docs/walkthroughs/embedded-mode-with-sqlite-nextjs#how-to-embed-keystone-sq-lite-in-a-next-js-app). diff --git a/keystone.ts b/keystone.ts index 25d9571a..45d2649e 100644 --- a/keystone.ts +++ b/keystone.ts @@ -1,17 +1,21 @@ -import { config } from '@keystone-next/keystone/schema'; +import { config } from '@keystone-next/keystone'; import { statelessSessions } from '@keystone-next/keystone/session'; import { createAuth } from '@keystone-next/auth'; - -import { lists, extendGraphqlSchema } from './schema'; +import { lists, extendGraphqlSchema } from './schema'; import { rules } from './schema/access'; const dbUrl = - process.env.DATABASE_URL || - `postgres://${process.env.USER}@localhost/prisma-day-workshop`; + `postgres://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.POSTGRES_HOST}/keyn_25_0_3`; const sessionSecret = - process.env.SESSION_SECERT || - 'iLqbHhm7qwiBNc8KgL4NQ8tD8fFVhNhNqZ2nRdprgnKNjgJHgvitWx6DPoZJpYHa'; + process.env.SESSION_SECRET || + 'iLqbHhm7qwihbsdfjvb87876sdfIUSHSJZ2nRdprgnKNjgJHgvitWx6DPoZJpYHa'; + + + +let sessionMaxAge = 60 * 60 * 24 * 30; // 30 days + + const auth = createAuth({ identityField: 'email', @@ -45,8 +49,10 @@ export default auth.withAuth( ui: { isAccessAllowed: rules.canUseAdminUI }, lists, session: statelessSessions({ + maxAge: sessionMaxAge, secret: sessionSecret, }), extendGraphqlSchema, }) ); + diff --git a/migrations/20211008052137_initial_migrate/migration.sql b/migrations/20211008052137_initial_migrate/migration.sql new file mode 100644 index 00000000..784ff836 --- /dev/null +++ b/migrations/20211008052137_initial_migrate/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Post" ADD COLUMN "image_url" TEXT; diff --git a/next-env.d.ts b/next-env.d.ts index c6643fda..534a39ea 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,3 +1,6 @@ -/// -/// -/// +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/next.config.js b/next.config.js index 7ebcc584..0456254c 100644 --- a/next.config.js +++ b/next.config.js @@ -1,10 +1,16 @@ -module.exports = { - async rewrites() { - return [ - { - source: '/api/graphql', - destination: 'http://localhost:3000/api/graphql', - }, - ]; - }, -}; + + +module.exports = { + async rewrites() { + return [ + { + source: '/api/graphql', + destination: 'http://localhost:3000/api/graphql', + }, + ]; + }, + images: { + domains: ['picsum.photos'], + }, + +}; diff --git a/package.json b/package.json index e0ae57f7..95fe05c7 100644 --- a/package.json +++ b/package.json @@ -1,40 +1,45 @@ { - "name": "keystone-react-todo-demo", - "version": "1.0.0", + "name": "keystone-app", + "version": "3.0.0", "private": true, "scripts": { + "clean": "rm -rf node_modules yarn.lock .keystone .next", "postinstall": "keystone-next postinstall", - "site:dev": "next dev -p 8000", - "site:build": "next build", - "site:start": "next start -p 8000", "dev": "keystone-next dev", "start": "keystone-next start", "build": "keystone-next build", "migrate": "keystone-next prisma migrate deploy", - "format": "prettier --write \"**/*.ts\"" + "format": "prettier --write \"**/*.ts\"", + "site:dev": "next dev -p 8000", + "site:build": "next build", + "site:start": "next start -p 8000" }, "dependencies": { - "@keystone-next/auth": "30.0.0", + "@keystone-next/auth": "^32.0.0", "@keystone-next/document-renderer": "^4.0.0", - "@keystone-next/fields": "^13.0.0", - "@keystone-next/fields-document": "^7.0.3", - "@keystone-next/keystone": "^23.0.1", + "@keystone-next/fields-document": "^9.0.0", + "@keystone-next/keystone": "^25.0.3", + "@types/classnames": "^2.3.1", + "@types/react-dom": "^17.0.9", "classnames": "^2.3.1", - "graphql": "^15.5.1", - "next": "^11.0.1", - "node-fetch": "^2.6.1", + "next": "^11.1.2", + "next-images": "^1.8.1", "react": "^17.0.2", "react-dom": "^17.0.2", - "urql": "^2.0.4" + "sharp": "^0.29.1", + "tailwindcss": "^2.2.16", + "urql": "^2.0.5" }, "devDependencies": { - "@tailwindcss/forms": "^0.3.3", - "@types/react": "17.0.11", - "autoprefixer": "^10.3.1", - "postcss": "^8.3.5", - "prettier": "^2.3.2", - "tailwindcss": "^2.2.4", - "typescript": "4.3.4" + "@tailwindcss/forms": "^0.3.4", + "@types/react": "^17.0.27", + "autoprefixer": "^10.3.7", + "eslint": "7.32.0", + "eslint-config-next": "11.1.2", + "postcss": "^8.3.9", + "prettier": "^2.4.1", + "tailwindcss": "^2.2.16", + "typescript": "^4.4.3" }, "prettier": { "singleQuote": true, diff --git a/pages/_app.tsx b/pages/_app.tsx index 500a029f..a6cf2ce8 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,9 +1,15 @@ import 'tailwindcss/tailwind.css'; + import type { AppProps } from 'next/app'; +import App from 'next/app'; + import { createClient, Provider } from 'urql'; import { AuthProvider } from '../components/auth'; + + + export const client = createClient({ url: typeof window === undefined @@ -11,13 +17,24 @@ export const client = createClient({ : '/api/graphql', }); -function MyApp({ Component, pageProps }: AppProps) { - return ( - - - +class MyApp extends App { + render() { + const { Component, pageProps, router } = this.props; + + return ( + + + + + + - - ); + + + ); + } } + + + export default MyApp; diff --git a/pages/index.tsx b/pages/index.tsx index c2b18506..af3c5851 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,90 +1,127 @@ -import React, { useState } from 'react'; -import { GetStaticPropsContext } from 'next'; - -import { fetchGraphQL, gql } from '../utils'; -import { DocumentRenderer } from '../schema/fields/content/renderers'; - -import { Container } from '../components/ui/layout'; -import { Link } from '../components/ui/link'; -import { H1 } from '../components/ui/typography'; -import { useAuth } from '../components/auth'; - -type Post = { - id: string; - slug: string; - title: string; - publishedDate: string; - intro: { - document: any; - }; - author: { - name: string; - }; -}; - -export default function Home({ posts }: { posts: Post[] }) { - const auth = useAuth(); - return ( - -

My Blog

- {auth.ready && auth.sessionData ? ( -

- You're signed in as {auth.sessionData.name} |{' '} - -

- ) : ( -

- Sign In | Join -

- )} - -
- {posts.map(post => { - const date = post.publishedDate - ? new Date(post.publishedDate).toLocaleDateString() - : null; - return ( -
-
{date}
-

- {post.title} -

- {post.intro?.document && ( - - )} -
- Read the full story -
-
- ); - })} -
-
- ); -} - -export async function getStaticProps({ params }: GetStaticPropsContext) { - const data = await fetchGraphQL( - gql` - query { - allPosts( - where: { status: "published" } - orderBy: [{ publishedDate: desc }] - ) { - id - title - slug - publishedDate - intro { - document(hydrateRelationships: true) - } - author { - id - name - } - } - } - ` - ); - return { props: { posts: data.allPosts }, revalidate: 60 }; -} + + +import React, { useState, useEffect, useRef, useCallback} from 'react'; + + +import { GetStaticPropsContext } from 'next'; + +import { fetchGraphQL, gql } from '../utils'; +import { DocumentRenderer } from '../schema/fields/content/renderers'; + +import { Container } from '../components/ui/layout'; +import { Link } from '../components/ui/link'; +import { H1 } from '../components/ui/typography'; +import { useAuth } from '../components/auth'; +import { statelessSessions } from '@keystone-next/keystone/session'; +import Image from 'next/image'; + + +export type InternalPost = { + id: string; + slug: string; + title: string; + publishedDate: string; + intro: { + document: any; + }; + content: { + document: any; + }; + author: { + name: string; + }; +}; + + + +export default function Home({ posts }: { posts: InternalPost[] }) { + + const auth = useAuth(); + + return ( + + + + +

My Blog

+ {auth.ready && auth.sessionData ? ( +

+ You're signed in as {auth.sessionData.name} |{' '} + +

+ ) : ( +

+ Sign In | Join +

+ )} + +
+ {posts.map(post => { + const date = post.publishedDate + ? new Date(post.publishedDate).toLocaleDateString() + : null; + return ( +
+
{date}
+

+ {post.title} +

+ {post.intro?.document && ( + + )} +
+ Read the full story +
+ + +
+ ); + })} +
+ + +
+ + + ); + +} + +/* +( + + where: { + status: { equals: "published" }, + } + orderBy: [{ publishedDate: desc }] + + +) +*/ + +export async function getStaticProps({ params }: GetStaticPropsContext) { + const data = await fetchGraphQL( + gql` + query { + posts ( + + orderBy: [{ publishedDate: desc }] + ) + { + id + title + slug + publishedDate + intro { + document(hydrateRelationships: true) + } + author { + id + name + } + } + } + ` + ); + return { props: { posts: data.posts }, revalidate: 60 }; +} diff --git a/pages/post/[slug].tsx b/pages/post/[slug].tsx index 747f4606..ffcf5dba 100644 --- a/pages/post/[slug].tsx +++ b/pages/post/[slug].tsx @@ -7,15 +7,39 @@ import { DocumentRenderer } from '../../schema/fields/content/renderers'; import { Container, HomeLink } from '../../components/ui/layout'; import { Link } from '../../components/ui/link'; import { H1 } from '../../components/ui/typography'; +import { writeFile } from 'fs'; +import Image from 'next/image' + +export function jsfun(str: string) +{ + const parms_body = str.split(':',2); + return Function(parms_body[0],'return (' + parms_body[1] + ')'); +} + +export function jsthunk(body: string) +{ + + return Function('','return (' + body + ')'); +} export default function Post({ post }: { post: any }) { + let image_src = 'https://picsum.photos/seed/' + post.id + '/300/200'; return ( - + + < Image + src={image_src} + alt = "A random picture" + width = "300" + height = "200" + /> +

{post.title}

+ {post.author?.name && (

+ By {post.author.name}

)} @@ -31,24 +55,35 @@ export async function getStaticPaths(): Promise { const data = await fetchGraphQL( gql` query { - allPosts { + posts { slug + image_url + id + content { + document(hydrateRelationships: true) + } } } ` ); + + return { - paths: data.allPosts.map((post: any) => ({ params: { slug: post.slug } })), + paths: data.posts.map((post: any) => ({ params: { slug: post.slug } })), fallback: 'blocking', }; } export async function getStaticProps({ params }: GetStaticPropsContext) { + const data = await fetchGraphQL( gql` query ($slug: String!) { - Post(where: { slug: $slug }) { + post( + where: { slug: $slug } ) { + id title + image_url content { document(hydrateRelationships: true) } @@ -61,6 +96,8 @@ export async function getStaticProps({ params }: GetStaticPropsContext) { } `, { slug: params!.slug } + ); - return { props: { post: data.Post }, revalidate: 60 }; + + return { props: { post: data.post }, revalidate: 60 }; } diff --git a/pages/signin.tsx b/pages/signin.tsx index bd46651d..39071e4c 100644 --- a/pages/signin.tsx +++ b/pages/signin.tsx @@ -10,11 +10,11 @@ import { useAuth } from '../components/auth'; export default function SigninPage() { const auth = useAuth(); - const [email, setEmail] = useState('admin@demo.com'); - const [password, setPassword] = useState('password'); + const [email, setEmail] = useState('jonathan@fourcube.net'); + const [password, setPassword] = useState('SzALAUc@WedD9s8'); const [error, setError] = useState(''); - // const router = useRouter(); + const router = useRouter(); const signIn = async () => { if (!auth.ready) { @@ -28,10 +28,12 @@ export default function SigninPage() { setError(''); const result = await auth.signIn({ email, password }); if (result.success) { + // Commented out code now seems to work! + //router.push('/'); + // FIXME: there's a cache issue with Urql where it's not reloading the // current user properly if we do a client-side redirect here. - // router.push('/'); - top.location.href = '/'; + top!.location!.href = '/'; } else { setEmail(''); setPassword(''); diff --git a/pages/signup.tsx b/pages/signup.tsx index 555246b7..03cbd942 100644 --- a/pages/signup.tsx +++ b/pages/signup.tsx @@ -35,10 +35,12 @@ export default function SignupPage() { event.preventDefault(); signup({ name, email, password }).then(result => { if (result.data?.createUser) { + + //router.push('/'); // FIXME: there's a cache issue with Urql where it's not reloading the // current user properly if we do a client-side redirect here. - // router.push('/'); - top.location.href = '/'; + + top!.location!.href = '/'; } }); }} diff --git a/schema.graphql b/schema.graphql index 2a097881..3594c2a2 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1,40 +1,107 @@ +# This file is automatically generated by Keystone, do not modify it manually. +# Modify your Keystone config when you want to change this. + +type Mutation { + voteForPoll(answerId: ID!): Boolean + clearVoteForPoll(pollId: ID!): Boolean + createInitialUser( + data: CreateInitialUserInput! + ): UserAuthenticationWithPasswordSuccess! + authenticateUserWithPassword( + email: String! + password: String! + ): UserAuthenticationWithPasswordResult! + createPost(data: PostCreateInput!): Post + createPosts(data: [PostCreateInput!]!): [Post] + updatePost(where: PostWhereUniqueInput!, data: PostUpdateInput!): Post + updatePosts(data: [PostUpdateArgs!]!): [Post] + deletePost(where: PostWhereUniqueInput!): Post + deletePosts(where: [PostWhereUniqueInput!]!): [Post] + createLabel(data: LabelCreateInput!): Label + createLabels(data: [LabelCreateInput!]!): [Label] + updateLabel(where: LabelWhereUniqueInput!, data: LabelUpdateInput!): Label + updateLabels(data: [LabelUpdateArgs!]!): [Label] + deleteLabel(where: LabelWhereUniqueInput!): Label + deleteLabels(where: [LabelWhereUniqueInput!]!): [Label] + createPoll(data: PollCreateInput!): Poll + createPolls(data: [PollCreateInput!]!): [Poll] + updatePoll(where: PollWhereUniqueInput!, data: PollUpdateInput!): Poll + updatePolls(data: [PollUpdateArgs!]!): [Poll] + deletePoll(where: PollWhereUniqueInput!): Poll + deletePolls(where: [PollWhereUniqueInput!]!): [Poll] + createPollAnswer(data: PollAnswerCreateInput!): PollAnswer + createPollAnswers(data: [PollAnswerCreateInput!]!): [PollAnswer] + updatePollAnswer( + where: PollAnswerWhereUniqueInput! + data: PollAnswerUpdateInput! + ): PollAnswer + updatePollAnswers(data: [PollAnswerUpdateArgs!]!): [PollAnswer] + deletePollAnswer(where: PollAnswerWhereUniqueInput!): PollAnswer + deletePollAnswers(where: [PollAnswerWhereUniqueInput!]!): [PollAnswer] + createUser(data: UserCreateInput!): User + createUsers(data: [UserCreateInput!]!): [User] + updateUser(where: UserWhereUniqueInput!, data: UserUpdateInput!): User + updateUsers(data: [UserUpdateArgs!]!): [User] + deleteUser(where: UserWhereUniqueInput!): User + deleteUsers(where: [UserWhereUniqueInput!]!): [User] + createRole(data: RoleCreateInput!): Role + createRoles(data: [RoleCreateInput!]!): [Role] + updateRole(where: RoleWhereUniqueInput!, data: RoleUpdateInput!): Role + updateRoles(data: [RoleUpdateArgs!]!): [Role] + deleteRole(where: RoleWhereUniqueInput!): Role + deleteRoles(where: [RoleWhereUniqueInput!]!): [Role] + endSession: Boolean! +} + +input CreateInitialUserInput { + name: String + email: String + password: String +} + +union AuthenticatedItem = User + +union UserAuthenticationWithPasswordResult = + UserAuthenticationWithPasswordSuccess + | UserAuthenticationWithPasswordFailure + +type UserAuthenticationWithPasswordSuccess { + sessionToken: String! + item: User! +} + +type UserAuthenticationWithPasswordFailure { + code: PasswordAuthErrorCode! + message: String! +} + +enum PasswordAuthErrorCode { + FAILURE + IDENTITY_NOT_FOUND + SECRET_NOT_SET + MULTIPLE_IDENTITY_MATCHES + SECRET_MISMATCH +} + type Post { id: ID! title: String + image_url: String slug: String status: String publishedDate: String author: User labels( where: LabelWhereInput! = {} - search: String - sortBy: [SortLabelsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [LabelOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [Label!] - _labelsMeta( - where: LabelWhereInput! = {} - search: String - sortBy: [SortLabelsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [LabelOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use labelsCount instead." - ) labelsCount(where: LabelWhereInput! = {}): Int intro: Post_intro_DocumentField content: Post_content_DocumentField } -type _QueryMeta { - count: Int -} - type Post_intro_DocumentField { document(hydrateRelationships: Boolean! = false): JSON! } @@ -43,95 +110,79 @@ type Post_content_DocumentField { document(hydrateRelationships: Boolean! = false): JSON! } +input PostWhereUniqueInput { + id: ID + slug: String +} + input PostWhereInput { AND: [PostWhereInput!] OR: [PostWhereInput!] - id: ID - id_not: ID - id_lt: ID - id_lte: ID - id_gt: ID - id_gte: ID - id_in: [ID!] - id_not_in: [ID!] - title: String - title_not: String - title_contains: String - title_not_contains: String - title_starts_with: String - title_not_starts_with: String - title_ends_with: String - title_not_ends_with: String - title_i: String - title_not_i: String - title_contains_i: String - title_not_contains_i: String - title_starts_with_i: String - title_not_starts_with_i: String - title_ends_with_i: String - title_not_ends_with_i: String - title_in: [String] - title_not_in: [String] - slug: String - slug_not: String - slug_contains: String - slug_not_contains: String - slug_starts_with: String - slug_not_starts_with: String - slug_ends_with: String - slug_not_ends_with: String - slug_i: String - slug_not_i: String - slug_contains_i: String - slug_not_contains_i: String - slug_starts_with_i: String - slug_not_starts_with_i: String - slug_ends_with_i: String - slug_not_ends_with_i: String - slug_in: [String] - slug_not_in: [String] - status: String - status_not: String - status_in: [String] - status_not_in: [String] - publishedDate: String - publishedDate_not: String - publishedDate_lt: String - publishedDate_lte: String - publishedDate_gt: String - publishedDate_gte: String - publishedDate_in: [String] - publishedDate_not_in: [String] - author: UserWhereInput - author_is_null: Boolean - labels_every: LabelWhereInput - labels_some: LabelWhereInput - labels_none: LabelWhereInput + NOT: [PostWhereInput!] + id: IDFilter + slug: StringNullableFilter + status: StringNullableFilter + publishedDate: DateTimeNullableFilter +} + +input IDFilter { + equals: ID + in: [ID!] + notIn: [ID!] + lt: ID + lte: ID + gt: ID + gte: ID + not: IDFilter +} + +input StringNullableFilter { + equals: String + in: [String!] + notIn: [String!] + lt: String + lte: String + gt: String + gte: String + contains: String + startsWith: String + endsWith: String + mode: QueryMode + not: NestedStringNullableFilter } -input PostWhereUniqueInput { - id: ID - slug: String +enum QueryMode { + default + insensitive } -enum SortPostsBy { - id_ASC - id_DESC - title_ASC - title_DESC - slug_ASC - slug_DESC - status_ASC - status_DESC - publishedDate_ASC - publishedDate_DESC +input NestedStringNullableFilter { + equals: String + in: [String!] + notIn: [String!] + lt: String + lte: String + gt: String + gte: String + contains: String + startsWith: String + endsWith: String + not: NestedStringNullableFilter +} + +input DateTimeNullableFilter { + equals: String + in: [String!] + notIn: [String!] + lt: String + lte: String + gt: String + gte: String + not: DateTimeNullableFilter } input PostOrderByInput { id: OrderDirection - title: OrderDirection - slug: OrderDirection - status: OrderDirection publishedDate: OrderDirection } @@ -142,47 +193,54 @@ enum OrderDirection { input PostUpdateInput { title: String + image_url: String slug: String status: String publishedDate: String - author: UserRelateToOneInput - labels: LabelRelateToManyInput + author: UserRelateToOneForUpdateInput + labels: LabelRelateToManyForUpdateInput intro: JSON content: JSON } -input UserRelateToOneInput { +input UserRelateToOneForUpdateInput { create: UserCreateInput connect: UserWhereUniqueInput - disconnect: UserWhereUniqueInput - disconnectAll: Boolean + disconnect: Boolean } -input LabelRelateToManyInput { - create: [LabelCreateInput] - connect: [LabelWhereUniqueInput] - disconnect: [LabelWhereUniqueInput] - disconnectAll: Boolean +input LabelRelateToManyForUpdateInput { + disconnect: [LabelWhereUniqueInput!] + set: [LabelWhereUniqueInput!] + create: [LabelCreateInput!] + connect: [LabelWhereUniqueInput!] } -input PostsUpdateInput { - id: ID! - data: PostUpdateInput +input PostUpdateArgs { + where: PostWhereUniqueInput! + data: PostUpdateInput! } input PostCreateInput { title: String + image_url: String slug: String status: String publishedDate: String - author: UserRelateToOneInput - labels: LabelRelateToManyInput + author: UserRelateToOneForCreateInput + labels: LabelRelateToManyForCreateInput intro: JSON content: JSON } -input PostsCreateInput { - data: PostCreateInput +input UserRelateToOneForCreateInput { + create: UserCreateInput + connect: UserWhereUniqueInput +} + +input LabelRelateToManyForCreateInput { + create: [LabelCreateInput!] + connect: [LabelWhereUniqueInput!] } type Label { @@ -190,102 +248,53 @@ type Label { name: String posts( where: PostWhereInput! = {} - search: String - sortBy: [SortPostsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [PostOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [Post!] - _postsMeta( - where: PostWhereInput! = {} - search: String - sortBy: [SortPostsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [PostOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use postsCount instead." - ) postsCount(where: PostWhereInput! = {}): Int } -input LabelWhereInput { - AND: [LabelWhereInput!] - OR: [LabelWhereInput!] - id: ID - id_not: ID - id_lt: ID - id_lte: ID - id_gt: ID - id_gte: ID - id_in: [ID!] - id_not_in: [ID!] - name: String - name_not: String - name_contains: String - name_not_contains: String - name_starts_with: String - name_not_starts_with: String - name_ends_with: String - name_not_ends_with: String - name_i: String - name_not_i: String - name_contains_i: String - name_not_contains_i: String - name_starts_with_i: String - name_not_starts_with_i: String - name_ends_with_i: String - name_not_ends_with_i: String - name_in: [String] - name_not_in: [String] - posts_every: PostWhereInput - posts_some: PostWhereInput - posts_none: PostWhereInput -} - input LabelWhereUniqueInput { id: ID } -enum SortLabelsBy { - id_ASC - id_DESC - name_ASC - name_DESC +input LabelWhereInput { + AND: [LabelWhereInput!] + OR: [LabelWhereInput!] + NOT: [LabelWhereInput!] + id: IDFilter } input LabelOrderByInput { id: OrderDirection - name: OrderDirection } input LabelUpdateInput { name: String - posts: PostRelateToManyInput + posts: PostRelateToManyForUpdateInput } -input PostRelateToManyInput { - create: [PostCreateInput] - connect: [PostWhereUniqueInput] - disconnect: [PostWhereUniqueInput] - disconnectAll: Boolean +input PostRelateToManyForUpdateInput { + disconnect: [PostWhereUniqueInput!] + set: [PostWhereUniqueInput!] + create: [PostCreateInput!] + connect: [PostWhereUniqueInput!] } -input LabelsUpdateInput { - id: ID! - data: LabelUpdateInput +input LabelUpdateArgs { + where: LabelWhereUniqueInput! + data: LabelUpdateInput! } input LabelCreateInput { name: String - posts: PostRelateToManyInput + posts: PostRelateToManyForCreateInput } -input LabelsCreateInput { - data: LabelCreateInput +input PostRelateToManyForCreateInput { + create: [PostCreateInput!] + connect: [PostWhereUniqueInput!] } type Poll { @@ -293,104 +302,62 @@ type Poll { label: String answers( where: PollAnswerWhereInput! = {} - search: String - sortBy: [SortPollAnswersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [PollAnswerOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [PollAnswer!] - _answersMeta( - where: PollAnswerWhereInput! = {} - search: String - sortBy: [SortPollAnswersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [PollAnswerOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use answersCount instead." - ) answersCount(where: PollAnswerWhereInput! = {}): Int responsesCount: Int userAnswer: PollAnswer } -input PollWhereInput { - AND: [PollWhereInput!] - OR: [PollWhereInput!] +input PollWhereUniqueInput { id: ID - id_not: ID - id_lt: ID - id_lte: ID - id_gt: ID - id_gte: ID - id_in: [ID!] - id_not_in: [ID!] - label: String - label_not: String - label_contains: String - label_not_contains: String - label_starts_with: String - label_not_starts_with: String - label_ends_with: String - label_not_ends_with: String - label_i: String - label_not_i: String - label_contains_i: String - label_not_contains_i: String - label_starts_with_i: String - label_not_starts_with_i: String - label_ends_with_i: String - label_not_ends_with_i: String - label_in: [String] - label_not_in: [String] - answers_every: PollAnswerWhereInput - answers_some: PollAnswerWhereInput - answers_none: PollAnswerWhereInput } -input PollWhereUniqueInput { - id: ID +input PollWhereInput { + AND: [PollWhereInput!] + OR: [PollWhereInput!] + NOT: [PollWhereInput!] + id: IDFilter + answers: PollAnswerManyRelationFilter } -enum SortPollsBy { - id_ASC - id_DESC - label_ASC - label_DESC +input PollAnswerManyRelationFilter { + every: PollAnswerWhereInput + some: PollAnswerWhereInput + none: PollAnswerWhereInput } input PollOrderByInput { id: OrderDirection - label: OrderDirection } input PollUpdateInput { label: String - answers: PollAnswerRelateToManyInput + answers: PollAnswerRelateToManyForUpdateInput } -input PollAnswerRelateToManyInput { - create: [PollAnswerCreateInput] - connect: [PollAnswerWhereUniqueInput] - disconnect: [PollAnswerWhereUniqueInput] - disconnectAll: Boolean +input PollAnswerRelateToManyForUpdateInput { + disconnect: [PollAnswerWhereUniqueInput!] + set: [PollAnswerWhereUniqueInput!] + create: [PollAnswerCreateInput!] + connect: [PollAnswerWhereUniqueInput!] } -input PollsUpdateInput { - id: ID! - data: PollUpdateInput +input PollUpdateArgs { + where: PollWhereUniqueInput! + data: PollUpdateInput! } input PollCreateInput { label: String - answers: PollAnswerRelateToManyInput + answers: PollAnswerRelateToManyForCreateInput } -input PollsCreateInput { - data: PollCreateInput +input PollAnswerRelateToManyForCreateInput { + create: [PollAnswerCreateInput!] + connect: [PollAnswerWhereUniqueInput!] } type PollAnswer { @@ -400,113 +367,74 @@ type PollAnswer { voteCount: Int answeredByUsers( where: UserWhereInput! = {} - search: String - sortBy: [SortUsersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [UserOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [User!] - _answeredByUsersMeta( - where: UserWhereInput! = {} - search: String - sortBy: [SortUsersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [UserOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use answeredByUsersCount instead." - ) answeredByUsersCount(where: UserWhereInput! = {}): Int } +input PollAnswerWhereUniqueInput { + id: ID +} + input PollAnswerWhereInput { AND: [PollAnswerWhereInput!] OR: [PollAnswerWhereInput!] - id: ID - id_not: ID - id_lt: ID - id_lte: ID - id_gt: ID - id_gte: ID - id_in: [ID!] - id_not_in: [ID!] - label: String - label_not: String - label_contains: String - label_not_contains: String - label_starts_with: String - label_not_starts_with: String - label_ends_with: String - label_not_ends_with: String - label_i: String - label_not_i: String - label_contains_i: String - label_not_contains_i: String - label_starts_with_i: String - label_not_starts_with_i: String - label_ends_with_i: String - label_not_ends_with_i: String - label_in: [String] - label_not_in: [String] + NOT: [PollAnswerWhereInput!] + id: IDFilter poll: PollWhereInput - poll_is_null: Boolean - answeredByUsers_every: UserWhereInput - answeredByUsers_some: UserWhereInput - answeredByUsers_none: UserWhereInput + answeredByUsers: UserManyRelationFilter } -input PollAnswerWhereUniqueInput { - id: ID -} - -enum SortPollAnswersBy { - id_ASC - id_DESC - label_ASC - label_DESC +input UserManyRelationFilter { + every: UserWhereInput + some: UserWhereInput + none: UserWhereInput } input PollAnswerOrderByInput { id: OrderDirection - label: OrderDirection } input PollAnswerUpdateInput { label: String - poll: PollRelateToOneInput - answeredByUsers: UserRelateToManyInput + poll: PollRelateToOneForUpdateInput + answeredByUsers: UserRelateToManyForUpdateInput } -input PollRelateToOneInput { +input PollRelateToOneForUpdateInput { create: PollCreateInput connect: PollWhereUniqueInput - disconnect: PollWhereUniqueInput - disconnectAll: Boolean + disconnect: Boolean } -input UserRelateToManyInput { - create: [UserCreateInput] - connect: [UserWhereUniqueInput] - disconnect: [UserWhereUniqueInput] - disconnectAll: Boolean +input UserRelateToManyForUpdateInput { + disconnect: [UserWhereUniqueInput!] + set: [UserWhereUniqueInput!] + create: [UserCreateInput!] + connect: [UserWhereUniqueInput!] } -input PollAnswersUpdateInput { - id: ID! - data: PollAnswerUpdateInput +input PollAnswerUpdateArgs { + where: PollAnswerWhereUniqueInput! + data: PollAnswerUpdateInput! } input PollAnswerCreateInput { label: String - poll: PollRelateToOneInput - answeredByUsers: UserRelateToManyInput + poll: PollRelateToOneForCreateInput + answeredByUsers: UserRelateToManyForCreateInput } -input PollAnswersCreateInput { - data: PollAnswerCreateInput +input PollRelateToOneForCreateInput { + create: PollCreateInput + connect: PollWhereUniqueInput +} + +input UserRelateToManyForCreateInput { + create: [UserCreateInput!] + connect: [UserWhereUniqueInput!] } type User { @@ -519,47 +447,17 @@ type User { githubRepos: [GitHubRepo]! authoredPosts( where: PostWhereInput! = {} - search: String - sortBy: [SortPostsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [PostOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [Post!] - _authoredPostsMeta( - where: PostWhereInput! = {} - search: String - sortBy: [SortPostsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [PostOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use authoredPostsCount instead." - ) authoredPostsCount(where: PostWhereInput! = {}): Int pollAnswers( where: PollAnswerWhereInput! = {} - search: String - sortBy: [SortPollAnswersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [PollAnswerOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [PollAnswer!] - _pollAnswersMeta( - where: PollAnswerWhereInput! = {} - search: String - sortBy: [SortPollAnswersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [PollAnswerOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use pollAnswersCount instead." - ) pollAnswersCount(where: PollAnswerWhereInput! = {}): Int } @@ -584,139 +482,66 @@ type GitHubRepo { forksCount: Int } -input UserWhereInput { - AND: [UserWhereInput!] - OR: [UserWhereInput!] +input UserWhereUniqueInput { id: ID - id_not: ID - id_lt: ID - id_lte: ID - id_gt: ID - id_gte: ID - id_in: [ID!] - id_not_in: [ID!] - name: String - name_not: String - name_contains: String - name_not_contains: String - name_starts_with: String - name_not_starts_with: String - name_ends_with: String - name_not_ends_with: String - name_i: String - name_not_i: String - name_contains_i: String - name_not_contains_i: String - name_starts_with_i: String - name_not_starts_with_i: String - name_ends_with_i: String - name_not_ends_with_i: String - name_in: [String] - name_not_in: [String] email: String - email_not: String - email_contains: String - email_not_contains: String - email_starts_with: String - email_not_starts_with: String - email_ends_with: String - email_not_ends_with: String - email_i: String - email_not_i: String - email_contains_i: String - email_not_contains_i: String - email_starts_with_i: String - email_not_starts_with_i: String - email_ends_with_i: String - email_not_ends_with_i: String - email_in: [String] - email_not_in: [String] - password_is_set: Boolean - role: RoleWhereInput - role_is_null: Boolean - githubUsername: String - githubUsername_not: String - githubUsername_contains: String - githubUsername_not_contains: String - githubUsername_starts_with: String - githubUsername_not_starts_with: String - githubUsername_ends_with: String - githubUsername_not_ends_with: String - githubUsername_i: String - githubUsername_not_i: String - githubUsername_contains_i: String - githubUsername_not_contains_i: String - githubUsername_starts_with_i: String - githubUsername_not_starts_with_i: String - githubUsername_ends_with_i: String - githubUsername_not_ends_with_i: String - githubUsername_in: [String] - githubUsername_not_in: [String] - authoredPosts_every: PostWhereInput - authoredPosts_some: PostWhereInput - authoredPosts_none: PostWhereInput - pollAnswers_every: PollAnswerWhereInput - pollAnswers_some: PollAnswerWhereInput - pollAnswers_none: PollAnswerWhereInput } -input UserWhereUniqueInput { - id: ID - email: String +input UserWhereInput { + AND: [UserWhereInput!] + OR: [UserWhereInput!] + NOT: [UserWhereInput!] + id: IDFilter + email: StringNullableFilter + githubUsername: StringNullableFilter + authoredPosts: PostManyRelationFilter + pollAnswers: PollAnswerManyRelationFilter } -enum SortUsersBy { - id_ASC - id_DESC - name_ASC - name_DESC - email_ASC - email_DESC - githubUsername_ASC - githubUsername_DESC +input PostManyRelationFilter { + every: PostWhereInput + some: PostWhereInput + none: PostWhereInput } input UserOrderByInput { id: OrderDirection - name: OrderDirection - email: OrderDirection - githubUsername: OrderDirection } input UserUpdateInput { name: String email: String password: String - role: RoleRelateToOneInput + role: RoleRelateToOneForUpdateInput githubUsername: String - authoredPosts: PostRelateToManyInput - pollAnswers: PollAnswerRelateToManyInput + authoredPosts: PostRelateToManyForUpdateInput + pollAnswers: PollAnswerRelateToManyForUpdateInput } -input RoleRelateToOneInput { +input RoleRelateToOneForUpdateInput { create: RoleCreateInput connect: RoleWhereUniqueInput - disconnect: RoleWhereUniqueInput - disconnectAll: Boolean + disconnect: Boolean } -input UsersUpdateInput { - id: ID! - data: UserUpdateInput +input UserUpdateArgs { + where: UserWhereUniqueInput! + data: UserUpdateInput! } input UserCreateInput { name: String email: String password: String - role: RoleRelateToOneInput + role: RoleRelateToOneForCreateInput githubUsername: String - authoredPosts: PostRelateToManyInput - pollAnswers: PollAnswerRelateToManyInput + authoredPosts: PostRelateToManyForCreateInput + pollAnswers: PollAnswerRelateToManyForCreateInput } -input UsersCreateInput { - data: UserCreateInput +input RoleRelateToOneForCreateInput { + create: RoleCreateInput + connect: RoleWhereUniqueInput } type Role { @@ -726,109 +551,45 @@ type Role { canManageUsers: Boolean users( where: UserWhereInput! = {} - search: String - sortBy: [SortUsersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [UserOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [User!] - _usersMeta( - where: UserWhereInput! = {} - search: String - sortBy: [SortUsersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [UserOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use usersCount instead." - ) usersCount(where: UserWhereInput! = {}): Int } -input RoleWhereInput { - AND: [RoleWhereInput!] - OR: [RoleWhereInput!] - id: ID - id_not: ID - id_lt: ID - id_lte: ID - id_gt: ID - id_gte: ID - id_in: [ID!] - id_not_in: [ID!] - name: String - name_not: String - name_contains: String - name_not_contains: String - name_starts_with: String - name_not_starts_with: String - name_ends_with: String - name_not_ends_with: String - name_i: String - name_not_i: String - name_contains_i: String - name_not_contains_i: String - name_starts_with_i: String - name_not_starts_with_i: String - name_ends_with_i: String - name_not_ends_with_i: String - name_in: [String] - name_not_in: [String] - canManageContent: Boolean - canManageContent_not: Boolean - canManageUsers: Boolean - canManageUsers_not: Boolean - users_every: UserWhereInput - users_some: UserWhereInput - users_none: UserWhereInput -} - input RoleWhereUniqueInput { id: ID } -enum SortRolesBy { - id_ASC - id_DESC - name_ASC - name_DESC - canManageContent_ASC - canManageContent_DESC - canManageUsers_ASC - canManageUsers_DESC +input RoleWhereInput { + AND: [RoleWhereInput!] + OR: [RoleWhereInput!] + NOT: [RoleWhereInput!] + id: IDFilter } input RoleOrderByInput { id: OrderDirection - name: OrderDirection - canManageContent: OrderDirection - canManageUsers: OrderDirection } input RoleUpdateInput { name: String canManageContent: Boolean canManageUsers: Boolean - users: UserRelateToManyInput + users: UserRelateToManyForUpdateInput } -input RolesUpdateInput { - id: ID! - data: RoleUpdateInput +input RoleUpdateArgs { + where: RoleWhereUniqueInput! + data: RoleUpdateInput! } input RoleCreateInput { name: String canManageContent: Boolean canManageUsers: Boolean - users: UserRelateToManyInput -} - -input RolesCreateInput { - data: RoleCreateInput + users: UserRelateToManyForCreateInput } """ @@ -839,225 +600,56 @@ scalar JSON url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf" ) -type Mutation { - createPost(data: PostCreateInput): Post - createPosts(data: [PostsCreateInput]): [Post] - updatePost(id: ID!, data: PostUpdateInput): Post - updatePosts(data: [PostsUpdateInput]): [Post] - deletePost(id: ID!): Post - deletePosts(ids: [ID!]): [Post] - createLabel(data: LabelCreateInput): Label - createLabels(data: [LabelsCreateInput]): [Label] - updateLabel(id: ID!, data: LabelUpdateInput): Label - updateLabels(data: [LabelsUpdateInput]): [Label] - deleteLabel(id: ID!): Label - deleteLabels(ids: [ID!]): [Label] - createPoll(data: PollCreateInput): Poll - createPolls(data: [PollsCreateInput]): [Poll] - updatePoll(id: ID!, data: PollUpdateInput): Poll - updatePolls(data: [PollsUpdateInput]): [Poll] - deletePoll(id: ID!): Poll - deletePolls(ids: [ID!]): [Poll] - createPollAnswer(data: PollAnswerCreateInput): PollAnswer - createPollAnswers(data: [PollAnswersCreateInput]): [PollAnswer] - updatePollAnswer(id: ID!, data: PollAnswerUpdateInput): PollAnswer - updatePollAnswers(data: [PollAnswersUpdateInput]): [PollAnswer] - deletePollAnswer(id: ID!): PollAnswer - deletePollAnswers(ids: [ID!]): [PollAnswer] - createUser(data: UserCreateInput): User - createUsers(data: [UsersCreateInput]): [User] - updateUser(id: ID!, data: UserUpdateInput): User - updateUsers(data: [UsersUpdateInput]): [User] - deleteUser(id: ID!): User - deleteUsers(ids: [ID!]): [User] - createRole(data: RoleCreateInput): Role - createRoles(data: [RolesCreateInput]): [Role] - updateRole(id: ID!, data: RoleUpdateInput): Role - updateRoles(data: [RolesUpdateInput]): [Role] - deleteRole(id: ID!): Role - deleteRoles(ids: [ID!]): [Role] - authenticateUserWithPassword( - email: String! - password: String! - ): UserAuthenticationWithPasswordResult! - createInitialUser( - data: CreateInitialUserInput! - ): UserAuthenticationWithPasswordSuccess! - voteForPoll(answerId: ID!): Boolean - clearVoteForPoll(pollId: ID!): Boolean - endSession: Boolean! -} - -union AuthenticatedItem = User - -union UserAuthenticationWithPasswordResult = - UserAuthenticationWithPasswordSuccess - | UserAuthenticationWithPasswordFailure - -type UserAuthenticationWithPasswordSuccess { - sessionToken: String! - item: User! -} - -type UserAuthenticationWithPasswordFailure { - code: PasswordAuthErrorCode! - message: String! -} - -enum PasswordAuthErrorCode { - FAILURE - IDENTITY_NOT_FOUND - SECRET_NOT_SET - MULTIPLE_IDENTITY_MATCHES - SECRET_MISMATCH -} - -input CreateInitialUserInput { - name: String - email: String - password: String -} - type Query { - allPosts( + authenticatedItem: AuthenticatedItem + posts( where: PostWhereInput! = {} - search: String - sortBy: [SortPostsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [PostOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [Post!] - Post(where: PostWhereUniqueInput!): Post - _allPostsMeta( - where: PostWhereInput! = {} - search: String - sortBy: [SortPostsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [PostOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use postsCount instead." - ) + post(where: PostWhereUniqueInput!): Post postsCount(where: PostWhereInput! = {}): Int - allLabels( + labels( where: LabelWhereInput! = {} - search: String - sortBy: [SortLabelsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [LabelOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [Label!] - Label(where: LabelWhereUniqueInput!): Label - _allLabelsMeta( - where: LabelWhereInput! = {} - search: String - sortBy: [SortLabelsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [LabelOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use labelsCount instead." - ) + label(where: LabelWhereUniqueInput!): Label labelsCount(where: LabelWhereInput! = {}): Int - allPolls( + polls( where: PollWhereInput! = {} - search: String - sortBy: [SortPollsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [PollOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [Poll!] - Poll(where: PollWhereUniqueInput!): Poll - _allPollsMeta( - where: PollWhereInput! = {} - search: String - sortBy: [SortPollsBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [PollOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use pollsCount instead." - ) + poll(where: PollWhereUniqueInput!): Poll pollsCount(where: PollWhereInput! = {}): Int - allPollAnswers( + pollAnswers( where: PollAnswerWhereInput! = {} - search: String - sortBy: [SortPollAnswersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [PollAnswerOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [PollAnswer!] - PollAnswer(where: PollAnswerWhereUniqueInput!): PollAnswer - _allPollAnswersMeta( - where: PollAnswerWhereInput! = {} - search: String - sortBy: [SortPollAnswersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [PollAnswerOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use pollAnswersCount instead." - ) + pollAnswer(where: PollAnswerWhereUniqueInput!): PollAnswer pollAnswersCount(where: PollAnswerWhereInput! = {}): Int - allUsers( + users( where: UserWhereInput! = {} - search: String - sortBy: [SortUsersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [UserOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [User!] - User(where: UserWhereUniqueInput!): User - _allUsersMeta( - where: UserWhereInput! = {} - search: String - sortBy: [SortUsersBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [UserOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use usersCount instead." - ) + user(where: UserWhereUniqueInput!): User usersCount(where: UserWhereInput! = {}): Int - allRoles( + roles( where: RoleWhereInput! = {} - search: String - sortBy: [SortRolesBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") orderBy: [RoleOrderByInput!]! = [] - first: Int + take: Int skip: Int! = 0 ): [Role!] - Role(where: RoleWhereUniqueInput!): Role - _allRolesMeta( - where: RoleWhereInput! = {} - search: String - sortBy: [SortRolesBy!] - @deprecated(reason: "sortBy has been deprecated in favour of orderBy") - orderBy: [RoleOrderByInput!]! = [] - first: Int - skip: Int! = 0 - ): _QueryMeta - @deprecated( - reason: "This query will be removed in a future version. Please use rolesCount instead." - ) + role(where: RoleWhereUniqueInput!): Role rolesCount(where: RoleWhereInput! = {}): Int - authenticatedItem: AuthenticatedItem keystone: KeystoneMeta! } @@ -1095,6 +687,7 @@ type KeystoneAdminUIFieldMeta { path: String! label: String! isOrderable: Boolean! + isFilterable: Boolean! fieldMeta: JSON viewsIndex: Int! customViewsIndex: Int @@ -1132,11 +725,6 @@ enum KeystoneAdminUIFieldMetaItemViewFieldMode { hidden } -enum QueryMode { - default - insensitive -} - type KeystoneAdminUISort { field: String! direction: KeystoneAdminUISortDirection! diff --git a/schema.prisma b/schema.prisma index 2d2a3f95..58c51c4f 100644 --- a/schema.prisma +++ b/schema.prisma @@ -1,3 +1,6 @@ +// This file is automatically generated by Keystone, do not modify it manually. +// Modify your Keystone config when you want to change this. + datasource postgresql { url = env("DATABASE_URL") provider = "postgresql" @@ -11,6 +14,7 @@ generator client { model Post { id String @id @default(cuid()) title String? + image_url String? slug String? @unique status String? publishedDate DateTime? diff --git a/schema.ts b/schema.ts index 148d0aaf..39f2cdfc 100644 --- a/schema.ts +++ b/schema.ts @@ -1,4 +1,4 @@ -import { createSchema } from '@keystone-next/keystone/schema'; +import { createSchema } from '@keystone-next/keystone'; import { Poll, PollAnswer } from './schema/polls'; import { User, Role } from './schema/users'; diff --git a/schema/access.ts b/schema/access.ts index d1aaa014..a11f07a2 100644 --- a/schema/access.ts +++ b/schema/access.ts @@ -11,7 +11,9 @@ type SessionContext = { listKey: string; }; }; -type ItemContext = { item: any } & SessionContext; + + +export type ItemContext = { item: any } & SessionContext; export const isSignedIn = ({ session }: SessionContext) => { return !!session; @@ -28,20 +30,25 @@ export const permissions = { export const rules = { canUseAdminUI: ({ session }: SessionContext) => { - return !!session?.data.role; + return !!session?.data?.role; }, canReadContentList: ({ session }: SessionContext) => { if (permissions.canManageContent({ session })) return true; - return { status: 'published' }; + //return { status: 'published' }; + return false; }, canManageUser: ({ session, item }: ItemContext) => { if (permissions.canManageUsers({ session })) return true; if (session?.itemId === item.id) return true; return false; }, - canManageUserList: ({ session }: ItemContext) => { + operationCanManageUserList: ({ session }: ItemContext) => { if (permissions.canManageUsers({ session })) return true; - if (!isSignedIn({ session })) return false; - return { id: session!.itemId }; + if (!isSignedIn({ session })) + return false; + return true; }, + filterCanManageUserList: { + canManageUsers: { equals: true } + } }; diff --git a/schema/content.ts b/schema/content.ts index 37b09cb9..bcb173c2 100644 --- a/schema/content.ts +++ b/schema/content.ts @@ -1,15 +1,55 @@ -import { relationship, select, text, timestamp } from '@keystone-next/fields'; +import { relationship, select, text, timestamp } from '@keystone-next/keystone/fields'; import { document } from '@keystone-next/fields-document'; -import { list } from '@keystone-next/keystone/schema'; +import { list } from '@keystone-next/keystone'; import { permissions, rules } from './access'; -import { componentBlocks } from '../schema/fields/content/components'; +import { componentBlocks } from './fields/content/components'; + +import { ListAccessControl, BaseGeneratedListTypes } from '@keystone-next/keystone/types'; + + + +export const contentListAccess : ListAccessControl = { + + operation: + { + create: ({ session, context, listKey, operation }) => !!permissions.canManageContent(context), + query: ({ session, context, listKey, operation }) => true, + update: ({ session, context, listKey, operation }) => !!permissions.canManageContent(context), + delete: ({ session, context, listKey, operation }) => !!permissions.canManageContent(context) + } + } + + + /* + let acc = contentListAccess + acc!.operation!.query = ({ session, context, listKey, operation }) => + rules.canReadContentList(session) + + acc!.filter!.query = ({ session, context, listKey, operation }) => + { + return { status: { equals: 'published' } } + } + operation: + { + create: ({ session, context, listKey, operation }) => rules.canReadContentList(session) + }, + */ + +export const postListAccess : ListAccessControl = { + operation: + { + create: ({ session, context, listKey, operation }) => !!permissions.canManageContent(context), + query: ({ session, context, listKey, operation }) => true, + update: ({ session, context, listKey, operation }) => !!permissions.canManageContent(context), + delete: ({ session, context, listKey, operation }) => !!permissions.canManageContent(context) + }, + +} +/* + +*/ -export const contentListAccess = { - create: permissions.canManageContent, - update: permissions.canManageContent, - delete: permissions.canManageContent, -}; export const contentUIConfig = { hideCreate: (context: any) => !permissions.canManageContent(context), @@ -53,17 +93,17 @@ function defaultTimestamp() { } export const Post = list({ - access: { - ...contentListAccess, - read: rules.canReadContentList, - }, + access: + postListAccess, ui: contentUIConfig, fields: { title: text(), + image_url: text(), slug: text({ defaultValue: defaultSlug, ui: { createView: { fieldMode: 'hidden' } }, - isUnique: true, + isFilterable: true, + isIndexed: 'unique' }), status: select({ options: [ @@ -71,10 +111,13 @@ export const Post = list({ { label: 'Published', value: 'published' }, { label: 'Archived', value: 'archived' }, ], + isFilterable: true, defaultValue: 'draft', ui: { displayMode: 'segmented-control' }, }), publishedDate: timestamp({ + isFilterable: true, + isOrderable: true, defaultValue: defaultTimestamp, }), author: relationship({ ref: 'User.authoredPosts' }), @@ -109,6 +152,7 @@ export const Post = list({ }`, }, }, + componentBlocks, ui: { views: require.resolve('./fields/content/components') }, }), diff --git a/schema/fields/content/components.tsx b/schema/fields/content/components.tsx index b4f86c60..ae34062e 100644 --- a/schema/fields/content/components.tsx +++ b/schema/fields/content/components.tsx @@ -42,7 +42,7 @@ const appearances = { icon: CheckCircleIcon, backgroundColor: '#D1FAE5', borderColor: '#34D399', - foregroundColor: '#064E3B', + foregroundColor: '#068E3B', }, } as const; diff --git a/schema/fields/content/renderers.tsx b/schema/fields/content/renderers.tsx index 835a1cb0..04ad58a0 100644 --- a/schema/fields/content/renderers.tsx +++ b/schema/fields/content/renderers.tsx @@ -5,6 +5,9 @@ import { } from '@keystone-next/document-renderer'; import React, { ComponentProps, Fragment } from 'react'; + + + import { H1, H2, H3, H4, H5, H6, P } from '../../../components/ui/typography'; import { Divider } from '../../../components/ui/layout'; import { useAuth } from '../../../components/auth'; @@ -53,6 +56,10 @@ type Poll = { userAnswer: { id: string } | null; }; + + + + const calloutStyles = { info: 'text-blue-800 bg-blue-50 border-blue-300', error: 'text-red-800 bg-red-50 border-red-300', @@ -60,6 +67,8 @@ const calloutStyles = { success: 'text-green-800 bg-green-50 border-green-300', }; + + export const componentBlockRenderers: InferRenderersForComponentBlocks< typeof import('./components').componentBlocks > = { @@ -89,7 +98,7 @@ export const componentBlockRenderers: InferRenderersForComponentBlocks< const [{ data }] = useQuery({ query: gql` query ($id: ID!) { - Poll(where: { id: $id }) { + poll(where: { id: $id }) { id label answers { @@ -105,7 +114,7 @@ export const componentBlockRenderers: InferRenderersForComponentBlocks< `, variables: { id: relatedPoll.id }, }); - const poll = (data?.Poll || relatedPoll.data) as Poll; + const poll = (data?.poll || relatedPoll.data) as Poll; const [{}, voteForPoll] = useMutation(gql` mutation ($answerId: ID!) { @@ -160,6 +169,7 @@ export const componentBlockRenderers: InferRenderersForComponentBlocks< {poll.userAnswer?.id && (