Skip to content

Commit

Permalink
Factor out appVersion support into a separate package (keystonejs#2535)
Browse files Browse the repository at this point in the history
  • Loading branch information
timleslie authored Mar 17, 2020
1 parent 83bdf74 commit d30b749
Show file tree
Hide file tree
Showing 11 changed files with 156 additions and 24 deletions.
6 changes: 6 additions & 0 deletions .changeset/grumpy-jars-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@keystonejs/app-version': major
'@keystonejs/keystone': patch
---

The new package `@keystonejs/app-version` consolidates the express middleware and graphQL provider support for returning the `appVersion`.
2 changes: 2 additions & 0 deletions packages/app-version/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/*.md
**/*.test.js
1 change: 1 addition & 0 deletions packages/app-version/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @keystonejs/app-version
34 changes: 34 additions & 0 deletions packages/app-version/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!--[meta]
section: api
subSection: utilities
title: App Version Plugin
[meta]-->

# App Version Plugin

This package provides support for including a version string both as an HTTP response header and as a graphQL query.

The function `appVersionMiddleware(version)` will return a piece of middleware which will set the `X-Keystone-App-Version` response header to `version` on all HTTP requests.

The graphQL provider `AppVersionProvider` will add an `{ appVersion }` query to your graphQL API which returns `version` as a string.

## Usage

This package is designed to be used indirectly via the conveniance API on the `Keystone` class:

```javascript
const keystone = new Keystone({ ..., appVersion: { version: '1.0.0', addVersionToHttpHeaders: true, access: true } });
```

It can also be used directly if you would like to manually manage your middleware stack of graphQL providers.

```javascript
const { AppVersionProvider, appVersionMiddleware } = require('@keystonejs/app-version');

...

const version = '1.0.0';
app.use(appVersionMiddleware(version));

keystone._providers.push(new AppVersionProvider({ version, access: true, schemaNames: ['public'] }));
```
3 changes: 3 additions & 0 deletions packages/app-version/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { AppVersionProvider, appVersionMiddleware } = require('./lib/app-version');

module.exports = { AppVersionProvider, appVersionMiddleware };
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
const { parseCustomAccess } = require('@keystonejs/access-control');

class VersionProvider {
constructor({ appVersion, schemaNames }) {
appVersion.access = parseCustomAccess({
access: appVersion.access,
class AppVersionProvider {
constructor({ version, access, schemaNames }) {
this._access = parseCustomAccess({
access: access,
schemaNames,
defaultAccess: true,
});
this._appVersion = appVersion;
this._version = version;
}

getTypes({}) {
return [];
}
getQueries({ schemaName }) {
return this._appVersion.access[schemaName]
return this._access[schemaName]
? [
`"""The version of the Keystone application serving this API."""
appVersion: String`,
Expand All @@ -29,13 +29,16 @@ class VersionProvider {
return {};
}
getQueryResolvers({ schemaName }) {
return this._appVersion.access[schemaName]
? { appVersion: () => this._appVersion.version }
: {};
return this._access[schemaName] ? { appVersion: () => this._version } : {};
}
getMutationResolvers({}) {
return {};
}
}

module.exports = { VersionProvider };
const appVersionMiddleware = version => (req, res, next) => {
res.set('X-Keystone-App-Version', version);
next();
};

module.exports = { AppVersionProvider, appVersionMiddleware };
14 changes: 14 additions & 0 deletions packages/app-version/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "@keystonejs/app-version",
"description": "KeystoneJS App Version Plugin",
"version": "0.0.0",
"author": "The KeystoneJS Development Team",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"dependencies": {
"@keystonejs/access-control": "^5.2.0"
},
"repository": "https://github.com/keystonejs/keystone/tree/master/packages/app-version"
}
73 changes: 73 additions & 0 deletions packages/app-version/tests/app-version.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const { AppVersionProvider, appVersionMiddleware } = require('../lib/app-version');

describe('AppVersionProvider', () => {
test('AppVersionProvider - simple access', async () => {
const appVersion = new AppVersionProvider({
version: '1.0.0',
access: true,
schemaNames: ['public'],
});

let schemaName = 'public';
expect(appVersion.getTypes({ schemaName })).toEqual([]);
expect(appVersion.getQueries({ schemaName })).toEqual([
`"""The version of the Keystone application serving this API."""
appVersion: String`,
]);
expect(appVersion.getMutations({ schemaName })).toEqual([]);
expect(appVersion.getTypeResolvers({ schemaName })).toEqual({});
expect(appVersion.getQueryResolvers({ schemaName })).toHaveProperty('appVersion');
expect(appVersion.getQueryResolvers({ schemaName }).appVersion()).toEqual('1.0.0');
expect(appVersion.getMutationResolvers({ schemaName })).toEqual({});

schemaName = 'other';
expect(appVersion.getTypes({ schemaName })).toEqual([]);
expect(appVersion.getQueries({ schemaName })).toEqual([]);
expect(appVersion.getMutations({ schemaName })).toEqual([]);
expect(appVersion.getTypeResolvers({ schemaName })).toEqual({});
expect(appVersion.getQueryResolvers({ schemaName })).toEqual({});
expect(appVersion.getMutationResolvers({ schemaName })).toEqual({});
});

test('AppVersionProvider - complex access', async () => {
const appVersion = new AppVersionProvider({
version: '1.0.0',
access: { public: true, other: false },
schemaNames: ['public', 'other'],
});

let schemaName = 'public';
expect(appVersion.getTypes({ schemaName })).toEqual([]);
expect(appVersion.getQueries({ schemaName })).toEqual([
`"""The version of the Keystone application serving this API."""
appVersion: String`,
]);
expect(appVersion.getMutations({ schemaName })).toEqual([]);
expect(appVersion.getTypeResolvers({ schemaName })).toEqual({});
expect(appVersion.getQueryResolvers({ schemaName })).toHaveProperty('appVersion');
expect(appVersion.getQueryResolvers({ schemaName }).appVersion()).toEqual('1.0.0');
expect(appVersion.getMutationResolvers({ schemaName })).toEqual({});

schemaName = 'other';
expect(appVersion.getTypes({ schemaName })).toEqual([]);
expect(appVersion.getQueries({ schemaName })).toEqual([]);
expect(appVersion.getMutations({ schemaName })).toEqual([]);
expect(appVersion.getTypeResolvers({ schemaName })).toEqual({});
expect(appVersion.getQueryResolvers({ schemaName })).toEqual({});
expect(appVersion.getMutationResolvers({ schemaName })).toEqual({});
});
});

describe('appVersionMiddleware', () => {
test('appVersionMiddleware', async () => {
const middleware = appVersionMiddleware('1.0.0');

const req = {};
const res = { set: jest.fn() };
const next = jest.fn();

middleware(req, res, next);
expect(res.set).toHaveBeenCalledWith('X-Keystone-App-Version', '1.0.0');
expect(next).toHaveBeenCalled();
});
});
20 changes: 8 additions & 12 deletions packages/keystone/lib/Keystone/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const {
endAuthedSession,
commonSessionMiddleware,
} = require('@keystonejs/session');
const { AppVersionProvider, appVersionMiddleware } = require('@keystonejs/app-version');

const {
unmergeRelationships,
Expand All @@ -35,12 +36,7 @@ const {
} = require('./relationship-utils');
const List = require('../List');
const { DEFAULT_DIST_DIR } = require('../../constants');
const {
CustomProvider,
ListAuthProvider,
ListCRUDProvider,
VersionProvider,
} = require('../providers');
const { CustomProvider, ListAuthProvider, ListCRUDProvider } = require('../providers');

module.exports = class Keystone {
constructor({
Expand Down Expand Up @@ -87,7 +83,11 @@ module.exports = class Keystone {
this._providers = [
this._listCRUDProvider,
this._customProvider,
new VersionProvider({ appVersion, schemaNames }),
new AppVersionProvider({
version: appVersion.version,
access: appVersion.access,
schemaNames,
}),
];

if (adapters) {
Expand Down Expand Up @@ -511,11 +511,7 @@ module.exports = class Keystone {

async _prepareMiddlewares({ dev, apps, distDir, pinoOptions, cors }) {
return flattenDeep([
this.appVersion.addVersionToHttpHeaders &&
((req, res, next) => {
res.set('X-Keystone-App-Version', this.appVersion.version);
next();
}),
this.appVersion.addVersionToHttpHeaders && appVersionMiddleware(this.appVersion.version),
// Used by other middlewares such as authentication strategies. Important
// to be first so the methods added to `req` are available further down
// the request pipeline.
Expand Down
3 changes: 1 addition & 2 deletions packages/keystone/lib/providers/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const { CustomProvider } = require('./custom');
const { ListAuthProvider } = require('./listAuth');
const { ListCRUDProvider } = require('./listCRUD');
const { VersionProvider } = require('./version');

// The GraphQL Provider Framework expects to see classes with the following API:
//
Expand Down Expand Up @@ -29,4 +28,4 @@ const { VersionProvider } = require('./version');
// }
// }

module.exports = { CustomProvider, ListAuthProvider, ListCRUDProvider, VersionProvider };
module.exports = { CustomProvider, ListAuthProvider, ListCRUDProvider };
1 change: 1 addition & 0 deletions packages/keystone/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
},
"dependencies": {
"@keystonejs/access-control": "^5.2.0",
"@keystonejs/app-version": "^0.0.0",
"@keystonejs/fields": "^7.0.0",
"@keystonejs/logger": "^5.1.1",
"@keystonejs/session": "^5.1.1",
Expand Down

0 comments on commit d30b749

Please sign in to comment.