From 7fc0d45b893782f917237a6ceb30c8c966538c4f Mon Sep 17 00:00:00 2001 From: Olivier Allouch Date: Mon, 3 Jun 2019 23:58:21 +0200 Subject: [PATCH] Database version in features (#5627) * adding database.version in the serverInfo (only MongoDB, it gives undefined when using Postgres) * . correction of old 'features' tests . adding engine and database in the StorageAdapter interface and implementations * . version retrieval done in performInitialization . PostgreSQL version * performInitialization now returns a Promise --- spec/features.spec.js | 37 +++++++++++++++---- .../Storage/Mongo/MongoStorageAdapter.js | 13 ++++++- .../Postgres/PostgresStorageAdapter.js | 9 +++++ src/Adapters/Storage/StorageAdapter.js | 2 + src/Routers/FeaturesRouter.js | 12 ++++-- 5 files changed, 61 insertions(+), 12 deletions(-) diff --git a/spec/features.spec.js b/spec/features.spec.js index 100f6d20..ec23afe2 100644 --- a/spec/features.spec.js +++ b/spec/features.spec.js @@ -3,20 +3,41 @@ const request = require('../lib/request'); describe('features', () => { - it('requires the master key to get features', done => { - request({ + it('should return the serverInfo', async () => { + const response = await request({ url: 'http://localhost:8378/1/serverInfo', json: true, headers: { 'X-Parse-Application-Id': 'test', 'X-Parse-REST-API-Key': 'rest', + 'X-Parse-Master-Key': 'test', }, - }).then(fail, response => { - expect(response.status).toEqual(403); - expect(response.data.error).toEqual( - 'unauthorized: master key is required' - ); - done(); }); + const data = response.data; + expect(data).toBeDefined(); + expect(data.features).toBeDefined(); + expect(data.parseServerVersion).toBeDefined(); + expect(data.database).toBeDefined(); + expect(['MongoDB', 'PostgreSQL']).toContain(data.database.engine); + }); + + it('requires the master key to get features', async done => { + try { + await request({ + url: 'http://localhost:8378/1/serverInfo', + json: true, + headers: { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', + }, + }); + done.fail( + 'The serverInfo request should be rejected without the master key' + ); + } catch (error) { + expect(error.status).toEqual(403); + expect(error.data.error).toEqual('unauthorized: master key is required'); + done(); + } }); }); diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index a289ccee..d8ecc3c6 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -126,6 +126,8 @@ export class MongoStorageAdapter implements StorageAdapter { client: MongoClient; _maxTimeMS: ?number; canSortOnJoinTables: boolean; + databaseVersion: string; + engine: string; constructor({ uri = defaults.DefaultMongoURI, @@ -136,6 +138,7 @@ export class MongoStorageAdapter implements StorageAdapter { this._collectionPrefix = collectionPrefix; this._mongoOptions = mongoOptions; this._mongoOptions.useNewUrlParser = true; + this.engine = 'MongoDB'; // MaxTimeMS is not a global MongoDB client option, it is applied per operation. this._maxTimeMS = mongoOptions.maxTimeMS; @@ -959,7 +962,15 @@ export class MongoStorageAdapter implements StorageAdapter { } performInitialization(): Promise { - return Promise.resolve(); + // databaseVersion + return this.connect() + .then(() => { + const adminDb = this.database.admin(); + return adminDb.serverStatus(); + }) + .then(status => { + this.databaseVersion = status.version; + }); } createIndex(className: string, index: any) { diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 5ba3a005..ef7faead 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -778,6 +778,8 @@ const buildWhereClause = ({ schema, query, index }): WhereClause => { export class PostgresStorageAdapter implements StorageAdapter { canSortOnJoinTables: boolean; + databaseVersion: string; + engine: string; // Private _collectionPrefix: string; @@ -790,6 +792,7 @@ export class PostgresStorageAdapter implements StorageAdapter { this._client = client; this._pgp = pgp; this.canSortOnJoinTables = false; + this.engine = 'PostgreSQL'; } handleShutdown() { @@ -2276,6 +2279,12 @@ export class PostgresStorageAdapter implements StorageAdapter { }) .then(data => { debug(`initializationDone in ${data.duration}`); + // databaseVersion + return this._client.query('SHOW server_version'); + }) + .then(versionData => { + // versionData is like [ { server_version: '11.3' } ] + this.databaseVersion = versionData[0].server_version; }) .catch(error => { /* eslint-disable no-console */ diff --git a/src/Adapters/Storage/StorageAdapter.js b/src/Adapters/Storage/StorageAdapter.js index 31afe569..b1ba9891 100644 --- a/src/Adapters/Storage/StorageAdapter.js +++ b/src/Adapters/Storage/StorageAdapter.js @@ -25,6 +25,8 @@ export type FullQueryOptions = QueryOptions & UpdateQueryOptions; export interface StorageAdapter { canSortOnJoinTables: boolean; + databaseVersion: string; + engine: string; classExists(className: string): Promise; setClassLevelPermissions(className: string, clps: any): Promise; diff --git a/src/Routers/FeaturesRouter.js b/src/Routers/FeaturesRouter.js index 48923087..443c142b 100644 --- a/src/Routers/FeaturesRouter.js +++ b/src/Routers/FeaturesRouter.js @@ -9,6 +9,7 @@ export class FeaturesRouter extends PromiseRouter { '/serverInfo', middleware.promiseEnforceMasterKeyAccess, req => { + const { config } = req; const features = { globalConfig: { create: true, @@ -33,9 +34,9 @@ export class FeaturesRouter extends PromiseRouter { from: true, }, push: { - immediatePush: req.config.hasPushSupport, - scheduledPush: req.config.hasPushScheduledSupport, - storedPushData: req.config.hasPushSupport, + immediatePush: config.hasPushSupport, + scheduledPush: config.hasPushScheduledSupport, + storedPushData: config.hasPushSupport, pushAudiences: true, localization: true, }, @@ -51,10 +52,15 @@ export class FeaturesRouter extends PromiseRouter { }, }; + const dbAdapter = config.database.adapter; return { response: { features: features, parseServerVersion: version, + database: { + engine: dbAdapter.engine, + version: dbAdapter.databaseVersion, + }, }, }; }