diff --git a/spec/CacheController.spec.js b/spec/CacheController.spec.js new file mode 100644 index 0000000000..1e02d59e2f --- /dev/null +++ b/spec/CacheController.spec.js @@ -0,0 +1,74 @@ +var CacheController = require('../src/Controllers/CacheController.js').default; + +describe('CacheController', function() { + var FakeCacheAdapter; + var FakeAppID = 'foo'; + var KEY = 'hello'; + + beforeEach(() => { + FakeCacheAdapter = { + get: () => Promise.resolve(null), + put: jasmine.createSpy('put'), + del: jasmine.createSpy('del'), + clear: jasmine.createSpy('clear') + } + + spyOn(FakeCacheAdapter, 'get').and.callThrough(); + }); + + + it('should expose role and user caches', (done) => { + var cache = new CacheController(FakeCacheAdapter, FakeAppID); + + expect(cache.role).not.toEqual(null); + expect(cache.role.get).not.toEqual(null); + expect(cache.user).not.toEqual(null); + expect(cache.user.get).not.toEqual(null); + + done(); + }); + + + ['role', 'user'].forEach((cacheName) => { + it('should prefix ' + cacheName + ' cache', () => { + var cache = new CacheController(FakeCacheAdapter, FakeAppID)[cacheName]; + + cache.put(KEY, 'world'); + var firstPut = FakeCacheAdapter.put.calls.first(); + expect(firstPut.args[0]).toEqual([FakeAppID, cacheName, KEY].join(':')); + + cache.get(KEY); + var firstGet = FakeCacheAdapter.get.calls.first(); + expect(firstGet.args[0]).toEqual([FakeAppID, cacheName, KEY].join(':')); + + cache.del(KEY); + var firstDel = FakeCacheAdapter.del.calls.first(); + expect(firstDel.args[0]).toEqual([FakeAppID, cacheName, KEY].join(':')); + }); + }); + + it('should clear the entire cache', () => { + var cache = new CacheController(FakeCacheAdapter, FakeAppID); + + cache.clear(); + expect(FakeCacheAdapter.clear.calls.count()).toEqual(1); + + cache.user.clear(); + expect(FakeCacheAdapter.clear.calls.count()).toEqual(2); + + cache.role.clear(); + expect(FakeCacheAdapter.clear.calls.count()).toEqual(3); + }); + + it('should handle cache rejections', (done) => { + + FakeCacheAdapter.get = () => Promise.reject(); + + var cache = new CacheController(FakeCacheAdapter, FakeAppID); + + cache.get('foo').then(done, () => { + fail('Promise should not be rejected.'); + }); + }); + +}); diff --git a/spec/InMemoryCache.spec.js b/spec/InMemoryCache.spec.js new file mode 100644 index 0000000000..3c0fb47bbb --- /dev/null +++ b/spec/InMemoryCache.spec.js @@ -0,0 +1,74 @@ +const InMemoryCache = require('../src/Adapters/Cache/InMemoryCache').default; + + +describe('InMemoryCache', function() { + var BASE_TTL = { + ttl: 10 + }; + var NO_EXPIRE_TTL = { + ttl: NaN + }; + var KEY = 'hello'; + var KEY_2 = KEY + '_2'; + + var VALUE = 'world'; + + + function wait(sleep) { + return new Promise(function(resolve, reject) { + setTimeout(resolve, sleep); + }) + } + + it('should destroy a expire items in the cache', (done) => { + var cache = new InMemoryCache(BASE_TTL); + + cache.put(KEY, VALUE); + + var value = cache.get(KEY); + expect(value).toEqual(VALUE); + + wait(BASE_TTL.ttl * 5).then(() => { + value = cache.get(KEY) + expect(value).toEqual(null); + done(); + }); + }); + + it('should delete items', (done) => { + var cache = new InMemoryCache(NO_EXPIRE_TTL); + cache.put(KEY, VALUE); + cache.put(KEY_2, VALUE); + expect(cache.get(KEY)).toEqual(VALUE); + expect(cache.get(KEY_2)).toEqual(VALUE); + + cache.del(KEY); + expect(cache.get(KEY)).toEqual(null); + expect(cache.get(KEY_2)).toEqual(VALUE); + + cache.del(KEY_2); + expect(cache.get(KEY)).toEqual(null); + expect(cache.get(KEY_2)).toEqual(null); + done(); + }); + + it('should clear all items', (done) => { + var cache = new InMemoryCache(NO_EXPIRE_TTL); + cache.put(KEY, VALUE); + cache.put(KEY_2, VALUE); + + expect(cache.get(KEY)).toEqual(VALUE); + expect(cache.get(KEY_2)).toEqual(VALUE); + cache.clear(); + + expect(cache.get(KEY)).toEqual(null); + expect(cache.get(KEY_2)).toEqual(null); + done(); + }); + + it('should deafult TTL to 5 seconds', () => { + var cache = new InMemoryCache({}); + expect(cache.ttl).toEqual(5 * 1000); + }); + +}); diff --git a/spec/InMemoryCacheAdapter.spec.js b/spec/InMemoryCacheAdapter.spec.js new file mode 100644 index 0000000000..405da6f7ad --- /dev/null +++ b/spec/InMemoryCacheAdapter.spec.js @@ -0,0 +1,59 @@ +var InMemoryCacheAdapter = require('../src/Adapters/Cache/InMemoryCacheAdapter').default; + +describe('InMemoryCacheAdapter', function() { + var KEY = 'hello'; + var VALUE = 'world'; + + function wait(sleep) { + return new Promise(function(resolve, reject) { + setTimeout(resolve, sleep); + }) + } + + it('should expose promisifyed methods', (done) => { + var cache = new InMemoryCacheAdapter({ + ttl: NaN + }); + + var noop = () => {}; + + // Verify all methods return promises. + Promise.all([ + cache.put(KEY, VALUE), + cache.del(KEY), + cache.get(KEY), + cache.clear() + ]).then(() => { + done(); + }); + }); + + it('should get/set/clear', (done) => { + var cache = new InMemoryCacheAdapter({ + ttl: NaN + }); + + cache.put(KEY, VALUE) + .then(() => cache.get(KEY)) + .then((value) => expect(value).toEqual(VALUE)) + .then(() => cache.clear()) + .then(() => cache.get(KEY)) + .then((value) => expect(value).toEqual(null)) + .then(done); + }); + + it('should expire after ttl', (done) => { + var cache = new InMemoryCacheAdapter({ + ttl: 10 + }); + + cache.put(KEY, VALUE) + .then(() => cache.get(KEY)) + .then((value) => expect(value).toEqual(VALUE)) + .then(wait.bind(null, 50)) + .then(() => cache.get(KEY)) + .then((value) => expect(value).toEqual(null)) + .then(done); + }) + +}); diff --git a/spec/helper.js b/spec/helper.js index 0d6379fbf8..aa63ff0f4d 100644 --- a/spec/helper.js +++ b/spec/helper.js @@ -63,7 +63,7 @@ const setServerConfiguration = configuration => { DatabaseAdapter.clearDatabaseSettings(); currentConfiguration = configuration; server.close(); - cache.clearCache(); + cache.clear(); app = express(); api = new ParseServer(configuration); app.use('/1', api); diff --git a/src/Adapters/Cache/CacheAdapter.js b/src/Adapters/Cache/CacheAdapter.js new file mode 100644 index 0000000000..7d65381763 --- /dev/null +++ b/src/Adapters/Cache/CacheAdapter.js @@ -0,0 +1,27 @@ +export class CacheAdapter { + /** + * Get a value in the cache + * @param key Cache key to get + * @return Promise that will eventually resolve to the value in the cache. + */ + get(key) {} + + /** + * Set a value in the cache + * @param key Cache key to set + * @param value Value to set the key + * @param ttl Optional TTL + */ + put(key, value, ttl) {} + + /** + * Remove a value from the cache. + * @param key Cache key to remove + */ + del(key) {} + + /** + * Empty a cache + */ + clear() {} +} diff --git a/src/Adapters/Cache/InMemoryCache.js b/src/Adapters/Cache/InMemoryCache.js new file mode 100644 index 0000000000..37eeb43b02 --- /dev/null +++ b/src/Adapters/Cache/InMemoryCache.js @@ -0,0 +1,66 @@ +const DEFAULT_CACHE_TTL = 5 * 1000; + + +export class InMemoryCache { + constructor({ + ttl = DEFAULT_CACHE_TTL + }) { + this.ttl = ttl; + this.cache = Object.create(null); + } + + get(key) { + let record = this.cache[key]; + if (record == null) { + return null; + } + + // Has Record and isnt expired + if (isNaN(record.expire) || record.expire >= Date.now()) { + return record.value; + } + + // Record has expired + delete this.cache[key]; + return null; + } + + put(key, value, ttl = this.ttl) { + if (ttl < 0 || isNaN(ttl)) { + ttl = NaN; + } + + var record = { + value: value, + expire: ttl + Date.now() + } + + if (!isNaN(record.expire)) { + record.timeout = setTimeout(() => { + this.del(key); + }, ttl); + } + + this.cache[key] = record; + } + + del(key) { + var record = this.cache[key]; + if (record == null) { + return; + } + + if (record.timeout) { + clearTimeout(record.timeout); + } + + delete this.cache[key]; + } + + clear() { + this.cache = Object.create(null); + } + +} + +export default InMemoryCache; diff --git a/src/Adapters/Cache/InMemoryCacheAdapter.js b/src/Adapters/Cache/InMemoryCacheAdapter.js new file mode 100644 index 0000000000..09e1c12a11 --- /dev/null +++ b/src/Adapters/Cache/InMemoryCacheAdapter.js @@ -0,0 +1,36 @@ +import {InMemoryCache} from './InMemoryCache'; + +export class InMemoryCacheAdapter { + + constructor(ctx) { + this.cache = new InMemoryCache(ctx) + } + + get(key) { + return new Promise((resolve, reject) => { + let record = this.cache.get(key); + if (record == null) { + return resolve(null); + } + + return resolve(JSON.parse(record)); + }) + } + + put(key, value, ttl) { + this.cache.put(key, JSON.stringify(value), ttl); + return Promise.resolve(); + } + + del(key) { + this.cache.del(key); + return Promise.resolve(); + } + + clear() { + this.cache.clear(); + return Promise.resolve(); + } +} + +export default InMemoryCacheAdapter; diff --git a/src/Auth.js b/src/Auth.js index f21bdc763f..8f21567903 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -2,8 +2,6 @@ var deepcopy = require('deepcopy'); var Parse = require('parse/node').Parse; var RestQuery = require('./RestQuery'); -import cache from './cache'; - // An Auth object tells you who is requesting something and whether // the master key was used. // userObject is a Parse.User and can be null if there's no user. @@ -42,36 +40,42 @@ function nobody(config) { return new Auth({ config, isMaster: false }); } + // Returns a promise that resolves to an Auth object var getAuthForSessionToken = function({ config, sessionToken, installationId } = {}) { - var cachedUser = cache.users.get(sessionToken); - if (cachedUser) { - return Promise.resolve(new Auth({ config, isMaster: false, installationId, user: cachedUser })); - } - var restOptions = { - limit: 1, - include: 'user' - }; - var query = new RestQuery(config, master(config), '_Session', { sessionToken }, restOptions); - return query.execute().then((response) => { - var results = response.results; - if (results.length !== 1 || !results[0]['user']) { - return nobody(config); + return config.cacheController.user.get(sessionToken).then((userJSON) => { + if (userJSON) { + let cachedUser = Parse.Object.fromJSON(userJSON); + return Promise.resolve(new Auth({config, isMaster: false, installationId, user: cachedUser})); } - var now = new Date(), + var restOptions = { + limit: 1, + include: 'user' + }; + + var query = new RestQuery(config, master(config), '_Session', {sessionToken}, restOptions); + return query.execute().then((response) => { + var results = response.results; + if (results.length !== 1 || !results[0]['user']) { + return nobody(config); + } + + var now = new Date(), expiresAt = results[0].expiresAt ? new Date(results[0].expiresAt.iso) : undefined; - if(expiresAt < now) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, - 'Session token is expired.'); - } - var obj = results[0]['user']; - delete obj.password; - obj['className'] = '_User'; - obj['sessionToken'] = sessionToken; - let userObject = Parse.Object.fromJSON(obj); - cache.users.set(sessionToken, userObject); - return new Auth({ config, isMaster: false, installationId, user: userObject }); + if (expiresAt < now) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, + 'Session token is expired.'); + } + var obj = results[0]['user']; + delete obj.password; + obj['className'] = '_User'; + obj['sessionToken'] = sessionToken; + config.cacheController.user.put(sessionToken, obj); + + let userObject = Parse.Object.fromJSON(obj); + return new Auth({config, isMaster: false, installationId, user: userObject}); + }); }); }; @@ -92,39 +96,50 @@ Auth.prototype.getUserRoles = function() { // Iterates through the role tree and compiles a users roles Auth.prototype._loadRoles = function() { - var restWhere = { - 'users': { - __type: 'Pointer', - className: '_User', - objectId: this.user.id - } - }; - // First get the role ids this user is directly a member of - var query = new RestQuery(this.config, master(this.config), '_Role', - restWhere, {}); - return query.execute().then((response) => { - var results = response.results; - if (!results.length) { - this.userRoles = []; - this.fetchedRoles = true; - this.rolePromise = null; - return Promise.resolve(this.userRoles); + var cacheAdapter = this.config.cacheController; + return cacheAdapter.role.get(this.user.id).then((cachedRoles) => { + if (cachedRoles != null) { + this.fetchedroles = true; + return Promise.resolve(cachedRoles); } - var rolesMap = results.reduce((m, r) => { - m.names.push(r.name); - m.ids.push(r.objectId); - return m; - }, {ids: [], names: []}); - // run the recursive finding - return this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names) - .then((roleNames) => { - this.userRoles = roleNames.map((r) => { - return 'role:' + r; - }); - this.fetchedRoles = true; - this.rolePromise = null; - return Promise.resolve(this.userRoles); + var restWhere = { + 'users': { + __type: 'Pointer', + className: '_User', + objectId: this.user.id + } + }; + // First get the role ids this user is directly a member of + var query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {}); + return query.execute().then((response) => { + var results = response.results; + if (!results.length) { + this.userRoles = []; + this.fetchedRoles = true; + this.rolePromise = null; + + cacheAdapter.role.put(this.user.id, this.userRoles); + return Promise.resolve(this.userRoles); + } + var rolesMap = results.reduce((m, r) => { + m.names.push(r.name); + m.ids.push(r.objectId); + return m; + }, {ids: [], names: []}); + + // run the recursive finding + return this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names) + .then((roleNames) => { + this.userRoles = roleNames.map((r) => { + return 'role:' + r; + }); + this.fetchedRoles = true; + this.rolePromise = null; + + cacheAdapter.role.put(this.user.id, this.userRoles); + return Promise.resolve(this.userRoles); + }); }); }); }; diff --git a/src/Config.js b/src/Config.js index badff9da40..faaa6c2235 100644 --- a/src/Config.js +++ b/src/Config.js @@ -2,7 +2,7 @@ // configured. // mount is the URL for the root of the API; includes http, domain, etc. -import cache from './cache'; +import AppCache from './cache'; function removeTrailingSlash(str) { if (!str) { @@ -17,7 +17,7 @@ function removeTrailingSlash(str) { export class Config { constructor(applicationId: string, mount: string) { let DatabaseAdapter = require('./DatabaseAdapter'); - let cacheInfo = cache.apps.get(applicationId); + let cacheInfo = AppCache.get(applicationId); if (!cacheInfo) { return; } @@ -38,6 +38,7 @@ export class Config { this.verifyUserEmails = cacheInfo.verifyUserEmails; this.appName = cacheInfo.appName; + this.cacheController = cacheInfo.cacheController; this.hooksController = cacheInfo.hooksController; this.filesController = cacheInfo.filesController; this.pushController = cacheInfo.pushController; diff --git a/src/Controllers/CacheController.js b/src/Controllers/CacheController.js new file mode 100644 index 0000000000..27dc4936f7 --- /dev/null +++ b/src/Controllers/CacheController.js @@ -0,0 +1,75 @@ +import AdaptableController from './AdaptableController'; +import CacheAdapter from '../Adapters/Cache/CacheAdapter'; + +const KEY_SEPARATOR_CHAR = ':'; + +function joinKeys(...keys) { + return keys.join(KEY_SEPARATOR_CHAR); +} + +/** + * Prefix all calls to the cache via a prefix string, useful when grouping Cache by object type. + * + * eg "Role" or "Session" + */ +export class SubCache { + constructor(prefix, cacheController) { + this.prefix = prefix; + this.cache = cacheController; + } + + get(key) { + let cacheKey = joinKeys(this.prefix, key); + return this.cache.get(cacheKey); + } + + put(key, value, ttl) { + let cacheKey = joinKeys(this.prefix, key); + return this.cache.put(cacheKey, value, ttl); + } + + del(key) { + let cacheKey = joinKeys(this.prefix, key); + return this.cache.del(cacheKey); + } + + clear() { + return this.cache.clear(); + } +} + + +export class CacheController extends AdaptableController { + + constructor(adapter, appId, options = {}) { + super(adapter, appId, options); + + this.role = new SubCache('role', this); + this.user = new SubCache('user', this); + } + + get(key) { + let cacheKey = joinKeys(this.appId, key); + return this.adapter.get(cacheKey).then(null, () => Promise.resolve(null)); + } + + put(key, value, ttl) { + let cacheKey = joinKeys(this.appId, key); + return this.adapter.put(cacheKey, value, ttl); + } + + del(key) { + let cacheKey = joinKeys(this.appId, key); + return this.adapter.del(cacheKey); + } + + clear() { + return this.adapter.clear(); + } + + expectedAdapterType() { + return CacheAdapter; + } +} + +export default CacheController; diff --git a/src/DatabaseAdapter.js b/src/DatabaseAdapter.js index 73bc09334f..25ce69b422 100644 --- a/src/DatabaseAdapter.js +++ b/src/DatabaseAdapter.js @@ -60,7 +60,7 @@ function getDatabaseConnection(appId: string, collectionPrefix: string) { uri: appDatabaseURIs[appId], //may be undefined if the user didn't supply a URI, in which case the default will be used } - dbConnections[appId] = new DatabaseController(new MongoStorageAdapter(mongoAdapterOptions)); + dbConnections[appId] = new DatabaseController(new MongoStorageAdapter(mongoAdapterOptions), {appId: appId}); return dbConnections[appId]; } diff --git a/src/ParseServer.js b/src/ParseServer.js index baeb1d69e5..85b2b40c02 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -15,8 +15,8 @@ if (!global._babelPolyfill) { } import { logger, - configureLogger } from './logger'; -import cache from './cache'; + configureLogger } from './logger'; +import AppCache from './cache'; import Config from './Config'; import parseServerPackage from '../package.json'; import PromiseRouter from './PromiseRouter'; @@ -24,6 +24,8 @@ import requiredParameter from './requiredParameter'; import { AnalyticsRouter } from './Routers/AnalyticsRouter'; import { ClassesRouter } from './Routers/ClassesRouter'; import { FeaturesRouter } from './Routers/FeaturesRouter'; +import { InMemoryCacheAdapter } from './Adapters/Cache/InMemoryCacheAdapter'; +import { CacheController } from './Controllers/CacheController'; import { FileLoggerAdapter } from './Adapters/Logger/FileLoggerAdapter'; import { FilesController } from './Controllers/FilesController'; import { FilesRouter } from './Routers/FilesRouter'; @@ -104,6 +106,7 @@ class ParseServer { serverURL = requiredParameter('You must provide a serverURL!'), maxUploadSize = '20mb', verifyUserEmails = false, + cacheAdapter, emailAdapter, publicServerURL, customPages = { @@ -156,6 +159,8 @@ class ParseServer { const pushControllerAdapter = loadAdapter(push && push.adapter, ParsePushAdapter, push); const loggerControllerAdapter = loadAdapter(loggerAdapter, FileLoggerAdapter); const emailControllerAdapter = loadAdapter(emailAdapter); + const cacheControllerAdapter = loadAdapter(cacheAdapter, InMemoryCacheAdapter, {appId: appId}); + // We pass the options and the base class for the adatper, // Note that passing an instance would work too const filesController = new FilesController(filesControllerAdapter, appId); @@ -164,8 +169,9 @@ class ParseServer { const hooksController = new HooksController(appId, collectionPrefix); const userController = new UserController(emailControllerAdapter, appId, { verifyUserEmails }); const liveQueryController = new LiveQueryController(liveQuery); + const cacheController = new CacheController(cacheControllerAdapter, appId); - cache.apps.set(appId, { + AppCache.put(appId, { masterKey: masterKey, serverURL: serverURL, collectionPrefix: collectionPrefix, @@ -175,6 +181,7 @@ class ParseServer { restAPIKey: restAPIKey, fileKey: fileKey, facebookAppIds: facebookAppIds, + cacheController: cacheController, filesController: filesController, pushController: pushController, loggerController: loggerController, @@ -195,11 +202,11 @@ class ParseServer { // To maintain compatibility. TODO: Remove in some version that breaks backwards compatability if (process.env.FACEBOOK_APP_ID) { - cache.apps.get(appId)['facebookAppIds'].push(process.env.FACEBOOK_APP_ID); + AppCache.get(appId)['facebookAppIds'].push(process.env.FACEBOOK_APP_ID); } - Config.validate(cache.apps.get(appId)); - this.config = cache.apps.get(appId); + Config.validate(AppCache.get(appId)); + this.config = AppCache.get(appId); hooksController.load(); } diff --git a/src/RestWrite.js b/src/RestWrite.js index ecb92a85e4..f6e758f6bf 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -2,7 +2,6 @@ // that writes to the database. // This could be either a "create" or an "update". -import cache from './cache'; var SchemaController = require('./Controllers/SchemaController'); var deepcopy = require('deepcopy'); @@ -310,6 +309,7 @@ RestWrite.prototype.handleAuthData = function(authData) { }); } + // The non-third-party parts of User transformation RestWrite.prototype.transformUser = function() { if (this.className !== '_User') { @@ -320,7 +320,8 @@ RestWrite.prototype.transformUser = function() { // If we're updating a _User object, clear the user cache for the session if (this.query && this.auth.user && this.auth.user.getSessionToken()) { - cache.users.remove(this.auth.user.getSessionToken()); + let cacheAdapter = this.config.cacheController; + cacheAdapter.user.del(this.auth.user.getSessionToken()); } return promise.then(() => { @@ -441,24 +442,6 @@ RestWrite.prototype.handleFollowup = function() { } }; -// Handles the _Role class specialness. -// Does nothing if this isn't a role object. -RestWrite.prototype.handleRole = function() { - if (this.response || this.className !== '_Role') { - return; - } - - if (!this.auth.user && !this.auth.isMaster) { - throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, - 'Session token required.'); - } - - if (!this.data.name) { - throw new Parse.Error(Parse.Error.INVALID_ROLE_NAME, - 'Invalid role name.'); - } -}; - // Handles the _Session class specialness. // Does nothing if this isn't an installation object. RestWrite.prototype.handleSession = function() { @@ -716,6 +699,10 @@ RestWrite.prototype.runDatabaseOperation = function() { return; } + if (this.className === '_Role') { + this.config.cacheController.role.clear(); + } + if (this.className === '_User' && this.query && !this.auth.couldUpdateUserId(this.query.objectId)) { diff --git a/src/cache.js b/src/cache.js index 8893f29b1b..96b00b4534 100644 --- a/src/cache.js +++ b/src/cache.js @@ -1,35 +1,4 @@ -/** @flow weak */ +import {InMemoryCache} from './Adapters/Cache/InMemoryCache'; -export function CacheStore() { - let dataStore: {[id:KeyType]:ValueType} = {}; - return { - get: (key: KeyType): ValueType => { - return dataStore[key]; - }, - set(key: KeyType, value: ValueType): void { - dataStore[key] = value; - }, - remove(key: KeyType): void { - delete dataStore[key]; - }, - clear(): void { - dataStore = {}; - } - }; -} - -const apps = CacheStore(); -const users = CacheStore(); - -//So far used only in tests -export function clearCache(): void { - apps.clear(); - users.clear(); -} - -export default { - apps, - users, - clearCache, - CacheStore -}; +export var AppCache = new InMemoryCache({ttl: NaN}); +export default AppCache; diff --git a/src/middlewares.js b/src/middlewares.js index 10115d6853..c500609363 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -1,5 +1,5 @@ -import cache from './cache'; -import log from './logger'; +import AppCache from './cache'; +import log from './logger'; var Parse = require('parse/node').Parse; @@ -36,7 +36,7 @@ function handleParseHeaders(req, res, next) { var fileViaJSON = false; - if (!info.appId || !cache.apps.get(info.appId)) { + if (!info.appId || !AppCache.get(info.appId)) { // See if we can find the app id on the body. if (req.body instanceof Buffer) { // The only chance to find the app id is if this is a file @@ -51,8 +51,8 @@ function handleParseHeaders(req, res, next) { if (req.body && req.body._ApplicationId && - cache.apps.get(req.body._ApplicationId) && - (!info.masterKey || cache.apps.get(req.body._ApplicationId).masterKey === info.masterKey) + AppCache.get(req.body._ApplicationId) && + (!info.masterKey || AppCache.get(req.body._ApplicationId).masterKey === info.masterKey) ) { info.appId = req.body._ApplicationId; info.javascriptKey = req.body._JavaScriptKey || ''; @@ -87,7 +87,7 @@ function handleParseHeaders(req, res, next) { req.body = new Buffer(base64, 'base64'); } - info.app = cache.apps.get(info.appId); + info.app = AppCache.get(info.appId); req.config = new Config(info.appId, mount); req.info = info; diff --git a/src/rest.js b/src/rest.js index 60f017213e..d1894165a6 100644 --- a/src/rest.js +++ b/src/rest.js @@ -8,8 +8,7 @@ // things. var Parse = require('parse/node').Parse; -import cache from './cache'; -import Auth from './Auth'; +import Auth from './Auth'; var RestQuery = require('./RestQuery'); var RestWrite = require('./RestWrite'); @@ -48,7 +47,9 @@ function del(config, auth, className, objectId) { .then((response) => { if (response && response.results && response.results.length) { response.results[0].className = className; - cache.users.remove(response.results[0].sessionToken); + + var cacheAdapter = config.cacheController; + cacheAdapter.user.del(response.results[0].sessionToken); inflatedObject = Parse.Object.fromJSON(response.results[0]); // Notify LiveQuery server if possible config.liveQueryController.onAfterDelete(inflatedObject.className, inflatedObject); @@ -96,6 +97,7 @@ function create(config, auth, className, restObject) { // Usually, this is just updatedAt. function update(config, auth, className, objectId, restObject) { enforceRoleSecurity('update', className, auth); + return Promise.resolve().then(() => { if (triggers.getTrigger(className, triggers.Types.beforeSave, config.applicationId) || triggers.getTrigger(className, triggers.Types.afterSave, config.applicationId) || diff --git a/src/testing-routes.js b/src/testing-routes.js index 04b5cf83ed..eee022d9f9 100644 --- a/src/testing-routes.js +++ b/src/testing-routes.js @@ -1,5 +1,5 @@ // testing-routes.js -import cache from './cache'; +import AppCache from './cache'; import * as middlewares from './middlewares'; import { ParseServer } from './index'; import { Parse } from 'parse/node'; @@ -47,7 +47,7 @@ function dropApp(req, res) { return res.status(401).send({ "error": "unauthorized" }); } return req.config.database.deleteEverything().then(() => { - cache.apps.remove(req.config.applicationId); + AppCache.del(req.config.applicationId); res.status(200).send({}); }); } diff --git a/src/triggers.js b/src/triggers.js index 7ab1ea1902..c7c9ba2c1c 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -1,6 +1,6 @@ // triggers.js import Parse from 'parse/node'; -import cache from './cache'; +import AppCache from './cache'; export const Types = { beforeSave: 'beforeSave',