create-keystone-app 7.0.2 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/create-keystone-app.cjs.dev.js +1 -15
- package/dist/create-keystone-app.cjs.prod.js +1 -15
- package/package.json +1 -1
- package/starter/.keystone/config.js +135 -0
- package/starter/auth.ts +42 -30
- package/starter/keystone.ts +12 -16
- package/starter/package.json +4 -16
- package/starter/schema.graphql +29 -104
- package/starter/schema.prisma +13 -14
- package/starter/schema.ts +77 -70
|
@@ -44,7 +44,7 @@ var terminalLink__default = /*#__PURE__*/_interopDefault(terminalLink);
|
|
|
44
44
|
|
|
45
45
|
var currentPkgJson = {
|
|
46
46
|
name: "create-keystone-app",
|
|
47
|
-
version: "
|
|
47
|
+
version: "8.0.1",
|
|
48
48
|
main: "dist/create-keystone-app.cjs.js",
|
|
49
49
|
files: [
|
|
50
50
|
"dist",
|
|
@@ -73,11 +73,9 @@ async function checkVersion() {
|
|
|
73
73
|
const {
|
|
74
74
|
version
|
|
75
75
|
} = await getPackageJson__default["default"]('create-keystone-app');
|
|
76
|
-
|
|
77
76
|
if (typeof version !== 'string') {
|
|
78
77
|
throw new Error('version from package metadata was expected to be a string but was not');
|
|
79
78
|
}
|
|
80
|
-
|
|
81
79
|
if (semver__namespace.lt(currentPkgJson.version, version)) {
|
|
82
80
|
console.error(`⚠️ You're running an old version of create-keystone-app, please update to ${version}`);
|
|
83
81
|
}
|
|
@@ -94,16 +92,13 @@ const cli = meow__default["default"](`
|
|
|
94
92
|
Usage
|
|
95
93
|
$ create-keystone-app [directory]
|
|
96
94
|
`);
|
|
97
|
-
|
|
98
95
|
const versionInfo = () => {
|
|
99
96
|
process.stdout.write('\n');
|
|
100
97
|
console.log(`✨ You're about to generate a project using ${c__default["default"].bold('Keystone 6')} packages.
|
|
101
98
|
`);
|
|
102
99
|
};
|
|
103
|
-
|
|
104
100
|
async function normalizeArgs() {
|
|
105
101
|
let directory = cli.input[0];
|
|
106
|
-
|
|
107
102
|
if (!directory) {
|
|
108
103
|
({
|
|
109
104
|
directory
|
|
@@ -115,15 +110,12 @@ async function normalizeArgs() {
|
|
|
115
110
|
}));
|
|
116
111
|
process.stdout.write('\n');
|
|
117
112
|
}
|
|
118
|
-
|
|
119
113
|
return {
|
|
120
114
|
directory: path__default["default"].resolve(directory)
|
|
121
115
|
};
|
|
122
116
|
}
|
|
123
|
-
|
|
124
117
|
const installDeps = async cwd => {
|
|
125
118
|
const spinner = ora__default["default"]('Installing dependencies with yarn. This may take a few minutes.').start();
|
|
126
|
-
|
|
127
119
|
try {
|
|
128
120
|
await execa__default["default"]('yarn', ['install'], {
|
|
129
121
|
cwd
|
|
@@ -132,12 +124,10 @@ const installDeps = async cwd => {
|
|
|
132
124
|
return 'yarn';
|
|
133
125
|
} catch (_err) {
|
|
134
126
|
let err = _err;
|
|
135
|
-
|
|
136
127
|
if (err.failed) {
|
|
137
128
|
process.stdout.write('\n');
|
|
138
129
|
spinner.warn('Failed to install with yarn.');
|
|
139
130
|
spinner.start('Installing dependencies with npm. This may take a few minutes.');
|
|
140
|
-
|
|
141
131
|
try {
|
|
142
132
|
await execa__default["default"]('npm', ['install'], {
|
|
143
133
|
cwd
|
|
@@ -147,15 +137,12 @@ const installDeps = async cwd => {
|
|
|
147
137
|
spinner.fail('Failed to install with npm.');
|
|
148
138
|
throw npmErr;
|
|
149
139
|
}
|
|
150
|
-
|
|
151
140
|
process.stdout.write('\n');
|
|
152
141
|
return 'npm';
|
|
153
142
|
}
|
|
154
|
-
|
|
155
143
|
throw err;
|
|
156
144
|
}
|
|
157
145
|
};
|
|
158
|
-
|
|
159
146
|
(async () => {
|
|
160
147
|
versionInfo();
|
|
161
148
|
await checkVersion();
|
|
@@ -187,6 +174,5 @@ const installDeps = async cwd => {
|
|
|
187
174
|
} else {
|
|
188
175
|
console.error(err);
|
|
189
176
|
}
|
|
190
|
-
|
|
191
177
|
process.exit(1);
|
|
192
178
|
});
|
|
@@ -44,7 +44,7 @@ var terminalLink__default = /*#__PURE__*/_interopDefault(terminalLink);
|
|
|
44
44
|
|
|
45
45
|
var currentPkgJson = {
|
|
46
46
|
name: "create-keystone-app",
|
|
47
|
-
version: "
|
|
47
|
+
version: "8.0.1",
|
|
48
48
|
main: "dist/create-keystone-app.cjs.js",
|
|
49
49
|
files: [
|
|
50
50
|
"dist",
|
|
@@ -73,11 +73,9 @@ async function checkVersion() {
|
|
|
73
73
|
const {
|
|
74
74
|
version
|
|
75
75
|
} = await getPackageJson__default["default"]('create-keystone-app');
|
|
76
|
-
|
|
77
76
|
if (typeof version !== 'string') {
|
|
78
77
|
throw new Error('version from package metadata was expected to be a string but was not');
|
|
79
78
|
}
|
|
80
|
-
|
|
81
79
|
if (semver__namespace.lt(currentPkgJson.version, version)) {
|
|
82
80
|
console.error(`⚠️ You're running an old version of create-keystone-app, please update to ${version}`);
|
|
83
81
|
}
|
|
@@ -94,16 +92,13 @@ const cli = meow__default["default"](`
|
|
|
94
92
|
Usage
|
|
95
93
|
$ create-keystone-app [directory]
|
|
96
94
|
`);
|
|
97
|
-
|
|
98
95
|
const versionInfo = () => {
|
|
99
96
|
process.stdout.write('\n');
|
|
100
97
|
console.log(`✨ You're about to generate a project using ${c__default["default"].bold('Keystone 6')} packages.
|
|
101
98
|
`);
|
|
102
99
|
};
|
|
103
|
-
|
|
104
100
|
async function normalizeArgs() {
|
|
105
101
|
let directory = cli.input[0];
|
|
106
|
-
|
|
107
102
|
if (!directory) {
|
|
108
103
|
({
|
|
109
104
|
directory
|
|
@@ -115,15 +110,12 @@ async function normalizeArgs() {
|
|
|
115
110
|
}));
|
|
116
111
|
process.stdout.write('\n');
|
|
117
112
|
}
|
|
118
|
-
|
|
119
113
|
return {
|
|
120
114
|
directory: path__default["default"].resolve(directory)
|
|
121
115
|
};
|
|
122
116
|
}
|
|
123
|
-
|
|
124
117
|
const installDeps = async cwd => {
|
|
125
118
|
const spinner = ora__default["default"]('Installing dependencies with yarn. This may take a few minutes.').start();
|
|
126
|
-
|
|
127
119
|
try {
|
|
128
120
|
await execa__default["default"]('yarn', ['install'], {
|
|
129
121
|
cwd
|
|
@@ -132,12 +124,10 @@ const installDeps = async cwd => {
|
|
|
132
124
|
return 'yarn';
|
|
133
125
|
} catch (_err) {
|
|
134
126
|
let err = _err;
|
|
135
|
-
|
|
136
127
|
if (err.failed) {
|
|
137
128
|
process.stdout.write('\n');
|
|
138
129
|
spinner.warn('Failed to install with yarn.');
|
|
139
130
|
spinner.start('Installing dependencies with npm. This may take a few minutes.');
|
|
140
|
-
|
|
141
131
|
try {
|
|
142
132
|
await execa__default["default"]('npm', ['install'], {
|
|
143
133
|
cwd
|
|
@@ -147,15 +137,12 @@ const installDeps = async cwd => {
|
|
|
147
137
|
spinner.fail('Failed to install with npm.');
|
|
148
138
|
throw npmErr;
|
|
149
139
|
}
|
|
150
|
-
|
|
151
140
|
process.stdout.write('\n');
|
|
152
141
|
return 'npm';
|
|
153
142
|
}
|
|
154
|
-
|
|
155
143
|
throw err;
|
|
156
144
|
}
|
|
157
145
|
};
|
|
158
|
-
|
|
159
146
|
(async () => {
|
|
160
147
|
versionInfo();
|
|
161
148
|
await checkVersion();
|
|
@@ -187,6 +174,5 @@ const installDeps = async cwd => {
|
|
|
187
174
|
} else {
|
|
188
175
|
console.error(err);
|
|
189
176
|
}
|
|
190
|
-
|
|
191
177
|
process.exit(1);
|
|
192
178
|
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// keystone.ts
|
|
21
|
+
var keystone_exports = {};
|
|
22
|
+
__export(keystone_exports, {
|
|
23
|
+
default: () => keystone_default
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(keystone_exports);
|
|
26
|
+
var import_core2 = require("@keystone-6/core");
|
|
27
|
+
|
|
28
|
+
// schema.ts
|
|
29
|
+
var import_core = require("@keystone-6/core");
|
|
30
|
+
var import_access = require("@keystone-6/core/access");
|
|
31
|
+
var import_fields = require("@keystone-6/core/fields");
|
|
32
|
+
var import_fields_document = require("@keystone-6/fields-document");
|
|
33
|
+
var lists = {
|
|
34
|
+
User: (0, import_core.list)({
|
|
35
|
+
access: import_access.allowAll,
|
|
36
|
+
fields: {
|
|
37
|
+
name: (0, import_fields.text)({ validation: { isRequired: true } }),
|
|
38
|
+
email: (0, import_fields.text)({
|
|
39
|
+
validation: { isRequired: true },
|
|
40
|
+
isIndexed: "unique"
|
|
41
|
+
}),
|
|
42
|
+
password: (0, import_fields.password)({ validation: { isRequired: true } }),
|
|
43
|
+
posts: (0, import_fields.relationship)({ ref: "Post.author", many: true }),
|
|
44
|
+
createdAt: (0, import_fields.timestamp)({
|
|
45
|
+
defaultValue: { kind: "now" }
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
}),
|
|
49
|
+
Post: (0, import_core.list)({
|
|
50
|
+
access: import_access.allowAll,
|
|
51
|
+
fields: {
|
|
52
|
+
title: (0, import_fields.text)({ validation: { isRequired: true } }),
|
|
53
|
+
content: (0, import_fields_document.document)({
|
|
54
|
+
formatting: true,
|
|
55
|
+
layouts: [
|
|
56
|
+
[1, 1],
|
|
57
|
+
[1, 1, 1],
|
|
58
|
+
[2, 1],
|
|
59
|
+
[1, 2],
|
|
60
|
+
[1, 2, 1]
|
|
61
|
+
],
|
|
62
|
+
links: true,
|
|
63
|
+
dividers: true
|
|
64
|
+
}),
|
|
65
|
+
author: (0, import_fields.relationship)({
|
|
66
|
+
ref: "User.posts",
|
|
67
|
+
ui: {
|
|
68
|
+
displayMode: "cards",
|
|
69
|
+
cardFields: ["name", "email"],
|
|
70
|
+
inlineEdit: { fields: ["name", "email"] },
|
|
71
|
+
linkToItem: true,
|
|
72
|
+
inlineConnect: true
|
|
73
|
+
},
|
|
74
|
+
many: false
|
|
75
|
+
}),
|
|
76
|
+
tags: (0, import_fields.relationship)({
|
|
77
|
+
ref: "Tag.posts",
|
|
78
|
+
many: true,
|
|
79
|
+
ui: {
|
|
80
|
+
displayMode: "cards",
|
|
81
|
+
cardFields: ["name"],
|
|
82
|
+
inlineEdit: { fields: ["name"] },
|
|
83
|
+
linkToItem: true,
|
|
84
|
+
inlineConnect: true,
|
|
85
|
+
inlineCreate: { fields: ["name"] }
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}),
|
|
90
|
+
Tag: (0, import_core.list)({
|
|
91
|
+
access: import_access.allowAll,
|
|
92
|
+
ui: {
|
|
93
|
+
isHidden: true
|
|
94
|
+
},
|
|
95
|
+
fields: {
|
|
96
|
+
name: (0, import_fields.text)(),
|
|
97
|
+
posts: (0, import_fields.relationship)({ ref: "Post.tags", many: true })
|
|
98
|
+
}
|
|
99
|
+
})
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// auth.ts
|
|
103
|
+
var import_crypto = require("crypto");
|
|
104
|
+
var import_auth = require("@keystone-6/auth");
|
|
105
|
+
var import_session = require("@keystone-6/core/session");
|
|
106
|
+
var sessionSecret = process.env.SESSION_SECRET;
|
|
107
|
+
if (!sessionSecret && true) {
|
|
108
|
+
sessionSecret = (0, import_crypto.randomBytes)(32).toString("hex");
|
|
109
|
+
}
|
|
110
|
+
var { withAuth } = (0, import_auth.createAuth)({
|
|
111
|
+
listKey: "User",
|
|
112
|
+
identityField: "email",
|
|
113
|
+
sessionData: "name createdAt",
|
|
114
|
+
secretField: "password",
|
|
115
|
+
initFirstItem: {
|
|
116
|
+
fields: ["name", "email", "password"]
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
var sessionMaxAge = 60 * 60 * 24 * 30;
|
|
120
|
+
var session = (0, import_session.statelessSessions)({
|
|
121
|
+
maxAge: sessionMaxAge,
|
|
122
|
+
secret: sessionSecret
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// keystone.ts
|
|
126
|
+
var keystone_default = withAuth(
|
|
127
|
+
(0, import_core2.config)({
|
|
128
|
+
db: {
|
|
129
|
+
provider: "sqlite",
|
|
130
|
+
url: "file:./keystone.db"
|
|
131
|
+
},
|
|
132
|
+
lists,
|
|
133
|
+
session
|
|
134
|
+
})
|
|
135
|
+
);
|
package/starter/auth.ts
CHANGED
|
@@ -1,51 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
// Welcome to some authentication for Keystone
|
|
2
|
+
//
|
|
3
|
+
// This is using @keystone-6/auth to add the following
|
|
4
|
+
// - A sign-in page for your Admin UI
|
|
5
|
+
// - A cookie-based stateless session strategy
|
|
6
|
+
// - Using a User email as the identifier
|
|
7
|
+
// - 30 day cookie expiration
|
|
8
|
+
//
|
|
9
|
+
// This file does not configure what Users can do, and the default for this starter
|
|
10
|
+
// project is to allow anyone - logged-in or not - to do anything.
|
|
11
|
+
//
|
|
12
|
+
// If you want to prevent random people on the internet from accessing your data,
|
|
13
|
+
// you can find out how by reading https://keystonejs.com/docs/guides/auth-and-access-control
|
|
14
|
+
//
|
|
15
|
+
// If you want to learn more about how our out-of-the-box authentication works, please
|
|
16
|
+
// read https://keystonejs.com/docs/apis/auth#authentication-api
|
|
9
17
|
|
|
18
|
+
import { randomBytes } from 'crypto';
|
|
10
19
|
import { createAuth } from '@keystone-6/auth';
|
|
11
20
|
|
|
12
|
-
//
|
|
21
|
+
// see https://keystonejs.com/docs/apis/session for the session docs
|
|
13
22
|
import { statelessSessions } from '@keystone-6/core/session';
|
|
14
23
|
|
|
24
|
+
// for a stateless session, a SESSION_SECRET should always be provided
|
|
25
|
+
// especially in production (statelessSessions will throw if SESSION_SECRET is undefined)
|
|
15
26
|
let sessionSecret = process.env.SESSION_SECRET;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// however it should always be there in production.
|
|
19
|
-
if (!sessionSecret) {
|
|
20
|
-
if (process.env.NODE_ENV === 'production') {
|
|
21
|
-
throw new Error(
|
|
22
|
-
'The SESSION_SECRET environment variable must be set in production'
|
|
23
|
-
);
|
|
24
|
-
} else {
|
|
25
|
-
sessionSecret = '-- DEV COOKIE SECRET; CHANGE ME --';
|
|
26
|
-
}
|
|
27
|
+
if (!sessionSecret && process.env.NODE_ENV !== 'production') {
|
|
28
|
+
sessionSecret = randomBytes(32).toString('hex');
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
//
|
|
30
|
-
// What we are saying here is that we want to use the list `User`, and to log in
|
|
31
|
-
// we will need their email and password.
|
|
31
|
+
// withAuth is a function we can use to wrap our base configuration
|
|
32
32
|
const { withAuth } = createAuth({
|
|
33
33
|
listKey: 'User',
|
|
34
34
|
identityField: 'email',
|
|
35
|
-
|
|
35
|
+
|
|
36
|
+
// this is a GraphQL query fragment for fetching what data will be attached to a context.session
|
|
37
|
+
// this can be helpful for when you are writing your access control functions
|
|
38
|
+
// you can find out more at https://keystonejs.com/docs/guides/auth-and-access-control
|
|
39
|
+
sessionData: 'name createdAt',
|
|
36
40
|
secretField: 'password',
|
|
41
|
+
|
|
42
|
+
// WARNING: remove initFirstItem functionality in production
|
|
43
|
+
// see https://keystonejs.com/docs/config/auth#init-first-item for more
|
|
37
44
|
initFirstItem: {
|
|
38
|
-
//
|
|
39
|
-
//
|
|
45
|
+
// if there are no items in the database, by configuring this field
|
|
46
|
+
// you are asking the Keystone AdminUI to create a new user
|
|
47
|
+
// providing inputs for these fields
|
|
40
48
|
fields: ['name', 'email', 'password'],
|
|
49
|
+
|
|
50
|
+
// it uses context.sudo() to do this, which bypasses any access control you might have
|
|
51
|
+
// you shouldn't use this in production
|
|
41
52
|
},
|
|
42
53
|
});
|
|
43
54
|
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
55
|
+
// statelessSessions uses cookies for session tracking
|
|
56
|
+
// these cookies have an expiry, in seconds
|
|
57
|
+
// we use an expiry of 30 days for this starter
|
|
58
|
+
const sessionMaxAge = 60 * 60 * 24 * 30;
|
|
47
59
|
|
|
48
|
-
//
|
|
60
|
+
// you can find out more at https://keystonejs.com/docs/apis/session#session-api
|
|
49
61
|
const session = statelessSessions({
|
|
50
62
|
maxAge: sessionMaxAge,
|
|
51
63
|
secret: sessionSecret!,
|
package/starter/keystone.ts
CHANGED
|
@@ -1,32 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*/
|
|
1
|
+
// Welcome to Keystone!
|
|
2
|
+
//
|
|
3
|
+
// This file is what Keystone uses as the entry-point to your headless backend
|
|
4
|
+
//
|
|
5
|
+
// Keystone imports the default export of this file, expecting a Keystone configuration object
|
|
6
|
+
// you can find out more at https://keystonejs.com/docs/apis/config
|
|
8
7
|
|
|
9
8
|
import { config } from '@keystone-6/core';
|
|
10
9
|
|
|
11
|
-
//
|
|
10
|
+
// to keep this file tidy, we define our schema in a different file
|
|
12
11
|
import { lists } from './schema';
|
|
13
12
|
|
|
14
|
-
//
|
|
13
|
+
// authentication is configured separately here too, but you might move this elsewhere
|
|
14
|
+
// when you write your list-level access control functions, as they typically rely on session data
|
|
15
15
|
import { withAuth, session } from './auth';
|
|
16
16
|
|
|
17
17
|
export default withAuth(
|
|
18
|
-
// Using the config function helps typescript guide you to the available options.
|
|
19
18
|
config({
|
|
20
|
-
// the db sets the database provider - we're using sqlite for the fastest startup experience
|
|
21
19
|
db: {
|
|
20
|
+
// we're using sqlite for the fastest startup experience
|
|
21
|
+
// for more information on what database might be appropriate for you
|
|
22
|
+
// see https://keystonejs.com/docs/guides/choosing-a-database#title
|
|
22
23
|
provider: 'sqlite',
|
|
23
24
|
url: 'file:./keystone.db',
|
|
24
25
|
},
|
|
25
|
-
// This config allows us to set up features of the Admin UI https://keystonejs.com/docs/apis/config#ui
|
|
26
|
-
ui: {
|
|
27
|
-
// For our starter, we check that someone has session data before letting them see the Admin UI.
|
|
28
|
-
isAccessAllowed: (context) => !!context.session?.data,
|
|
29
|
-
},
|
|
30
26
|
lists,
|
|
31
27
|
session,
|
|
32
28
|
})
|
package/starter/package.json
CHANGED
|
@@ -9,21 +9,9 @@
|
|
|
9
9
|
"postinstall": "keystone postinstall"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@keystone-6/auth": "^
|
|
13
|
-
"@keystone-6/core": "^
|
|
14
|
-
"@keystone-6/fields-document": "^
|
|
15
|
-
"
|
|
16
|
-
"next": "12.2.4",
|
|
17
|
-
"typescript": "^4.7.4"
|
|
18
|
-
},
|
|
19
|
-
"// npm": "this is a temporary workaround for npm users, see https://github.com/keystonejs/create-keystone-app/pull/350",
|
|
20
|
-
"overrides": {
|
|
21
|
-
"graphql": "^15.8.0",
|
|
22
|
-
"next": "12.2.4"
|
|
23
|
-
},
|
|
24
|
-
"// yarn": "this is a temporary workaround for yarn users, see https://github.com/keystonejs/create-keystone-app/pull/350",
|
|
25
|
-
"resolutions": {
|
|
26
|
-
"graphql": "^15.8.0",
|
|
27
|
-
"next": "12.2.4"
|
|
12
|
+
"@keystone-6/auth": "^5.0.0",
|
|
13
|
+
"@keystone-6/core": "^3.0.1",
|
|
14
|
+
"@keystone-6/fields-document": "^5.0.0",
|
|
15
|
+
"typescript": "^4.8.0"
|
|
28
16
|
}
|
|
29
17
|
}
|
package/starter/schema.graphql
CHANGED
|
@@ -6,19 +6,17 @@ type User {
|
|
|
6
6
|
name: String
|
|
7
7
|
email: String
|
|
8
8
|
password: PasswordState
|
|
9
|
-
posts(
|
|
10
|
-
where: PostWhereInput! = {}
|
|
11
|
-
orderBy: [PostOrderByInput!]! = []
|
|
12
|
-
take: Int
|
|
13
|
-
skip: Int! = 0
|
|
14
|
-
): [Post!]
|
|
9
|
+
posts(where: PostWhereInput! = {}, orderBy: [PostOrderByInput!]! = [], take: Int, skip: Int! = 0): [Post!]
|
|
15
10
|
postsCount(where: PostWhereInput! = {}): Int
|
|
11
|
+
createdAt: DateTime
|
|
16
12
|
}
|
|
17
13
|
|
|
18
14
|
type PasswordState {
|
|
19
15
|
isSet: Boolean!
|
|
20
16
|
}
|
|
21
17
|
|
|
18
|
+
scalar DateTime @specifiedBy(url: "https://datatracker.ietf.org/doc/html/rfc3339#section-5.6")
|
|
19
|
+
|
|
22
20
|
input UserWhereUniqueInput {
|
|
23
21
|
id: ID
|
|
24
22
|
email: String
|
|
@@ -32,6 +30,7 @@ input UserWhereInput {
|
|
|
32
30
|
name: StringFilter
|
|
33
31
|
email: StringFilter
|
|
34
32
|
posts: PostManyRelationFilter
|
|
33
|
+
createdAt: DateTimeNullableFilter
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
input IDFilter {
|
|
@@ -79,10 +78,22 @@ input PostManyRelationFilter {
|
|
|
79
78
|
none: PostWhereInput
|
|
80
79
|
}
|
|
81
80
|
|
|
81
|
+
input DateTimeNullableFilter {
|
|
82
|
+
equals: DateTime
|
|
83
|
+
in: [DateTime!]
|
|
84
|
+
notIn: [DateTime!]
|
|
85
|
+
lt: DateTime
|
|
86
|
+
lte: DateTime
|
|
87
|
+
gt: DateTime
|
|
88
|
+
gte: DateTime
|
|
89
|
+
not: DateTimeNullableFilter
|
|
90
|
+
}
|
|
91
|
+
|
|
82
92
|
input UserOrderByInput {
|
|
83
93
|
id: OrderDirection
|
|
84
94
|
name: OrderDirection
|
|
85
95
|
email: OrderDirection
|
|
96
|
+
createdAt: OrderDirection
|
|
86
97
|
}
|
|
87
98
|
|
|
88
99
|
enum OrderDirection {
|
|
@@ -95,6 +106,7 @@ input UserUpdateInput {
|
|
|
95
106
|
email: String
|
|
96
107
|
password: String
|
|
97
108
|
posts: PostRelateToManyForUpdateInput
|
|
109
|
+
createdAt: DateTime
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
input PostRelateToManyForUpdateInput {
|
|
@@ -114,6 +126,7 @@ input UserCreateInput {
|
|
|
114
126
|
email: String
|
|
115
127
|
password: String
|
|
116
128
|
posts: PostRelateToManyForCreateInput
|
|
129
|
+
createdAt: DateTime
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
input PostRelateToManyForCreateInput {
|
|
@@ -124,16 +137,9 @@ input PostRelateToManyForCreateInput {
|
|
|
124
137
|
type Post {
|
|
125
138
|
id: ID!
|
|
126
139
|
title: String
|
|
127
|
-
status: String
|
|
128
140
|
content: Post_content_Document
|
|
129
|
-
publishDate: DateTime
|
|
130
141
|
author: User
|
|
131
|
-
tags(
|
|
132
|
-
where: TagWhereInput! = {}
|
|
133
|
-
orderBy: [TagOrderByInput!]! = []
|
|
134
|
-
take: Int
|
|
135
|
-
skip: Int! = 0
|
|
136
|
-
): [Tag!]
|
|
142
|
+
tags(where: TagWhereInput! = {}, orderBy: [TagOrderByInput!]! = [], take: Int, skip: Int! = 0): [Tag!]
|
|
137
143
|
tagsCount(where: TagWhereInput! = {}): Int
|
|
138
144
|
}
|
|
139
145
|
|
|
@@ -141,9 +147,6 @@ type Post_content_Document {
|
|
|
141
147
|
document(hydrateRelationships: Boolean! = false): JSON!
|
|
142
148
|
}
|
|
143
149
|
|
|
144
|
-
scalar DateTime
|
|
145
|
-
@specifiedBy(url: "https://datatracker.ietf.org/doc/html/rfc3339#section-5.6")
|
|
146
|
-
|
|
147
150
|
input PostWhereUniqueInput {
|
|
148
151
|
id: ID
|
|
149
152
|
}
|
|
@@ -154,51 +157,10 @@ input PostWhereInput {
|
|
|
154
157
|
NOT: [PostWhereInput!]
|
|
155
158
|
id: IDFilter
|
|
156
159
|
title: StringFilter
|
|
157
|
-
status: StringNullableFilter
|
|
158
|
-
publishDate: DateTimeNullableFilter
|
|
159
160
|
author: UserWhereInput
|
|
160
161
|
tags: TagManyRelationFilter
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
input StringNullableFilter {
|
|
164
|
-
equals: String
|
|
165
|
-
in: [String!]
|
|
166
|
-
notIn: [String!]
|
|
167
|
-
lt: String
|
|
168
|
-
lte: String
|
|
169
|
-
gt: String
|
|
170
|
-
gte: String
|
|
171
|
-
contains: String
|
|
172
|
-
startsWith: String
|
|
173
|
-
endsWith: String
|
|
174
|
-
not: NestedStringNullableFilter
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
input NestedStringNullableFilter {
|
|
178
|
-
equals: String
|
|
179
|
-
in: [String!]
|
|
180
|
-
notIn: [String!]
|
|
181
|
-
lt: String
|
|
182
|
-
lte: String
|
|
183
|
-
gt: String
|
|
184
|
-
gte: String
|
|
185
|
-
contains: String
|
|
186
|
-
startsWith: String
|
|
187
|
-
endsWith: String
|
|
188
|
-
not: NestedStringNullableFilter
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
input DateTimeNullableFilter {
|
|
192
|
-
equals: DateTime
|
|
193
|
-
in: [DateTime!]
|
|
194
|
-
notIn: [DateTime!]
|
|
195
|
-
lt: DateTime
|
|
196
|
-
lte: DateTime
|
|
197
|
-
gt: DateTime
|
|
198
|
-
gte: DateTime
|
|
199
|
-
not: DateTimeNullableFilter
|
|
200
|
-
}
|
|
201
|
-
|
|
202
164
|
input TagManyRelationFilter {
|
|
203
165
|
every: TagWhereInput
|
|
204
166
|
some: TagWhereInput
|
|
@@ -208,15 +170,11 @@ input TagManyRelationFilter {
|
|
|
208
170
|
input PostOrderByInput {
|
|
209
171
|
id: OrderDirection
|
|
210
172
|
title: OrderDirection
|
|
211
|
-
status: OrderDirection
|
|
212
|
-
publishDate: OrderDirection
|
|
213
173
|
}
|
|
214
174
|
|
|
215
175
|
input PostUpdateInput {
|
|
216
176
|
title: String
|
|
217
|
-
status: String
|
|
218
177
|
content: JSON
|
|
219
|
-
publishDate: DateTime
|
|
220
178
|
author: UserRelateToOneForUpdateInput
|
|
221
179
|
tags: TagRelateToManyForUpdateInput
|
|
222
180
|
}
|
|
@@ -241,9 +199,7 @@ input PostUpdateArgs {
|
|
|
241
199
|
|
|
242
200
|
input PostCreateInput {
|
|
243
201
|
title: String
|
|
244
|
-
status: String
|
|
245
202
|
content: JSON
|
|
246
|
-
publishDate: DateTime
|
|
247
203
|
author: UserRelateToOneForCreateInput
|
|
248
204
|
tags: TagRelateToManyForCreateInput
|
|
249
205
|
}
|
|
@@ -261,12 +217,7 @@ input TagRelateToManyForCreateInput {
|
|
|
261
217
|
type Tag {
|
|
262
218
|
id: ID!
|
|
263
219
|
name: String
|
|
264
|
-
posts(
|
|
265
|
-
where: PostWhereInput! = {}
|
|
266
|
-
orderBy: [PostOrderByInput!]! = []
|
|
267
|
-
take: Int
|
|
268
|
-
skip: Int! = 0
|
|
269
|
-
): [Post!]
|
|
220
|
+
posts(where: PostWhereInput! = {}, orderBy: [PostOrderByInput!]! = [], take: Int, skip: Int! = 0): [Post!]
|
|
270
221
|
postsCount(where: PostWhereInput! = {}): Int
|
|
271
222
|
}
|
|
272
223
|
|
|
@@ -306,10 +257,7 @@ input TagCreateInput {
|
|
|
306
257
|
"""
|
|
307
258
|
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
|
|
308
259
|
"""
|
|
309
|
-
scalar JSON
|
|
310
|
-
@specifiedBy(
|
|
311
|
-
url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf"
|
|
312
|
-
)
|
|
260
|
+
scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf")
|
|
313
261
|
|
|
314
262
|
type Mutation {
|
|
315
263
|
createUser(data: UserCreateInput!): User
|
|
@@ -331,18 +279,11 @@ type Mutation {
|
|
|
331
279
|
deleteTag(where: TagWhereUniqueInput!): Tag
|
|
332
280
|
deleteTags(where: [TagWhereUniqueInput!]!): [Tag]
|
|
333
281
|
endSession: Boolean!
|
|
334
|
-
authenticateUserWithPassword(
|
|
335
|
-
|
|
336
|
-
password: String!
|
|
337
|
-
): UserAuthenticationWithPasswordResult
|
|
338
|
-
createInitialUser(
|
|
339
|
-
data: CreateInitialUserInput!
|
|
340
|
-
): UserAuthenticationWithPasswordSuccess!
|
|
282
|
+
authenticateUserWithPassword(email: String!, password: String!): UserAuthenticationWithPasswordResult
|
|
283
|
+
createInitialUser(data: CreateInitialUserInput!): UserAuthenticationWithPasswordSuccess!
|
|
341
284
|
}
|
|
342
285
|
|
|
343
|
-
union UserAuthenticationWithPasswordResult =
|
|
344
|
-
UserAuthenticationWithPasswordSuccess
|
|
345
|
-
| UserAuthenticationWithPasswordFailure
|
|
286
|
+
union UserAuthenticationWithPasswordResult = UserAuthenticationWithPasswordSuccess | UserAuthenticationWithPasswordFailure
|
|
346
287
|
|
|
347
288
|
type UserAuthenticationWithPasswordSuccess {
|
|
348
289
|
sessionToken: String!
|
|
@@ -360,28 +301,13 @@ input CreateInitialUserInput {
|
|
|
360
301
|
}
|
|
361
302
|
|
|
362
303
|
type Query {
|
|
363
|
-
users(
|
|
364
|
-
where: UserWhereInput! = {}
|
|
365
|
-
orderBy: [UserOrderByInput!]! = []
|
|
366
|
-
take: Int
|
|
367
|
-
skip: Int! = 0
|
|
368
|
-
): [User!]
|
|
304
|
+
users(where: UserWhereInput! = {}, orderBy: [UserOrderByInput!]! = [], take: Int, skip: Int! = 0): [User!]
|
|
369
305
|
user(where: UserWhereUniqueInput!): User
|
|
370
306
|
usersCount(where: UserWhereInput! = {}): Int
|
|
371
|
-
posts(
|
|
372
|
-
where: PostWhereInput! = {}
|
|
373
|
-
orderBy: [PostOrderByInput!]! = []
|
|
374
|
-
take: Int
|
|
375
|
-
skip: Int! = 0
|
|
376
|
-
): [Post!]
|
|
307
|
+
posts(where: PostWhereInput! = {}, orderBy: [PostOrderByInput!]! = [], take: Int, skip: Int! = 0): [Post!]
|
|
377
308
|
post(where: PostWhereUniqueInput!): Post
|
|
378
309
|
postsCount(where: PostWhereInput! = {}): Int
|
|
379
|
-
tags(
|
|
380
|
-
where: TagWhereInput! = {}
|
|
381
|
-
orderBy: [TagOrderByInput!]! = []
|
|
382
|
-
take: Int
|
|
383
|
-
skip: Int! = 0
|
|
384
|
-
): [Tag!]
|
|
310
|
+
tags(where: TagWhereInput! = {}, orderBy: [TagOrderByInput!]! = [], take: Int, skip: Int! = 0): [Tag!]
|
|
385
311
|
tag(where: TagWhereUniqueInput!): Tag
|
|
386
312
|
tagsCount(where: TagWhereInput! = {}): Int
|
|
387
313
|
keystone: KeystoneMeta!
|
|
@@ -395,8 +321,6 @@ type KeystoneMeta {
|
|
|
395
321
|
}
|
|
396
322
|
|
|
397
323
|
type KeystoneAdminMeta {
|
|
398
|
-
enableSignout: Boolean!
|
|
399
|
-
enableSessionItem: Boolean!
|
|
400
324
|
lists: [KeystoneAdminUIListMeta!]!
|
|
401
325
|
list(key: String!): KeystoneAdminUIListMeta
|
|
402
326
|
}
|
|
@@ -418,6 +342,7 @@ type KeystoneAdminUIListMeta {
|
|
|
418
342
|
fields: [KeystoneAdminUIFieldMeta!]!
|
|
419
343
|
initialSort: KeystoneAdminUISort
|
|
420
344
|
isHidden: Boolean!
|
|
345
|
+
isSingleton: Boolean
|
|
421
346
|
}
|
|
422
347
|
|
|
423
348
|
type KeystoneAdminUIFieldMeta {
|
package/starter/schema.prisma
CHANGED
|
@@ -13,22 +13,21 @@ generator client {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
model User {
|
|
16
|
-
id
|
|
17
|
-
name
|
|
18
|
-
email
|
|
19
|
-
password
|
|
20
|
-
posts
|
|
16
|
+
id String @id @default(cuid())
|
|
17
|
+
name String @default("")
|
|
18
|
+
email String @unique @default("")
|
|
19
|
+
password String
|
|
20
|
+
posts Post[] @relation("Post_author")
|
|
21
|
+
createdAt DateTime? @default(now())
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
model Post {
|
|
24
|
-
id
|
|
25
|
-
title
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
authorId String? @map("author")
|
|
31
|
-
tags Tag[] @relation("Post_tags")
|
|
25
|
+
id String @id @default(cuid())
|
|
26
|
+
title String @default("")
|
|
27
|
+
content String @default("[{\"type\":\"paragraph\",\"children\":[{\"text\":\"\"}]}]")
|
|
28
|
+
author User? @relation("Post_author", fields: [authorId], references: [id])
|
|
29
|
+
authorId String? @map("author")
|
|
30
|
+
tags Tag[] @relation("Post_tags")
|
|
32
31
|
|
|
33
32
|
@@index([authorId])
|
|
34
33
|
}
|
|
@@ -37,4 +36,4 @@ model Tag {
|
|
|
37
36
|
id String @id @default(cuid())
|
|
38
37
|
name String @default("")
|
|
39
38
|
posts Post[] @relation("Post_tags")
|
|
40
|
-
}
|
|
39
|
+
}
|
package/starter/schema.ts
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Welcome to your schema
|
|
2
|
+
// Schema driven development is Keystone's modus operandi
|
|
3
|
+
//
|
|
4
|
+
// This file is where we define the lists, fields and hooks for our data.
|
|
5
|
+
// If you want to learn more about how lists are configured, please read
|
|
6
|
+
// - https://keystonejs.com/docs/config/lists
|
|
3
7
|
|
|
4
|
-
Here we define our 'lists', which will then be used both for the GraphQL
|
|
5
|
-
API definition, our database tables, and our Admin UI layout.
|
|
6
|
-
|
|
7
|
-
Some quick definitions to help out:
|
|
8
|
-
A list: A definition of a collection of fields with a name. For the starter
|
|
9
|
-
we have `User`, `Post`, and `Tag` lists.
|
|
10
|
-
A field: The individual bits of data on your list, each with its own type.
|
|
11
|
-
you can see some of the lists in what we use below.
|
|
12
|
-
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// Like the `config` function we use in keystone.ts, we use functions
|
|
16
|
-
// for putting in our config so we get useful errors. With typescript,
|
|
17
|
-
// we get these even before code runs.
|
|
18
8
|
import { list } from '@keystone-6/core';
|
|
9
|
+
import { allowAll } from '@keystone-6/core/access';
|
|
19
10
|
|
|
20
|
-
//
|
|
21
|
-
//
|
|
11
|
+
// see https://keystonejs.com/docs/fields/overview for the full list of fields
|
|
12
|
+
// this is a few common fields for an example
|
|
22
13
|
import {
|
|
23
14
|
text,
|
|
24
15
|
relationship,
|
|
@@ -26,69 +17,62 @@ import {
|
|
|
26
17
|
timestamp,
|
|
27
18
|
select,
|
|
28
19
|
} from '@keystone-6/core/fields';
|
|
29
|
-
|
|
30
|
-
//
|
|
31
|
-
// custom ones.
|
|
20
|
+
|
|
21
|
+
// the document field is a more complicated field, so it has it's own package
|
|
32
22
|
import { document } from '@keystone-6/fields-document';
|
|
23
|
+
// if you want to make your own fields, see https://keystonejs.com/docs/guides/custom-fields
|
|
33
24
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
// that Typescript cannot easily infer.
|
|
38
|
-
import { Lists } from '.keystone/types';
|
|
25
|
+
// when using Typescript, you can refine your types to a stricter subset by importing
|
|
26
|
+
// the generated types from '.keystone/types'
|
|
27
|
+
import type { Lists } from '.keystone/types';
|
|
39
28
|
|
|
40
|
-
// We have a users list, a blogs list, and tags for blog posts, so they can be filtered.
|
|
41
|
-
// Each property on the exported object will become the name of a list (a.k.a. the `listKey`),
|
|
42
|
-
// with the value being the definition of the list, including the fields.
|
|
43
29
|
export const lists: Lists = {
|
|
44
|
-
// Here we define the user list.
|
|
45
30
|
User: list({
|
|
46
|
-
//
|
|
47
|
-
//
|
|
31
|
+
// WARNING
|
|
32
|
+
// for this starter project, anyone can create, query, update and delete anything
|
|
33
|
+
// if you want to prevent random people on the internet from accessing your data,
|
|
34
|
+
// you can find out more at https://keystonejs.com/docs/guides/auth-and-access-control
|
|
35
|
+
access: allowAll,
|
|
36
|
+
|
|
37
|
+
// this is the fields for our User list
|
|
48
38
|
fields: {
|
|
39
|
+
// by adding isRequired, we enforce that every User should have a name
|
|
40
|
+
// if no name is provided, an error will be displayed
|
|
49
41
|
name: text({ validation: { isRequired: true } }),
|
|
42
|
+
|
|
50
43
|
email: text({
|
|
51
44
|
validation: { isRequired: true },
|
|
45
|
+
// by adding isIndexed: 'unique', we're saying that no user can have the same
|
|
46
|
+
// email as another user - this may or may not be a good idea for your project
|
|
52
47
|
isIndexed: 'unique',
|
|
53
|
-
isFilterable: true,
|
|
54
48
|
}),
|
|
55
|
-
|
|
49
|
+
|
|
56
50
|
password: password({ validation: { isRequired: true } }),
|
|
57
|
-
|
|
58
|
-
// we
|
|
59
|
-
//
|
|
60
|
-
// Make sure you read the docs to understand how they work: https://keystonejs.com/docs/guides/relationships#understanding-relationships
|
|
51
|
+
|
|
52
|
+
// we can use this field to see what Posts this User has authored
|
|
53
|
+
// more on that in the Post list below
|
|
61
54
|
posts: relationship({ ref: 'Post.author', many: true }),
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
},
|
|
55
|
+
|
|
56
|
+
createdAt: timestamp({
|
|
57
|
+
// this sets the timestamp to Date.now() when the user is first created
|
|
58
|
+
defaultValue: { kind: 'now' },
|
|
59
|
+
}),
|
|
68
60
|
},
|
|
69
61
|
}),
|
|
70
|
-
|
|
71
|
-
// so we have all the info we need for displaying posts.
|
|
62
|
+
|
|
72
63
|
Post: list({
|
|
64
|
+
// WARNING
|
|
65
|
+
// for this starter project, anyone can create, query, update and delete anything
|
|
66
|
+
// if you want to prevent random people on the internet from accessing your data,
|
|
67
|
+
// you can find out more at https://keystonejs.com/docs/guides/auth-and-access-control
|
|
68
|
+
access: allowAll,
|
|
69
|
+
|
|
70
|
+
// this is the fields for our Post list
|
|
73
71
|
fields: {
|
|
74
|
-
title: text(),
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
options: [
|
|
79
|
-
{ label: 'Published', value: 'published' },
|
|
80
|
-
{ label: 'Draft', value: 'draft' },
|
|
81
|
-
],
|
|
82
|
-
// We want to make sure new posts start off as a draft when they are created
|
|
83
|
-
defaultValue: 'draft',
|
|
84
|
-
// fields also have the ability to configure their appearance in the Admin UI
|
|
85
|
-
ui: {
|
|
86
|
-
displayMode: 'segmented-control',
|
|
87
|
-
},
|
|
88
|
-
}),
|
|
89
|
-
// The document field can be used for making highly editable content. Check out our
|
|
90
|
-
// guide on the document field https://keystonejs.com/docs/guides/document-fields#how-to-use-document-fields
|
|
91
|
-
// for more information
|
|
72
|
+
title: text({ validation: { isRequired: true } }),
|
|
73
|
+
|
|
74
|
+
// the document field can be used for making rich editable content
|
|
75
|
+
// you can find out more at https://keystonejs.com/docs/guides/document-fields
|
|
92
76
|
content: document({
|
|
93
77
|
formatting: true,
|
|
94
78
|
layouts: [
|
|
@@ -101,11 +85,13 @@ export const lists: Lists = {
|
|
|
101
85
|
links: true,
|
|
102
86
|
dividers: true,
|
|
103
87
|
}),
|
|
104
|
-
|
|
105
|
-
//
|
|
106
|
-
// We've configured its UI display quite a lot to make the experience of editing posts better.
|
|
88
|
+
|
|
89
|
+
// with this field, you can set a User as the author for a Post
|
|
107
90
|
author: relationship({
|
|
91
|
+
// we could have used 'User', but then the relationship would only be 1-way
|
|
108
92
|
ref: 'User.posts',
|
|
93
|
+
|
|
94
|
+
// this is some customisations for changing how this will look in the AdminUI
|
|
109
95
|
ui: {
|
|
110
96
|
displayMode: 'cards',
|
|
111
97
|
cardFields: ['name', 'email'],
|
|
@@ -113,10 +99,21 @@ export const lists: Lists = {
|
|
|
113
99
|
linkToItem: true,
|
|
114
100
|
inlineConnect: true,
|
|
115
101
|
},
|
|
102
|
+
|
|
103
|
+
// a Post can only have one author
|
|
104
|
+
// this is the default, but we show it here for verbosity
|
|
105
|
+
many: false,
|
|
116
106
|
}),
|
|
117
|
-
|
|
107
|
+
|
|
108
|
+
// with this field, you can add some Tags to Posts
|
|
118
109
|
tags: relationship({
|
|
110
|
+
// we could have used 'Tag', but then the relationship would only be 1-way
|
|
119
111
|
ref: 'Tag.posts',
|
|
112
|
+
|
|
113
|
+
// a Post can have many Tags, not just one
|
|
114
|
+
many: true,
|
|
115
|
+
|
|
116
|
+
// this is some customisations for changing how this will look in the AdminUI
|
|
120
117
|
ui: {
|
|
121
118
|
displayMode: 'cards',
|
|
122
119
|
cardFields: ['name'],
|
|
@@ -125,17 +122,27 @@ export const lists: Lists = {
|
|
|
125
122
|
inlineConnect: true,
|
|
126
123
|
inlineCreate: { fields: ['name'] },
|
|
127
124
|
},
|
|
128
|
-
many: true,
|
|
129
125
|
}),
|
|
130
126
|
},
|
|
131
127
|
}),
|
|
132
|
-
|
|
128
|
+
|
|
129
|
+
// this last list is our Tag list, it only has a name field for now
|
|
133
130
|
Tag: list({
|
|
131
|
+
// WARNING
|
|
132
|
+
// for this starter project, anyone can create, query, update and delete anything
|
|
133
|
+
// if you want to prevent random people on the internet from accessing your data,
|
|
134
|
+
// you can find out more at https://keystonejs.com/docs/guides/auth-and-access-control
|
|
135
|
+
access: allowAll,
|
|
136
|
+
|
|
137
|
+
// setting this to isHidden for the user interface prevents this list being visible in the Admin UI
|
|
134
138
|
ui: {
|
|
135
139
|
isHidden: true,
|
|
136
140
|
},
|
|
141
|
+
|
|
142
|
+
// this is the fields for our Tag list
|
|
137
143
|
fields: {
|
|
138
144
|
name: text(),
|
|
145
|
+
// this can be helpful to find out all the Posts associated with a Tag
|
|
139
146
|
posts: relationship({ ref: 'Post.tags', many: true }),
|
|
140
147
|
},
|
|
141
148
|
}),
|