From 2d05fa16669d4281da08ae910fa00fead5715ebf Mon Sep 17 00:00:00 2001 From: Tim Leslie Date: Mon, 3 May 2021 16:07:49 +1000 Subject: [PATCH] Simplify the todo example --- .changeset/six-numbers-join.md | 5 + .gitignore | 5 - examples/basic/schema.graphql | 724 ++++++++++++++++++++++++ examples/basic/schema.prisma | 51 ++ examples/todo/keystone.ts | 34 +- examples/todo/package.json | 8 +- examples/todo/schema.graphql | 420 ++++++++++++++ examples/todo/schema.prisma | 27 + examples/todo/schema.ts | 82 +-- tests/examples-smoke-tests/todo.test.ts | 7 +- 10 files changed, 1252 insertions(+), 111 deletions(-) create mode 100644 .changeset/six-numbers-join.md create mode 100644 examples/basic/schema.graphql create mode 100644 examples/basic/schema.prisma create mode 100644 examples/todo/schema.graphql create mode 100644 examples/todo/schema.prisma diff --git a/.changeset/six-numbers-join.md b/.changeset/six-numbers-join.md new file mode 100644 index 00000000000..f1f24c3b76e --- /dev/null +++ b/.changeset/six-numbers-join.md @@ -0,0 +1,5 @@ +--- +'@keystone-next/example-todo': major +--- + +Simplified the Todo application to just the basics to allow other examples to build on it. diff --git a/.gitignore b/.gitignore index bcda7e38dcb..59888b3f120 100644 --- a/.gitignore +++ b/.gitignore @@ -123,8 +123,3 @@ temp/ # SQLite databases *.db - -# These are here temporarily until the examples properly use the new CLI -# This should be removed and these files should be committed -examples/*/schema.graphql -examples/*/schema.prisma diff --git a/examples/basic/schema.graphql b/examples/basic/schema.graphql new file mode 100644 index 00000000000..246a1ae8bcc --- /dev/null +++ b/examples/basic/schema.graphql @@ -0,0 +1,724 @@ +input ImageFieldInput { + upload: Upload + ref: String +} + +enum ImageExtension { + jpg + png + webp + gif +} + +interface ImageFieldOutput { + id: ID! + filesize: Int! + width: Int! + height: Int! + extension: ImageExtension! + ref: String! + src: String! +} + +type LocalImageFieldOutput implements ImageFieldOutput { + id: ID! + filesize: Int! + width: Int! + height: Int! + extension: ImageExtension! + ref: String! + src: String! +} + +input FileFieldInput { + upload: Upload + ref: String +} + +interface FileFieldOutput { + filename: String! + filesize: Int! + ref: String! + src: String! +} + +type LocalFileFieldOutput implements FileFieldOutput { + filename: String! + filesize: Int! + ref: String! + src: String! +} + +input PhoneNumberRelateToManyInput { + create: [PhoneNumberCreateInput] + connect: [PhoneNumberWhereUniqueInput] + disconnect: [PhoneNumberWhereUniqueInput] + disconnectAll: Boolean +} + +input PostRelateToManyInput { + create: [PostCreateInput] + connect: [PostWhereUniqueInput] + disconnect: [PostWhereUniqueInput] + disconnectAll: Boolean +} + +""" + A keystone list +""" +type User { + id: ID! + name: String + email: String + avatar: ImageFieldOutput + attachment: FileFieldOutput + password_is_set: Boolean + isAdmin: Boolean + roles: String + phoneNumbers( + where: PhoneNumberWhereInput + search: String + sortBy: [SortPhoneNumbersBy!] + orderBy: String + first: Int + skip: Int + ): [PhoneNumber!]! + _phoneNumbersMeta( + where: PhoneNumberWhereInput + search: String + sortBy: [SortPhoneNumbersBy!] + orderBy: String + first: Int + skip: Int + ): _QueryMeta + posts( + where: PostWhereInput + search: String + sortBy: [SortPostsBy!] + orderBy: String + first: Int + skip: Int + ): [Post!]! + _postsMeta( + where: PostWhereInput + search: String + sortBy: [SortPostsBy!] + orderBy: String + first: Int + skip: Int + ): _QueryMeta + randomNumber: Float +} + +input UserWhereInput { + AND: [UserWhereInput] + OR: [UserWhereInput] + 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_in: [String] + name_not_in: [String] + email: String + email_not: String + email_contains: String + email_not_contains: String + email_in: [String] + email_not_in: [String] + password_is_set: Boolean + isAdmin: Boolean + isAdmin_not: Boolean + roles: String + roles_not: String + roles_contains: String + roles_not_contains: String + roles_in: [String] + roles_not_in: [String] + + """ + condition must be true for all nodes + """ + phoneNumbers_every: PhoneNumberWhereInput + + """ + condition must be true for at least 1 node + """ + phoneNumbers_some: PhoneNumberWhereInput + + """ + condition must be false for all nodes + """ + phoneNumbers_none: PhoneNumberWhereInput + + """ + condition must be true for all nodes + """ + posts_every: PostWhereInput + + """ + condition must be true for at least 1 node + """ + posts_some: PostWhereInput + + """ + condition must be false for all nodes + """ + posts_none: PostWhereInput +} + +input UserWhereUniqueInput { + id: ID! +} + +enum SortUsersBy { + id_ASC + id_DESC + name_ASC + name_DESC + email_ASC + email_DESC + isAdmin_ASC + isAdmin_DESC + roles_ASC + roles_DESC + phoneNumbers_ASC + phoneNumbers_DESC + posts_ASC + posts_DESC +} + +input UserUpdateInput { + name: String + email: String + avatar: ImageFieldInput + attachment: FileFieldInput + password: String + isAdmin: Boolean + roles: String + phoneNumbers: PhoneNumberRelateToManyInput + posts: PostRelateToManyInput +} + +input UsersUpdateInput { + id: ID! + data: UserUpdateInput +} + +input UserCreateInput { + name: String + email: String + avatar: ImageFieldInput + attachment: FileFieldInput + password: String + isAdmin: Boolean + roles: String + phoneNumbers: PhoneNumberRelateToManyInput + posts: PostRelateToManyInput +} + +input UsersCreateInput { + data: UserCreateInput +} + +input UserRelateToOneInput { + create: UserCreateInput + connect: UserWhereUniqueInput + disconnect: UserWhereUniqueInput + disconnectAll: Boolean +} + +""" + A keystone list +""" +type PhoneNumber { + id: ID! + label: String + user: User + type: String + value: String +} + +input PhoneNumberWhereInput { + AND: [PhoneNumberWhereInput] + OR: [PhoneNumberWhereInput] + id: ID + id_not: ID + id_lt: ID + id_lte: ID + id_gt: ID + id_gte: ID + id_in: [ID] + id_not_in: [ID] + user: UserWhereInput + user_is_null: Boolean + type: String + type_not: String + type_in: [String] + type_not_in: [String] + value: String + value_not: String + value_contains: String + value_not_contains: String + value_in: [String] + value_not_in: [String] +} + +input PhoneNumberWhereUniqueInput { + id: ID! +} + +enum SortPhoneNumbersBy { + id_ASC + id_DESC + user_ASC + user_DESC + type_ASC + type_DESC + value_ASC + value_DESC +} + +input PhoneNumberUpdateInput { + user: UserRelateToOneInput + type: String + value: String +} + +input PhoneNumbersUpdateInput { + id: ID! + data: PhoneNumberUpdateInput +} + +input PhoneNumberCreateInput { + user: UserRelateToOneInput + type: String + value: String +} + +input PhoneNumbersCreateInput { + data: PhoneNumberCreateInput +} + +type Post_content_DocumentField { + document(hydrateRelationships: Boolean! = false): JSON! +} + +""" + A keystone list +""" +type Post { + id: ID! + title: String + status: String + content: Post_content_DocumentField + publishDate: String + author: User +} + +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_in: [String] + title_not_in: [String] + status: String + status_not: String + status_in: [String] + status_not_in: [String] + publishDate: String + publishDate_not: String + publishDate_lt: String + publishDate_lte: String + publishDate_gt: String + publishDate_gte: String + publishDate_in: [String] + publishDate_not_in: [String] + author: UserWhereInput + author_is_null: Boolean +} + +input PostWhereUniqueInput { + id: ID! +} + +enum SortPostsBy { + id_ASC + id_DESC + title_ASC + title_DESC + status_ASC + status_DESC + publishDate_ASC + publishDate_DESC + author_ASC + author_DESC +} + +input PostUpdateInput { + title: String + status: String + content: JSON + publishDate: String + author: UserRelateToOneInput +} + +input PostsUpdateInput { + id: ID! + data: PostUpdateInput +} + +input PostCreateInput { + title: String + status: String + content: JSON + publishDate: String + author: UserRelateToOneInput +} + +input PostsCreateInput { + data: PostCreateInput +} + +""" +The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). +""" +scalar JSON + @specifiedBy( + url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf" + ) + +type _QueryMeta { + count: Int +} + +type Mutation { + """ + Create a single User item. + """ + createUser(data: UserCreateInput): User + + """ + Create multiple User items. + """ + createUsers(data: [UsersCreateInput]): [User] + + """ + Update a single User item by ID. + """ + updateUser(id: ID!, data: UserUpdateInput): User + + """ + Update multiple User items by ID. + """ + updateUsers(data: [UsersUpdateInput]): [User] + + """ + Delete a single User item by ID. + """ + deleteUser(id: ID!): User + + """ + Delete multiple User items by ID. + """ + deleteUsers(ids: [ID!]): [User] + + """ + Create a single PhoneNumber item. + """ + createPhoneNumber(data: PhoneNumberCreateInput): PhoneNumber + + """ + Create multiple PhoneNumber items. + """ + createPhoneNumbers(data: [PhoneNumbersCreateInput]): [PhoneNumber] + + """ + Update a single PhoneNumber item by ID. + """ + updatePhoneNumber(id: ID!, data: PhoneNumberUpdateInput): PhoneNumber + + """ + Update multiple PhoneNumber items by ID. + """ + updatePhoneNumbers(data: [PhoneNumbersUpdateInput]): [PhoneNumber] + + """ + Delete a single PhoneNumber item by ID. + """ + deletePhoneNumber(id: ID!): PhoneNumber + + """ + Delete multiple PhoneNumber items by ID. + """ + deletePhoneNumbers(ids: [ID!]): [PhoneNumber] + + """ + Create a single Post item. + """ + createPost(data: PostCreateInput): Post + + """ + Create multiple Post items. + """ + createPosts(data: [PostsCreateInput]): [Post] + + """ + Update a single Post item by ID. + """ + updatePost(id: ID!, data: PostUpdateInput): Post + + """ + Update multiple Post items by ID. + """ + updatePosts(data: [PostsUpdateInput]): [Post] + + """ + Delete a single Post item by ID. + """ + deletePost(id: ID!): Post + + """ + Delete multiple Post items by ID. + """ + deletePosts(ids: [ID!]): [Post] + authenticateUserWithPassword( + email: String! + password: String! + ): UserAuthenticationWithPasswordResult! + createInitialUser( + data: CreateInitialUserInput! + ): UserAuthenticationWithPasswordSuccess! + createRandomPosts: [Post!]! + endSession: Boolean! +} + +""" +The `Upload` scalar type represents a file upload. +""" +scalar Upload + +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 RandomNumber { + number: Int + generatedAt: Int +} + +type Query { + """ + Search for all User items which match the where clause. + """ + allUsers( + where: UserWhereInput + search: String + sortBy: [SortUsersBy!] + orderBy: String + first: Int + skip: Int + ): [User] + + """ + Search for the User item with the matching ID. + """ + User(where: UserWhereUniqueInput!): User + + """ + Perform a meta-query on all User items which match the where clause. + """ + _allUsersMeta( + where: UserWhereInput + search: String + sortBy: [SortUsersBy!] + orderBy: String + first: Int + skip: Int + ): _QueryMeta + + """ + Search for all PhoneNumber items which match the where clause. + """ + allPhoneNumbers( + where: PhoneNumberWhereInput + search: String + sortBy: [SortPhoneNumbersBy!] + orderBy: String + first: Int + skip: Int + ): [PhoneNumber] + + """ + Search for the PhoneNumber item with the matching ID. + """ + PhoneNumber(where: PhoneNumberWhereUniqueInput!): PhoneNumber + + """ + Perform a meta-query on all PhoneNumber items which match the where clause. + """ + _allPhoneNumbersMeta( + where: PhoneNumberWhereInput + search: String + sortBy: [SortPhoneNumbersBy!] + orderBy: String + first: Int + skip: Int + ): _QueryMeta + + """ + Search for all Post items which match the where clause. + """ + allPosts( + where: PostWhereInput + search: String + sortBy: [SortPostsBy!] + orderBy: String + first: Int + skip: Int + ): [Post] + + """ + Search for the Post item with the matching ID. + """ + Post(where: PostWhereUniqueInput!): Post + + """ + Perform a meta-query on all Post items which match the where clause. + """ + _allPostsMeta( + where: PostWhereInput + search: String + sortBy: [SortPostsBy!] + orderBy: String + first: Int + skip: Int + ): _QueryMeta + authenticatedItem: AuthenticatedItem + randomNumber: RandomNumber + keystone: KeystoneMeta! +} + +type KeystoneMeta { + adminMeta: KeystoneAdminMeta! +} + +type KeystoneAdminMeta { + enableSignout: Boolean! + enableSessionItem: Boolean! + lists: [KeystoneAdminUIListMeta!]! + list(key: String!): KeystoneAdminUIListMeta +} + +type KeystoneAdminUIListMeta { + key: String! + itemQueryName: String! + listQueryName: String! + hideCreate: Boolean! + hideDelete: Boolean! + path: String! + label: String! + singular: String! + plural: String! + description: String + initialColumns: [String!]! + pageSize: Int! + labelField: String! + fields: [KeystoneAdminUIFieldMeta!]! + initialSort: KeystoneAdminUISort + isHidden: Boolean! +} + +type KeystoneAdminUIFieldMeta { + path: String! + label: String! + isOrderable: Boolean! + fieldMeta: JSON + viewsIndex: Int! + customViewsIndex: Int + createView: KeystoneAdminUIFieldMetaCreateView! + listView: KeystoneAdminUIFieldMetaListView! + itemView(id: ID!): KeystoneAdminUIFieldMetaItemView +} + +type KeystoneAdminUIFieldMetaCreateView { + fieldMode: KeystoneAdminUIFieldMetaCreateViewFieldMode! +} + +enum KeystoneAdminUIFieldMetaCreateViewFieldMode { + edit + hidden +} + +type KeystoneAdminUIFieldMetaListView { + fieldMode: KeystoneAdminUIFieldMetaListViewFieldMode! +} + +enum KeystoneAdminUIFieldMetaListViewFieldMode { + read + hidden +} + +type KeystoneAdminUIFieldMetaItemView { + fieldMode: KeystoneAdminUIFieldMetaItemViewFieldMode! +} + +enum KeystoneAdminUIFieldMetaItemViewFieldMode { + edit + read + hidden +} + +type KeystoneAdminUISort { + field: String! + direction: KeystoneAdminUISortDirection! +} + +enum KeystoneAdminUISortDirection { + ASC + DESC +} diff --git a/examples/basic/schema.prisma b/examples/basic/schema.prisma new file mode 100644 index 00000000000..a3014a6dd12 --- /dev/null +++ b/examples/basic/schema.prisma @@ -0,0 +1,51 @@ +datasource sqlite { + url = env("DATABASE_URL") + provider = "sqlite" +} + +generator client { + provider = "prisma-client-js" + output = "node_modules/.prisma/client" +} + +model User { + id Int @id @default(autoincrement()) + name String? + email String? @unique + avatar_filesize Int? + avatar_extension String? + avatar_width Int? + avatar_height Int? + avatar_mode String? + avatar_id String? + attachment_filesize Int? + attachment_mode String? + attachment_filename String? + password String? + isAdmin Boolean? + roles String? + phoneNumbers PhoneNumber[] @relation("PhoneNumberuser") + posts Post[] @relation("Postauthor") +} + +model PhoneNumber { + id Int @id @default(autoincrement()) + type String? + value String? + user User? @relation("PhoneNumberuser", fields: [userId], references: [id]) + userId Int? @map("user") + + @@index([userId]) +} + +model Post { + id Int @id @default(autoincrement()) + title String? + status String? + content String? + publishDate DateTime? + author User? @relation("Postauthor", fields: [authorId], references: [id]) + authorId Int? @map("author") + + @@index([authorId]) +} \ No newline at end of file diff --git a/examples/todo/keystone.ts b/examples/todo/keystone.ts index 5941f702eea..e961168f85e 100644 --- a/examples/todo/keystone.ts +++ b/examples/todo/keystone.ts @@ -1,34 +1,10 @@ import { config } from '@keystone-next/keystone/schema'; -import { statelessSessions, withItemData } from '@keystone-next/keystone/session'; -import { createAuth } from '@keystone-next/auth'; import { lists } from './schema'; -const sessionSecret = '-- DEV COOKIE SECRET; CHANGE ME --'; -const sessionMaxAge = 60 * 60 * 24 * 30; // 30 days -const sessionConfig = { - maxAge: sessionMaxAge, - secret: sessionSecret, -}; - -const { withAuth } = createAuth({ - listKey: 'User', - identityField: 'email', - secretField: 'password', - initFirstItem: { - fields: ['name', 'email', 'password'], +export default config({ + db: { + provider: 'sqlite', + url: process.env.DATABASE_URL || 'file:./keystone-example.db', }, + lists, }); - -export default withAuth( - config({ - db: { - provider: 'sqlite', - url: process.env.DATABASE_URL || 'file:./keystone-example.db', - }, - lists, - ui: { - isAccessAllowed: ({ session }) => !!session, - }, - session: withItemData(statelessSessions(sessionConfig)), - }) -); diff --git a/examples/todo/package.json b/examples/todo/package.json index ac5ee8a0847..8d5d7676145 100644 --- a/examples/todo/package.json +++ b/examples/todo/package.json @@ -9,14 +9,8 @@ "build": "keystone-next build" }, "dependencies": { - "@keystone-next/admin-ui": "^14.1.0", - "@keystone-next/auth": "^22.0.0", "@keystone-next/fields": "^8.0.0", - "@keystone-next/keystone": "^17.0.0", - "dotenv": "^8.2.0", - "next": "^10.2.0", - "react": "^17.0.2", - "react-dom": "^17.0.2" + "@keystone-next/keystone": "^17.0.0" }, "devDependencies": { "typescript": "^4.2.4" diff --git a/examples/todo/schema.graphql b/examples/todo/schema.graphql new file mode 100644 index 00000000000..47a6edee8d5 --- /dev/null +++ b/examples/todo/schema.graphql @@ -0,0 +1,420 @@ +enum TaskPriorityType { + low + medium + high +} + +input PersonRelateToOneInput { + create: PersonCreateInput + connect: PersonWhereUniqueInput + disconnect: PersonWhereUniqueInput + disconnectAll: Boolean +} + +""" + A keystone list +""" +type Task { + id: ID! + label: String + priority: TaskPriorityType + isComplete: Boolean + assignedTo: Person + finishBy: String +} + +input TaskWhereInput { + AND: [TaskWhereInput] + OR: [TaskWhereInput] + 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_in: [String] + label_not_in: [String] + priority: TaskPriorityType + priority_not: TaskPriorityType + priority_in: [TaskPriorityType] + priority_not_in: [TaskPriorityType] + isComplete: Boolean + isComplete_not: Boolean + assignedTo: PersonWhereInput + assignedTo_is_null: Boolean + finishBy: String + finishBy_not: String + finishBy_lt: String + finishBy_lte: String + finishBy_gt: String + finishBy_gte: String + finishBy_in: [String] + finishBy_not_in: [String] +} + +input TaskWhereUniqueInput { + id: ID! +} + +enum SortTasksBy { + id_ASC + id_DESC + label_ASC + label_DESC + priority_ASC + priority_DESC + isComplete_ASC + isComplete_DESC + assignedTo_ASC + assignedTo_DESC + finishBy_ASC + finishBy_DESC +} + +input TaskUpdateInput { + label: String + priority: TaskPriorityType + isComplete: Boolean + assignedTo: PersonRelateToOneInput + finishBy: String +} + +input TasksUpdateInput { + id: ID! + data: TaskUpdateInput +} + +input TaskCreateInput { + label: String + priority: TaskPriorityType + isComplete: Boolean + assignedTo: PersonRelateToOneInput + finishBy: String +} + +input TasksCreateInput { + data: TaskCreateInput +} + +input TaskRelateToManyInput { + create: [TaskCreateInput] + connect: [TaskWhereUniqueInput] + disconnect: [TaskWhereUniqueInput] + disconnectAll: Boolean +} + +""" + A keystone list +""" +type Person { + id: ID! + name: String + tasks( + where: TaskWhereInput + search: String + sortBy: [SortTasksBy!] + orderBy: String + first: Int + skip: Int + ): [Task!]! + _tasksMeta( + where: TaskWhereInput + search: String + sortBy: [SortTasksBy!] + orderBy: String + first: Int + skip: Int + ): _QueryMeta +} + +input PersonWhereInput { + AND: [PersonWhereInput] + OR: [PersonWhereInput] + 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_in: [String] + name_not_in: [String] + + """ + condition must be true for all nodes + """ + tasks_every: TaskWhereInput + + """ + condition must be true for at least 1 node + """ + tasks_some: TaskWhereInput + + """ + condition must be false for all nodes + """ + tasks_none: TaskWhereInput +} + +input PersonWhereUniqueInput { + id: ID! +} + +enum SortPeopleBy { + id_ASC + id_DESC + name_ASC + name_DESC + tasks_ASC + tasks_DESC +} + +input PersonUpdateInput { + name: String + tasks: TaskRelateToManyInput +} + +input PeopleUpdateInput { + id: ID! + data: PersonUpdateInput +} + +input PersonCreateInput { + name: String + tasks: TaskRelateToManyInput +} + +input PeopleCreateInput { + data: PersonCreateInput +} + +""" +The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). +""" +scalar JSON + @specifiedBy( + url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf" + ) + +type _QueryMeta { + count: Int +} + +type Mutation { + """ + Create a single Task item. + """ + createTask(data: TaskCreateInput): Task + + """ + Create multiple Task items. + """ + createTasks(data: [TasksCreateInput]): [Task] + + """ + Update a single Task item by ID. + """ + updateTask(id: ID!, data: TaskUpdateInput): Task + + """ + Update multiple Task items by ID. + """ + updateTasks(data: [TasksUpdateInput]): [Task] + + """ + Delete a single Task item by ID. + """ + deleteTask(id: ID!): Task + + """ + Delete multiple Task items by ID. + """ + deleteTasks(ids: [ID!]): [Task] + + """ + Create a single Person item. + """ + createPerson(data: PersonCreateInput): Person + + """ + Create multiple Person items. + """ + createPeople(data: [PeopleCreateInput]): [Person] + + """ + Update a single Person item by ID. + """ + updatePerson(id: ID!, data: PersonUpdateInput): Person + + """ + Update multiple Person items by ID. + """ + updatePeople(data: [PeopleUpdateInput]): [Person] + + """ + Delete a single Person item by ID. + """ + deletePerson(id: ID!): Person + + """ + Delete multiple Person items by ID. + """ + deletePeople(ids: [ID!]): [Person] +} + +""" +The `Upload` scalar type represents a file upload. +""" +scalar Upload + +type Query { + """ + Search for all Task items which match the where clause. + """ + allTasks( + where: TaskWhereInput + search: String + sortBy: [SortTasksBy!] + orderBy: String + first: Int + skip: Int + ): [Task] + + """ + Search for the Task item with the matching ID. + """ + Task(where: TaskWhereUniqueInput!): Task + + """ + Perform a meta-query on all Task items which match the where clause. + """ + _allTasksMeta( + where: TaskWhereInput + search: String + sortBy: [SortTasksBy!] + orderBy: String + first: Int + skip: Int + ): _QueryMeta + + """ + Search for all Person items which match the where clause. + """ + allPeople( + where: PersonWhereInput + search: String + sortBy: [SortPeopleBy!] + orderBy: String + first: Int + skip: Int + ): [Person] + + """ + Search for the Person item with the matching ID. + """ + Person(where: PersonWhereUniqueInput!): Person + + """ + Perform a meta-query on all Person items which match the where clause. + """ + _allPeopleMeta( + where: PersonWhereInput + search: String + sortBy: [SortPeopleBy!] + orderBy: String + first: Int + skip: Int + ): _QueryMeta + keystone: KeystoneMeta! +} + +type KeystoneMeta { + adminMeta: KeystoneAdminMeta! +} + +type KeystoneAdminMeta { + enableSignout: Boolean! + enableSessionItem: Boolean! + lists: [KeystoneAdminUIListMeta!]! + list(key: String!): KeystoneAdminUIListMeta +} + +type KeystoneAdminUIListMeta { + key: String! + itemQueryName: String! + listQueryName: String! + hideCreate: Boolean! + hideDelete: Boolean! + path: String! + label: String! + singular: String! + plural: String! + description: String + initialColumns: [String!]! + pageSize: Int! + labelField: String! + fields: [KeystoneAdminUIFieldMeta!]! + initialSort: KeystoneAdminUISort + isHidden: Boolean! +} + +type KeystoneAdminUIFieldMeta { + path: String! + label: String! + isOrderable: Boolean! + fieldMeta: JSON + viewsIndex: Int! + customViewsIndex: Int + createView: KeystoneAdminUIFieldMetaCreateView! + listView: KeystoneAdminUIFieldMetaListView! + itemView(id: ID!): KeystoneAdminUIFieldMetaItemView +} + +type KeystoneAdminUIFieldMetaCreateView { + fieldMode: KeystoneAdminUIFieldMetaCreateViewFieldMode! +} + +enum KeystoneAdminUIFieldMetaCreateViewFieldMode { + edit + hidden +} + +type KeystoneAdminUIFieldMetaListView { + fieldMode: KeystoneAdminUIFieldMetaListViewFieldMode! +} + +enum KeystoneAdminUIFieldMetaListViewFieldMode { + read + hidden +} + +type KeystoneAdminUIFieldMetaItemView { + fieldMode: KeystoneAdminUIFieldMetaItemViewFieldMode! +} + +enum KeystoneAdminUIFieldMetaItemViewFieldMode { + edit + read + hidden +} + +type KeystoneAdminUISort { + field: String! + direction: KeystoneAdminUISortDirection! +} + +enum KeystoneAdminUISortDirection { + ASC + DESC +} diff --git a/examples/todo/schema.prisma b/examples/todo/schema.prisma new file mode 100644 index 00000000000..6635b24e6bd --- /dev/null +++ b/examples/todo/schema.prisma @@ -0,0 +1,27 @@ +datasource sqlite { + url = env("DATABASE_URL") + provider = "sqlite" +} + +generator client { + provider = "prisma-client-js" + output = "node_modules/.prisma/client" +} + +model Task { + id Int @id @default(autoincrement()) + label String? + priority String? + isComplete Boolean? + finishBy DateTime? + assignedTo Person? @relation("TaskassignedTo", fields: [assignedToId], references: [id]) + assignedToId Int? @map("assignedTo") + + @@index([assignedToId]) +} + +model Person { + id Int @id @default(autoincrement()) + name String? + tasks Task[] @relation("TaskassignedTo") +} \ No newline at end of file diff --git a/examples/todo/schema.ts b/examples/todo/schema.ts index f590920ff40..9b539803be8 100644 --- a/examples/todo/schema.ts +++ b/examples/todo/schema.ts @@ -1,82 +1,28 @@ import { createSchema, list } from '@keystone-next/keystone/schema'; -import { checkbox, password, relationship, text, timestamp } from '@keystone-next/fields'; - -// this implementation for createdBy and updatedBy is currently wrong so they're disabled for now -const trackingFields = { - createdAt: timestamp({ - access: { create: false, read: true, update: false }, - defaultValue: () => new Date().toISOString(), - ui: { - createView: { fieldMode: 'hidden' }, - itemView: { fieldMode: 'read' }, - }, - }), - // createdBy: relationship({ - // ref: 'User', - // access: { create: false, read: true, update: false }, - // defaultValue: ({ context: { session } }) => - // session ? { connect: { id: session.itemId } } : null, - // ui: { - // createView: { fieldMode: 'hidden' }, - // itemView: { fieldMode: 'read' }, - // }, - // }), - updatedAt: timestamp({ - access: { create: false, read: true, update: false }, - hooks: { - resolveInput: () => new Date().toISOString(), - }, - ui: { - createView: { fieldMode: 'hidden' }, - itemView: { fieldMode: 'read' }, - }, - }), - // updatedBy: relationship({ - // ref: 'User', - // access: { create: false, read: true, update: false }, - // hooks: { - // resolveInput: ({ context: { session } }) => (session ? session.itemId : null), - // }, - // ui: { - // createView: { fieldMode: 'hidden' }, - // itemView: { fieldMode: 'read' }, - // }, - // }), -}; +import { checkbox, relationship, text, timestamp } from '@keystone-next/fields'; +import { select } from '@keystone-next/fields'; export const lists = createSchema({ - Todo: list({ - ui: { - listView: { - initialColumns: ['label', 'isComplete', 'createdAt', 'updatedAt'], - }, - }, + Task: list({ fields: { label: text({ isRequired: true }), + priority: select({ + dataType: 'enum', + options: [ + { label: 'Low', value: 'low' }, + { label: 'Medium', value: 'medium' }, + { label: 'High', value: 'high' }, + ], + }), isComplete: checkbox(), - assignedTo: relationship({ ref: 'User.tasks' }), + assignedTo: relationship({ ref: 'Person.tasks', many: false }), finishBy: timestamp(), - ...trackingFields, }, }), - User: list({ - ui: { - listView: { - initialColumns: ['name', 'tasks', 'createdAt', 'updatedAt'], - }, - }, + Person: list({ fields: { name: text({ isRequired: true }), - email: text(), - password: password(), - tasks: relationship({ - ref: 'Todo.assignedTo', - many: true, - ui: { - itemView: { fieldMode: 'read' }, - }, - }), - ...trackingFields, + tasks: relationship({ ref: 'Task.assignedTo', many: true }), }, }), }); diff --git a/tests/examples-smoke-tests/todo.test.ts b/tests/examples-smoke-tests/todo.test.ts index 1ab3f9bd4bb..978b965d61d 100644 --- a/tests/examples-smoke-tests/todo.test.ts +++ b/tests/examples-smoke-tests/todo.test.ts @@ -1,5 +1,5 @@ import { Browser, Page } from 'playwright'; -import { exampleProjectTests, initFirstItemTest } from './utils'; +import { exampleProjectTests } from './utils'; exampleProjectTests('todo', browserType => { let browser: Browser = undefined as any; @@ -9,7 +9,10 @@ exampleProjectTests('todo', browserType => { page = await browser.newPage(); page.goto('http://localhost:3000'); }); - initFirstItemTest(() => page); + test('Load list', async () => { + await Promise.all([page.waitForNavigation(), page.click('h3:has-text("People")')]); + await page.waitForSelector('button:has-text("Create Person")'); + }); afterAll(async () => { await browser.close(); });