├── .editorconfig
├── .eslintrc
├── .gitignore
├── .vscode
└── launch.json
├── LICENSE
├── Procfile
├── README.md
├── advanced.js
├── client
├── lib
│ ├── api.js
│ ├── clone.js
│ └── styles.js
└── scripts
│ ├── api.js
│ └── application.js
├── config.js
├── express.js
├── keystone.js
├── models
├── Autocreate.js
├── Company.js
├── Contact.js
├── Event.js
├── FieldTest.js
├── File.js
├── Gallery.js
├── Job.js
├── NoEdit.js
├── Post.js
├── PostCategory.js
├── UpdateHandlerTest.js
└── User.js
├── package.json
├── public
├── favicon.ico
├── images
│ └── logo.png
└── styles
│ └── site.less
├── routes
├── flashMessages.js
├── index.js
└── views
│ └── update-handler.js
├── templates
├── mixins
│ └── flash-messages.pug
└── views
│ ├── api.pug
│ ├── index.pug
│ └── update-handler.pug
├── tests
└── api-react
│ ├── file
│ └── 1-File.js
│ ├── image
│ ├── 1-Image.js
│ ├── 2-Image.js
│ └── 3-Image.js
│ ├── index.js
│ └── user
│ ├── 1-User.js
│ ├── 2-User.js
│ ├── 3-User.js
│ └── 4-User.js
└── updates
├── 1.0.0-users-companies.js
├── 1.1.0-contacts.js
├── 1.2.0-content.js
└── 1.3.0-categories.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = tab
6 | indent_size = 4
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [package.json]
15 | indent_style = space
16 | indent_size = 2
17 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "keystone-react"
4 | ],
5 | "rules": {
6 | "react/jsx-no-bind": 0
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Deployed apps should consider commenting this line out:
24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
25 | node_modules
26 |
27 | # Ignore .env configuration files
28 | .env
29 |
30 | # Ignore .DS_Store files on OS X
31 | .DS_Store
32 |
33 | # Ignore built css files
34 | *.min.css
35 |
36 | # Ignore temp/dev update scripts
37 | updates/0.0.0-*.js
38 |
39 | # Ignore built packages
40 | public/js/packages.js
41 |
42 | # Ignore uploaded files
43 | uploads
44 |
45 | # Ignore yarn lock
46 | yarn.lock
47 |
48 | # Ignore private keys + config
49 | .secrets
50 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Launch",
6 | "type": "node",
7 | "request": "launch",
8 | "program": "${workspaceRoot}/keystone.js",
9 | "stopOnEntry": false,
10 | "args": [],
11 | "cwd": "${workspaceRoot}",
12 | "preLaunchTask": null,
13 | "runtimeExecutable": null,
14 | "runtimeArgs": [
15 | "--nolazy"
16 | ],
17 | "env": {
18 | "NODE_ENV": "development",
19 | "KEYSTONE_DEV": "true",
20 | "DISABLE_CSRF": "true",
21 | },
22 | "externalConsole": false,
23 | "sourceMaps": false,
24 | "outDir": null
25 | },
26 | {
27 | "name": "Attach",
28 | "type": "node",
29 | "request": "attach",
30 | "port": 5858,
31 | "address": "localhost",
32 | "restart": false,
33 | "sourceMaps": false,
34 | "outDir": null,
35 | "localRoot": "${workspaceRoot}",
36 | "remoteRoot": null
37 | }
38 | ]
39 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Jed Watson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: node keystone.js
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
⚠️ Archived
3 |
This repository is archived and is no longer maintained.
4 |
For the latest Keystone release please visit the Keystone website.
5 |
6 |
7 |
8 |
9 | # keystone-test-project
10 |
11 | A KeystoneJS Project with various configurations for development and testing purposes
12 |
13 | **Note: This project requires Node.js v8.x or newer**
14 |
--------------------------------------------------------------------------------
/advanced.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 |
3 | var keystone = require('keystone');
4 | var config = require('./config');
5 |
6 | /**
7 | * This is an example of how to use the advanced API to start Keystone,
8 | * allowing you to customise how app.listen is called
9 | */
10 |
11 | keystone.init(config.options);
12 | keystone.import('models');
13 | keystone.set('locals', config.locals);
14 | keystone.set('routes', require('./routes'));
15 | keystone.set('nav', config.nav);
16 |
17 | keystone.initExpressApp();
18 | keystone.openDatabaseConnection(function () {
19 | var server = keystone.app.listen(process.env.PORT || 3002, function () {
20 | console.log('-------------------------------');
21 | console.log('Keystone server ready on port %d', server.address().port);
22 | console.log('-------------------------------');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/client/lib/api.js:
--------------------------------------------------------------------------------
1 | /* global Keystone */
2 |
3 | import xhr from 'xhr';
4 |
5 | const api = {
6 | post (url, options = {}, callback) {
7 | if (!options.headers) options.headers = {};
8 | options.headers[Keystone.csrf_header_key] = Keystone.csrf_token_value;
9 | xhr.post(url, options, (err, res, body) => {
10 | // Handle Unauthorized responses by redirecting to the signin page
11 | if (res && res.statusCode === 401) {
12 | alert('Please sign in to run the API Tests');
13 | var from = window.location.pathname;
14 | window.location.href = '/keystone/signin?from=' + from;
15 | } else {
16 | callback(err, res, body);
17 | }
18 | });
19 | },
20 | };
21 |
22 | module.exports = api;
23 |
--------------------------------------------------------------------------------
/client/lib/clone.js:
--------------------------------------------------------------------------------
1 | const clone = function () {
2 | var obj = {};
3 | for (var i = 0; i < arguments.length; i++) {
4 | Object.assign(obj, arguments[i]);
5 | }
6 | return obj;
7 | };
8 |
9 | module.exports = clone;
10 |
--------------------------------------------------------------------------------
/client/lib/styles.js:
--------------------------------------------------------------------------------
1 | const styles = {
2 | data: {
3 | fontFamily: 'Monaco',
4 | fontSize: '0.9em',
5 | marginTop: 20,
6 | },
7 | };
8 |
9 | module.exports = styles;
10 |
--------------------------------------------------------------------------------
/client/scripts/api.js:
--------------------------------------------------------------------------------
1 | import Domify from 'react-domify';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import { Button, Col, Row } from 'elemental';
5 |
6 | import steps from '../../tests/api-react';
7 |
8 | const FIRST_STEP = 1;
9 |
10 | function getStepContext (step) {
11 | const stepIndex = step - 1;
12 | if (!steps[stepIndex]) return {};
13 | const stepName = steps[stepIndex].displayName;
14 | let json = localStorage.getItem(`keystone-test-step-${stepName}`);
15 | try {
16 | return JSON.parse(json || '{}');
17 | } catch (e) {
18 | console.error(`Could not parse stepContext for ${stepName}:`);
19 | console.error(json);
20 | return {};
21 | }
22 | }
23 |
24 | function setStepContext (step, data) {
25 | const stepIndex = step - 1;
26 | if (!steps[stepIndex]) return;
27 | const stepName = steps[stepIndex].displayName;
28 | console.log('Setting stepContext for ' + stepName);
29 | localStorage.setItem(`keystone-test-step-${stepName}`, JSON.stringify(data || {}));
30 | }
31 |
32 | const App = React.createClass({
33 | displayName: 'API Test App',
34 | getInitialState () {
35 | return {
36 | log: [],
37 | step: FIRST_STEP,
38 | stepComplete: false,
39 | stepContext: getStepContext(FIRST_STEP),
40 | stepProgress: 0,
41 | stepReady: false,
42 | };
43 | },
44 | log (style, content) {
45 | if (arguments.length === 1) {
46 | content = style;
47 | style = styles.message;
48 | }
49 | this.setState({
50 | log: [{ style, content }].concat(this.state.log),
51 | });
52 | },
53 | runTest () {
54 | this.setState({
55 | stepReady: false,
56 | });
57 | this.log(`Step ${this.state.step} running...\n`);
58 | this.refs.test.runTest();
59 | },
60 | stepReady () {
61 | this.setState({
62 | stepProgress: this.state.stepProgress + 1,
63 | stepReady: true,
64 | }, () => {
65 | this.log(`Step ${this.state.step} ready\n`);
66 | ReactDOM.findDOMNode(this.refs.run).focus();
67 | });
68 | },
69 | stepResult () {
70 | this.log(`Step ${this.state.step} result:\n`);
71 | for (let i = 0; i < arguments.length; i++) {
72 | this.log(arguments[i]);
73 | }
74 | },
75 | stepAssert (msg) {
76 | var self = this;
77 | return {
78 | truthy (fn) {
79 | try {
80 | if (fn()) self.log(styles.pass, '[pass] ' + msg);
81 | else self.log(styles.fail, '[fail] ' + msg);
82 | } catch (e) {
83 | console.log(e);
84 | self.log(styles.fail, '[error] ' + e.name + ': ' + e.message);
85 | }
86 | },
87 | };
88 | },
89 | stepComplete (nextStepContext) {
90 | this.setState({
91 | stepComplete: true,
92 | });
93 | const nextStep = this.state.step + 1;
94 | setStepContext(nextStep, nextStepContext);
95 | this.log(`Step ${this.state.step} complete\n`);
96 | },
97 | nextStep () {
98 | const nextStep = this.state.step + 1;
99 | this.setState({
100 | log: [],
101 | step: nextStep,
102 | stepComplete: false,
103 | stepContext: getStepContext(nextStep),
104 | stepProgress: 0,
105 | });
106 | },
107 | renderLog () {
108 | return this.state.log.map((msg, i) => {
109 | if (typeof msg.content === 'object') {
110 | return ;
111 | }
112 | return {msg.content}
;
113 | });
114 | },
115 | render () {
116 | const canRunTest = this.state.stepReady;
117 | const canContinue = this.state.stepComplete;
118 | const runLabel = this.state.stepProgress > 1 ? 'Continue Test' : 'Run Test';
119 | const StepComponent = steps[this.state.step - 1];
120 | return (
121 |
122 |
123 |
124 |
125 |
{StepComponent.displayName}
126 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | {this.renderLog()}
148 |
149 |
150 |
151 |
152 | );
153 | },
154 | });
155 |
156 | const styles = {
157 | box: {
158 | backgroundColor: 'white',
159 | borderRadius: '0.3em',
160 | boxShadow: '0 2px 3px rgba(0, 0, 0, 0.075), 0 0 0 1px rgba(0,0,0,0.1)',
161 | margin: '6vh auto',
162 | padding: '3em',
163 | },
164 | obj: {
165 | border: '1px solid #ccc',
166 | fontFamily: 'Monaco',
167 | fontSize: '0.9em',
168 | margin: '0.3em -1em',
169 | padding: '0.5em 1.3em',
170 | },
171 | message: {
172 | fontSize: '1.2em',
173 | margin: '0.2em',
174 | },
175 | pass: {
176 | color: 'green',
177 | fontFamily: 'Monaco',
178 | margin: '0.2em',
179 | },
180 | fail: {
181 | color: 'red',
182 | fontFamily: 'Monaco',
183 | margin: '0.2em',
184 | },
185 | };
186 |
187 | ReactDOM.render(, document.getElementById('app'));
188 |
--------------------------------------------------------------------------------
/client/scripts/application.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Button, Form, FormField, FormInput } from 'elemental';
4 |
5 | const App = React.createClass({
6 | componentDidMount () {
7 | ReactDOM.findDOMNode(this.refs.btn).focus();
8 | },
9 | gotoKeystone () {
10 | window.location.href = '/keystone';
11 | },
12 | render () {
13 | return (
14 |
15 |
16 |

17 |
18 |
Welcome,
19 |
An admin user has been created for you:
20 |
28 |
29 |
30 |
31 |
32 | );
33 | },
34 | });
35 |
36 | const styles = {
37 | box: {
38 | backgroundColor: 'white',
39 | borderRadius: '0.3em',
40 | boxShadow: '0 2px 3px rgba(0, 0, 0, 0.075), 0 0 0 1px rgba(0,0,0,0.1)',
41 | margin: '6vh auto',
42 | maxWidth: 480,
43 | padding: '3em',
44 | },
45 | };
46 |
47 | ReactDOM.render(, document.getElementById('app'));
48 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | exports.options = {
2 |
3 | 'name': 'Keystone Test',
4 | 'brand': 'Keystone Test',
5 |
6 | 'less': 'public',
7 | 'static': 'public',
8 | 'favicon': 'public/favicon.ico',
9 | 'views': 'templates/views',
10 | 'view engine': 'pug',
11 |
12 | 'cloudinary config': 'cloudinary://333779167276662:_8jbSi9FB3sWYrfimcl8VKh34rI@keystone-demo',
13 |
14 | 'auto update': true,
15 | 'session': true,
16 | 'auth': true,
17 | 'user model': 'User',
18 | 'cookie secret': '"fF-ELbvoJ|P6:$<;3c-Cen8OJJy[W1&i@O.M)-%<>QTiTvC93<n;R@!vD@A6N=7',
19 |
20 | };
21 |
22 | exports.locals = {
23 | env: process.NODE_ENV,
24 | utils: require('keystone-utils'),
25 | };
26 |
27 | exports.nav = {
28 | 'people': ['users', 'companies', 'contacts'],
29 | 'content': ['posts', 'post-categories', 'events', 'jobs', 'galleries', 'files'],
30 | 'test-schemas': ['autocreates', 'field-tests', 'update-handler-tests', 'no-edits'],
31 | };
32 |
--------------------------------------------------------------------------------
/express.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 |
3 | var compression = require('compression');
4 | var express = require('express');
5 | var keystone = require('keystone');
6 | var morgan = require('morgan');
7 |
8 | /**
9 | * This is an example of creating a custom express app, binding the Admin UI
10 | * router to it, and using Keystone to intialise the database connection
11 | */
12 |
13 | var config = require('./config');
14 |
15 | var app = new express();
16 |
17 | keystone.init(config.options);
18 | keystone.import('models');
19 | keystone.set('locals', config.locals);
20 | keystone.set('routes', require('./routes'));
21 | keystone.set('nav', config.nav);
22 |
23 | keystone.initDatabaseConfig();
24 | keystone.initExpressSession();
25 |
26 | app.use(compression());
27 | app.use('/keystone', keystone.Admin.Server.createStaticRouter(keystone));
28 | app.use(express.static('public'));
29 |
30 | app.use(keystone.get('session options').cookieParser);
31 | app.use(keystone.expressSession);
32 | app.use(keystone.session.persist);
33 | app.use(require('connect-flash')());
34 |
35 | app.use(morgan('tiny'));
36 | app.use('/keystone', keystone.Admin.Server.createDynamicRouter(keystone));
37 |
38 | app.use(function (req, res) {
39 | res.redirect('/keystone');
40 | });
41 |
42 | keystone.openDatabaseConnection(function () {
43 | var server = app.listen(process.env.PORT || 3001, function () {
44 | console.log('-------------------------------');
45 | console.log('Express server ready on port %d', server.address().port);
46 | console.log('-------------------------------');
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/keystone.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 |
3 | var keystone = require('keystone');
4 | var config = require('./config');
5 |
6 | keystone.init(config.options);
7 | keystone.import('models');
8 | keystone.set('locals', config.locals);
9 | keystone.set('routes', require('./routes'));
10 | keystone.set('nav', config.nav);
11 |
12 | keystone.start({
13 | onMount () { console.log('Application Mounted'); },
14 | onStart () { console.log('Application Started'); },
15 | });
16 |
--------------------------------------------------------------------------------
/models/Autocreate.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var lipsum = require('lorem-ipsum');
3 | var randomkey = require('randomkey');
4 | var transform = require('model-transform');
5 |
6 | var Autocreate = new keystone.List('Autocreate', {
7 | autokey: { from: 'text', path: 'key', unique: true },
8 | autocreate: true,
9 | noedit: true,
10 | });
11 |
12 | function randomNumber () {
13 | return Math.round(Math.random() * 100000);
14 | }
15 |
16 | function randomBoolean () {
17 | return Math.random() > 0.5;
18 | }
19 |
20 | function randomString () {
21 | return randomkey([10, 20]);
22 | }
23 |
24 | Autocreate.add({
25 | text: { type: String, index: true, default: randomString },
26 | number: { type: Number, index: true, default: randomNumber },
27 | boolean: { type: Boolean, index: true, default: randomBoolean },
28 | datetime: { type: Date, index: true, default: Date.now },
29 | html: { type: String, default: lipsum.bind(null, { count: 2, units: 'paragraphs' }) },
30 | markdown: { type: String, index: true, default: lipsum },
31 | });
32 |
33 | transform.toJSON(Autocreate);
34 | Autocreate.defaultColumns = 'text, number, boolean, datetime';
35 | Autocreate.register();
36 |
--------------------------------------------------------------------------------
/models/Company.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 | var Types = keystone.Field.Types;
4 |
5 | var Company = new keystone.List('Company', {
6 | autokey: { from: 'name', path: 'key', unique: true },
7 | });
8 |
9 | Company.add({
10 | name: { type: String, required: true, index: true },
11 | website: { type: Types.Url, index: true },
12 | github: { type: String, size: 'small', index: true },
13 | twitter: { type: String, size: 'small', index: true },
14 | });
15 |
16 | Company.relationship({ ref: 'User', path: 'users', refPath: 'company' });
17 |
18 | transform.toJSON(Company);
19 | Company.defaultColumns = 'name, website, github, twitter';
20 | Company.register();
21 |
--------------------------------------------------------------------------------
/models/Contact.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 | var Types = keystone.Field.Types;
4 |
5 | var Contact = new keystone.List('Contact', {
6 | searchUsesTextIndex: true,
7 | });
8 |
9 | Contact.add({
10 | name: { type: Types.Name, required: true, index: true },
11 | email: { type: Types.Email, initial: true, index: true, unique: true },
12 | favouriteFlavour: { type: Types.Select, options: 'chocolate, vanilla, strawberry', index: true },
13 | birthday: { type: Types.Date, index: true },
14 | homepage: { type: Types.Url, index: true },
15 | favouriteWords: { type: Types.TextArray, index: true },
16 | favouriteNumbers: { type: Types.TextArray, index: true },
17 | address: { type: Types.Location, collapse: true },
18 | bio: { type: Types.Markdown, collapse: true },
19 | });
20 |
21 | transform.toJSON(Contact);
22 | Contact.defaultColumns = 'name, email, favouriteFlavour, birthday, homepage';
23 | Contact.searchFields = 'name, email';
24 | Contact.register();
25 |
--------------------------------------------------------------------------------
/models/Event.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 | var Types = keystone.Field.Types;
4 |
5 | var Event = new keystone.List('Event', {
6 | autokey: { from: 'name', path: 'key', unique: true },
7 | track: true,
8 | });
9 |
10 | Event.add({
11 | name: { type: String, initial: true, required: true, index: true },
12 | eventType: { type: Types.Select, options: ['workshop', 'retreat', 'course', 'festival'], index: true },
13 | eventState: { type: Types.Select, options: 'new, draft, published, suspended, archived', index: true },
14 | startDate: { type: Types.Date, initial: true, required: true },
15 | endDate: { type: Types.Date, initial: true },
16 | });
17 |
18 | Event.add('Contact Information', {
19 | phone: { type: String, width: 'short' },
20 | website: { type: Types.Url, collapse: true },
21 | location: { type: Types.Location, collapse: true, initial: true, required: ['suburb'] },
22 | bookingUrl: { type: Types.Url, collapse: true },
23 | });
24 |
25 | Event.add('Description', {
26 | price: { type: String, collapse: true },
27 | content: {
28 | brief: { type: Types.Html, wysiwyg: true, height: 150 },
29 | summary: { type: Types.Html, hidden: true },
30 | extended: { type: Types.Html, wysiwyg: true, height: 400 },
31 | },
32 | schedule: { type: Types.Html, wysiwyg: true, collapse: true },
33 | });
34 |
35 | transform.toJSON(Event);
36 | Event.defaultColumns = 'name, eventType|15%, eventState|15%, startDate|15%';
37 | Event.register();
38 |
--------------------------------------------------------------------------------
/models/FieldTest.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 | var Types = keystone.Field.Types;
4 |
5 | var FieldTest = new keystone.List('FieldTest', {
6 | track: true,
7 | });
8 |
9 | FieldTest.add({
10 | longLabelForHeadingTesting: String,
11 | });
12 |
13 | FieldTest.add('Model Heading', {
14 | // azureFile: { type: Types.AzureFile },
15 | boolean: { type: Types.Boolean },
16 | // cloudinaryImage: { type: Types.CloudinaryImage },
17 | // cloudinaryImages: { type: Types.CloudinaryImages },
18 | code: { type: Types.Code },
19 | color: { type: Types.Color },
20 | date: { type: Types.Date },
21 | dateArray: { type: Types.DateArray },
22 | dateTime: { type: Types.Datetime },
23 | email: { type: Types.Email },
24 | embedlyPath: { type: String },
25 | // embedlyInfo: { type: Types.Embedly, from: 'embedlyPath' },
26 | geoPoint: { type: Types.GeoPoint },
27 | html: { type: Types.Html },
28 | htmlWYSIWYG: { type: Types.Html, wysiwyg: true },
29 | key: { type: Types.Key },
30 | // localFile: { type: Types.LocalFile, dest: './public/images', filename: function(item, file) { return item.id + '.' + file.extension } },
31 | // localFiles: { type: Types.LocalFiles, dest: './public/images', filename: function(item, file) { return item.id + '.' + file.extension } },
32 | location: { type: Types.Location },
33 | markdown: { type: Types.Markdown },
34 | money: { type: Types.Money },
35 | name: { type: Types.Name },
36 | number: { type: Types.Number },
37 | numberArray: { type: Types.NumberArray },
38 | password: { type: Types.Password },
39 | relationship: { type: Types.Relationship, ref: 'User' },
40 | relationshipMany: { type: Types.Relationship, ref: 'User', many: true },
41 | // S3File: { type: Types.S3File, label: 'S3 File' },
42 | select: { type: Types.Select, options: ['chocolate', 'vanilla', 'strawberry', 'caramel'] },
43 | text: { type: Types.Text },
44 | textarea: { type: Types.Textarea },
45 | textArray: { type: Types.TextArray },
46 | url: { type: Types.Url },
47 | });
48 |
49 | FieldTest.add('Needs Configuration', {
50 | // azureFile: { type: Types.AzureFile },
51 | // cloudinaryImage: { type: Types.CloudinaryImage },
52 | // cloudinaryImages: { type: Types.CloudinaryImages },
53 | // embedlyInfo: { type: Types.Embedly, from: 'embedlyPath' },
54 | // embedlyPath: { type: String },
55 | // localFile: { type: Types.LocalFile, dest: './public/images', filename: function(item, file) { return item.id + '.' + file.extension } },
56 | // localFiles: { type: Types.LocalFiles, dest: './public/images', filename: function(item, file) { return item.id + '.' + file.extension } },
57 | // S3File: { type: Types.S3File, label: 'S3 File' },
58 | });
59 |
60 | transform.toJSON(FieldTest);
61 | FieldTest.defaultColumns = 'name, boolean, date, number, password';
62 | FieldTest.register();
63 |
--------------------------------------------------------------------------------
/models/File.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 | var Types = keystone.Field.Types;
4 |
5 | var File = new keystone.List('File');
6 |
7 | var storage = new keystone.Storage({
8 | adapter: keystone.Storage.Adapters.FS,
9 | fs: {
10 | path: keystone.expandPath('./uploads'),
11 | publicPath: '/public/uploads/',
12 | },
13 | schema: {
14 | originalname: true,
15 | },
16 | });
17 |
18 | File.add({
19 | name: { type: String },
20 | file: { type: Types.File, storage: storage },
21 | });
22 |
23 | // Optional - Test the Azure Storage adapter if environment variables are set
24 | if (process.env.AZURE_STORAGE_ACCOUNT
25 | && process.env.AZURE_STORAGE_ACCESS_KEY
26 | && process.env.AZURE_STORAGE_CONTAINER
27 | ) {
28 | var azureStorage = new keystone.Storage({
29 | adapter: require('keystone-storage-adapter-azure'),
30 | azure: {
31 | generateFilename: keystone.Storage.originalFilename,
32 | // other options set in .env
33 | },
34 | schema: {
35 | container: true,
36 | etag: true,
37 | url: true,
38 | },
39 | });
40 | File.add({
41 | azureFile: { type: Types.File, storage: azureStorage },
42 | });
43 | }
44 |
45 | // Optional - Test the S3 Storage adapter if environment variables are set
46 | if (process.env.S3_KEY
47 | && process.env.S3_SECRET
48 | && process.env.S3_BUCKET
49 | ) {
50 | var s3storage = new keystone.Storage({
51 | adapter: require('keystone-storage-adapter-s3'),
52 | s3: {
53 | headers: {
54 | 'x-amz-acl': 'public-read', // files should be publicly accessible
55 | },
56 | // other options set in .env
57 | },
58 | schema: {
59 | originalname: true,
60 | bucket: true,
61 | etag: true,
62 | path: true,
63 | url: true,
64 | },
65 | });
66 | File.add({
67 | s3File: { type: Types.File, storage: s3storage },
68 | });
69 | }
70 |
71 | transform.toJSON(File);
72 | File.defaultColumns = 'name, file';
73 | File.register();
74 |
--------------------------------------------------------------------------------
/models/Gallery.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 | var Types = keystone.Field.Types;
4 |
5 | var Gallery = new keystone.List('Gallery', {
6 | autokey: { from: 'name', path: 'key' },
7 | });
8 |
9 | Gallery.add({
10 | name: { type: String, required: true },
11 | publishedDate: { type: Date, default: Date.now },
12 | heroImage: { type: Types.CloudinaryImage, required: true, initial: true },
13 | images: { type: Types.CloudinaryImages },
14 | });
15 |
16 | transform.toJSON(Gallery);
17 | Gallery.track = true;
18 | Gallery.register();
19 |
--------------------------------------------------------------------------------
/models/Job.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 | var Types = keystone.Field.Types;
4 |
5 | var Job = new keystone.List('Job', {
6 | autokey: { from: 'name', path: 'key', unique: true },
7 | track: true,
8 | });
9 |
10 | Job.set('notes', {
11 | isFeatured: 'Featured jobs are displayed first on the home page',
12 | });
13 |
14 | Job.add({
15 | name: { type: String, initial: true, required: true, index: true },
16 | jobState: { type: Types.Select, options: 'draft, published, filled, archived', default: 'draft', index: true },
17 | jobType: { type: Types.Select, options: ['full-time', 'part-time', 'casual', 'other'], index: true },
18 | jobRole: { type: Types.Select, options: ['accounting', 'admin', 'customer-service', 'human-resources', 'information-technology', 'sales-marketing', 'other'], index: true },
19 | isFeatured: { type: Boolean, index: true },
20 | startDate: { type: Types.Date },
21 | publishedOn: { type: Types.Date, default: Date.now, noedit: true, index: true },
22 | });
23 |
24 | Job.add('Contact Information', {
25 | email: { type: Types.Email },
26 | phone: { type: String, width: 'short' },
27 | phoneViews: { type: Types.Number },
28 | website: { type: Types.Url, collapse: true },
29 | location: { type: Types.Location, collapse: true },
30 | });
31 |
32 | Job.add('Description', {
33 | pay: { type: Types.Money },
34 | payPer: { type: Types.Select, options: ['hour', 'week', 'fortnight', 'month', 'year'], index: true },
35 | payDescription: { type: Types.Textarea, collapse: true },
36 | imageCaption: { type: String, collapse: true },
37 | content: {
38 | brief: { type: Types.Html, wysiwyg: true, height: 150 },
39 | summary: { type: Types.Html, hidden: true },
40 | extended: { type: Types.Html, wysiwyg: true, height: 400 },
41 | },
42 | });
43 |
44 | transform.toJSON(Job);
45 | Job.defaultColumns = 'name, jobState|15%, jobType|15%, jobRole|15%';
46 | Job.register();
47 |
--------------------------------------------------------------------------------
/models/NoEdit.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 |
4 | var NoEdit = new keystone.List('NoEdit', {
5 | autokey: { from: 'name', path: 'key', unique: true },
6 | sortable: true,
7 | noedit: true,
8 | });
9 |
10 | NoEdit.add({
11 | name: { type: String, required: true },
12 | });
13 |
14 | transform.toJSON(NoEdit);
15 | NoEdit.register();
16 |
--------------------------------------------------------------------------------
/models/Post.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 | var Types = keystone.Field.Types;
4 |
5 | var Post = new keystone.List('Post', {
6 | autokey: { path: 'slug', from: 'name', unique: true },
7 | track: true,
8 | });
9 |
10 | Post.add({
11 | name: { type: String, required: true },
12 | state: { type: Types.Select, options: 'draft, published, archived', default: 'draft', index: true },
13 | author: { type: Types.Relationship, ref: 'User', index: true },
14 | publishedDate: { type: Types.Date, index: true, dependsOn: { state: 'published' } },
15 | image: { type: Types.CloudinaryImage },
16 | content: {
17 | brief: { type: Types.Html, wysiwyg: true, height: 150 },
18 | extended: { type: Types.Html, wysiwyg: true, height: 400, collapse: true },
19 | },
20 | categories: { type: Types.Relationship, ref: 'PostCategory', many: true },
21 | });
22 |
23 | Post.schema.virtual('content.full').get(() => {
24 | if (!this.content) return '';
25 | return this.content.extended || this.content.brief;
26 | });
27 |
28 | transform.toJSON(Post);
29 | Post.defaultColumns = 'name, state|20%, author|20%, categories|20%, publishedDate|20%';
30 | Post.register();
31 |
--------------------------------------------------------------------------------
/models/PostCategory.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 |
4 | var PostCategory = new keystone.List('PostCategory', {
5 | autokey: { from: 'name', path: 'key', unique: true },
6 | sortable: true,
7 | });
8 |
9 | PostCategory.add({
10 | name: { type: String, required: true },
11 | });
12 |
13 | PostCategory.relationship({ ref: 'Post', refPath: 'categories' });
14 |
15 | transform.toJSON(PostCategory);
16 | PostCategory.register();
17 |
--------------------------------------------------------------------------------
/models/UpdateHandlerTest.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 | var Types = keystone.Field.Types;
4 |
5 | var UpdateHandlerTest = new keystone.List('UpdateHandlerTest', {
6 | nocreate: true,
7 | track: true,
8 | });
9 |
10 | UpdateHandlerTest.add({
11 | name: { type: Types.Name, required: true },
12 | email: { type: Types.Email },
13 | image: { type: Types.CloudinaryImage },
14 | address: { type: Types.Location, required: true, initial: true },
15 | });
16 |
17 | transform.toJSON(UpdateHandlerTest);
18 | UpdateHandlerTest.track = true;
19 | UpdateHandlerTest.defaultSort = '-createdAt';
20 | UpdateHandlerTest.defaultColumns = 'name, email, createdAt';
21 | UpdateHandlerTest.register();
22 |
--------------------------------------------------------------------------------
/models/User.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 | var transform = require('model-transform');
3 | var Types = keystone.Field.Types;
4 |
5 | var User = new keystone.List('User');
6 |
7 | User.add({
8 | name: { type: Types.Name, required: true, index: true },
9 | email: { type: Types.Email, initial: true, required: true, index: true, unique: true },
10 | password: { type: Types.Password, initial: true, required: true },
11 | company: { type: Types.Relationship, ref: 'Company', initial: true, index: true },
12 | address: { type: Types.Location, collapse: true },
13 | }, 'Permissions', {
14 | isAdmin: { type: Boolean, label: 'Can access Keystone', index: true },
15 | isProtected: { type: Boolean, noedit: true },
16 | });
17 |
18 | // Provide access to Keystone
19 | User.schema.virtual('canAccessKeystone').get(function () {
20 | return this.isAdmin;
21 | });
22 |
23 | /**
24 | * PROTECTING THE DEMO USER
25 | * The following hooks prevent anyone from editing the main demo user itself,
26 | * and breaking access to the website cms.
27 | */
28 |
29 | var protect = function (path) {
30 | User.schema.path(path).set(value => {
31 | return (this.isProtected) ? this.get(path) : value;
32 | });
33 | };
34 | var protectedPaths = ['name.first', 'name.last', 'email', 'isAdmin'];
35 | protectedPaths.forEach(protect);
36 |
37 | User.schema.path('password').set(value => {
38 | return (this.isProtected) ? '$2a$10$8oUbHJPIUrW5z2aHoIGfP.q0SC5DrLDrX1qLkwhjQ3nYQ9Ay2nGPu' : value;
39 | });
40 |
41 | transform.toJSON(User);
42 | User.defaultColumns = 'name, email, company, isAdmin';
43 | User.register();
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "keystone-test",
3 | "version": "1.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "babel-core": "^6.9.1",
7 | "babel-plugin-transform-object-assign": "^6.8.0",
8 | "babel-plugin-transform-object-rest-spread": "^6.8.0",
9 | "babel-preset-es2015": "^6.9.0",
10 | "babel-preset-react": "^6.5.0",
11 | "babelify": "^7.3.0",
12 | "blacklist": "^1.1.2",
13 | "browserify-middleware": "^7.0.0",
14 | "compression": "^1.6.2",
15 | "connect-flash": "^0.1.1",
16 | "dotenv": "^4.0.0",
17 | "elemental": "^0.6.1",
18 | "express": "^4.13.4",
19 | "helmet": "^3.1.0",
20 | "keystone": "4.0.0-beta.5",
21 | "keystone-storage-adapter-azure": "^1.1.0",
22 | "keystone-storage-adapter-s3": "^1.1.0",
23 | "keystone-utils": "^0.4.0",
24 | "lodash": "^4.17.11",
25 | "lorem-ipsum": "^1.0.3",
26 | "model-transform": "^2.0.0",
27 | "morgan": "^1.7.0",
28 | "pug": "^2.0.3",
29 | "random-word": "^2.0.0",
30 | "randomkey": "^1.0.0",
31 | "react": "^15.4.2",
32 | "react-addons-css-transition-group": "^15.4.2",
33 | "react-dom": "^15.4.2",
34 | "react-domify": "^0.2.6",
35 | "store-prototype": "^1.1.1",
36 | "xhr": "^2.2.0"
37 | },
38 | "engines": {
39 | "node": ">=0.12.7",
40 | "npm": ">=2.14.2"
41 | },
42 | "scripts": {
43 | "dev": "KEYSTONE_DEV=true node keystone.js",
44 | "dev:windows": "set KEYSTONE_DEV=true&&node keystone.js",
45 | "lint": "eslint .",
46 | "lint:fix": "eslint . --fix",
47 | "start": "node keystone.js",
48 | "start:linked": "node --preserve-symlinks keystone",
49 | "start:dev": "KEYSTONE_DEV=true DISABLE_CSRF=true node --preserve-symlinks keystone.js",
50 | "test": "npm run lint"
51 | },
52 | "main": "keystone.js",
53 | "devDependencies": {
54 | "eslint": "^3.14.0",
55 | "eslint-config-keystone": "^3.0.0",
56 | "eslint-config-keystone-react": "^1.0.0",
57 | "eslint-plugin-react": "^6.9.0"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keystonejs/keystone-test-project/18e1d885fc137e548a3fc675e232458f3edc04f1/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/keystonejs/keystone-test-project/18e1d885fc137e548a3fc675e232458f3edc04f1/public/images/logo.png
--------------------------------------------------------------------------------
/public/styles/site.less:
--------------------------------------------------------------------------------
1 | @import "../../node_modules/elemental/less/elemental.less";
2 |
--------------------------------------------------------------------------------
/routes/flashMessages.js:
--------------------------------------------------------------------------------
1 | var _ = require('lodash');
2 | module.exports = function flashMessages (req, res, next) {
3 | var flashMessages = {
4 | info: req.flash('info'),
5 | success: req.flash('success'),
6 | warning: req.flash('warning'),
7 | error: req.flash('error'),
8 | };
9 |
10 | res.locals.messages = _.some(flashMessages, function (msgs) { return msgs.length; }) ? flashMessages : false;
11 | next();
12 | };
13 |
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 | var babelify = require('babelify');
2 | var browserify = require('browserify-middleware');
3 | var helmet = require('helmet');
4 | var keystone = require('keystone');
5 | var flashMessages = require('./flashMessages');
6 |
7 | var clientConfig = {
8 | commonPackages: [
9 | 'elemental',
10 | 'react',
11 | 'react-addons-css-transition-group',
12 | 'react-dom',
13 | 'store-prototype',
14 | 'xhr',
15 | ],
16 | };
17 |
18 | // Middleware
19 |
20 | keystone.pre('static', helmet({
21 | contentSecurityPolicy: {
22 | directives: {
23 | defaultSrc: ["'self'"],
24 | imgSrc: ["'self'", 'data:', 'res.cloudinary.com'], // ['*'],
25 | scriptSrc: ["'self'", "'unsafe-inline'", 'www.google-analytics.com'],
26 | styleSrc: ["'self'", "'unsafe-inline'", 'maxcdn.bootstrapcdn.com'],
27 | },
28 | },
29 | }));
30 |
31 | keystone.pre('render', flashMessages);
32 |
33 | // Setup Route Bindings
34 | exports = module.exports = function (app) {
35 |
36 | // Bundle common packages
37 | app.get('/js/packages.js', browserify(clientConfig.commonPackages, {
38 | cache: true,
39 | precompile: true,
40 | }));
41 |
42 | // Serve script bundles
43 | app.use('/js', browserify('./client/scripts', {
44 | external: clientConfig.commonPackages,
45 | transform: [babelify.configure({
46 | plugins: [require('babel-plugin-transform-object-rest-spread'), require('babel-plugin-transform-object-assign')],
47 | presets: [require('babel-preset-es2015'), require('babel-preset-react')],
48 | })],
49 | }));
50 |
51 | // Views
52 | app.get('/api', function (req, res) {
53 | res.render('api', {
54 | Keystone: {
55 | csrf_header_key: keystone.security.csrf.CSRF_HEADER_KEY,
56 | csrf_token_value: keystone.security.csrf.getToken(req, res),
57 | },
58 | });
59 | });
60 |
61 | app.all('/update-handler', require('./views/update-handler'));
62 |
63 | // Views
64 | app.use(function (req, res) {
65 | res.render('index');
66 | });
67 |
68 | };
69 |
--------------------------------------------------------------------------------
/routes/views/update-handler.js:
--------------------------------------------------------------------------------
1 | var keystone = require('keystone');
2 |
3 | var UpdateHandlerTest = keystone.list('UpdateHandlerTest');
4 |
5 | module.exports = (req, res) => {
6 | var view = new keystone.View(req, res);
7 |
8 | res.locals.formData = {};
9 | res.locals.validationErrors = {};
10 | res.locals.result = false;
11 |
12 | view.on('post', { action: 'test-update-handler' }, next => {
13 | // Get the UpdateHandler
14 | var application = new UpdateHandlerTest.model();
15 | application.getUpdateHandler(req).process(req.body, {
16 | // Show flash messages for errors
17 | flashErrors: true,
18 | // Log database errors
19 | logErrors: true,
20 | // Save these fields to the model
21 | fields: 'name, email, image, address',
22 | // Require all fields to be specified, no matter what it says in the model
23 | required: 'name, email',
24 | // Show custom title in error flash message
25 | errorMessage: '[Custom Error Message] There were some errors:',
26 | // Show custom required messages
27 | requiredMessages: {
28 | name: 'Please enter your name',
29 | },
30 | // Show custom invalid messages
31 | invalidMessages: {
32 | email: 'Email must be in the format hello@thinkmill.com.au',
33 | },
34 | }, function (err) {
35 | if (err) {
36 | // Pass the error messages to the Jade template for inline handling
37 | res.locals.validationErrors = err.detail;
38 | // Pass req.body as formData to default fields to previously entered values
39 | res.locals.formData = req.body;
40 | return next();
41 | } else {
42 | // Flash a success message when everything worked out
43 | req.flash('success', 'Item saved successfully');
44 | res.locals.result = application;
45 | return next();
46 | }
47 | });
48 | });
49 |
50 | view.render('update-handler');
51 | };
52 |
--------------------------------------------------------------------------------
/templates/mixins/flash-messages.pug:
--------------------------------------------------------------------------------
1 | mixin flash-messages(messages)
2 | if messages
3 | each message in messages.info
4 | +flash-message(message, 'info')
5 | each message in messages.success
6 | +flash-message(message, 'success')
7 | each message in messages.warning
8 | +flash-message(message, 'warning')
9 | each message in messages.error
10 | +flash-message(message, 'danger')
11 |
12 | mixin flash-message(message, type)
13 | div(class='alert alert-' + type)
14 | if utils.isObject(message)
15 | if message.title
16 | h4= message.title
17 | if message.detail
18 | p= message.detail
19 | if message.list
20 | ul
21 | each item in message.list
22 | li= item
23 | else
24 | = message
25 |
--------------------------------------------------------------------------------
/templates/views/api.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | //- HTML HEADER
4 | head
5 | meta(charset="utf-8")
6 | meta(name="viewport", content="width=device-width, initial-scale=1.0")
7 | meta(http-equiv="Content-Language", content="en")
8 | title Keystone API Test
9 | link(rel="shortcut icon", href="/favicon.ico", type="image/x-icon")
10 | link(href="/styles/site.min.css", rel="stylesheet")
11 | script.
12 | window.Keystone = !{JSON.stringify(Keystone)};
13 |
14 | //- HTML BODY
15 | body
16 | #app
17 | script(src='/js/packages.js')
18 | script(src='/js/api.js')
19 |
--------------------------------------------------------------------------------
/templates/views/index.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | //- HTML HEADER
4 | head
5 | meta(charset="utf-8")
6 | meta(name="viewport", content="width=device-width, initial-scale=1.0")
7 | meta(http-equiv="Content-Language", content="en")
8 | title Keystone Test
9 | link(rel="shortcut icon", href="/favicon.ico", type="image/x-icon")
10 | link(href="/styles/site.min.css", rel="stylesheet")
11 |
12 | //- HTML BODY
13 | body
14 | #app
15 | script(src='/js/packages.js')
16 | script(src='/js/application.js')
17 |
--------------------------------------------------------------------------------
/templates/views/update-handler.pug:
--------------------------------------------------------------------------------
1 | include ../mixins/flash-messages
2 |
3 | doctype html
4 | html
5 | head
6 | meta(charset='utf-8')
7 | meta(name='viewport', content='width=device-width, initial-scale=1, shrink-to-fit=no')
8 | meta(http-equiv='x-ua-compatible', content='ie=edge')
9 | title Keystone UpdateHandler Test
10 | link(rel='shortcut icon', href='/favicon.ico', type='image/x-icon')
11 | link(rel='stylesheet', href='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.4/css/bootstrap.min.css', integrity='2hfp1SzUoho7/TsGGGDaFdsuuDL0LX2hnUp6VkX3CUQ2K4K+xjboZdsXyp4oUHZj', crossorigin='anonymous')
12 |
13 | body
14 | .container(style='margin-top: 30px')
15 | h1 UpdateHandler Test #1
16 | // Display flash messages
17 | +flash-messages(messages)
18 | // Display form
19 | form(method='post', enctype='multipart/form-data').form-horizontal
20 | input(type='hidden', name='action', value='test-update-handler')
21 | .form-group.row(class=validationErrors.name ? 'has-danger' : null)
22 | label(for='name.first').col-sm-2.col-form-label First name
23 | .col-lg-6
24 | input(type='text', name='name.first', value=formData['name.first'], placeholder='Arnold').form-control
25 | .form-group.row(class=validationErrors.name ? 'has-danger' : null)
26 | label(for='name.last').col-sm-2.col-form-label Last name
27 | .col-lg-6
28 | input(type='text', name='name.last', value=formData['name.last'], placeholder='Schwarzenegger').form-control
29 | if validationErrors.name
30 | .form-control-feedback= validationErrors.name.message
31 | .form-group.row(class=validationErrors.email ? 'has-danger' : null)
32 | label(for='email').col-sm-2.col-form-label Email
33 | .col-lg-6
34 | input(name='email', value=formData.email, placeholder='arnold@schwarzenegger.at').form-control
35 | if validationErrors.email
36 | .form-control-feedback= validationErrors.email.message
37 | .form-group.row(class=validationErrors.image ? 'has-danger' : null)
38 | label(for='image').col-sm-2.col-form-label Image
39 | .col-lg-6
40 | input(type='file', name='image_upload').form-control
41 | if validationErrors.image
42 | .form-control-feedback= validationErrors.image.message
43 | .form-group.row(class=validationErrors.address ? 'has-danger' : null)
44 | label(for='street1').col-sm-2.col-form-label Address
45 | .col-sm-6
46 | .form-group
47 | input(name='address.street1', value=formData['address.street1'], placeholder='Street').form-control
48 | .form-group
49 | input(name='address.suburb', value=formData['address.suburb'], placeholder='Suburb').form-control
50 | .col-sm-4
51 | .form-check
52 | label.form-check-label
53 | input(type='checkbox', name='address_improve', value='true', checked).form-check-input
54 | | Google Lookup
55 | if validationErrors.address
56 | .col-lg-6
57 | .form-control-feedback= validationErrors.address.message
58 | .form-group
59 | button(type='submit').btn.btn-primary Submit
60 | if result
61 | a(href='/keystone/update-handler-tests/' + result.id, target='_blank').btn.btn-secondary.m-b-2 View in Keystone
62 | pre
63 | code= JSON.stringify(result, null, ' ')
64 | script.
65 | console.log('Result:', !{JSON.stringify(result)});
66 |
--------------------------------------------------------------------------------
/tests/api-react/file/1-File.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Form, FormField, FileUpload } from 'elemental';
3 |
4 | import api from '../../../client/lib/api';
5 |
6 | const Test = React.createClass({
7 | displayName: 'Upload File',
8 | getInitialState () {
9 | return {
10 | file: null,
11 | dataURI: null,
12 | };
13 | },
14 | componentDidMount () {
15 | this.props.ready();
16 | },
17 | handleFile (e, data) {
18 | this.setState({
19 | file: data.file,
20 | dataURI: data.dataURI,
21 | });
22 | },
23 | runTest () {
24 | var formData = new window.FormData();
25 | formData.append('name', 'Test ' + Date.now());
26 | if (this.state.file) {
27 | formData.append('file', 'upload:xyz123');
28 | formData.append('xyz123', this.state.file);
29 | }
30 | api.post('/keystone/api/files/create', {
31 | body: formData,
32 | responseType: 'json',
33 | }, (err, res, body) => {
34 | this.props.result('Received response:', body);
35 | if (this.state.file) {
36 | this.props.assert('status code is 200').truthy(() => res.statusCode === 200);
37 | // this.props.assert('file has been uploaded').truthy(() => body.fields.file.url.substr(0, 25) === 'http://res.cloudinary.com');
38 | // this.props.complete({ gallery: body });
39 | this.props.ready();
40 | } else {
41 | this.props.assert('status code is 400').truthy(() => res.statusCode === 400);
42 | this.props.assert('error is "validation errors"').truthy(() => body.error === 'validation errors');
43 | this.props.assert('file is required').truthy(() => body.detail.file.type === 'required');
44 | this.props.ready();
45 | }
46 | });
47 | },
48 | render () {
49 | return (
50 |
55 | );
56 | },
57 | });
58 |
59 | const localStyles = {
60 | field: {
61 | marginTop: 20,
62 | },
63 | };
64 |
65 | module.exports = Test;
66 |
--------------------------------------------------------------------------------
/tests/api-react/image/1-Image.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Form, FormField, FileUpload, Radio } from 'elemental';
3 |
4 | import api from '../../../client/lib/api';
5 |
6 | const Test = React.createClass({
7 | displayName: 'Upload Image',
8 | getInitialState () {
9 | return {
10 | file: null,
11 | dataURI: null,
12 | uploadMode: 'fileData',
13 | };
14 | },
15 | componentDidMount () {
16 | this.props.ready();
17 | },
18 | handleFile (e, data) {
19 | this.setState({
20 | file: data.file,
21 | dataURI: data.dataURI,
22 | });
23 | },
24 | setUploadMode (e) {
25 | this.setState({
26 | uploadMode: e.target.value,
27 | });
28 | },
29 | runTest () {
30 | var formData = new window.FormData();
31 | formData.append('name', 'Test ' + Date.now());
32 | if (this.state.file && this.state.uploadMode === 'fileData') {
33 | formData.append('heroImage', 'upload:abcd1234');
34 | formData.append('abcd1234', this.state.file);
35 | } else if (this.state.dataURI && this.state.uploadMode === 'base64') {
36 | formData.append('heroImage', this.state.dataURI);
37 | } else if (this.state.uploadMode === 'remoteImage') {
38 | formData.append('heroImage', 'http://keystonejs.com/images/logo.png');
39 | }
40 | api.post('/keystone/api/galleries/create', {
41 | body: formData,
42 | responseType: 'json',
43 | }, (err, res, body) => {
44 | this.props.result('Received response:', body);
45 | if (this.state.file) {
46 | this.props.assert('status code is 200').truthy(() => res.statusCode === 200);
47 | this.props.assert('image has been uploaded').truthy(() => body.fields.heroImage.url.substr(0, 25) === 'http://res.cloudinary.com');
48 | this.props.complete({ gallery: body });
49 | } else if (this.state.uploadMode === 'remoteImage') {
50 | this.props.assert('status code is 200').truthy(() => res.statusCode === 200);
51 | this.props.assert('image has been uploaded').truthy(() => body.fields.heroImage.url.substr(0, 25) === 'http://res.cloudinary.com');
52 | this.props.assert('image is the correct size').truthy(() => body.fields.heroImage.width === 207);
53 | this.props.complete({ gallery: body });
54 | } else {
55 | this.props.assert('status code is 400').truthy(() => res.statusCode === 400);
56 | this.props.assert('error is "validation errors"').truthy(() => body.error === 'validation errors');
57 | this.props.assert('image is required').truthy(() => body.detail.heroImage.type === 'required');
58 | this.props.ready();
59 | }
60 | });
61 | },
62 | render () {
63 | return (
64 |
76 | );
77 | },
78 | });
79 |
80 | const localStyles = {
81 | field: {
82 | marginTop: 20,
83 | },
84 | };
85 |
86 | module.exports = Test;
87 |
--------------------------------------------------------------------------------
/tests/api-react/image/2-Image.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Form, FormField, FileUpload } from 'elemental';
3 |
4 | import api from '../../../client/lib/api';
5 |
6 | const Test = React.createClass({
7 | displayName: 'Upload Multiple Images',
8 | getInitialState () {
9 | return {
10 | image_one: null,
11 | image_two: null,
12 | };
13 | },
14 | componentDidMount () {
15 | this.props.ready();
16 | },
17 | handleFile (img, data) {
18 | this.setState({
19 | [`image_${img}`]: {
20 | file: data.file,
21 | dataURI: data.dataURI,
22 | },
23 | });
24 | },
25 | runTest () {
26 | var formData = new window.FormData();
27 | var images = 0;
28 | formData.append('name', 'Test Updated ' + Date.now());
29 | if (this.state.image_one) {
30 | images++;
31 | formData.append('images', 'upload:image_one');
32 | formData.append('image_one', this.state.image_one.file);
33 | }
34 | if (this.state.image_two) {
35 | images++;
36 | formData.append('images', 'upload:image_two');
37 | formData.append('image_two', this.state.image_two.file);
38 | }
39 | if (!images) {
40 | formData.append('images', '');
41 | }
42 |
43 | api.post('/keystone/api/galleries/' + this.props.stepContext.gallery.id, {
44 | body: formData,
45 | responseType: 'json',
46 | }, (err, res, body) => {
47 | this.props.result('Received response:', body);
48 | this.props.assert('status code is 200').truthy(() => res.statusCode === 200);
49 | if (images >= 1) {
50 | this.props.assert('image 1 has been uploaded').truthy(() => body.fields.images[0].url.substr(0, 25) === 'http://res.cloudinary.com');
51 | }
52 | if (images >= 2) {
53 | this.props.assert('image 2 has been uploaded').truthy(() => body.fields.images[1].url.substr(0, 25) === 'http://res.cloudinary.com');
54 | }
55 | this.props.assert('images array contains the right number of items').truthy(() => body.fields.images.length === images);
56 | this.props.complete({ gallery: body });
57 | });
58 | },
59 | render () {
60 | return (
61 |
69 | );
70 | },
71 | });
72 |
73 | const localStyles = {
74 | field: {
75 | marginTop: 20,
76 | },
77 | };
78 |
79 | module.exports = Test;
80 |
--------------------------------------------------------------------------------
/tests/api-react/image/3-Image.js:
--------------------------------------------------------------------------------
1 | import Domify from 'react-domify';
2 | import React from 'react';
3 |
4 | import api from '../../../client/lib/api';
5 | import styles from '../../../client/lib/styles';
6 |
7 | const Test = React.createClass({
8 | displayName: 'Manage Images',
9 | getInitialState () {
10 | var images = [].concat(this.props.stepContext.gallery.fields.images).reverse();
11 | return {
12 | data: {
13 | id: this.props.stepContext.gallery.id,
14 | images: images,
15 | },
16 | };
17 | },
18 | componentDidMount () {
19 | this.props.ready();
20 | },
21 | runTest () {
22 | api.post('/keystone/api/galleries/' + this.props.stepContext.gallery.id, {
23 | json: this.state.data,
24 | }, (err, res, body) => {
25 | this.props.result('Received response:', body);
26 | this.props.assert('status code is 200').truthy(() => res.statusCode === 200);
27 | this.props.assert('images have been reordered').truthy(() => body.fields.images[0].public_id === this.state.data.images[0].public_id);
28 | this.props.complete();
29 | });
30 | },
31 | render () {
32 | return (
33 |
34 |
35 |
36 | );
37 | },
38 | });
39 |
40 | module.exports = Test;
41 |
--------------------------------------------------------------------------------
/tests/api-react/index.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | require('./user/1-User'), // Tests User validation
3 | require('./user/2-User'), // Creates a new User
4 | require('./user/3-User'), // Updates the User
5 | require('./user/4-User'), // Deletes the User
6 | require('./image/1-Image'), // Creates a Gallery and uploads an image
7 | require('./image/2-Image'), // Updates the Gallery with multiple images
8 | require('./image/3-Image'), // Reorders Gallery images then removes them
9 | require('./file/1-File'), // Creates a new File item and uploads a file
10 | ];
11 |
--------------------------------------------------------------------------------
/tests/api-react/user/1-User.js:
--------------------------------------------------------------------------------
1 | import Domify from 'react-domify';
2 | import React from 'react';
3 |
4 | import api from '../../../client/lib/api';
5 | import styles from '../../../client/lib/styles';
6 |
7 | const Test = React.createClass({
8 | displayName: 'Create User',
9 | getInitialState () {
10 | return {
11 | action: 'Start Test',
12 | data: {
13 | name: '',
14 | email: '',
15 | password: '',
16 | },
17 | };
18 | },
19 | componentDidMount () {
20 | this.props.ready();
21 | },
22 | runTest () {
23 | api.post('/keystone/api/users/create', {
24 | json: this.state.data,
25 | }, (err, res, body) => {
26 | this.props.result('Received response:', body);
27 | this.props.assert('status code is 400').truthy(() => res.statusCode === 400);
28 | this.props.assert('error is "validation errors"').truthy(() => body.error === 'validation errors');
29 | if (!this.state.data.password) {
30 | this.props.assert('name is required').truthy(() => body.detail.name.type === 'required');
31 | this.props.assert('email is required').truthy(() => body.detail.email.type === 'required');
32 | this.props.assert('password is required').truthy(() => body.detail.password.type === 'required');
33 | this.setState({
34 | data: {
35 | 'name.full': 'first last',
36 | 'email': 'not an email',
37 | 'password': 'abcd1234',
38 | 'password_confirm': 'abcd',
39 | },
40 | });
41 | this.props.ready();
42 | } else {
43 | this.props.assert('name passed validation').truthy(() => !body.detail.name);
44 | this.props.assert('email is required').truthy(() => body.detail.email.type === 'invalid');
45 | this.props.assert('passwords don\'t match').truthy(() => body.detail.password.type === 'invalid');
46 | this.props.complete();
47 | }
48 | });
49 | },
50 | render () {
51 | return (
52 |
53 |
54 |
55 | );
56 | },
57 | });
58 |
59 | module.exports = Test;
60 |
--------------------------------------------------------------------------------
/tests/api-react/user/2-User.js:
--------------------------------------------------------------------------------
1 | import Domify from 'react-domify';
2 | import React from 'react';
3 |
4 | import api from '../../../client/lib/api';
5 | import clone from '../../../client/lib/clone';
6 | import styles from '../../../client/lib/styles';
7 |
8 | const Test = React.createClass({
9 | displayName: 'Create User (2)',
10 | getInitialState () {
11 | return {
12 | action: 'Start Test',
13 | data: {
14 | 'name.full': 'Test User',
15 | 'email': 'user@keystonejs.com',
16 | 'password': 'test1234',
17 | 'password_confirm': 'test1234',
18 | },
19 | };
20 | },
21 | componentDidMount () {
22 | this.props.ready();
23 | },
24 | runTest () {
25 | api.post('/keystone/api/users/create', {
26 | json: this.state.data,
27 | }, (err, res, body) => {
28 | this.props.result('Received response:', body);
29 | if (this.state.data.email === 'user@keystonejs.com') {
30 | this.props.assert('status code is 500').truthy(() => res.statusCode === 500);
31 | this.props.assert('error is "database error"').truthy(() => body.error === 'database error');
32 | this.setState({
33 | data: clone(this.state.data, {
34 | email: 'user-' + Date.now() + '@keystonejs.com',
35 | }),
36 | });
37 | this.props.ready();
38 | } else {
39 | this.props.assert('status code is 200').truthy(() => res.statusCode === 200);
40 | this.props.assert('email returned successfully').truthy(() => body.fields.email === this.state.data.email);
41 | this.props.complete({ user: body });
42 | }
43 | });
44 | },
45 | render () {
46 | return (
47 |
48 |
49 |
50 | );
51 | },
52 | });
53 |
54 | module.exports = Test;
55 |
--------------------------------------------------------------------------------
/tests/api-react/user/3-User.js:
--------------------------------------------------------------------------------
1 | import blacklist from 'blacklist';
2 | import Domify from 'react-domify';
3 | import React from 'react';
4 |
5 | import api from '../../../client/lib/api';
6 | import styles from '../../../client/lib/styles';
7 |
8 | const Test = React.createClass({
9 | displayName: 'Update User',
10 | getInitialState () {
11 | return {
12 | data: {
13 | name: { first: 'Test', last: 'Update' },
14 | password: '',
15 | },
16 | };
17 | },
18 | componentDidMount () {
19 | this.props.ready();
20 | },
21 | runTest () {
22 | api.post('/keystone/api/users/' + this.props.stepContext.user.id, {
23 | json: this.state.data,
24 | }, (err, res, body) => {
25 | this.props.result('Received response:', body);
26 | if (this.state.data.password === '') {
27 | this.props.assert('status code is 400').truthy(() => res.statusCode === 400);
28 | this.props.assert('error is "validation errors"').truthy(() => body.error === 'validation errors');
29 | this.props.assert('password is required').truthy(() => body.detail.password.type === 'required');
30 | this.setState({
31 | data: blacklist(this.state.data, 'password'),
32 | });
33 | this.props.ready();
34 | } else {
35 | this.props.assert('status code is 200').truthy(() => res.statusCode === 200);
36 | this.props.assert('name has been updated').truthy(() => body.name === `${this.state.data.name.first} ${this.state.data.name.last}`);
37 | this.props.complete({ user: body });
38 | }
39 | });
40 | },
41 | render () {
42 | return (
43 |
44 |
45 |
46 | );
47 | },
48 | });
49 |
50 | module.exports = Test;
51 |
--------------------------------------------------------------------------------
/tests/api-react/user/4-User.js:
--------------------------------------------------------------------------------
1 | import blacklist from 'blacklist';
2 | import Domify from 'react-domify';
3 | import React from 'react';
4 |
5 | import api from '../../../client/lib/api';
6 | import styles from '../../../client/lib/styles';
7 |
8 | const Test = React.createClass({
9 | displayName: 'Delete User',
10 | getInitialState () {
11 | return {
12 | deleted: false,
13 | user: blacklist(this.props.stepContext.user, 'fields'),
14 | };
15 | },
16 | componentDidMount () {
17 | this.props.ready();
18 | },
19 | runTest () {
20 | api.post(`/keystone/api/users/${this.state.user.id}/delete`, {
21 | json: {},
22 | }, (err, res, body) => {
23 | this.props.result('Received response:', body);
24 | if (!this.state.deleted) {
25 | this.props.assert('status code is 200').truthy(() => res.statusCode === 200);
26 | this.setState({
27 | deleted: true,
28 | });
29 | this.props.ready();
30 | } else if (this.state.user.id !== '1234') {
31 | this.props.assert('status code is 404').truthy(() => res.statusCode === 404);
32 | this.setState({
33 | user: {
34 | id: '1234',
35 | name: 'Invalid User',
36 | },
37 | });
38 | this.props.ready();
39 | } else {
40 | this.props.assert('status code is 500').truthy(() => res.statusCode === 500);
41 | this.props.assert('error should be "database error"').truthy(() => body.error === 'database error');
42 | this.props.complete();
43 | }
44 | });
45 | },
46 | render () {
47 | return (
48 |
49 |
50 |
51 | );
52 | },
53 | });
54 |
55 | module.exports = Test;
56 |
--------------------------------------------------------------------------------
/updates/1.0.0-users-companies.js:
--------------------------------------------------------------------------------
1 | exports.create = {
2 | User: [
3 | {
4 | 'name.full': 'Admin User',
5 | 'email': 'user@keystonejs.com',
6 | 'password': 'admin',
7 | 'isAdmin': true,
8 | 'isProtected': true,
9 | },
10 | {
11 | 'name.full': 'Jed Watson',
12 | 'email': 'jed@tm.com',
13 | 'password': 'admin',
14 | 'company': 'tm',
15 | 'isAdmin': true,
16 | },
17 | {
18 | 'name.full': 'Joss Mackison',
19 | 'email': 'joss@tm.com',
20 | 'password': 'admin',
21 | 'company': 'tm',
22 | 'isAdmin': true,
23 | },
24 | {
25 | 'name.full': 'Boris Bozic',
26 | 'email': 'boris@bozic.com',
27 | 'password': 'admin',
28 | 'company': 'tm',
29 | 'isAdmin': true,
30 | },
31 | {
32 | 'name.full': 'Tom Walker',
33 | 'email': 'tom@walker.com',
34 | 'password': 'admin',
35 | 'company': 'tm',
36 | 'isAdmin': true,
37 | },
38 | {
39 | 'name.full': 'Tuan Hoang',
40 | 'email': 'tuan@hoang.com',
41 | 'password': 'admin',
42 | 'company': 'tm',
43 | 'isAdmin': true,
44 | },
45 | {
46 | 'name.full': 'Vito Belgiorno-Zegna',
47 | 'email': 'vito@bz.com',
48 | 'password': 'admin',
49 | 'company': 'tm',
50 | },
51 | {
52 | 'name.full': 'Daniel Cousens',
53 | 'email': 'dan@btc.com',
54 | 'password': 'admin',
55 | },
56 | {
57 | 'name.full': 'Rob Morris',
58 | 'email': 'rob@prismatik.com.au',
59 | 'password': 'admin',
60 | 'company': 'pk',
61 | },
62 | {
63 | 'name.full': 'John Molomby',
64 | 'email': 'john@tm.com',
65 | 'password': 'admin',
66 | },
67 | {
68 | 'name.full': 'Max Stoiber',
69 | 'email': 'mxstbr@tm.com',
70 | 'password': 'admin',
71 | },
72 | ],
73 | Company: [
74 | {
75 | __ref: 'tm',
76 | name: 'Thinkmill',
77 | website: 'http://www.thinkmill.com.au',
78 | github: 'thinkmill',
79 | twitter: '@thethinkmill',
80 | },
81 | {
82 | __ref: 'pk',
83 | name: 'Prismatik',
84 | website: 'http://www.prismatik.com.au',
85 | github: 'prismatik',
86 | },
87 | {
88 | __ref: 'yahoo',
89 | name: 'Yahoo',
90 | website: 'http://www.yahoo.com',
91 | },
92 | {
93 | __ref: 'fb',
94 | name: 'Facebook',
95 | website: 'http://www.facebook.com',
96 | github: 'facebook',
97 | twitter: '@facebook',
98 | },
99 | {
100 | __ref: 'tw',
101 | name: 'Twitter',
102 | website: 'http://www.twitter.com',
103 | },
104 | {
105 | __ref: 'ms',
106 | name: 'Microsoft',
107 | website: 'http://www.microsoft.com',
108 | twitter: '@microsoft',
109 | },
110 | ],
111 | };
112 |
--------------------------------------------------------------------------------
/updates/1.1.0-contacts.js:
--------------------------------------------------------------------------------
1 | exports.create = {
2 | Contact: [
3 | {
4 | 'name.first': 'Dolan',
5 | 'name.last': 'Knight',
6 | 'email': 'sit@molestiedapibusligula.org',
7 | },
8 | {
9 | 'name.first': 'Carly',
10 | 'name.last': 'Velazquez',
11 | 'email': 'primis.in@non.net',
12 | },
13 | {
14 | 'name.first': 'Oliver',
15 | 'name.last': 'Gerard',
16 | 'email': 'cursus@nuncinterdum.ca',
17 | },
18 | {
19 | 'name.first': 'Dominic',
20 | 'name.last': 'Boone',
21 | 'email': 'mi.pede.nonummy@sociosquadlitora.com',
22 | },
23 | {
24 | 'name.first': 'Jada',
25 | 'name.last': 'Porter',
26 | 'email': 'lacus.quisque@etultrices.ca',
27 | },
28 | {
29 | 'name.first': 'Howard',
30 | 'name.last': 'Bowen',
31 | 'email': 'malesuada.integer.id@habitant.org',
32 | },
33 | {
34 | 'name.first': 'Samuel',
35 | 'name.last': 'Brock',
36 | 'email': 'vestibulum.ante@cubilia.org',
37 | },
38 | {
39 | 'name.first': 'Inez',
40 | 'name.last': 'Rivas',
41 | 'email': 'ut.tincidunt.vehicula@magna.ca',
42 | },
43 | {
44 | 'name.first': 'Teegan',
45 | 'name.last': 'Dixon',
46 | 'email': 'ac@auctorvelit.org',
47 | },
48 | {
49 | 'name.first': 'Ronan',
50 | 'name.last': 'Green',
51 | 'email': 'sed.congue@estarcu.net',
52 | },
53 | {
54 | 'name.first': 'Carla',
55 | 'name.last': 'Greer',
56 | 'email': 'aenean@duis.com',
57 | },
58 | {
59 | 'name.first': 'Veronica',
60 | 'name.last': 'Stout',
61 | 'email': 'in@tincidunt.org',
62 | },
63 | {
64 | 'name.first': 'Hop',
65 | 'name.last': 'Bishop',
66 | 'email': 'amet@ataugue.co.uk',
67 | },
68 | {
69 | 'name.first': 'Rooney',
70 | 'name.last': 'Patterson',
71 | 'email': 'et@suspendisseeleifendcras.org',
72 | },
73 | {
74 | 'name.first': 'Lila',
75 | 'name.last': 'Middleton',
76 | 'email': 'nibh.dolor.nonummy@enimnonnisi.org',
77 | },
78 | {
79 | 'name.first': 'Dante',
80 | 'name.last': 'Trujillo',
81 | 'email': 'vulputate.mauris@infaucibus.org',
82 | },
83 | {
84 | 'name.first': 'Thane',
85 | 'name.last': 'Hardy',
86 | 'email': 'pharetra.ut@etiam.net',
87 | },
88 | {
89 | 'name.first': 'Harrison',
90 | 'name.last': 'Livingston',
91 | 'email': 'et@mauris.org',
92 | },
93 | {
94 | 'name.first': 'Kimberley',
95 | 'name.last': 'Farmer',
96 | 'email': 'sed.diam@duilectusrutrum.net',
97 | },
98 | {
99 | 'name.first': 'Edan',
100 | 'name.last': 'Davidson',
101 | 'email': 'dui@placeratvelit.com',
102 | },
103 | {
104 | 'name.first': 'Vladimir',
105 | 'name.last': 'Barrett',
106 | 'email': 'et.ultrices@mauriseuelit.edu',
107 | },
108 | {
109 | 'name.first': 'Xyla',
110 | 'name.last': 'Quinn',
111 | 'email': 'amet.ornare@placeratorcilacus.org',
112 | },
113 | {
114 | 'name.first': 'Dolan',
115 | 'name.last': 'Mooney',
116 | 'email': 'blandit.enim.consequat@mialiquam.com',
117 | },
118 | {
119 | 'name.first': 'Judah',
120 | 'name.last': 'Wooten',
121 | 'email': 'ipsum.sodales.purus@nonegestas.ca',
122 | },
123 | {
124 | 'name.first': 'Mark',
125 | 'name.last': 'Snow',
126 | 'email': 'nec@augueut.edu',
127 | },
128 | {
129 | 'name.first': 'Fitzgerald',
130 | 'name.last': 'Puckett',
131 | 'email': 'donec@malesuadainteger.co.uk',
132 | },
133 | {
134 | 'name.first': 'Lyle',
135 | 'name.last': 'Carpenter',
136 | 'email': 'est@lectussitamet.org',
137 | },
138 | {
139 | 'name.first': 'Perry',
140 | 'name.last': 'Finley',
141 | 'email': 'lacus.cras.interdum@miac.ca',
142 | },
143 | {
144 | 'name.first': 'Austin',
145 | 'name.last': 'Bartlett',
146 | 'email': 'natoque.penatibus@commodo.ca',
147 | },
148 | {
149 | 'name.first': 'Michelle',
150 | 'name.last': 'Todd',
151 | 'email': 'ut.eros.non@dapibusrutrumjusto.ca',
152 | },
153 | {
154 | 'name.first': 'Merrill',
155 | 'name.last': 'Walters',
156 | 'email': 'dolor.nonummy.ac@pedecrasvulputate.edu',
157 | },
158 | {
159 | 'name.first': 'Rebecca',
160 | 'name.last': 'George',
161 | 'email': 'nibh@gravidanonsollicitudin.com',
162 | },
163 | {
164 | 'name.first': 'Justina',
165 | 'name.last': 'Schroeder',
166 | 'email': 'massa.vestibulum.accumsan@risus.ca',
167 | },
168 | {
169 | 'name.first': 'Kermit',
170 | 'name.last': 'Brooks',
171 | 'email': 'morbi.accumsan.laoreet@ligula.com',
172 | },
173 | {
174 | 'name.first': 'Dominique',
175 | 'name.last': 'Delgado',
176 | 'email': 'fermentum@dui.edu',
177 | },
178 | {
179 | 'name.first': 'Owen',
180 | 'name.last': 'Mercer',
181 | 'email': 'eget.magna@at.org',
182 | },
183 | {
184 | 'name.first': 'Jade',
185 | 'name.last': 'Castillo',
186 | 'email': 'est.ac@varius.ca',
187 | },
188 | {
189 | 'name.first': 'Melvin',
190 | 'name.last': 'Gates',
191 | 'email': 'in@leoelementum.org',
192 | },
193 | {
194 | 'name.first': 'Garrett',
195 | 'name.last': 'Jones',
196 | 'email': 'at.risus.nunc@odioetiam.edu',
197 | },
198 | {
199 | 'name.first': 'Desirae',
200 | 'name.last': 'Mcbride',
201 | 'email': 'sit.amet@magnis.com',
202 | },
203 | {
204 | 'name.first': 'Inez',
205 | 'name.last': 'Mcclain',
206 | 'email': 'non.lobortis.quis@idliberodonec.net',
207 | },
208 | {
209 | 'name.first': 'Keelie',
210 | 'name.last': 'Downs',
211 | 'email': 'in@commodoat.org',
212 | },
213 | {
214 | 'name.first': 'Sage',
215 | 'name.last': 'Levy',
216 | 'email': 'felis.donec@donecest.co.uk',
217 | },
218 | {
219 | 'name.first': 'Mara',
220 | 'name.last': 'Everett',
221 | 'email': 'montes.nascetur@mollislectuspede.co.uk',
222 | },
223 | {
224 | 'name.first': 'Deacon',
225 | 'name.last': 'Cash',
226 | 'email': 'a@quamcurabitur.net',
227 | },
228 | {
229 | 'name.first': 'Indira',
230 | 'name.last': 'Ware',
231 | 'email': 'luctus.curabitur@donecporttitortellus.ca',
232 | },
233 | {
234 | 'name.first': 'Ann',
235 | 'name.last': 'Dejesus',
236 | 'email': 'morbi.non@molestiepharetranibh.edu',
237 | },
238 | {
239 | 'name.first': 'Burke',
240 | 'name.last': 'Hale',
241 | 'email': 'urna.nunc@augue.com',
242 | },
243 | {
244 | 'name.first': 'Madonna',
245 | 'name.last': 'Russo',
246 | 'email': 'mauris.sapien@dui.edu',
247 | },
248 | {
249 | 'name.first': 'Macaulay',
250 | 'name.last': 'Landry',
251 | 'email': 'dolor.egestas.rhoncus@lacus.ca',
252 | },
253 | {
254 | 'name.first': 'Destiny',
255 | 'name.last': 'Dunlap',
256 | 'email': 'nisi@idante.net',
257 | },
258 | {
259 | 'name.first': 'Mason',
260 | 'name.last': 'Parker',
261 | 'email': 'lobortis@torquentper.net',
262 | },
263 | {
264 | 'name.first': 'Janna',
265 | 'name.last': 'Walls',
266 | 'email': 'non.enim@donectempuslorem.edu',
267 | },
268 | {
269 | 'name.first': 'Declan',
270 | 'name.last': 'Delgado',
271 | 'email': 'pede.ac@cum.edu',
272 | },
273 | {
274 | 'name.first': 'Jaime',
275 | 'name.last': 'Craft',
276 | 'email': 'velit.dui@adipiscing.ca',
277 | },
278 | {
279 | 'name.first': 'Gregory',
280 | 'name.last': 'Galloway',
281 | 'email': 'luctus@nulla.edu',
282 | },
283 | {
284 | 'name.first': 'Carl',
285 | 'name.last': 'Bonner',
286 | 'email': 'dui.in@mauris.co.uk',
287 | },
288 | {
289 | 'name.first': 'Adrienne',
290 | 'name.last': 'Brady',
291 | 'email': 'mi.duis@sociisnatoque.edu',
292 | },
293 | {
294 | 'name.first': 'Sydnee',
295 | 'name.last': 'Lloyd',
296 | 'email': 'arcu.vestibulum@necorci.net',
297 | },
298 | {
299 | 'name.first': 'Amal',
300 | 'name.last': 'Gates',
301 | 'email': 'duis.elementum.dui@neque.co.uk',
302 | },
303 | {
304 | 'name.first': 'Zahir',
305 | 'name.last': 'Delaney',
306 | 'email': 'ut.erat.sed@fames.org',
307 | },
308 | {
309 | 'name.first': 'Aspen',
310 | 'name.last': 'Moran',
311 | 'email': 'est@maurisvelturpis.edu',
312 | },
313 | {
314 | 'name.first': 'Coby',
315 | 'name.last': 'Silva',
316 | 'email': 'mauris.elit.dictum@tellusfaucibusleo.net',
317 | },
318 | {
319 | 'name.first': 'Jada',
320 | 'name.last': 'David',
321 | 'email': 'etiam.laoreet@nulla.com',
322 | },
323 | {
324 | 'name.first': 'Belle',
325 | 'name.last': 'Vincent',
326 | 'email': 'elit.erat@augueeutellus.edu',
327 | },
328 | {
329 | 'name.first': 'Uma',
330 | 'name.last': 'Hewitt',
331 | 'email': 'adipiscing.ligula@scelerisquescelerisquedui.org',
332 | },
333 | {
334 | 'name.first': 'Kevyn',
335 | 'name.last': 'Ford',
336 | 'email': 'elit.etiam@enim.edu',
337 | },
338 | {
339 | 'name.first': 'Walker',
340 | 'name.last': 'Robles',
341 | 'email': 'congue.elit@eu.ca',
342 | },
343 | {
344 | 'name.first': 'Jillian',
345 | 'name.last': 'Hale',
346 | 'email': 'mauris.integer.sem@elitpretiumet.edu',
347 | },
348 | {
349 | 'name.first': 'Quinlan',
350 | 'name.last': 'Dale',
351 | 'email': 'ultrices.mauris.ipsum@penatibus.com',
352 | },
353 | {
354 | 'name.first': 'Aquila',
355 | 'name.last': 'Reese',
356 | 'email': 'aenean.euismod.mauris@nectempusscelerisque.org',
357 | },
358 | {
359 | 'name.first': 'Joel',
360 | 'name.last': 'Watkins',
361 | 'email': 'lacus@penatibusetmagnis.co.uk',
362 | },
363 | {
364 | 'name.first': 'Cyrus',
365 | 'name.last': 'Prince',
366 | 'email': 'cursus.non.egestas@ac.co.uk',
367 | },
368 | {
369 | 'name.first': 'Anjolie',
370 | 'name.last': 'Dillard',
371 | 'email': 'sagittis.semper.nam@semper.com',
372 | },
373 | {
374 | 'name.first': 'Barclay',
375 | 'name.last': 'Estes',
376 | 'email': 'enim.sit.amet@cursus.com',
377 | },
378 | {
379 | 'name.first': 'Brooke',
380 | 'name.last': 'Graves',
381 | 'email': 'nonummy.ultricies@donecfringilla.com',
382 | },
383 | {
384 | 'name.first': 'Remedios',
385 | 'name.last': 'Villarreal',
386 | 'email': 'nunc.lectus@quispedesuspendisse.co.uk',
387 | },
388 | {
389 | 'name.first': 'Denton',
390 | 'name.last': 'Bender',
391 | 'email': 'mi@cursusinhendrerit.edu',
392 | },
393 | {
394 | 'name.first': 'Slade',
395 | 'name.last': 'Thompson',
396 | 'email': 'metus.vivamus@in.org',
397 | },
398 | {
399 | 'name.first': 'Kiona',
400 | 'name.last': 'Barron',
401 | 'email': 'amet.orci.ut@risus.ca',
402 | },
403 | {
404 | 'name.first': 'Inga',
405 | 'name.last': 'Jenkins',
406 | 'email': 'sapien@pretium.edu',
407 | },
408 | {
409 | 'name.first': 'Stacey',
410 | 'name.last': 'Hampton',
411 | 'email': 'sit.amet.massa@metusvitae.ca',
412 | },
413 | {
414 | 'name.first': 'Alexis',
415 | 'name.last': 'Harmon',
416 | 'email': 'mi@egetmagnasuspendisse.org',
417 | },
418 | {
419 | 'name.first': 'Clare',
420 | 'name.last': 'Shaw',
421 | 'email': 'magna.sed.dui@arcuvelquam.ca',
422 | },
423 | {
424 | 'name.first': 'Dexter',
425 | 'name.last': 'Rasmussen',
426 | 'email': 'semper.rutrum@idante.edu',
427 | },
428 | {
429 | 'name.first': 'Alea',
430 | 'name.last': 'Hays',
431 | 'email': 'pede.cras.vulputate@fermentumvel.ca',
432 | },
433 | {
434 | 'name.first': 'Cassady',
435 | 'name.last': 'Franco',
436 | 'email': 'mi.duis.risus@elitfermentum.ca',
437 | },
438 | {
439 | 'name.first': 'Eleanor',
440 | 'name.last': 'Mcdaniel',
441 | 'email': 'dolor@auctor.com',
442 | },
443 | {
444 | 'name.first': 'Bernard',
445 | 'name.last': 'Sherman',
446 | 'email': 'lorem.vitae.odio@semelit.com',
447 | },
448 | {
449 | 'name.first': 'Kareem',
450 | 'name.last': 'Ingram',
451 | 'email': 'donec@accumsan.co.uk',
452 | },
453 | {
454 | 'name.first': 'Robert',
455 | 'name.last': 'Marsh',
456 | 'email': 'mauris.erat@montesnascetur.org',
457 | },
458 | {
459 | 'name.first': 'Nina',
460 | 'name.last': 'Faulkner',
461 | 'email': 'mi.fringilla@nullatemporaugue.co.uk',
462 | },
463 | {
464 | 'name.first': 'Kato',
465 | 'name.last': 'Warner',
466 | 'email': 'cursus.integer.mollis@justonecante.ca',
467 | },
468 | {
469 | 'name.first': 'Scarlet',
470 | 'name.last': 'Morse',
471 | 'email': 'adipiscing.elit.etiam@cras.edu',
472 | },
473 | {
474 | 'name.first': 'Thomas',
475 | 'name.last': 'Anderson',
476 | 'email': 'metus.sit.amet@nisimagna.ca',
477 | },
478 | {
479 | 'name.first': 'Eve',
480 | 'name.last': 'Chan',
481 | 'email': 'interdum@mollisintegertincidunt.org',
482 | },
483 | {
484 | 'name.first': 'Henry',
485 | 'name.last': 'Roberts',
486 | 'email': 'arcu.et@etmalesuadafames.net',
487 | },
488 | {
489 | 'name.first': 'Vincent',
490 | 'name.last': 'Ball',
491 | 'email': 'ipsum@vitaeerat.edu',
492 | },
493 | {
494 | 'name.first': 'Dean',
495 | 'name.last': 'Trujillo',
496 | 'email': 'suspendisse.aliquet.molestie@curabitursedtortor.com',
497 | },
498 | {
499 | 'name.first': 'Kato',
500 | 'name.last': 'Moses',
501 | 'email': 'nullam.velit.dui@maurismorbinon.edu',
502 | },
503 | {
504 | 'name.first': 'Celeste',
505 | 'name.last': 'Love',
506 | 'email': 'aptent.taciti.sociosqu@quisdiampellentesque.com',
507 | },
508 | {
509 | 'name.first': 'Sacha',
510 | 'name.last': 'Dunlap',
511 | 'email': 'arcu.iaculis.enim@classaptenttaciti.ca',
512 | },
513 | {
514 | 'name.first': 'Regina',
515 | 'name.last': 'Saunders',
516 | 'email': 'libero.dui.nec@aliquetmetusurna.org',
517 | },
518 | {
519 | 'name.first': 'Aristotle',
520 | 'name.last': 'Rhodes',
521 | 'email': 'arcu.vestibulum@ante.co.uk',
522 | },
523 | {
524 | 'name.first': 'Christen',
525 | 'name.last': 'Reed',
526 | 'email': 'odio@egestassed.ca',
527 | },
528 | {
529 | 'name.first': 'Emmanuel',
530 | 'name.last': 'Tran',
531 | 'email': 'nibh@porttitorscelerisqueneque.org',
532 | },
533 | {
534 | 'name.first': 'Clio',
535 | 'name.last': 'Glass',
536 | 'email': 'dui@erat.com',
537 | },
538 | {
539 | 'name.first': 'Lael',
540 | 'name.last': 'Bass',
541 | 'email': 'mi.duis@elitnullafacilisi.ca',
542 | },
543 | {
544 | 'name.first': 'Cathleen',
545 | 'name.last': 'Moon',
546 | 'email': 'scelerisque.neque@quisque.co.uk',
547 | },
548 | {
549 | 'name.first': 'Kessie',
550 | 'name.last': 'Sullivan',
551 | 'email': 'molestie.dapibus@nuncsit.net',
552 | },
553 | {
554 | 'name.first': 'Arsenio',
555 | 'name.last': 'Rios',
556 | 'email': 'class.aptent.taciti@luctus.co.uk',
557 | },
558 | {
559 | 'name.first': 'Kaitlin',
560 | 'name.last': 'Patterson',
561 | 'email': 'non.feugiat@inconsectetueripsum.co.uk',
562 | },
563 | {
564 | 'name.first': 'Yen',
565 | 'name.last': 'Martin',
566 | 'email': 'nec.ligula.consectetuer@non.edu',
567 | },
568 | {
569 | 'name.first': 'Shannon',
570 | 'name.last': 'Barron',
571 | 'email': 'quisque.varius@semper.ca',
572 | },
573 | {
574 | 'name.first': 'Lavinia',
575 | 'name.last': 'Fuentes',
576 | 'email': 'amet.consectetuer.adipiscing@primisinfaucibus.edu',
577 | },
578 | {
579 | 'name.first': 'Ciara',
580 | 'name.last': 'Shannon',
581 | 'email': 'sed.libero.proin@sapienmolestie.com',
582 | },
583 | {
584 | 'name.first': 'Eve',
585 | 'name.last': 'Norton',
586 | 'email': 'ac@orciinconsequat.com',
587 | },
588 | {
589 | 'name.first': 'Leroy',
590 | 'name.last': 'Cannon',
591 | 'email': 'neque.et@mauris.com',
592 | },
593 | {
594 | 'name.first': 'Kyle',
595 | 'name.last': 'Melton',
596 | 'email': 'nunc.mauris@sed.net',
597 | },
598 | {
599 | 'name.first': 'Kaseem',
600 | 'name.last': 'Tillman',
601 | 'email': 'ligula@atfringillapurus.edu',
602 | },
603 | {
604 | 'name.first': 'Jerome',
605 | 'name.last': 'Small',
606 | 'email': 'commodo.hendrerit@commodoipsumsuspendisse.co.uk',
607 | },
608 | {
609 | 'name.first': 'Levi',
610 | 'name.last': 'Fulton',
611 | 'email': 'neque@inloremdonec.org',
612 | },
613 | {
614 | 'name.first': 'Maite',
615 | 'name.last': 'Haney',
616 | 'email': 'lorem.ipsum.dolor@duilectus.co.uk',
617 | },
618 | {
619 | 'name.first': 'Adele',
620 | 'name.last': 'Todd',
621 | 'email': 'malesuada@gravidasitamet.edu',
622 | },
623 | {
624 | 'name.first': 'Russell',
625 | 'name.last': 'Estrada',
626 | 'email': 'mauris.id.sapien@penatibusetmagnis.org',
627 | },
628 | {
629 | 'name.first': 'Reed',
630 | 'name.last': 'Figueroa',
631 | 'email': 'dui@sedliberoproin.edu',
632 | },
633 | {
634 | 'name.first': 'Harding',
635 | 'name.last': 'Oconnor',
636 | 'email': 'cras.interdum.nunc@rutrumfuscedolor.com',
637 | },
638 | {
639 | 'name.first': 'Rhiannon',
640 | 'name.last': 'Salas',
641 | 'email': 'dignissim.tempor@condimentumdonecat.ca',
642 | },
643 | {
644 | 'name.first': 'Theodore',
645 | 'name.last': 'Ayala',
646 | 'email': 'sociis.natoque.penatibus@crasvulputatevelit.edu',
647 | },
648 | {
649 | 'name.first': 'Cairo',
650 | 'name.last': 'Hayes',
651 | 'email': 'mattis@idante.com',
652 | },
653 | {
654 | 'name.first': 'Cullen',
655 | 'name.last': 'Tyler',
656 | 'email': 'in.tempus@maurisblandit.edu',
657 | },
658 | {
659 | 'name.first': 'Amity',
660 | 'name.last': 'Whitaker',
661 | 'email': 'aenean@risusdonecegestas.com',
662 | },
663 | {
664 | 'name.first': 'Celeste',
665 | 'name.last': 'Jones',
666 | 'email': 'duis.elementum@auctorquistristique.edu',
667 | },
668 | {
669 | 'name.first': 'Raphael',
670 | 'name.last': 'Santana',
671 | 'email': 'nibh@turpis.edu',
672 | },
673 | {
674 | 'name.first': 'Nerea',
675 | 'name.last': 'Peters',
676 | 'email': 'sapien.aenean@necanteblandit.edu',
677 | },
678 | {
679 | 'name.first': 'Martina',
680 | 'name.last': 'Griffith',
681 | 'email': 'orci.lobortis.augue@atsemmolestie.co.uk',
682 | },
683 | {
684 | 'name.first': 'Jaime',
685 | 'name.last': 'Bond',
686 | 'email': 'mi.eleifend@vivamusrhoncusdonec.com',
687 | },
688 | {
689 | 'name.first': 'Tamara',
690 | 'name.last': 'Andrews',
691 | 'email': 'nonummy.ut@orci.ca',
692 | },
693 | {
694 | 'name.first': 'Amal',
695 | 'name.last': 'Rivera',
696 | 'email': 'integer.id@necantemaecenas.ca',
697 | },
698 | {
699 | 'name.first': 'Laith',
700 | 'name.last': 'Lee',
701 | 'email': 'dapibus@rutrumfusce.co.uk',
702 | },
703 | {
704 | 'name.first': 'Wylie',
705 | 'name.last': 'Rosario',
706 | 'email': 'mi.lorem.vehicula@et.ca',
707 | },
708 | {
709 | 'name.first': 'Lamar',
710 | 'name.last': 'Silva',
711 | 'email': 'commodo.auctor@tinciduntaliquam.ca',
712 | },
713 | {
714 | 'name.first': 'Joel',
715 | 'name.last': 'Mercado',
716 | 'email': 'eu@ante.com',
717 | },
718 | {
719 | 'name.first': 'Justine',
720 | 'name.last': 'Patel',
721 | 'email': 'in.molestie.tortor@nequesed.net',
722 | },
723 | {
724 | 'name.first': 'Idola',
725 | 'name.last': 'Hopkins',
726 | 'email': 'eget@molestiepharetra.net',
727 | },
728 | {
729 | 'name.first': 'Alfreda',
730 | 'name.last': 'Rios',
731 | 'email': 'a.mi.fringilla@sodalespurusin.edu',
732 | },
733 | {
734 | 'name.first': 'Zena',
735 | 'name.last': 'Stout',
736 | 'email': 'et.malesuada@arcu.com',
737 | },
738 | {
739 | 'name.first': 'Alana',
740 | 'name.last': 'Conway',
741 | 'email': 'fermentum.vel.mauris@mauris.org',
742 | },
743 | {
744 | 'name.first': 'Brent',
745 | 'name.last': 'Ochoa',
746 | 'email': 'magnis@in.ca',
747 | },
748 | {
749 | 'name.first': 'Benjamin',
750 | 'name.last': 'Daugherty',
751 | 'email': 'urna@fusce.org',
752 | },
753 | {
754 | 'name.first': 'Nero',
755 | 'name.last': 'Wheeler',
756 | 'email': 'sed.tortor@consequatpurusmaecenas.com',
757 | },
758 | {
759 | 'name.first': 'Courtney',
760 | 'name.last': 'Conrad',
761 | 'email': 'convallis.erat.eget@vulputatevelit.net',
762 | },
763 | {
764 | 'name.first': 'Kendall',
765 | 'name.last': 'Hester',
766 | 'email': 'curabitur@aliquamnisl.com',
767 | },
768 | {
769 | 'name.first': 'Coby',
770 | 'name.last': 'Clarke',
771 | 'email': 'ultricies@egestas.edu',
772 | },
773 | {
774 | 'name.first': 'Ocean',
775 | 'name.last': 'Gibson',
776 | 'email': 'natoque.penatibus.et@lectusconvallis.ca',
777 | },
778 | {
779 | 'name.first': 'Alexis',
780 | 'name.last': 'Lara',
781 | 'email': 'magnis.dis.parturient@sociis.ca',
782 | },
783 | {
784 | 'name.first': 'Meghan',
785 | 'name.last': 'Guerra',
786 | 'email': 'est.mauris.rhoncus@venenatisvel.org',
787 | },
788 | {
789 | 'name.first': 'Cole',
790 | 'name.last': 'Nicholson',
791 | 'email': 'est.mauris.eu@nisicumsociis.com',
792 | },
793 | {
794 | 'name.first': 'Vivian',
795 | 'name.last': 'Aguilar',
796 | 'email': 'non@massasuspendisseeleifend.net',
797 | },
798 | {
799 | 'name.first': 'Ciara',
800 | 'name.last': 'Fitzgerald',
801 | 'email': 'faucibus@nunclaoreetlectus.net',
802 | },
803 | {
804 | 'name.first': 'Cain',
805 | 'name.last': 'Sutton',
806 | 'email': 'dis@magnatellus.org',
807 | },
808 | {
809 | 'name.first': 'Ainsley',
810 | 'name.last': 'Tran',
811 | 'email': 'feugiat.nec.diam@idante.org',
812 | },
813 | {
814 | 'name.first': 'Kenyon',
815 | 'name.last': 'Fischer',
816 | 'email': 'elit.pretium@antedictum.net',
817 | },
818 | {
819 | 'name.first': 'Jaden',
820 | 'name.last': 'Larsen',
821 | 'email': 'dolor.elit.pellentesque@antebibendumullamcorper.com',
822 | },
823 | {
824 | 'name.first': 'Keefe',
825 | 'name.last': 'Washington',
826 | 'email': 'convallis@nec.ca',
827 | },
828 | {
829 | 'name.first': 'Wilma',
830 | 'name.last': 'Bush',
831 | 'email': 'donec.at@etiam.com',
832 | },
833 | {
834 | 'name.first': 'Raven',
835 | 'name.last': 'Mcdowell',
836 | 'email': 'senectus@ullamcorper.ca',
837 | },
838 | {
839 | 'name.first': 'Jaime',
840 | 'name.last': 'Grimes',
841 | 'email': 'proin.eget@pedeetrisus.net',
842 | },
843 | {
844 | 'name.first': 'Callum',
845 | 'name.last': 'Williams',
846 | 'email': 'tellus.id.nunc@nuncquisarcu.com',
847 | },
848 | {
849 | 'name.first': 'Clayton',
850 | 'name.last': 'Short',
851 | 'email': 'a.sollicitudin.orci@lectusconvallis.org',
852 | },
853 | {
854 | 'name.first': 'Hop',
855 | 'name.last': 'Conner',
856 | 'email': 'odio.etiam@ipsumleoelementum.co.uk',
857 | },
858 | {
859 | 'name.first': 'Victor',
860 | 'name.last': 'Mcintosh',
861 | 'email': 'euismod@innec.edu',
862 | },
863 | {
864 | 'name.first': 'Diana',
865 | 'name.last': 'Lamb',
866 | 'email': 'consectetuer@odio.co.uk',
867 | },
868 | {
869 | 'name.first': 'Kylee',
870 | 'name.last': 'Peterson',
871 | 'email': 'facilisis.magna.tellus@dictumproineget.org',
872 | },
873 | {
874 | 'name.first': 'Farrah',
875 | 'name.last': 'Hartman',
876 | 'email': 'ad.litora@aultriciesadipiscing.co.uk',
877 | },
878 | {
879 | 'name.first': 'Cheyenne',
880 | 'name.last': 'Levine',
881 | 'email': 'purus@eunibh.co.uk',
882 | },
883 | {
884 | 'name.first': 'Odette',
885 | 'name.last': 'Erickson',
886 | 'email': 'blandit.at.nisi@laciniavitae.org',
887 | },
888 | {
889 | 'name.first': 'Uta',
890 | 'name.last': 'Rutledge',
891 | 'email': 'fermentum@ullamcorpermagna.net',
892 | },
893 | {
894 | 'name.first': 'Sydnee',
895 | 'name.last': 'Barlow',
896 | 'email': 'lorem.luctus.ut@ornare.ca',
897 | },
898 | {
899 | 'name.first': 'Garrison',
900 | 'name.last': 'Daniels',
901 | 'email': 'non@sed.co.uk',
902 | },
903 | {
904 | 'name.first': 'Phoebe',
905 | 'name.last': 'Hull',
906 | 'email': 'convallis.erat.eget@urnaconvallis.edu',
907 | },
908 | {
909 | 'name.first': 'Solomon',
910 | 'name.last': 'Marquez',
911 | 'email': 'auctor.mauris.vel@fusce.com',
912 | },
913 | {
914 | 'name.first': 'Hasad',
915 | 'name.last': 'Gutierrez',
916 | 'email': 'netus.et@sempernam.ca',
917 | },
918 | {
919 | 'name.first': 'Branden',
920 | 'name.last': 'Patel',
921 | 'email': 'nulla@semmollisdui.edu',
922 | },
923 | {
924 | 'name.first': 'Iliana',
925 | 'name.last': 'Nelson',
926 | 'email': 'placerat@elementumloremut.ca',
927 | },
928 | {
929 | 'name.first': 'Norman',
930 | 'name.last': 'Duffy',
931 | 'email': 'posuere@ut.edu',
932 | },
933 | {
934 | 'name.first': 'Marny',
935 | 'name.last': 'Watts',
936 | 'email': 'nulla.at.sem@mollis.edu',
937 | },
938 | {
939 | 'name.first': 'Sigourney',
940 | 'name.last': 'Webster',
941 | 'email': 'quis.turpis.vitae@disparturient.co.uk',
942 | },
943 | {
944 | 'name.first': 'Ciaran',
945 | 'name.last': 'Meadows',
946 | 'email': 'ligula.aliquam@dolorsitamet.ca',
947 | },
948 | {
949 | 'name.first': 'Kimberley',
950 | 'name.last': 'Travis',
951 | 'email': 'duis@maecenasmalesuada.co.uk',
952 | },
953 | {
954 | 'name.first': 'Ferdinand',
955 | 'name.last': 'Sloan',
956 | 'email': 'nunc@leovivamusnibh.ca',
957 | },
958 | {
959 | 'name.first': 'Skyler',
960 | 'name.last': 'Mann',
961 | 'email': 'sit@amet.edu',
962 | },
963 | {
964 | 'name.first': 'Galena',
965 | 'name.last': 'Cleveland',
966 | 'email': 'interdum.libero.dui@sitametlorem.ca',
967 | },
968 | {
969 | 'name.first': 'Nichole',
970 | 'name.last': 'Gaines',
971 | 'email': 'eget.tincidunt.dui@suscipitnonummy.org',
972 | },
973 | {
974 | 'name.first': 'Patrick',
975 | 'name.last': 'Mckee',
976 | 'email': 'massa@ultricies.net',
977 | },
978 | {
979 | 'name.first': 'Melissa',
980 | 'name.last': 'Roberson',
981 | 'email': 'litora@variusultricesmauris.com',
982 | },
983 | {
984 | 'name.first': 'Alana',
985 | 'name.last': 'Wilson',
986 | 'email': 'phasellus.elit.pede@scelerisquelorem.com',
987 | },
988 | {
989 | 'name.first': 'Blaine',
990 | 'name.last': 'Gallegos',
991 | 'email': 'eget.nisi.dictum@sapienaeneanmassa.com',
992 | },
993 | {
994 | 'name.first': 'Valentine',
995 | 'name.last': 'Pearson',
996 | 'email': 'facilisis.vitae@velarcueu.net',
997 | },
998 | {
999 | 'name.first': 'Francis',
1000 | 'name.last': 'Mooney',
1001 | 'email': 'dis@vestibulum.edu',
1002 | },
1003 | {
1004 | 'name.first': 'Janna',
1005 | 'name.last': 'Ray',
1006 | 'email': 'magnis@morbitristiquesenectus.ca',
1007 | },
1008 | {
1009 | 'name.first': 'Zeus',
1010 | 'name.last': 'Joseph',
1011 | 'email': 'ac.metus@turpisnon.net',
1012 | },
1013 | {
1014 | 'name.first': 'Janna',
1015 | 'name.last': 'Whitney',
1016 | 'email': 'ut.sem@ligulanullamfeugiat.org',
1017 | },
1018 | {
1019 | 'name.first': 'Beau',
1020 | 'name.last': 'Bruce',
1021 | 'email': 'felis.eget@nisinibh.edu',
1022 | },
1023 | {
1024 | 'name.first': 'Lucius',
1025 | 'name.last': 'Hays',
1026 | 'email': 'vivamus@cursusa.com',
1027 | },
1028 | {
1029 | 'name.first': 'Vernon',
1030 | 'name.last': 'Drews',
1031 | 'email': 'pharetra.sed.hendrerit@enimnectempus.com',
1032 | },
1033 | {
1034 | 'name.first': 'Graiden',
1035 | 'name.last': 'Powers',
1036 | 'email': 'praesent.interdum@aliquetvel.co.uk',
1037 | },
1038 | {
1039 | 'name.first': 'James',
1040 | 'name.last': 'Squire',
1041 | 'email': 'sember.optimus@killkenn.co.uk',
1042 | },
1043 | {
1044 | 'name.first': 'Zephr',
1045 | 'name.last': 'Odom',
1046 | 'email': 'mauris.integer.sem@mauriselitdictum.net',
1047 | },
1048 | {
1049 | 'name.first': 'Heidi',
1050 | 'name.last': 'Burch',
1051 | 'email': 'sagittis.lobortis@sagittisfelis.edu',
1052 | },
1053 | {
1054 | 'name.first': 'Quinn',
1055 | 'name.last': 'Bruce',
1056 | 'email': 'dolor.nonummy.ac@vel.ca',
1057 | },
1058 | {
1059 | 'name.first': 'Veda',
1060 | 'name.last': 'Swanson',
1061 | 'email': 'a.magna@tinciduntvehicula.co.uk',
1062 | },
1063 | {
1064 | 'name.first': 'Deborah',
1065 | 'name.last': 'Ortiz',
1066 | 'email': 'urna.justo@ultriciesornareelit.net',
1067 | },
1068 | {
1069 | 'name.first': 'Laurel',
1070 | 'name.last': 'Bishop',
1071 | 'email': 'laoreet@risusduisa.net',
1072 | },
1073 | {
1074 | 'name.first': 'Rylee',
1075 | 'name.last': 'Gibson',
1076 | 'email': 'ut@acnulla.net',
1077 | },
1078 | {
1079 | 'name.first': 'Shelly',
1080 | 'name.last': 'Turner',
1081 | 'email': 'sociosqu.ad@sedpede.ca',
1082 | },
1083 | {
1084 | 'name.first': 'Mannix',
1085 | 'name.last': 'Brennan',
1086 | 'email': 'sed@donectempus.net',
1087 | },
1088 | {
1089 | 'name.first': 'Allistair',
1090 | 'name.last': 'Melendez',
1091 | 'email': 'suspendisse@mauriselit.co.uk',
1092 | },
1093 | {
1094 | 'name.first': 'Heather',
1095 | 'name.last': 'Payne',
1096 | 'email': 'phasellus.at@luctussit.net',
1097 | },
1098 | {
1099 | 'name.first': 'Benedict',
1100 | 'name.last': 'Hughes',
1101 | 'email': 'quis.massa@neceuismodin.com',
1102 | },
1103 | {
1104 | 'name.first': 'Cadman',
1105 | 'name.last': 'Yates',
1106 | 'email': 'ridiculus@auctor.net',
1107 | },
1108 | {
1109 | 'name.first': 'Cameron',
1110 | 'name.last': 'Oliver',
1111 | 'email': 'vehicula@egetvolutpatornare.com',
1112 | },
1113 | {
1114 | 'name.first': 'Ayanna',
1115 | 'name.last': 'Page',
1116 | 'email': 'dolor@nisl.net',
1117 | },
1118 | {
1119 | 'name.first': 'Cassady',
1120 | 'name.last': 'Wells',
1121 | 'email': 'erat.in@velvenenatisvel.edu',
1122 | },
1123 | {
1124 | 'name.first': 'Emily',
1125 | 'name.last': 'Valenzuela',
1126 | 'email': 'egestas.nunc@et.com',
1127 | },
1128 | {
1129 | 'name.first': 'Ayanna',
1130 | 'name.last': 'Rodgers',
1131 | 'email': 'non@ligulaeuenim.edu',
1132 | },
1133 | {
1134 | 'name.first': 'Burton',
1135 | 'name.last': 'Douglas',
1136 | 'email': 'urna.vivamus@lacusquisque.com',
1137 | },
1138 | {
1139 | 'name.first': 'Bernard',
1140 | 'name.last': 'Elliott',
1141 | 'email': 'lorem.lorem@erat.edu',
1142 | },
1143 | {
1144 | 'name.first': 'Alea',
1145 | 'name.last': 'Austin',
1146 | 'email': 'dis@dis.net',
1147 | },
1148 | {
1149 | 'name.first': 'Reese',
1150 | 'name.last': 'Small',
1151 | 'email': 'rutrum.justo@massasuspendisseeleifend.com',
1152 | },
1153 | {
1154 | 'name.first': 'Odysseus',
1155 | 'name.last': 'Rhodes',
1156 | 'email': 'dignissim.tempor@dolornulla.co.uk',
1157 | },
1158 | {
1159 | 'name.first': 'Haley',
1160 | 'name.last': 'Serrano',
1161 | 'email': 'faucibus.morbi.vehicula@parturientmontes.com',
1162 | },
1163 | {
1164 | 'name.first': 'Olga',
1165 | 'name.last': 'Mcgee',
1166 | 'email': 'vitae.nibh.donec@proinnislsem.com',
1167 | },
1168 | {
1169 | 'name.first': 'Chadwick',
1170 | 'name.last': 'Poole',
1171 | 'email': 'ligula@nonarcuvivamus.ca',
1172 | },
1173 | {
1174 | 'name.first': 'Hammett',
1175 | 'name.last': 'Gutierrez',
1176 | 'email': 'augue@sed.edu',
1177 | },
1178 | {
1179 | 'name.first': 'Dai',
1180 | 'name.last': 'Saunders',
1181 | 'email': 'ullamcorper.duis@nunc.co.uk',
1182 | },
1183 | {
1184 | 'name.first': 'Hammett',
1185 | 'name.last': 'Mays',
1186 | 'email': 'venenatis.lacus.etiam@loremvitae.edu',
1187 | },
1188 | {
1189 | 'name.first': 'Jackson',
1190 | 'name.last': 'Mcfadden',
1191 | 'email': 'ad.litora@cursusin.edu',
1192 | },
1193 | {
1194 | 'name.first': 'Chase',
1195 | 'name.last': 'Wiggins',
1196 | 'email': 'lacus@luctus.com',
1197 | },
1198 | {
1199 | 'name.first': 'Travis',
1200 | 'name.last': 'White',
1201 | 'email': 'proin.nisl.sem@vehicula.com',
1202 | },
1203 | {
1204 | 'name.first': 'Igor',
1205 | 'name.last': 'Holmes',
1206 | 'email': 'vel.nisl.quisque@maurisvestibulumneque.com',
1207 | },
1208 | {
1209 | 'name.first': 'Brittany',
1210 | 'name.last': 'Hickman',
1211 | 'email': 'vitae.nibh.donec@egetipsumdonec.ca',
1212 | },
1213 | {
1214 | 'name.first': 'Ray',
1215 | 'name.last': 'Richards',
1216 | 'email': 'ac@laoreetposuereenim.co.uk',
1217 | },
1218 | {
1219 | 'name.first': 'Jaden',
1220 | 'name.last': 'Whitaker',
1221 | 'email': 'tristique@lacus.net',
1222 | },
1223 | {
1224 | 'name.first': 'Logan',
1225 | 'name.last': 'Burt',
1226 | 'email': 'leo.elementum@in.org',
1227 | },
1228 | {
1229 | 'name.first': 'Myles',
1230 | 'name.last': 'Waller',
1231 | 'email': 'urna@eudoloregestas.edu',
1232 | },
1233 | {
1234 | 'name.first': 'Lance',
1235 | 'name.last': 'Benjamin',
1236 | 'email': 'nascetur.ridiculus@eleifendvitae.net',
1237 | },
1238 | {
1239 | 'name.first': 'Alyssa',
1240 | 'name.last': 'Baldwin',
1241 | 'email': 'sed@sapien.co.uk',
1242 | },
1243 | {
1244 | 'name.first': 'Hoyt',
1245 | 'name.last': 'Holder',
1246 | 'email': 'nec@pulvinararcu.net',
1247 | },
1248 | {
1249 | 'name.first': 'Gary',
1250 | 'name.last': 'Dalton',
1251 | 'email': 'cursus.non@sagittis.co.uk',
1252 | },
1253 | {
1254 | 'name.first': 'Kay',
1255 | 'name.last': 'Kerr',
1256 | 'email': 'et@fringillaeuismodenim.co.uk',
1257 | },
1258 | {
1259 | 'name.first': 'Frances',
1260 | 'name.last': 'Velez',
1261 | 'email': 'neque@quisqueimperdieterat.org',
1262 | },
1263 | {
1264 | 'name.first': 'Cally',
1265 | 'name.last': 'Bishop',
1266 | 'email': 'bibendum.sed.est@dolorvitaedolor.org',
1267 | },
1268 | {
1269 | 'name.first': 'Britanney',
1270 | 'name.last': 'Lawson',
1271 | 'email': 'nec.cursus@etiam.net',
1272 | },
1273 | {
1274 | 'name.first': 'Aladdin',
1275 | 'name.last': 'Harrell',
1276 | 'email': 'quis.arcu.vel@etiamgravidamolestie.edu',
1277 | },
1278 | {
1279 | 'name.first': 'Dora',
1280 | 'name.last': 'Shepherd',
1281 | 'email': 'risus.in.mi@etarcu.co.uk',
1282 | },
1283 | {
1284 | 'name.first': 'Tana',
1285 | 'name.last': 'Morrow',
1286 | 'email': 'at.lacus@nuncsedorci.org',
1287 | },
1288 | {
1289 | 'name.first': 'Ainsley',
1290 | 'name.last': 'Valdez',
1291 | 'email': 'volutpat.nulla.facilisis@luctuslobortisclass.ca',
1292 | },
1293 | {
1294 | 'name.first': 'Zachary',
1295 | 'name.last': 'Bruce',
1296 | 'email': 'feugiat.nec.diam@mus.co.uk',
1297 | },
1298 | {
1299 | 'name.first': 'Geoffrey',
1300 | 'name.last': 'Ward',
1301 | 'email': 'metus.in@amet.net',
1302 | },
1303 | ],
1304 | };
1305 |
--------------------------------------------------------------------------------
/updates/1.2.0-content.js:
--------------------------------------------------------------------------------
1 | exports.create = {
2 | PostCategory: [
3 | { name: 'News' },
4 | { name: 'Ideas' },
5 | { name: 'Node.js' },
6 | { name: 'React.js' },
7 | { name: 'Other' },
8 | ],
9 | };
10 |
--------------------------------------------------------------------------------
/updates/1.3.0-categories.js:
--------------------------------------------------------------------------------
1 | var randomWord = require('random-word');
2 |
3 | var categories = [];
4 |
5 | for (var i = 0; i <= 1100; i++) {
6 | categories.push({ name: randomWord() });
7 | }
8 |
9 | exports.create = {
10 | PostCategory: categories,
11 | };
12 |
--------------------------------------------------------------------------------