├── .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 | KeystoneJS 17 |
18 |

Welcome,

19 |

An admin user has been created for you:

20 |
21 | 22 | user@keystonejs.com 23 | 24 | 25 | admin 26 | 27 |
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 |
51 | 52 | 53 | 54 |
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 |
65 | 66 |
67 | 68 | 69 | 70 |
71 |
72 | 73 | 74 | 75 |
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 |
62 | 63 | this.handleFile('one', data)} /> 64 | 65 | 66 | this.handleFile('two', data)} /> 67 | 68 |
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 | --------------------------------------------------------------------------------