Skip to content

Commit

Permalink
Merge branch 'master' into calendar-day-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
MadeByMike committed Jul 9, 2020
2 parents df99515 + 391b7d0 commit c1ac823
Show file tree
Hide file tree
Showing 15 changed files with 462 additions and 304 deletions.
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,15 @@
"contributions": [
"code"
]
},
{
"login": "matheuschimelli",
"name": "Matheus Chimelli",
"avatar_url": "https://avatars0.githubusercontent.com/u/10470074?v=4",
"profile": "https://github.com/matheuschimelli",
"contributions": [
"doc"
]
}
],
"contributorsPerLine": 7,
Expand Down
5 changes: 5 additions & 0 deletions .changeset/big-llamas-sparkle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystonejs/keystone': patch
---

Refactored hook management into a separate module.
5 changes: 5 additions & 0 deletions .changeset/famous-pumpkins-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystonejs/keystone': patch
---

Fixed a bug where `existingItem` had the wrong value in hooks during an `updateManyMutation`.
11 changes: 11 additions & 0 deletions .changeset/light-llamas-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@keystonejs/demo-project-meetup': patch
---

Updated usage of Apollo based on Next.js [example](https://github.com/vercel/next.js/blob/canary/examples/with-apollo).

The **key changes** are:
- Less boilerplate code for setting/initializing Apollo Client
- Update home, events and about page to opt in for SSG
- Removed MyApp.getIntialProps as it will stop the [Auto Static optimization](https://nextjs.org/docs/api-reference/data-fetching/getInitialProps#caveats).
- Handle the authentication in client-side only.
5 changes: 5 additions & 0 deletions .changeset/lucky-jokes-warn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystonejs/keystone': patch
---

Factored out utility `List` functions into a separate module.
5 changes: 5 additions & 0 deletions .changeset/pretty-radios-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystonejs/keystone': patch
---

Updated internal access control functions to directly accept access control definition.
6 changes: 1 addition & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,8 @@ We also build commonjs builds to run in node (for testing with jest or etc.) and
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->

<!-- prettier-ignore-start -->

<!-- markdownlint-disable -->

<table>
<tr>
<td align="center"><a href="http://www.thinkmill.com.au"><img src="https://avatars3.githubusercontent.com/u/872310?v=4" width="80px;" alt=""/><br /><sub><b>Jed Watson</b></sub></a><br /><a href="https://github.com/keystonejs/keystone/commits?author=JedWatson" title="Code">💻</a></td>
Expand Down Expand Up @@ -340,13 +337,12 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="http://hackweb.altervista.org"><img src="https://avatars0.githubusercontent.com/u/754139?v=4" width="80px;" alt=""/><br /><sub><b>frank10gm</b></sub></a><br /><a href="https://github.com/keystonejs/keystone/commits?author=frank10gm" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mbrodt"><img src="https://avatars2.githubusercontent.com/u/21239560?v=4" width="80px;" alt=""/><br /><sub><b>mbrodt</b></sub></a><br /><a href="https://github.com/keystonejs/keystone/commits?author=mbrodt" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/zamkevich"><img src="https://avatars0.githubusercontent.com/u/13717428?v=4" width="80px;" alt=""/><br /><sub><b>Misha Zamkevich</b></sub></a><br /><a href="https://github.com/keystonejs/keystone/commits?author=zamkevich" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/matheuschimelli"><img src="https://avatars0.githubusercontent.com/u/10470074?v=4" width="80px;" alt=""/><br /><sub><b>Matheus Chimelli</b></sub></a><br /><a href="https://github.com/keystonejs/keystone/commits?author=matheuschimelli" title="Documentation">📖</a></td>
</tr>
</table>

<!-- markdownlint-enable -->

<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
Expand Down
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,8 @@ We'd like to start by thanking all our wonderful contributors:
([emoji key](https://allcontributors.org/docs/en/emoji-key)):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->

<!-- prettier-ignore-start -->

<!-- markdownlint-disable -->

<table>
<tr>
<td align="center"><a href="http://www.thinkmill.com.au"><img src="https://avatars3.githubusercontent.com/u/872310?v=4" width="80px;" alt=""/><br /><sub><b>Jed Watson</b></sub></a><br /><a href="https://github.com/keystonejs/keystone/commits?author=JedWatson" title="Code">💻</a></td>
Expand Down Expand Up @@ -142,13 +139,12 @@ We'd like to start by thanking all our wonderful contributors:
<td align="center"><a href="http://hackweb.altervista.org"><img src="https://avatars0.githubusercontent.com/u/754139?v=4" width="80px;" alt=""/><br /><sub><b>frank10gm</b></sub></a><br /><a href="https://github.com/keystonejs/keystone/commits?author=frank10gm" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mbrodt"><img src="https://avatars2.githubusercontent.com/u/21239560?v=4" width="80px;" alt=""/><br /><sub><b>mbrodt</b></sub></a><br /><a href="https://github.com/keystonejs/keystone/commits?author=mbrodt" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/zamkevich"><img src="https://avatars0.githubusercontent.com/u/13717428?v=4" width="80px;" alt=""/><br /><sub><b>Misha Zamkevich</b></sub></a><br /><a href="https://github.com/keystonejs/keystone/commits?author=zamkevich" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/matheuschimelli"><img src="https://avatars0.githubusercontent.com/u/10470074?v=4" width="80px;" alt=""/><br /><sub><b>Matheus Chimelli</b></sub></a><br /><a href="https://github.com/keystonejs/keystone/commits?author=matheuschimelli" title="Documentation">📖</a></td>
</tr>
</table>

<!-- markdownlint-enable -->

<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

### Demo Projects
Expand Down
16 changes: 16 additions & 0 deletions packages/auth-passport/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,23 @@ keystone
process.exit(1);
});
```
Sometimes you just need to get the profile data provided by the auth provider. In that case, you
have to call the `resolveCreateData` with `serviceProfile` property. See an example below:

```javascript
// Here we wait for our serviceProfile to get the data provided by the auth provider
resolveCreateData: ({ createData, serviceProfile }) => {
// Once we had our seviceProfile, we set it on our list fields

// Google will return the profile data inside the _json key
// Each provider can return the user profile data in a different way.
// Check how it's returned on your provider documentation
createData.name = serviceProfile._json.name
createData.profilePicture = serviceProfile._json.picture

return createData;
}
```
## Using other Passport strategies

You can create your own strategies to work with Keystone by extending the
Expand Down
17 changes: 12 additions & 5 deletions packages/keystone/lib/Keystone/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,15 @@ module.exports = class Keystone {
);

const getListAccessControlForUser = memoize(
async (listKey, originalInput, operation, { gqlName, itemId, itemIds, context } = {}) => {
async (
access,
listKey,
originalInput,
operation,
{ gqlName, itemId, itemIds, context } = {}
) => {
return validateListAccessControl({
access: this.lists[listKey].access[schemaName],
access: access[schemaName],
originalInput,
operation,
authentication,
Expand All @@ -146,6 +152,7 @@ module.exports = class Keystone {

const getFieldAccessControlForUser = memoize(
async (
access,
listKey,
fieldKey,
originalInput,
Expand All @@ -154,7 +161,7 @@ module.exports = class Keystone {
{ gqlName, itemId, itemIds, context } = {}
) => {
return validateFieldAccessControl({
access: this.lists[listKey].fieldsByPath[fieldKey].access[schemaName],
access: access[schemaName],
originalInput,
existingItem,
operation,
Expand All @@ -171,9 +178,9 @@ module.exports = class Keystone {
);

const getAuthAccessControlForUser = memoize(
async (listKey, { gqlName, context } = {}) => {
async (access, listKey, { gqlName, context } = {}) => {
return validateAuthAccessControl({
access: this.lists[listKey].access[schemaName],
access: access[schemaName],
authentication,
listKey,
gqlName,
Expand Down
166 changes: 166 additions & 0 deletions packages/keystone/lib/ListTypes/hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
const { omitBy, arrayToObject } = require('@keystonejs/utils');
const { mapToFields } = require('./utils');
const { ValidationFailureError } = require('./graphqlErrors');

class HookManager {
constructor({ fields, hooks, listKey }) {
this.fields = fields;
this.hooks = hooks;
this.listKey = listKey;
this.fieldsByPath = arrayToObject(this.fields, 'path');
}

_fieldsFromObject(obj) {
return Object.keys(obj)
.map(fieldPath => this.fieldsByPath[fieldPath])
.filter(field => field);
}

_throwValidationFailure({ errors, operation, originalInput }) {
throw new ValidationFailureError({
data: {
messages: errors.map(e => e.msg),
errors: errors.map(e => e.data),
listKey: this.listKey,
operation,
},
internalData: { errors: errors.map(e => e.internalData), data: originalInput },
});
}

async resolveInput({ resolvedData, existingItem, context, operation, originalInput }) {
const args = { resolvedData, existingItem, context, originalInput, operation };

// First we run the field type hooks
// NOTE: resolveInput is run on _every_ field, regardless if it has a value
// passed in or not
resolvedData = await mapToFields(this.fields, field => field.resolveInput(args));

// We then filter out the `undefined` results (they should return `null` or
// a value)
resolvedData = omitBy(resolvedData, key => typeof resolvedData[key] === 'undefined');

// Run the schema-level field hooks, passing in the results from the field
// type hooks
resolvedData = {
...resolvedData,
...(await mapToFields(
this.fields.filter(field => field.hooks.resolveInput),
field => field.hooks.resolveInput({ ...args, resolvedData })
)),
};

// And filter out the `undefined`s again.
resolvedData = omitBy(resolvedData, key => typeof resolvedData[key] === 'undefined');

if (this.hooks.resolveInput) {
// And run any list-level hook
resolvedData = await this.hooks.resolveInput({ ...args, resolvedData });
if (typeof resolvedData !== 'object') {
throw new Error(
`Expected ${
this.listKey
}.hooks.resolveInput() to return an object, but got a ${typeof resolvedData}: ${resolvedData}`
);
}
}

// Finally returning the amalgamated result of all the hooks.
return resolvedData;
}

async validateInput({ resolvedData, existingItem, context, operation, originalInput }) {
const args = { resolvedData, existingItem, context, originalInput, operation };
// Check for isRequired
const fieldValidationErrors = this.fields
.filter(
field =>
field.isRequired &&
!field.isRelationship &&
((operation === 'create' &&
(resolvedData[field.path] === undefined || resolvedData[field.path] === null)) ||
(operation === 'update' &&
Object.prototype.hasOwnProperty.call(resolvedData, field.path) &&
(resolvedData[field.path] === undefined || resolvedData[field.path] === null)))
)
.map(f => ({
msg: `Required field "${f.path}" is null or undefined.`,
data: { resolvedData, operation, originalInput },
internalData: {},
}));
if (fieldValidationErrors.length) {
this._throwValidationFailure({ errors: fieldValidationErrors, operation, originalInput });
}

const fields = this._fieldsFromObject(resolvedData);
await this._validateHook({ args, fields, operation, hookName: 'validateInput' });
}

async validateDelete({ existingItem, context, operation }) {
const args = { existingItem, context, operation };
const fields = this.fields;
await this._validateHook({ args, fields, operation, hookName: 'validateDelete' });
}

async _validateHook({ args, fields, operation, hookName }) {
const { originalInput } = args;
const fieldValidationErrors = [];
// FIXME: Can we do this in a way where we simply return validation errors instead?
args.addFieldValidationError = (msg, _data = {}, internalData = {}) =>
fieldValidationErrors.push({ msg, data: _data, internalData });
await mapToFields(fields, field => field[hookName](args));
await mapToFields(
fields.filter(field => field.hooks[hookName]),
field => field.hooks[hookName](args)
);
if (fieldValidationErrors.length) {
this._throwValidationFailure({ errors: fieldValidationErrors, operation, originalInput });
}

if (this.hooks[hookName]) {
const listValidationErrors = [];
await this.hooks[hookName]({
...args,
addValidationError: (msg, _data = {}, internalData = {}) =>
listValidationErrors.push({ msg, data: _data, internalData }),
});
if (listValidationErrors.length) {
this._throwValidationFailure({ errors: listValidationErrors, operation, originalInput });
}
}
}

async beforeChange({ resolvedData, existingItem, context, operation, originalInput }) {
const args = { resolvedData, existingItem, context, originalInput, operation };
await this._runHook({ args, fieldObject: resolvedData, hookName: 'beforeChange' });
}

async beforeDelete({ existingItem, context, operation }) {
const args = { existingItem, context, operation };
await this._runHook({ args, fieldObject: existingItem, hookName: 'beforeDelete' });
}

async afterChange({ updatedItem, existingItem, context, operation, originalInput }) {
const args = { updatedItem, originalInput, existingItem, context, operation };
await this._runHook({ args, fieldObject: updatedItem, hookName: 'afterChange' });
}

async afterDelete({ existingItem, context, operation }) {
const args = { existingItem, context, operation };
await this._runHook({ args, fieldObject: existingItem, hookName: 'afterDelete' });
}

async _runHook({ args, fieldObject, hookName }) {
// Used to apply hooks that only produce side effects
const fields = this._fieldsFromObject(fieldObject);
await mapToFields(fields, field => field[hookName](args));
await mapToFields(
fields.filter(field => field.hooks[hookName]),
field => field.hooks[hookName](args)
);

if (this.hooks[hookName]) await this.hooks[hookName](args);
}
}

module.exports = { HookManager };
Loading

0 comments on commit c1ac823

Please sign in to comment.