Fix Prettier (#7066)

This commit is contained in:
Diamond Lewis
2020-12-13 11:19:04 -06:00
committed by GitHub
parent d4948572a8
commit 033a0bd443
64 changed files with 697 additions and 1887 deletions

View File

@@ -2,12 +2,9 @@ var https = require('https'),
crypto = require('crypto');
var Parse = require('parse/node').Parse;
var OAuth = function(options) {
var OAuth = function (options) {
if (!options) {
throw new Parse.Error(
Parse.Error.INTERNAL_SERVER_ERROR,
'No options passed to OAuth'
);
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'No options passed to OAuth');
}
this.consumer_key = options.consumer_key;
this.consumer_secret = options.consumer_secret;
@@ -17,22 +14,22 @@ var OAuth = function(options) {
this.oauth_params = options.oauth_params || {};
};
OAuth.prototype.send = function(method, path, params, body) {
OAuth.prototype.send = function (method, path, params, body) {
var request = this.buildRequest(method, path, params, body);
// Encode the body properly, the current Parse Implementation don't do it properly
return new Promise(function(resolve, reject) {
return new Promise(function (resolve, reject) {
var httpRequest = https
.request(request, function(res) {
.request(request, function (res) {
var data = '';
res.on('data', function(chunk) {
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function() {
res.on('end', function () {
data = JSON.parse(data);
resolve(data);
});
})
.on('error', function() {
.on('error', function () {
reject('Failed to make an OAuth request');
});
if (request.body) {
@@ -42,7 +39,7 @@ OAuth.prototype.send = function(method, path, params, body) {
});
};
OAuth.prototype.buildRequest = function(method, path, params, body) {
OAuth.prototype.buildRequest = function (method, path, params, body) {
if (path.indexOf('/') != 0) {
path = '/' + path;
}
@@ -62,12 +59,7 @@ OAuth.prototype.buildRequest = function(method, path, params, body) {
oauth_params['oauth_token'] = this.auth_token;
}
request = OAuth.signRequest(
request,
oauth_params,
this.consumer_secret,
this.auth_token_secret
);
request = OAuth.signRequest(request, oauth_params, this.consumer_secret, this.auth_token_secret);
if (body && Object.keys(body).length > 0) {
request.body = OAuth.buildParameterString(body);
@@ -75,18 +67,18 @@ OAuth.prototype.buildRequest = function(method, path, params, body) {
return request;
};
OAuth.prototype.get = function(path, params) {
OAuth.prototype.get = function (path, params) {
return this.send('GET', path, params);
};
OAuth.prototype.post = function(path, params, body) {
OAuth.prototype.post = function (path, params, body) {
return this.send('POST', path, params, body);
};
/*
Proper string %escape encoding
*/
OAuth.encode = function(str) {
OAuth.encode = function (str) {
// discuss at: http://phpjs.org/functions/rawurlencode/
// original by: Brett Zamir (http://brett-zamir.me)
// input by: travc
@@ -126,25 +118,23 @@ OAuth.version = '1.0';
/*
Generate a nonce
*/
OAuth.nonce = function() {
OAuth.nonce = function () {
var text = '';
var possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < 30; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
for (var i = 0; i < 30; i++) text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
};
OAuth.buildParameterString = function(obj) {
OAuth.buildParameterString = function (obj) {
// Sort keys and encode values
if (obj) {
var keys = Object.keys(obj).sort();
// Map key=value, join them by &
return keys
.map(function(key) {
.map(function (key) {
return key + '=' + OAuth.encode(obj[key]);
})
.join('&');
@@ -157,33 +147,19 @@ OAuth.buildParameterString = function(obj) {
Build the signature string from the object
*/
OAuth.buildSignatureString = function(method, url, parameters) {
return [
method.toUpperCase(),
OAuth.encode(url),
OAuth.encode(parameters),
].join('&');
OAuth.buildSignatureString = function (method, url, parameters) {
return [method.toUpperCase(), OAuth.encode(url), OAuth.encode(parameters)].join('&');
};
/*
Retuns encoded HMAC-SHA1 from key and text
*/
OAuth.signature = function(text, key) {
OAuth.signature = function (text, key) {
crypto = require('crypto');
return OAuth.encode(
crypto
.createHmac('sha1', key)
.update(text)
.digest('base64')
);
return OAuth.encode(crypto.createHmac('sha1', key).update(text).digest('base64'));
};
OAuth.signRequest = function(
request,
oauth_parameters,
consumer_secret,
auth_token_secret
) {
OAuth.signRequest = function (request, oauth_parameters, consumer_secret, auth_token_secret) {
oauth_parameters = oauth_parameters || {};
// Set default values
@@ -224,16 +200,9 @@ OAuth.signRequest = function(
// Build the signature string
var url = 'https://' + request.host + '' + request.path;
var signatureString = OAuth.buildSignatureString(
request.method,
url,
parameterString
);
var signatureString = OAuth.buildSignatureString(request.method, url, parameterString);
// Hash the signature string
var signatureKey = [
OAuth.encode(consumer_secret),
OAuth.encode(auth_token_secret),
].join('&');
var signatureKey = [OAuth.encode(consumer_secret), OAuth.encode(auth_token_secret)].join('&');
var signature = OAuth.signature(signatureString, signatureKey);
@@ -246,7 +215,7 @@ OAuth.signRequest = function(
// Set the authorization header
var authHeader = Object.keys(oauth_parameters)
.sort()
.map(function(key) {
.map(function (key) {
var value = oauth_parameters[key];
return key + '="' + value + '"';
})

View File

@@ -33,24 +33,15 @@ const getAppleKeyByKeyId = async (keyId, cacheMaxEntries, cacheMaxAge) => {
const getHeaderFromToken = token => {
const decodedToken = jwt.decode(token, { complete: true });
if (!decodedToken) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`provided token does not decode as JWT`
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `provided token does not decode as JWT`);
}
return decodedToken.header;
};
const verifyIdToken = async (
{ token, id },
{ clientId, cacheMaxEntries, cacheMaxAge }
) => {
const verifyIdToken = async ({ token, id }, { clientId, cacheMaxEntries, cacheMaxAge }) => {
if (!token) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`id token is invalid for this user.`
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `id token is invalid for this user.`);
}
const { kid: keyId, alg: algorithm } = getHeaderFromToken(token);
@@ -60,11 +51,7 @@ const verifyIdToken = async (
cacheMaxAge = cacheMaxAge || ONE_HOUR_IN_MS;
cacheMaxEntries = cacheMaxEntries || 5;
const appleKey = await getAppleKeyByKeyId(
keyId,
cacheMaxEntries,
cacheMaxAge
);
const appleKey = await getAppleKeyByKeyId(keyId, cacheMaxEntries, cacheMaxAge);
const signingKey = appleKey.publicKey || appleKey.rsaPublicKey;
try {
@@ -87,10 +74,7 @@ const verifyIdToken = async (
}
if (jwtClaims.sub !== id) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`auth data is invalid for this user.`
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `auth data is invalid for this user.`);
}
return jwtClaims;
};

View File

@@ -19,20 +19,12 @@ function getAppSecretPath(authData, options = {}) {
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData, options) {
return graphRequest(
'me?fields=id&access_token=' +
authData.access_token +
getAppSecretPath(authData, options)
'me?fields=id&access_token=' + authData.access_token + getAppSecretPath(authData, options)
).then(data => {
if (
(data && data.id == authData.id) ||
(process.env.TESTING && authData.id === 'test')
) {
if ((data && data.id == authData.id) || (process.env.TESTING && authData.id === 'test')) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Facebook auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.');
});
}
@@ -43,10 +35,7 @@ function validateAppId(appIds, authData, options) {
return Promise.resolve();
}
if (!appIds.length) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Facebook auth is not configured.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is not configured.');
}
return graphRequest(
'app?access_token=' + access_token + getAppSecretPath(authData, options)
@@ -54,10 +43,7 @@ function validateAppId(appIds, authData, options) {
if (data && appIds.indexOf(data.id) != -1) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Facebook auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Facebook auth is invalid for this user.');
});
}

View File

@@ -96,20 +96,14 @@ function verifySignature(publicKey, authData) {
verifier.update(authData.salt, 'base64');
if (!verifier.verify(publicKey, authData.signature, 'base64')) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Apple Game Center - invalid signature'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Apple Game Center - invalid signature');
}
}
// Returns a promise that fulfills if this user id is valid.
async function validateAuthData(authData) {
if (!authData.id) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Apple Game Center - authData id missing'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Apple Game Center - authData id missing');
}
authData.playerId = authData.id;
const publicKey = await getAppleCertificate(authData.publicKeyUrl);

View File

@@ -8,10 +8,7 @@ function validateAuthData(authData) {
if (data && data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Github auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Github auth is invalid for this user.');
});
}

View File

@@ -39,9 +39,7 @@ function getGoogleKeyByKeyId(keyId) {
if (expire) {
cache = Object.assign({}, pems, {
expiresAt: new Date(
new Date().getTime() + Number(expire[1]) * 1000
),
expiresAt: new Date(new Date().getTime() + Number(expire[1]) * 1000),
});
}
}
@@ -57,10 +55,7 @@ function getHeaderFromToken(token) {
const decodedToken = jwt.decode(token, { complete: true });
if (!decodedToken) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`provided token does not decode as JWT`
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `provided token does not decode as JWT`);
}
return decodedToken.header;
@@ -68,10 +63,7 @@ function getHeaderFromToken(token) {
async function verifyIdToken({ id_token: token, id }, { clientId }) {
if (!token) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`id token is invalid for this user.`
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `id token is invalid for this user.`);
}
const { kid: keyId, alg: algorithm } = getHeaderFromToken(token);
@@ -96,10 +88,7 @@ async function verifyIdToken({ id_token: token, id }, { clientId }) {
}
if (jwtClaims.sub !== id) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
`auth data is invalid for this user.`
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, `auth data is invalid for this user.`);
}
if (clientId && jwtClaims.aud !== clientId) {
@@ -140,9 +129,7 @@ function rsaPublicKeyToPEM(modulusB64, exponentB64) {
const encodedExplen = encodeLengthHex(explen);
const encodedPubkey =
'30' +
encodeLengthHex(
modlen + explen + encodedModlen.length / 2 + encodedExplen.length / 2 + 2
) +
encodeLengthHex(modlen + explen + encodedModlen.length / 2 + encodedExplen.length / 2 + 2) +
'02' +
encodedModlen +
modulusHex +

View File

@@ -1,7 +1,7 @@
const https = require('https');
function makeCallback(resolve, reject, noJSON) {
return function(res) {
return function (res) {
let data = '';
res.on('data', chunk => {
data += chunk;
@@ -23,9 +23,7 @@ function makeCallback(resolve, reject, noJSON) {
function get(options, noJSON = false) {
return new Promise((resolve, reject) => {
https
.get(options, makeCallback(resolve, reject, noJSON))
.on('error', reject);
https.get(options, makeCallback(resolve, reject, noJSON)).on('error', reject);
});
}

View File

@@ -92,11 +92,7 @@ function loadAuthAdapter(provider, authOptions) {
// Try the configuration methods
if (providerOptions) {
const optionalAdapter = loadAdapter(
providerOptions,
undefined,
providerOptions
);
const optionalAdapter = loadAdapter(providerOptions, undefined, providerOptions);
if (optionalAdapter) {
['validateAuthData', 'validateAppId'].forEach(key => {
if (optionalAdapter[key]) {
@@ -128,10 +124,7 @@ module.exports = function (authOptions = {}, enableAnonymousUsers = true) {
return;
}
const { adapter, appIds, providerOptions } = loadAuthAdapter(
provider,
authOptions
);
const { adapter, appIds, providerOptions } = loadAuthAdapter(provider, authOptions);
return authDataValidator(adapter, appIds, providerOptions);
};

View File

@@ -11,10 +11,7 @@ function validateAuthData(authData) {
if (response && response.data && response.data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Instagram auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Instagram auth is invalid for this user.');
});
}

View File

@@ -5,19 +5,17 @@ const httpsRequest = require('./httpsRequest');
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData, options) {
return request(options.janrain_capture_host, authData.access_token).then(
data => {
//successful response will have a "stat" (status) of 'ok' and a result node that stores the uuid, because that's all we asked for
//see: https://docs.janrain.com/api/registration/entity/#entity
if (data && data.stat == 'ok' && data.result == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Janrain capture auth is invalid for this user.'
);
return request(options.janrain_capture_host, authData.access_token).then(data => {
//successful response will have a "stat" (status) of 'ok' and a result node that stores the uuid, because that's all we asked for
//see: https://docs.janrain.com/api/registration/entity/#entity
if (data && data.stat == 'ok' && data.result == authData.id) {
return;
}
);
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Janrain capture auth is invalid for this user.'
);
});
}
// Returns a promise that fulfills iff this app id is valid.

View File

@@ -37,12 +37,7 @@ const { Parse } = require('parse/node');
const httpsRequest = require('./httpsRequest');
const arraysEqual = (_arr1, _arr2) => {
if (
!Array.isArray(_arr1) ||
!Array.isArray(_arr2) ||
_arr1.length !== _arr2.length
)
return false;
if (!Array.isArray(_arr1) || !Array.isArray(_arr2) || _arr1.length !== _arr2.length) return false;
var arr1 = _arr1.concat().sort();
var arr2 = _arr2.concat().sort();
@@ -54,21 +49,12 @@ const arraysEqual = (_arr1, _arr2) => {
return true;
};
const handleAuth = async (
{ access_token, id, roles, groups } = {},
{ config } = {}
) => {
const handleAuth = async ({ access_token, id, roles, groups } = {}, { config } = {}) => {
if (!(access_token && id)) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Missing access token and/or User id'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Missing access token and/or User id');
}
if (!config || !(config['auth-server-url'] && config['realm'])) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Missing keycloak configuration'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Missing keycloak configuration');
}
try {
const response = await httpsRequest.get({
@@ -87,10 +73,7 @@ const handleAuth = async (
) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Invalid authentication'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid authentication');
} catch (e) {
if (e instanceof Parse.Error) {
throw e;

View File

@@ -4,16 +4,12 @@ const Parse = require('parse/node').Parse;
function validateAuthData(authData, options) {
if (!optionsAreValid(options)) {
return new Promise((_, reject) => {
reject(
new Parse.Error(
Parse.Error.INTERNAL_SERVER_ERROR,
'LDAP auth configuration missing'
)
);
reject(new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'LDAP auth configuration missing'));
});
}
const clientOptions = (options.url.startsWith("ldaps://")) ?
{ url: options.url, tlsOptions: options.tlsOptions } : { url: options.url };
const clientOptions = options.url.startsWith('ldaps://')
? { url: options.url, tlsOptions: options.tlsOptions }
: { url: options.url };
const client = ldapjs.createClient(clientOptions);
const userCn =
@@ -23,28 +19,31 @@ function validateAuthData(authData, options) {
return new Promise((resolve, reject) => {
client.bind(userCn, authData.password, ldapError => {
delete(authData.password);
delete authData.password;
if (ldapError) {
let error;
switch (ldapError.code) {
case 49:
error = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'LDAP: Wrong username or password');
error = new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'LDAP: Wrong username or password'
);
break;
case "DEPTH_ZERO_SELF_SIGNED_CERT":
case 'DEPTH_ZERO_SELF_SIGNED_CERT':
error = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'LDAPS: Certificate mismatch');
break;
default:
error = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'LDAP: Somthing went wrong (' + ldapError.code + ')');
error = new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'LDAP: Somthing went wrong (' + ldapError.code + ')'
);
}
reject(error);
client.destroy(ldapError);
return;
}
if (
typeof options.groupCn === 'string' &&
typeof options.groupFilter === 'string'
) {
if (typeof options.groupCn === 'string' && typeof options.groupFilter === 'string') {
searchForGroup(client, options, authData.id, resolve, reject);
} else {
client.unbind();
@@ -61,7 +60,7 @@ function optionsAreValid(options) {
typeof options.suffix === 'string' &&
typeof options.url === 'string' &&
(options.url.startsWith('ldap://') ||
options.url.startsWith('ldaps://') && typeof options.tlsOptions === 'object')
(options.url.startsWith('ldaps://') && typeof options.tlsOptions === 'object'))
);
}
@@ -76,12 +75,7 @@ function searchForGroup(client, options, id, resolve, reject) {
if (searchError) {
client.unbind();
client.destroy();
return reject(
new Parse.Error(
Parse.Error.INTERNAL_SERVER_ERROR,
'LDAP group search failed'
)
);
return reject(new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'LDAP group search failed'));
}
res.on('searchEntry', entry => {
if (entry.object.cn === options.groupCn) {
@@ -96,20 +90,12 @@ function searchForGroup(client, options, id, resolve, reject) {
client.unbind();
client.destroy();
return reject(
new Parse.Error(
Parse.Error.INTERNAL_SERVER_ERROR,
'LDAP: User not in group'
)
new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'LDAP: User not in group')
);
}
});
res.on('error', () => {
return reject(
new Parse.Error(
Parse.Error.INTERNAL_SERVER_ERROR,
'LDAP group search failed'
)
);
return reject(new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'LDAP group search failed'));
});
});
}

View File

@@ -8,10 +8,7 @@ function validateAuthData(authData) {
if (response && response.userId && response.userId === authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Line auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Line auth is invalid for this user.');
});
}

View File

@@ -4,17 +4,12 @@ const httpsRequest = require('./httpsRequest');
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return request('me', authData.access_token, authData.is_mobile_sdk).then(
data => {
if (data && data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Linkedin auth is invalid for this user.'
);
return request('me', authData.access_token, authData.is_mobile_sdk).then(data => {
if (data && data.id == authData.id) {
return;
}
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Linkedin auth is invalid for this user.');
});
}
// Returns a promise that fulfills iff this app id is valid.

View File

@@ -8,10 +8,7 @@ function validateAuthData(authData) {
if (data && data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Meetup auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Meetup auth is invalid for this user.');
});
}

View File

@@ -4,17 +4,15 @@ const httpsRequest = require('./httpsRequest');
// Returns a promise that fulfills if this user mail is valid.
function validateAuthData(authData) {
return request('me', authData.access_token).then(
response => {
if (response && response.id && response.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Microsoft Graph auth is invalid for this user.'
);
return request('me', authData.access_token).then(response => {
if (response && response.id && response.id == authData.id) {
return;
}
);
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Microsoft Graph auth is invalid for this user.'
);
});
}
// Returns a promise that fulfills if this app id is valid.

View File

@@ -63,8 +63,7 @@ const INVALID_ACCESS_APPID =
"OAuth2: the access_token's appID is empty or is not in the list of permitted appIDs in the auth configuration.";
const MISSING_APPIDS =
'OAuth2 configuration is missing the client app IDs ("appIds" config parameter).';
const MISSING_URL =
'OAuth2 token introspection endpoint URL is missing from configuration!';
const MISSING_URL = 'OAuth2 token introspection endpoint URL is missing from configuration!';
// Returns a promise that fulfills if this user id is valid.
function validateAuthData(authData, options) {

View File

@@ -14,10 +14,7 @@ function validateAuthData(authData) {
if (data && data.sub == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'PhantAuth auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'PhantAuth auth is invalid for this user.');
});
}

View File

@@ -4,16 +4,11 @@ var Parse = require('parse/node').Parse;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return graphRequest('me?access_token=' + authData.access_token).then(function(
data
) {
return graphRequest('me?access_token=' + authData.access_token).then(function (data) {
if (data && data.openid == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'qq auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'qq auth is invalid for this user.');
});
}
@@ -24,21 +19,16 @@ function validateAppId() {
// A promisey wrapper for qq graph requests.
function graphRequest(path) {
return httpsRequest
.get('https://graph.qq.com/oauth2.0/' + path, true)
.then(data => {
return parseResponseData(data);
});
return httpsRequest.get('https://graph.qq.com/oauth2.0/' + path, true).then(data => {
return parseResponseData(data);
});
}
function parseResponseData(data) {
const starPos = data.indexOf('(');
const endPos = data.indexOf(')');
if (starPos == -1 || endPos == -1) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'qq auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'qq auth is invalid for this user.');
}
data = data.substring(starPos + 1, endPos - 1);
return JSON.parse(data);

View File

@@ -8,10 +8,7 @@ function validateAuthData(authData) {
if (data && data.id == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Spotify auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify auth is invalid for this user.');
});
}
@@ -19,19 +16,13 @@ function validateAuthData(authData) {
function validateAppId(appIds, authData) {
var access_token = authData.access_token;
if (!appIds.length) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Spotify auth is not configured.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify auth is not configured.');
}
return request('me', access_token).then(data => {
if (data && appIds.indexOf(data.id) != -1) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Spotify auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Spotify auth is invalid for this user.');
});
}

View File

@@ -5,10 +5,7 @@ var Parse = require('parse/node').Parse;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData, options) {
if (!options) {
throw new Parse.Error(
Parse.Error.INTERNAL_SERVER_ERROR,
'Twitter auth configuration missing'
);
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Twitter auth configuration missing');
}
options = handleMultipleConfigurations(authData, options);
var client = new OAuth(options);
@@ -20,10 +17,7 @@ function validateAuthData(authData, options) {
if (data && data.id_str == '' + authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Twitter auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.');
});
}
@@ -36,20 +30,14 @@ function handleMultipleConfigurations(authData, options) {
if (Array.isArray(options)) {
const consumer_key = authData.consumer_key;
if (!consumer_key) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Twitter auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.');
}
options = options.filter(option => {
return option.consumer_key == consumer_key;
});
if (options.length == 0) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Twitter auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Twitter auth is invalid for this user.');
}
options = options[0];
}

View File

@@ -11,10 +11,7 @@ function validateAuthData(authData, params) {
if (response && response.access_token) {
return request(
'api.vk.com',
'method/users.get?access_token=' +
authData.access_token +
'&v=' +
params.apiVersion
'method/users.get?access_token=' + authData.access_token + '&v=' + params.apiVersion
).then(function (response) {
if (
response &&
@@ -24,16 +21,10 @@ function validateAuthData(authData, params) {
) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Vk auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Vk auth is invalid for this user.');
});
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Vk appIds or appSecret is incorrect.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Vk appIds or appSecret is incorrect.');
});
}

View File

@@ -4,17 +4,14 @@ var Parse = require('parse/node').Parse;
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return graphRequest(
'auth?access_token=' + authData.access_token + '&openid=' + authData.id
).then(function(data) {
if (data.errcode == 0) {
return;
return graphRequest('auth?access_token=' + authData.access_token + '&openid=' + authData.id).then(
function (data) {
if (data.errcode == 0) {
return;
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'wechat auth is invalid for this user.');
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'wechat auth is invalid for this user.'
);
});
);
}
// Returns a promise that fulfills if this app id is valid.

View File

@@ -5,14 +5,11 @@ var querystring = require('querystring');
// Returns a promise that fulfills iff this user id is valid.
function validateAuthData(authData) {
return graphRequest(authData.access_token).then(function(data) {
return graphRequest(authData.access_token).then(function (data) {
if (data && data.uid == authData.id) {
return;
}
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'weibo auth is invalid for this user.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'weibo auth is invalid for this user.');
});
}

View File

@@ -9,7 +9,7 @@ function debug() {
logger.debug.apply(logger, ['RedisCacheAdapter', ...arguments]);
}
const isValidTTL = (ttl) => typeof ttl === 'number' && ttl > 0;
const isValidTTL = ttl => typeof ttl === 'number' && ttl > 0;
export class RedisCacheAdapter {
constructor(redisCtx, ttl = DEFAULT_REDIS_TTL) {
@@ -22,8 +22,8 @@ export class RedisCacheAdapter {
if (!this.client) {
return Promise.resolve();
}
return new Promise((resolve) => {
this.client.quit((err) => {
return new Promise(resolve => {
this.client.quit(err => {
if (err) {
logger.error('RedisCacheAdapter error on shutdown', { error: err });
}
@@ -37,7 +37,7 @@ export class RedisCacheAdapter {
return this.queue.enqueue(
key,
() =>
new Promise((resolve) => {
new Promise(resolve => {
this.client.get(key, function (err, res) {
debug('-> get', key, res);
if (!res) {
@@ -62,7 +62,7 @@ export class RedisCacheAdapter {
return this.queue.enqueue(
key,
() =>
new Promise((resolve) => {
new Promise(resolve => {
this.client.set(key, value, function () {
resolve();
});
@@ -77,7 +77,7 @@ export class RedisCacheAdapter {
return this.queue.enqueue(
key,
() =>
new Promise((resolve) => {
new Promise(resolve => {
this.client.psetex(key, ttl, value, function () {
resolve();
});
@@ -90,7 +90,7 @@ export class RedisCacheAdapter {
return this.queue.enqueue(
key,
() =>
new Promise((resolve) => {
new Promise(resolve => {
this.client.del(key, function () {
resolve();
});
@@ -103,7 +103,7 @@ export class RedisCacheAdapter {
return this.queue.enqueue(
FLUSH_DB_KEY,
() =>
new Promise((resolve) => {
new Promise(resolve => {
this.client.flushdb(function () {
resolve();
});

View File

@@ -38,12 +38,7 @@ export class FilesAdapter {
*
* @return {Promise} a promise that should fail if the storage didn't succeed
*/
createFile(
filename: string,
data,
contentType: string,
options: Object
): Promise {}
createFile(filename: string, data, contentType: string, options: Object): Promise {}
/** Responsible for deleting the specified file
*
@@ -111,10 +106,7 @@ export function validateFilename(filename): ?Parse.Error {
const regx = /^[_a-zA-Z0-9][a-zA-Z0-9@. ~_-]*$/;
if (!filename.match(regx)) {
return new Parse.Error(
Parse.Error.INVALID_FILE_NAME,
'Filename contains invalid characters.'
);
return new Parse.Error(Parse.Error.INVALID_FILE_NAME, 'Filename contains invalid characters.');
}
return null;
}

View File

@@ -28,11 +28,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
this._algorithm = 'aes-256-gcm';
this._encryptionKey =
encryptionKey !== undefined
? crypto
.createHash('sha256')
.update(String(encryptionKey))
.digest('base64')
.substr(0, 32)
? crypto.createHash('sha256').update(String(encryptionKey)).digest('base64').substr(0, 32)
: null;
const defaultMongoOptions = {
useNewUrlParser: true,
@@ -43,13 +39,12 @@ export class GridFSBucketAdapter extends FilesAdapter {
_connect() {
if (!this._connectionPromise) {
this._connectionPromise = MongoClient.connect(
this._databaseURI,
this._mongoOptions
).then(client => {
this._client = client;
return client.db(client.s.options.dbName);
});
this._connectionPromise = MongoClient.connect(this._databaseURI, this._mongoOptions).then(
client => {
this._client = client;
return client.db(client.s.options.dbName);
}
);
}
return this._connectionPromise;
}
@@ -68,11 +63,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
if (this._encryptionKey !== null) {
try {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(
this._algorithm,
this._encryptionKey,
iv
);
const cipher = crypto.createCipheriv(this._algorithm, this._encryptionKey, iv);
const encryptedResult = Buffer.concat([
cipher.update(data),
cipher.final(),
@@ -126,16 +117,9 @@ export class GridFSBucketAdapter extends FilesAdapter {
const authTag = data.slice(authTagLocation);
const iv = data.slice(ivLocation, authTagLocation);
const encrypted = data.slice(0, ivLocation);
const decipher = crypto.createDecipheriv(
this._algorithm,
this._encryptionKey,
iv
);
const decipher = crypto.createDecipheriv(this._algorithm, this._encryptionKey, iv);
decipher.setAuthTag(authTag);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final(),
]);
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
return resolve(decrypted);
} catch (err) {
return reject(err);
@@ -160,10 +144,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
options.oldKey
);
} else {
oldKeyFileAdapter = new GridFSBucketAdapter(
this._databaseURI,
this._mongoOptions
);
oldKeyFileAdapter = new GridFSBucketAdapter(this._databaseURI, this._mongoOptions);
}
if (options.fileNames !== undefined) {
fileNames = options.fileNames;
@@ -186,9 +167,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
this.createFile(fileName, plainTextData)
.then(() => {
fileNamesRotated.push(fileName);
fileNamesNotRotated = fileNamesNotRotated.filter(function (
value
) {
fileNamesNotRotated = fileNamesNotRotated.filter(function (value) {
return value !== fileName;
});
fileNameIndex += 1;
@@ -223,13 +202,7 @@ export class GridFSBucketAdapter extends FilesAdapter {
}
getFileLocation(config, filename) {
return (
config.mount +
'/files/' +
config.applicationId +
'/' +
encodeURIComponent(filename)
);
return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename);
}
async getMetadata(filename) {

View File

@@ -30,13 +30,12 @@ export class GridStoreAdapter extends FilesAdapter {
_connect() {
if (!this._connectionPromise) {
this._connectionPromise = MongoClient.connect(
this._databaseURI,
this._mongoOptions
).then(client => {
this._client = client;
return client.db(client.s.options.dbName);
});
this._connectionPromise = MongoClient.connect(this._databaseURI, this._mongoOptions).then(
client => {
this._client = client;
return client.db(client.s.options.dbName);
}
);
}
return this._connectionPromise;
}
@@ -85,13 +84,7 @@ export class GridStoreAdapter extends FilesAdapter {
}
getFileLocation(config, filename) {
return (
config.mount +
'/files/' +
config.applicationId +
'/' +
encodeURIComponent(filename)
);
return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename);
}
async handleFileStream(filename: string, req, res, contentType) {
@@ -152,14 +145,14 @@ function handleRangeRequest(stream, req, res, contentType) {
'Content-Type': contentType,
});
stream.seek(start, function() {
stream.seek(start, function () {
// Get gridFile stream
const gridFileStream = stream.stream(true);
let bufferAvail = 0;
let remainingBytesToWrite = contentLength;
let totalBytesWritten = 0;
// Write to response
gridFileStream.on('data', function(data) {
gridFileStream.on('data', function (data) {
bufferAvail += data.length;
if (bufferAvail > 0) {
// slice returns the same buffer if overflowing

View File

@@ -20,11 +20,7 @@ function configureTransports(options) {
{
filename: 'parse-server.info',
json: true,
format: format.combine(
format.timestamp(),
format.splat(),
format.json()
),
format: format.combine(format.timestamp(), format.splat(), format.json()),
},
options
)
@@ -37,11 +33,7 @@ function configureTransports(options) {
{
filename: 'parse-server.err',
json: true,
format: format.combine(
format.timestamp(),
format.splat(),
format.json()
),
format: format.combine(format.timestamp(), format.splat(), format.json()),
},
options,
{ level: 'error' }
@@ -120,9 +112,7 @@ export function addTransport(transport) {
export function removeTransport(transport) {
const matchingTransport = logger.transports.find(t1 => {
return typeof transport === 'string'
? t1.name === transport
: t1 === transport;
return typeof transport === 'string' ? t1.name === transport : t1 === transport;
});
if (matchingTransport) {

View File

@@ -28,8 +28,7 @@ export class WinstonLoggerAdapter extends LoggerAdapter {
options = {};
}
// defaults to 7 days prior
const from =
options.from || new Date(Date.now() - 7 * MILLISECONDS_IN_A_DAY);
const from = options.from || new Date(Date.now() - 7 * MILLISECONDS_IN_A_DAY);
const until = options.until || new Date();
const limit = options.size || 10;
const order = options.order || 'desc';

View File

@@ -15,17 +15,7 @@ export default class MongoCollection {
// idea. Or even if this behavior is a good idea.
find(
query,
{
skip,
limit,
sort,
keys,
maxTimeMS,
readPreference,
hint,
caseInsensitive,
explain,
} = {}
{ skip, limit, sort, keys, maxTimeMS, readPreference, hint, caseInsensitive, explain } = {}
) {
// Support for Full Text Search - $text
if (keys && keys.$score) {
@@ -44,10 +34,7 @@ export default class MongoCollection {
explain,
}).catch(error => {
// Check for "no geoindex" error
if (
error.code != 17007 &&
!error.message.match(/unable to find index for .geoNear/)
) {
if (error.code != 17007 && !error.message.match(/unable to find index for .geoNear/)) {
throw error;
}
// Figure out what key needs an index
@@ -88,17 +75,7 @@ export default class MongoCollection {
_rawFind(
query,
{
skip,
limit,
sort,
keys,
maxTimeMS,
readPreference,
hint,
caseInsensitive,
explain,
} = {}
{ skip, limit, sort, keys, maxTimeMS, readPreference, hint, caseInsensitive, explain } = {}
) {
let findOperation = this._mongoCollection.find(query, {
skip,
@@ -113,9 +90,7 @@ export default class MongoCollection {
}
if (caseInsensitive) {
findOperation = findOperation.collation(
MongoCollection.caseInsensitiveCollation()
);
findOperation = findOperation.collation(MongoCollection.caseInsensitiveCollation());
}
if (maxTimeMS) {

View File

@@ -41,9 +41,7 @@ function mongoFieldToParseSchemaField(type) {
const nonFieldSchemaKeys = ['_id', '_metadata', '_client_permissions'];
function mongoSchemaFieldsToParseSchemaFields(schema) {
var fieldNames = Object.keys(schema).filter(
(key) => nonFieldSchemaKeys.indexOf(key) === -1
);
var fieldNames = Object.keys(schema).filter(key => nonFieldSchemaKeys.indexOf(key) === -1);
var response = fieldNames.reduce((obj, fieldName) => {
obj[fieldName] = mongoFieldToParseSchemaField(schema[fieldName]);
if (
@@ -110,7 +108,7 @@ function mongoSchemaToParseSchema(mongoSchema) {
function _mongoSchemaQueryFromNameQuery(name: string, query) {
const object = { _id: name };
if (query) {
Object.keys(query).forEach((key) => {
Object.keys(query).forEach(key => {
object[key] = query[key];
});
}
@@ -156,15 +154,13 @@ class MongoSchemaCollection {
}
_fetchAllSchemasFrom_SCHEMA() {
return this._collection
._rawFind({})
.then((schemas) => schemas.map(mongoSchemaToParseSchema));
return this._collection._rawFind({}).then(schemas => schemas.map(mongoSchemaToParseSchema));
}
_fetchOneSchemaFrom_SCHEMA(name: string) {
return this._collection
._rawFind(_mongoSchemaQueryFromNameQuery(name), { limit: 1 })
.then((results) => {
.then(results => {
if (results.length === 1) {
return mongoSchemaToParseSchema(results[0]);
} else {
@@ -175,22 +171,17 @@ class MongoSchemaCollection {
// Atomically find and delete an object based on query.
findAndDeleteSchema(name: string) {
return this._collection._mongoCollection.findOneAndDelete(
_mongoSchemaQueryFromNameQuery(name)
);
return this._collection._mongoCollection.findOneAndDelete(_mongoSchemaQueryFromNameQuery(name));
}
insertSchema(schema: any) {
return this._collection
.insertOne(schema)
.then((result) => mongoSchemaToParseSchema(result.ops[0]))
.catch((error) => {
.then(result => mongoSchemaToParseSchema(result.ops[0]))
.catch(error => {
if (error.code === 11000) {
//Mongo's duplicate key error
throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'Class already exists.'
);
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Class already exists.');
} else {
throw error;
}
@@ -198,17 +189,11 @@ class MongoSchemaCollection {
}
updateSchema(name: string, update) {
return this._collection.updateOne(
_mongoSchemaQueryFromNameQuery(name),
update
);
return this._collection.updateOne(_mongoSchemaQueryFromNameQuery(name), update);
}
upsertSchema(name: string, query: string, update) {
return this._collection.upsertOne(
_mongoSchemaQueryFromNameQuery(name, query),
update
);
return this._collection.upsertOne(_mongoSchemaQueryFromNameQuery(name, query), update);
}
// Add a field to the schema. If database does not support the field
@@ -225,7 +210,7 @@ class MongoSchemaCollection {
addFieldIfNotExists(className: string, fieldName: string, fieldType: string) {
return this._fetchOneSchemaFrom_SCHEMA(className)
.then(
(schema) => {
schema => {
// If a field with this name already exists, it will be handled elsewhere.
if (schema.fields[fieldName] != undefined) {
return;
@@ -235,8 +220,7 @@ class MongoSchemaCollection {
// Make sure there are not other geopoint fields
if (
Object.keys(schema.fields).some(
(existingField) =>
schema.fields[existingField].type === 'GeoPoint'
existingField => schema.fields[existingField].type === 'GeoPoint'
)
) {
throw new Parse.Error(
@@ -247,7 +231,7 @@ class MongoSchemaCollection {
}
return;
},
(error) => {
error => {
// If error is undefined, the schema doesn't exist, and we can create the schema with the field.
// If some other error, reject with it.
if (error === undefined) {

View File

@@ -2,16 +2,8 @@
import MongoCollection from './MongoCollection';
import MongoSchemaCollection from './MongoSchemaCollection';
import { StorageAdapter } from '../StorageAdapter';
import type {
SchemaType,
QueryType,
StorageClass,
QueryOptions,
} from '../StorageAdapter';
import {
parse as parseUrl,
format as formatUrl,
} from '../../../vendor/mongodbUrl';
import type { SchemaType, QueryType, StorageClass, QueryOptions } from '../StorageAdapter';
import { parse as parseUrl, format as formatUrl } from '../../../vendor/mongodbUrl';
import {
parseObjectToMongoObjectForCreate,
mongoObjectToParseObject,
@@ -45,9 +37,7 @@ const storageAdapterAllCollections = mongoAdapter => {
}
// TODO: If you have one app with a collection prefix that happens to be a prefix of another
// apps prefix, this will go very very badly. We should fix that somehow.
return (
collection.collectionName.indexOf(mongoAdapter._collectionPrefix) == 0
);
return collection.collectionName.indexOf(mongoAdapter._collectionPrefix) == 0;
});
});
};
@@ -85,16 +75,13 @@ const mongoSchemaFromFieldsAndClassNameAndCLP = (
for (const fieldName in fields) {
const { type, targetClass, ...fieldOptions } = fields[fieldName];
mongoObject[
fieldName
] = MongoSchemaCollection.parseFieldTypeToMongoFieldType({
mongoObject[fieldName] = MongoSchemaCollection.parseFieldTypeToMongoFieldType({
type,
targetClass,
});
if (fieldOptions && Object.keys(fieldOptions).length > 0) {
mongoObject._metadata = mongoObject._metadata || {};
mongoObject._metadata.fields_options =
mongoObject._metadata.fields_options || {};
mongoObject._metadata.fields_options = mongoObject._metadata.fields_options || {};
mongoObject._metadata.fields_options[fieldName] = fieldOptions;
}
}
@@ -108,11 +95,7 @@ const mongoSchemaFromFieldsAndClassNameAndCLP = (
}
}
if (
indexes &&
typeof indexes === 'object' &&
Object.keys(indexes).length > 0
) {
if (indexes && typeof indexes === 'object' && Object.keys(indexes).length > 0) {
mongoObject._metadata = mongoObject._metadata || {};
mongoObject._metadata.indexes = indexes;
}
@@ -137,11 +120,7 @@ export class MongoStorageAdapter implements StorageAdapter {
_maxTimeMS: ?number;
canSortOnJoinTables: boolean;
constructor({
uri = defaults.DefaultMongoURI,
collectionPrefix = '',
mongoOptions = {},
}: any) {
constructor({ uri = defaults.DefaultMongoURI, collectionPrefix = '', mongoOptions = {} }: any) {
this._uri = uri;
this._collectionPrefix = collectionPrefix;
this._mongoOptions = mongoOptions;
@@ -225,9 +204,7 @@ export class MongoStorageAdapter implements StorageAdapter {
classExists(name: string) {
return this.connect()
.then(() => {
return this.database
.listCollections({ name: this._collectionPrefix + name })
.toArray();
return this.database.listCollections({ name: this._collectionPrefix + name }).toArray();
})
.then(collections => {
return collections.length > 0;
@@ -262,10 +239,7 @@ export class MongoStorageAdapter implements StorageAdapter {
Object.keys(submittedIndexes).forEach(name => {
const field = submittedIndexes[name];
if (existingIndexes[name] && field.__op !== 'Delete') {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
`Index ${name} exists, cannot update.`
);
throw new Parse.Error(Parse.Error.INVALID_QUERY, `Index ${name} exists, cannot update.`);
}
if (!existingIndexes[name] && field.__op === 'Delete') {
throw new Parse.Error(
@@ -349,26 +323,15 @@ export class MongoStorageAdapter implements StorageAdapter {
schema.indexes
);
mongoObject._id = className;
return this.setIndexesWithSchemaFormat(
className,
schema.indexes,
{},
schema.fields
)
return this.setIndexesWithSchemaFormat(className, schema.indexes, {}, schema.fields)
.then(() => this._schemaCollection())
.then(schemaCollection => schemaCollection.insertSchema(mongoObject))
.catch(err => this.handleError(err));
}
addFieldIfNotExists(
className: string,
fieldName: string,
type: any
): Promise<void> {
addFieldIfNotExists(className: string, fieldName: string, type: any): Promise<void> {
return this._schemaCollection()
.then(schemaCollection =>
schemaCollection.addFieldIfNotExists(className, fieldName, type)
)
.then(schemaCollection => schemaCollection.addFieldIfNotExists(className, fieldName, type))
.then(() => this.createIndexesIfNeeded(className, fieldName, type))
.catch(err => this.handleError(err));
}
@@ -388,9 +351,7 @@ export class MongoStorageAdapter implements StorageAdapter {
})
// We've dropped the collection, now remove the _SCHEMA document
.then(() => this._schemaCollection())
.then(schemaCollection =>
schemaCollection.findAndDeleteSchema(className)
)
.then(schemaCollection => schemaCollection.findAndDeleteSchema(className))
.catch(err => this.handleError(err))
);
}
@@ -398,9 +359,7 @@ export class MongoStorageAdapter implements StorageAdapter {
deleteAllClasses(fast: boolean) {
return storageAdapterAllCollections(this).then(collections =>
Promise.all(
collections.map(collection =>
fast ? collection.deleteMany({}) : collection.drop()
)
collections.map(collection => (fast ? collection.deleteMany({}) : collection.drop()))
)
);
}
@@ -450,13 +409,9 @@ export class MongoStorageAdapter implements StorageAdapter {
});
return this._adaptiveCollection(className)
.then(collection =>
collection.updateMany(collectionFilter, collectionUpdate)
)
.then(collection => collection.updateMany(collectionFilter, collectionUpdate))
.then(() => this._schemaCollection())
.then(schemaCollection =>
schemaCollection.updateSchema(className, schemaUpdate)
)
.then(schemaCollection => schemaCollection.updateSchema(className, schemaUpdate))
.catch(err => this.handleError(err));
}
@@ -465,9 +420,7 @@ export class MongoStorageAdapter implements StorageAdapter {
// rejection reason are TBD.
getAllClasses(): Promise<StorageClass[]> {
return this._schemaCollection()
.then(schemasCollection =>
schemasCollection._fetchAllSchemasFrom_SCHEMA()
)
.then(schemasCollection => schemasCollection._fetchAllSchemasFrom_SCHEMA())
.catch(err => this.handleError(err));
}
@@ -476,31 +429,18 @@ export class MongoStorageAdapter implements StorageAdapter {
// undefined as the reason.
getClass(className: string): Promise<StorageClass> {
return this._schemaCollection()
.then(schemasCollection =>
schemasCollection._fetchOneSchemaFrom_SCHEMA(className)
)
.then(schemasCollection => schemasCollection._fetchOneSchemaFrom_SCHEMA(className))
.catch(err => this.handleError(err));
}
// TODO: As yet not particularly well specified. Creates an object. Maybe shouldn't even need the schema,
// and should infer from the type. Or maybe does need the schema for validations. Or maybe needs
// the schema only for the legacy mongo format. We'll figure that out later.
createObject(
className: string,
schema: SchemaType,
object: any,
transactionalSession: ?any
) {
createObject(className: string, schema: SchemaType, object: any, transactionalSession: ?any) {
schema = convertParseSchemaToMongoSchema(schema);
const mongoObject = parseObjectToMongoObjectForCreate(
className,
object,
schema
);
const mongoObject = parseObjectToMongoObjectForCreate(className, object, schema);
return this._adaptiveCollection(className)
.then(collection =>
collection.insertOne(mongoObject, transactionalSession)
)
.then(collection => collection.insertOne(mongoObject, transactionalSession))
.catch(error => {
if (error.code === 11000) {
// Duplicate value
@@ -510,9 +450,7 @@ export class MongoStorageAdapter implements StorageAdapter {
);
err.underlyingError = error;
if (error.message) {
const matches = error.message.match(
/index:[\sa-zA-Z0-9_\-\.]+\$?([a-zA-Z_-]+)_1/
);
const matches = error.message.match(/index:[\sa-zA-Z0-9_\-\.]+\$?([a-zA-Z_-]+)_1/);
if (matches && Array.isArray(matches)) {
err.userInfo = { duplicated_field: matches[1] };
}
@@ -543,18 +481,12 @@ export class MongoStorageAdapter implements StorageAdapter {
.then(
({ result }) => {
if (result.n === 0) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Object not found.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
}
return Promise.resolve();
},
() => {
throw new Parse.Error(
Parse.Error.INTERNAL_SERVER_ERROR,
'Database adapter error'
);
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error');
}
);
}
@@ -571,9 +503,7 @@ export class MongoStorageAdapter implements StorageAdapter {
const mongoUpdate = transformUpdate(className, update, schema);
const mongoWhere = transformWhere(className, query, schema);
return this._adaptiveCollection(className)
.then(collection =>
collection.updateMany(mongoWhere, mongoUpdate, transactionalSession)
)
.then(collection => collection.updateMany(mongoWhere, mongoUpdate, transactionalSession))
.catch(err => this.handleError(err));
}
@@ -621,9 +551,7 @@ export class MongoStorageAdapter implements StorageAdapter {
const mongoUpdate = transformUpdate(className, update, schema);
const mongoWhere = transformWhere(className, query, schema);
return this._adaptiveCollection(className)
.then(collection =>
collection.upsertOne(mongoWhere, mongoUpdate, transactionalSession)
)
.then(collection => collection.upsertOne(mongoWhere, mongoUpdate, transactionalSession))
.catch(err => this.handleError(err));
}
@@ -632,16 +560,7 @@ export class MongoStorageAdapter implements StorageAdapter {
className: string,
schema: SchemaType,
query: QueryType,
{
skip,
limit,
sort,
keys,
readPreference,
hint,
caseInsensitive,
explain,
}: QueryOptions
{ skip, limit, sort, keys, readPreference, hint, caseInsensitive, explain }: QueryOptions
): Promise<any> {
schema = convertParseSchemaToMongoSchema(schema);
const mongoWhere = transformWhere(className, query, schema);
@@ -689,9 +608,7 @@ export class MongoStorageAdapter implements StorageAdapter {
if (explain) {
return objects;
}
return objects.map(object =>
mongoObjectToParseObject(className, object, schema)
);
return objects.map(object => mongoObjectToParseObject(className, object, schema));
})
.catch(err => this.handleError(err));
}
@@ -706,18 +623,14 @@ export class MongoStorageAdapter implements StorageAdapter {
): Promise<any> {
schema = convertParseSchemaToMongoSchema(schema);
const indexCreationRequest = {};
const mongoFieldNames = fieldNames.map(fieldName =>
transformKey(className, fieldName, schema)
);
const mongoFieldNames = fieldNames.map(fieldName => transformKey(className, fieldName, schema));
mongoFieldNames.forEach(fieldName => {
indexCreationRequest[fieldName] =
options.indexType !== undefined ? options.indexType : 1;
indexCreationRequest[fieldName] = options.indexType !== undefined ? options.indexType : 1;
});
const defaultOptions: Object = { background: true, sparse: true };
const indexNameOptions: Object = indexName ? { name: indexName } : {};
const ttlOptions: Object =
options.ttl !== undefined ? { expireAfterSeconds: options.ttl } : {};
const ttlOptions: Object = options.ttl !== undefined ? { expireAfterSeconds: options.ttl } : {};
const caseInsensitiveOptions: Object = caseInsensitive
? { collation: MongoCollection.caseInsensitiveCollation() }
: {};
@@ -732,10 +645,8 @@ export class MongoStorageAdapter implements StorageAdapter {
.then(
collection =>
new Promise((resolve, reject) =>
collection._mongoCollection.createIndex(
indexCreationRequest,
indexOptions,
error => (error ? reject(error) : resolve())
collection._mongoCollection.createIndex(indexCreationRequest, indexOptions, error =>
error ? reject(error) : resolve()
)
)
)
@@ -747,23 +658,15 @@ export class MongoStorageAdapter implements StorageAdapter {
// As such, we shouldn't expose this function to users of parse until we have an out-of-band
// Way of determining if a field is nullable. Undefined doesn't count against uniqueness,
// which is why we use sparse indexes.
ensureUniqueness(
className: string,
schema: SchemaType,
fieldNames: string[]
) {
ensureUniqueness(className: string, schema: SchemaType, fieldNames: string[]) {
schema = convertParseSchemaToMongoSchema(schema);
const indexCreationRequest = {};
const mongoFieldNames = fieldNames.map(fieldName =>
transformKey(className, fieldName, schema)
);
const mongoFieldNames = fieldNames.map(fieldName => transformKey(className, fieldName, schema));
mongoFieldNames.forEach(fieldName => {
indexCreationRequest[fieldName] = 1;
});
return this._adaptiveCollection(className)
.then(collection =>
collection._ensureSparseUniqueIndexInBackground(indexCreationRequest)
)
.then(collection => collection._ensureSparseUniqueIndexInBackground(indexCreationRequest))
.catch(error => {
if (error.code === 11000) {
throw new Parse.Error(
@@ -808,23 +711,14 @@ export class MongoStorageAdapter implements StorageAdapter {
.catch(err => this.handleError(err));
}
distinct(
className: string,
schema: SchemaType,
query: QueryType,
fieldName: string
) {
distinct(className: string, schema: SchemaType, query: QueryType, fieldName: string) {
schema = convertParseSchemaToMongoSchema(schema);
const isPointerField =
schema.fields[fieldName] && schema.fields[fieldName].type === 'Pointer';
const isPointerField = schema.fields[fieldName] && schema.fields[fieldName].type === 'Pointer';
const transformField = transformKey(className, fieldName, schema);
return this._adaptiveCollection(className)
.then(collection =>
collection.distinct(
transformField,
transformWhere(className, query, schema)
)
collection.distinct(transformField, transformWhere(className, query, schema))
)
.then(objects => {
objects = objects.filter(obj => obj != null);
@@ -862,16 +756,10 @@ export class MongoStorageAdapter implements StorageAdapter {
stage.$match = this._parseAggregateArgs(schema, stage.$match);
}
if (stage.$project) {
stage.$project = this._parseAggregateProjectArgs(
schema,
stage.$project
);
stage.$project = this._parseAggregateProjectArgs(schema, stage.$project);
}
if (stage.$geoNear && stage.$geoNear.query) {
stage.$geoNear.query = this._parseAggregateArgs(
schema,
stage.$geoNear.query
);
stage.$geoNear.query = this._parseAggregateArgs(schema, stage.$geoNear.query);
}
return stage;
});
@@ -894,8 +782,7 @@ export class MongoStorageAdapter implements StorageAdapter {
if (
result._id == null ||
result._id == undefined ||
(['object', 'string'].includes(typeof result._id) &&
_.isEmpty(result._id))
(['object', 'string'].includes(typeof result._id) && _.isEmpty(result._id))
) {
result._id = null;
}
@@ -905,11 +792,7 @@ export class MongoStorageAdapter implements StorageAdapter {
});
return results;
})
.then(objects =>
objects.map(object =>
mongoObjectToParseObject(className, object, schema)
)
)
.then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)))
.catch(err => this.handleError(err));
}
@@ -945,20 +828,12 @@ export class MongoStorageAdapter implements StorageAdapter {
// Pass objects down to MongoDB...this is more than likely an $exists operator.
returnValue[`_p_${field}`] = pipeline[field];
} else {
returnValue[
`_p_${field}`
] = `${schema.fields[field].targetClass}$${pipeline[field]}`;
returnValue[`_p_${field}`] = `${schema.fields[field].targetClass}$${pipeline[field]}`;
}
} else if (
schema.fields[field] &&
schema.fields[field].type === 'Date'
) {
} else if (schema.fields[field] && schema.fields[field].type === 'Date') {
returnValue[field] = this._convertToDate(pipeline[field]);
} else {
returnValue[field] = this._parseAggregateArgs(
schema,
pipeline[field]
);
returnValue[field] = this._parseAggregateArgs(schema, pipeline[field]);
}
if (field === 'objectId') {
@@ -1011,16 +886,11 @@ export class MongoStorageAdapter implements StorageAdapter {
// updatedAt or objectId and change it accordingly.
_parseAggregateGroupArgs(schema: any, pipeline: any): any {
if (Array.isArray(pipeline)) {
return pipeline.map(value =>
this._parseAggregateGroupArgs(schema, value)
);
return pipeline.map(value => this._parseAggregateGroupArgs(schema, value));
} else if (typeof pipeline === 'object') {
const returnValue = {};
for (const field in pipeline) {
returnValue[field] = this._parseAggregateGroupArgs(
schema,
pipeline[field]
);
returnValue[field] = this._parseAggregateGroupArgs(schema, pipeline[field]);
}
return returnValue;
} else if (typeof pipeline === 'string') {
@@ -1077,10 +947,7 @@ export class MongoStorageAdapter implements StorageAdapter {
case '':
break;
default:
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
'Not supported read preference.'
);
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Not supported read preference.');
}
return readPreference;
}
@@ -1111,11 +978,7 @@ export class MongoStorageAdapter implements StorageAdapter {
return Promise.resolve();
}
createTextIndexesIfNeeded(
className: string,
query: QueryType,
schema: any
): Promise<void> {
createTextIndexesIfNeeded(className: string, query: QueryType, schema: any): Promise<void> {
for (const fieldName in query) {
if (!query[fieldName] || !query[fieldName].$text) {
continue;

View File

@@ -20,27 +20,16 @@ const transformKey = (className, fieldName, schema) => {
return 'times_used';
}
if (
schema.fields[fieldName] &&
schema.fields[fieldName].__type == 'Pointer'
) {
if (schema.fields[fieldName] && schema.fields[fieldName].__type == 'Pointer') {
fieldName = '_p_' + fieldName;
} else if (
schema.fields[fieldName] &&
schema.fields[fieldName].type == 'Pointer'
) {
} else if (schema.fields[fieldName] && schema.fields[fieldName].type == 'Pointer') {
fieldName = '_p_' + fieldName;
}
return fieldName;
};
const transformKeyValueForUpdate = (
className,
restKey,
restValue,
parseFormatSchema
) => {
const transformKeyValueForUpdate = (className, restKey, restValue, parseFormatSchema) => {
// Check if the schema is known since it's a built-in field.
var key = restKey;
var timeField = false;
@@ -109,11 +98,8 @@ const transformKeyValueForUpdate = (
}
if (
(parseFormatSchema.fields[key] &&
parseFormatSchema.fields[key].type === 'Pointer') ||
(!parseFormatSchema.fields[key] &&
restValue &&
restValue.__type == 'Pointer')
(parseFormatSchema.fields[key] && parseFormatSchema.fields[key].type === 'Pointer') ||
(!parseFormatSchema.fields[key] && restValue && restValue.__type == 'Pointer')
) {
key = '_p_' + key;
}
@@ -179,7 +165,7 @@ const isAllValuesRegexOrNone = values => {
};
const isAnyValueRegex = values => {
return values.some(function(value) {
return values.some(function (value) {
return isRegex(value);
});
};
@@ -292,9 +278,7 @@ function transformQueryKeyValue(className, key, value, schema, count = false) {
case '$nor':
return {
key: key,
value: value.map(subQuery =>
transformWhere(className, subQuery, schema, count)
),
value: value.map(subQuery => transformWhere(className, subQuery, schema, count)),
};
case 'lastUsed':
if (valueAsDate(value)) {
@@ -315,17 +299,13 @@ function transformQueryKeyValue(className, key, value, schema, count = false) {
}
}
const expectedTypeIsArray =
schema && schema.fields[key] && schema.fields[key].type === 'Array';
const expectedTypeIsArray = schema && schema.fields[key] && schema.fields[key].type === 'Array';
const expectedTypeIsPointer =
schema && schema.fields[key] && schema.fields[key].type === 'Pointer';
const field = schema && schema.fields[key];
if (
expectedTypeIsPointer ||
(!schema && value && value.__type === 'Pointer')
) {
if (expectedTypeIsPointer || (!schema && value && value.__type === 'Pointer')) {
key = '_p_' + key;
}
@@ -362,23 +342,13 @@ function transformQueryKeyValue(className, key, value, schema, count = false) {
function transformWhere(className, restWhere, schema, count = false) {
const mongoWhere = {};
for (const restKey in restWhere) {
const out = transformQueryKeyValue(
className,
restKey,
restWhere[restKey],
schema,
count
);
const out = transformQueryKeyValue(className, restKey, restWhere[restKey], schema, count);
mongoWhere[out.key] = out.value;
}
return mongoWhere;
}
const parseObjectKeyValueToMongoObjectKeyValue = (
restKey,
restValue,
schema
) => {
const parseObjectKeyValueToMongoObjectKeyValue = (restKey, restValue, schema) => {
// Check if the schema is known since it's a built-in field.
let transformedValue;
let coercedToDate;
@@ -388,37 +358,27 @@ const parseObjectKeyValueToMongoObjectKeyValue = (
case 'expiresAt':
transformedValue = transformTopLevelAtom(restValue);
coercedToDate =
typeof transformedValue === 'string'
? new Date(transformedValue)
: transformedValue;
typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue;
return { key: 'expiresAt', value: coercedToDate };
case '_email_verify_token_expires_at':
transformedValue = transformTopLevelAtom(restValue);
coercedToDate =
typeof transformedValue === 'string'
? new Date(transformedValue)
: transformedValue;
typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue;
return { key: '_email_verify_token_expires_at', value: coercedToDate };
case '_account_lockout_expires_at':
transformedValue = transformTopLevelAtom(restValue);
coercedToDate =
typeof transformedValue === 'string'
? new Date(transformedValue)
: transformedValue;
typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue;
return { key: '_account_lockout_expires_at', value: coercedToDate };
case '_perishable_token_expires_at':
transformedValue = transformTopLevelAtom(restValue);
coercedToDate =
typeof transformedValue === 'string'
? new Date(transformedValue)
: transformedValue;
typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue;
return { key: '_perishable_token_expires_at', value: coercedToDate };
case '_password_changed_at':
transformedValue = transformTopLevelAtom(restValue);
coercedToDate =
typeof transformedValue === 'string'
? new Date(transformedValue)
: transformedValue;
typeof transformedValue === 'string' ? new Date(transformedValue) : transformedValue;
return { key: '_password_changed_at', value: coercedToDate };
case '_failed_login_count':
case '_rperm':
@@ -432,10 +392,7 @@ const parseObjectKeyValueToMongoObjectKeyValue = (
default:
// Auth data should have been transformed already
if (restKey.match(/^authData\.([a-zA-Z0-9_]+)\.id$/)) {
throw new Parse.Error(
Parse.Error.INVALID_KEY_NAME,
'can only query on ' + restKey
);
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'can only query on ' + restKey);
}
// Trust that the auth data has been transformed and save it directly
if (restKey.match(/^_auth_data_[a-zA-Z0-9_]+$/)) {
@@ -473,9 +430,7 @@ const parseObjectKeyValueToMongoObjectKeyValue = (
}
// Handle normal objects by recursing
if (
Object.keys(restValue).some(key => key.includes('$') || key.includes('.'))
) {
if (Object.keys(restValue).some(key => key.includes('$') || key.includes('.'))) {
throw new Parse.Error(
Parse.Error.INVALID_NESTED_KEY,
"Nested keys should not contain the '$' or '.' characters"
@@ -504,15 +459,11 @@ const parseObjectToMongoObjectForCreate = (className, restCreate, schema) => {
// Use the legacy mongo format for createdAt and updatedAt
if (mongoCreate.createdAt) {
mongoCreate._created_at = new Date(
mongoCreate.createdAt.iso || mongoCreate.createdAt
);
mongoCreate._created_at = new Date(mongoCreate.createdAt.iso || mongoCreate.createdAt);
delete mongoCreate.createdAt;
}
if (mongoCreate.updatedAt) {
mongoCreate._updated_at = new Date(
mongoCreate.updatedAt.iso || mongoCreate.updatedAt
);
mongoCreate._updated_at = new Date(mongoCreate.updatedAt.iso || mongoCreate.updatedAt);
delete mongoCreate.updatedAt;
}
@@ -593,22 +544,14 @@ function CannotTransform() {}
const transformInteriorAtom = atom => {
// TODO: check validity harder for the __type-defined types
if (
typeof atom === 'object' &&
atom &&
!(atom instanceof Date) &&
atom.__type === 'Pointer'
) {
if (typeof atom === 'object' && atom && !(atom instanceof Date) && atom.__type === 'Pointer') {
return {
__type: 'Pointer',
className: atom.className,
objectId: atom.objectId,
};
} else if (typeof atom === 'function' || typeof atom === 'symbol') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`cannot transform value: ${atom}`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `cannot transform value: ${atom}`);
} else if (DateCoder.isValidJSON(atom)) {
return DateCoder.JSONToDatabase(atom);
} else if (BytesCoder.isValidJSON(atom)) {
@@ -640,10 +583,7 @@ function transformTopLevelAtom(atom, field) {
return atom;
case 'symbol':
case 'function':
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`cannot transform value: ${atom}`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `cannot transform value: ${atom}`);
case 'object':
if (atom instanceof Date) {
// Technically dates are not rest format, but, it seems pretty
@@ -822,16 +762,11 @@ function transformConstraint(constraint, field, count = false) {
if (typeof constraint !== 'object' || !constraint) {
return CannotTransform;
}
const transformFunction = inArray
? transformInteriorAtom
: transformTopLevelAtom;
const transformFunction = inArray ? transformInteriorAtom : transformTopLevelAtom;
const transformer = atom => {
const result = transformFunction(atom, field);
if (result === CannotTransform) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad atom: ${JSON.stringify(atom)}`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad atom: ${JSON.stringify(atom)}`);
}
return result;
};
@@ -839,9 +774,7 @@ function transformConstraint(constraint, field, count = false) {
// This is a hack so that:
// $regex is handled before $options
// $nearSphere is handled before $maxDistance
var keys = Object.keys(constraint)
.sort()
.reverse();
var keys = Object.keys(constraint).sort().reverse();
var answer = {};
for (var key of keys) {
switch (key) {
@@ -892,10 +825,7 @@ function transformConstraint(constraint, field, count = false) {
case '$nin': {
const arr = constraint[key];
if (!(arr instanceof Array)) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'bad ' + key + ' value'
);
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + key + ' value');
}
answer[key] = _.flatMap(arr, value => {
return (atom => {
@@ -911,10 +841,7 @@ function transformConstraint(constraint, field, count = false) {
case '$all': {
const arr = constraint[key];
if (!(arr instanceof Array)) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'bad ' + key + ' value'
);
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad ' + key + ' value');
}
answer[key] = arr.map(transformInteriorAtom);
@@ -939,10 +866,7 @@ function transformConstraint(constraint, field, count = false) {
case '$containedBy': {
const arr = constraint[key];
if (!(arr instanceof Array)) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $containedBy: should be an array`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $containedBy: should be an array`);
}
answer.$elemMatch = {
$nin: arr.map(transformer),
@@ -956,33 +880,21 @@ function transformConstraint(constraint, field, count = false) {
case '$text': {
const search = constraint[key].$search;
if (typeof search !== 'object') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $text: $search, should be object`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $search, should be object`);
}
if (!search.$term || typeof search.$term !== 'string') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $text: $term, should be string`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $term, should be string`);
} else {
answer[key] = {
$search: search.$term,
};
}
if (search.$language && typeof search.$language !== 'string') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $text: $language, should be string`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $language, should be string`);
} else if (search.$language) {
answer[key].$language = search.$language;
}
if (
search.$caseSensitive &&
typeof search.$caseSensitive !== 'boolean'
) {
if (search.$caseSensitive && typeof search.$caseSensitive !== 'boolean') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $text: $caseSensitive, should be boolean`
@@ -990,10 +902,7 @@ function transformConstraint(constraint, field, count = false) {
} else if (search.$caseSensitive) {
answer[key].$caseSensitive = search.$caseSensitive;
}
if (
search.$diacriticSensitive &&
typeof search.$diacriticSensitive !== 'boolean'
) {
if (search.$diacriticSensitive && typeof search.$diacriticSensitive !== 'boolean') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $text: $diacriticSensitive, should be boolean`
@@ -1007,10 +916,7 @@ function transformConstraint(constraint, field, count = false) {
const point = constraint[key];
if (count) {
answer.$geoWithin = {
$centerSphere: [
[point.longitude, point.latitude],
constraint.$maxDistance,
],
$centerSphere: [[point.longitude, point.latitude], constraint.$maxDistance],
};
} else {
answer[key] = [point.longitude, point.latitude];
@@ -1046,10 +952,7 @@ function transformConstraint(constraint, field, count = false) {
case '$within':
var box = constraint[key]['$box'];
if (!box || box.length != 2) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'malformatted $within arg'
);
throw new Parse.Error(Parse.Error.INVALID_JSON, 'malformatted $within arg');
}
answer[key] = {
$box: [
@@ -1092,10 +995,7 @@ function transformConstraint(constraint, field, count = false) {
return point;
}
if (!GeoPointCoder.isValidJSON(point)) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'bad $geoWithin value'
);
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value');
} else {
Parse.GeoPoint._validate(point.latitude, point.longitude);
}
@@ -1156,10 +1056,7 @@ function transformConstraint(constraint, field, count = false) {
}
default:
if (key.match(/^\$+/)) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'bad constraint: ' + key
);
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad constraint: ' + key);
}
return CannotTransform;
}
@@ -1188,10 +1085,7 @@ function transformUpdateOperator({ __op, amount, objects }, flatten) {
case 'Increment':
if (typeof amount !== 'number') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'incrementing must provide a number'
);
throw new Parse.Error(Parse.Error.INVALID_JSON, 'incrementing must provide a number');
}
if (flatten) {
return amount;
@@ -1202,10 +1096,7 @@ function transformUpdateOperator({ __op, amount, objects }, flatten) {
case 'Add':
case 'AddUnique':
if (!(objects instanceof Array)) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'objects to add must be an array'
);
throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to add must be an array');
}
var toAdd = objects.map(transformInteriorAtom);
if (flatten) {
@@ -1220,10 +1111,7 @@ function transformUpdateOperator({ __op, amount, objects }, flatten) {
case 'Remove':
if (!(objects instanceof Array)) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'objects to remove must be an array'
);
throw new Parse.Error(Parse.Error.INVALID_JSON, 'objects to remove must be an array');
}
var toRemove = objects.map(transformInteriorAtom);
if (flatten) {
@@ -1379,15 +1267,11 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => {
break;
case 'updatedAt':
case '_updated_at':
restObject['updatedAt'] = Parse._encode(
new Date(mongoObject[key])
).iso;
restObject['updatedAt'] = Parse._encode(new Date(mongoObject[key])).iso;
break;
case 'createdAt':
case '_created_at':
restObject['createdAt'] = Parse._encode(
new Date(mongoObject[key])
).iso;
restObject['createdAt'] = Parse._encode(new Date(mongoObject[key])).iso;
break;
case 'expiresAt':
case '_expiresAt':
@@ -1395,9 +1279,7 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => {
break;
case 'lastUsed':
case '_last_used':
restObject['lastUsed'] = Parse._encode(
new Date(mongoObject[key])
).iso;
restObject['lastUsed'] = Parse._encode(new Date(mongoObject[key])).iso;
break;
case 'timesUsed':
case 'times_used':
@@ -1445,11 +1327,7 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => {
if (mongoObject[key] === null) {
break;
}
restObject[newKey] = transformPointerString(
schema,
newKey,
mongoObject[key]
);
restObject[newKey] = transformPointerString(schema, newKey, mongoObject[key]);
break;
} else if (key[0] == '_' && key != '__type') {
throw 'bad key in untransform: ' + key;
@@ -1488,9 +1366,7 @@ const mongoObjectToParseObject = (className, mongoObject, schema) => {
break;
}
}
restObject[key] = nestedMongoObjectToNestedParseObject(
mongoObject[key]
);
restObject[key] = nestedMongoObjectToNestedParseObject(mongoObject[key]);
}
}
@@ -1518,16 +1394,12 @@ var DateCoder = {
},
isValidJSON(value) {
return (
typeof value === 'object' && value !== null && value.__type === 'Date'
);
return typeof value === 'object' && value !== null && value.__type === 'Date';
},
};
var BytesCoder = {
base64Pattern: new RegExp(
'^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$'
),
base64Pattern: new RegExp('^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$'),
isBase64Value(object) {
if (typeof object !== 'string') {
return false;
@@ -1557,9 +1429,7 @@ var BytesCoder = {
},
isValidJSON(value) {
return (
typeof value === 'object' && value !== null && value.__type === 'Bytes'
);
return typeof value === 'object' && value !== null && value.__type === 'Bytes';
},
};
@@ -1581,9 +1451,7 @@ var GeoPointCoder = {
},
isValidJSON(value) {
return (
typeof value === 'object' && value !== null && value.__type === 'GeoPoint'
);
return typeof value === 'object' && value !== null && value.__type === 'GeoPoint';
},
};
@@ -1648,9 +1516,7 @@ var PolygonCoder = {
},
isValidJSON(value) {
return (
typeof value === 'object' && value !== null && value.__type === 'Polygon'
);
return typeof value === 'object' && value !== null && value.__type === 'Polygon';
},
};
@@ -1671,9 +1537,7 @@ var FileCoder = {
},
isValidJSON(value) {
return (
typeof value === 'object' && value !== null && value.__type === 'File'
);
return typeof value === 'object' && value !== null && value.__type === 'File';
},
};

View File

@@ -9,9 +9,7 @@ function getDatabaseOptionsFromURI(uri) {
databaseOptions.host = parsedURI.hostname || 'localhost';
databaseOptions.port = parsedURI.port ? parseInt(parsedURI.port) : 5432;
databaseOptions.database = parsedURI.pathname
? parsedURI.pathname.substr(1)
: undefined;
databaseOptions.database = parsedURI.pathname ? parsedURI.pathname.substr(1) : undefined;
databaseOptions.user = authParts.length > 0 ? authParts[0] : '';
databaseOptions.password = authParts.length > 1 ? authParts[1] : '';
@@ -55,14 +53,11 @@ function getDatabaseOptionsFromURI(uri) {
}
databaseOptions.binary =
queryParams.binary && queryParams.binary.toLowerCase() === 'true'
? true
: false;
queryParams.binary && queryParams.binary.toLowerCase() === 'true' ? true : false;
databaseOptions.client_encoding = queryParams.client_encoding;
databaseOptions.application_name = queryParams.application_name;
databaseOptions.fallback_application_name =
queryParams.fallback_application_name;
databaseOptions.fallback_application_name = queryParams.fallback_application_name;
if (queryParams.poolSize) {
databaseOptions.poolSize = parseInt(queryParams.poolSize) || 10;
@@ -77,8 +72,7 @@ function getDatabaseOptionsFromURI(uri) {
databaseOptions.idleTimeoutMillis = parseInt(queryParams.idleTimeoutMillis);
}
if (queryParams.keepAlive) {
databaseOptions.keepAlive =
queryParams.keepAlive.toLowerCase() === 'true' ? true : false;
databaseOptions.keepAlive = queryParams.keepAlive.toLowerCase() === 'true' ? true : false;
}
return databaseOptions;

View File

@@ -253,12 +253,7 @@ interface WhereClause {
sorts: Array<any>;
}
const buildWhereClause = ({
schema,
query,
index,
caseInsensitive,
}): WhereClause => {
const buildWhereClause = ({ schema, query, index, caseInsensitive }): WhereClause => {
const patterns = [];
let values = [];
const sorts = [];
@@ -266,9 +261,7 @@ const buildWhereClause = ({
schema = toPostgresSchema(schema);
for (const fieldName in query) {
const isArrayField =
schema.fields &&
schema.fields[fieldName] &&
schema.fields[fieldName].type === 'Array';
schema.fields && schema.fields[fieldName] && schema.fields[fieldName].type === 'Array';
const initialPatternsLength = patterns.length;
const fieldValue = query[fieldName];
@@ -284,10 +277,7 @@ const buildWhereClause = ({
if (authDataMatch) {
// TODO: Handle querying by _auth_data_provider, authData is stored in authData field
continue;
} else if (
caseInsensitive &&
(fieldName === 'username' || fieldName === 'email')
) {
} else if (caseInsensitive && (fieldName === 'username' || fieldName === 'email')) {
patterns.push(`LOWER($${index}:name) = LOWER($${index + 1})`);
values.push(fieldName, fieldValue);
index += 2;
@@ -324,10 +314,7 @@ const buildWhereClause = ({
} else if (typeof fieldValue === 'boolean') {
patterns.push(`$${index}:name = $${index + 1}`);
// Can't cast boolean to double precision
if (
schema.fields[fieldName] &&
schema.fields[fieldName].type === 'Number'
) {
if (schema.fields[fieldName] && schema.fields[fieldName].type === 'Number') {
// Should always return zero results
const MAX_INT_PLUS_ONE = 9223372036854775808;
values.push(fieldName, MAX_INT_PLUS_ONE);
@@ -377,9 +364,7 @@ const buildWhereClause = ({
// if not null, we need to manually exclude null
if (fieldValue.$ne.__type === 'GeoPoint') {
patterns.push(
`($${index}:name <> POINT($${index + 1}, $${
index + 2
}) OR $${index}:name IS NULL)`
`($${index}:name <> POINT($${index + 1}, $${index + 2}) OR $${index}:name IS NULL)`
);
} else {
if (fieldName.indexOf('.') >= 0) {
@@ -388,9 +373,7 @@ const buildWhereClause = ({
`(${constraintFieldName} <> $${index} OR ${constraintFieldName} IS NULL)`
);
} else {
patterns.push(
`($${index}:name <> $${index + 1} OR $${index}:name IS NULL)`
);
patterns.push(`($${index}:name <> $${index + 1} OR $${index}:name IS NULL)`);
}
}
}
@@ -421,8 +404,7 @@ const buildWhereClause = ({
}
}
}
const isInOrNin =
Array.isArray(fieldValue.$in) || Array.isArray(fieldValue.$nin);
const isInOrNin = Array.isArray(fieldValue.$in) || Array.isArray(fieldValue.$nin);
if (
Array.isArray(fieldValue.$in) &&
isArrayField &&
@@ -441,9 +423,7 @@ const buildWhereClause = ({
}
});
if (allowNull) {
patterns.push(
`($${index}:name IS NULL OR $${index}:name && ARRAY[${inPatterns.join()}])`
);
patterns.push(`($${index}:name IS NULL OR $${index}:name && ARRAY[${inPatterns.join()}])`);
} else {
patterns.push(`$${index}:name && ARRAY[${inPatterns.join()}]`);
}
@@ -453,9 +433,7 @@ const buildWhereClause = ({
const not = notIn ? ' NOT ' : '';
if (baseArray.length > 0) {
if (isArrayField) {
patterns.push(
`${not} array_contains($${index}:name, $${index + 1})`
);
patterns.push(`${not} array_contains($${index}:name, $${index + 1})`);
values.push(fieldName, JSON.stringify(baseArray));
index += 2;
} else {
@@ -518,13 +496,9 @@ const buildWhereClause = ({
const value = processRegexPattern(fieldValue.$all[i].$regex);
fieldValue.$all[i] = value.substring(1) + '%';
}
patterns.push(
`array_contains_all_regex($${index}:name, $${index + 1}::jsonb)`
);
patterns.push(`array_contains_all_regex($${index}:name, $${index + 1}::jsonb)`);
} else {
patterns.push(
`array_contains_all($${index}:name, $${index + 1}::jsonb)`
);
patterns.push(`array_contains_all($${index}:name, $${index + 1}::jsonb)`);
}
values.push(fieldName, JSON.stringify(fieldValue.$all));
index += 2;
@@ -549,10 +523,7 @@ const buildWhereClause = ({
if (fieldValue.$containedBy) {
const arr = fieldValue.$containedBy;
if (!(arr instanceof Array)) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $containedBy: should be an array`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $containedBy: should be an array`);
}
patterns.push(`$${index}:name <@ $${index + 1}::jsonb`);
@@ -564,22 +535,13 @@ const buildWhereClause = ({
const search = fieldValue.$text.$search;
let language = 'english';
if (typeof search !== 'object') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $text: $search, should be object`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $search, should be object`);
}
if (!search.$term || typeof search.$term !== 'string') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $text: $term, should be string`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $term, should be string`);
}
if (search.$language && typeof search.$language !== 'string') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $text: $language, should be string`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `bad $text: $language, should be string`);
} else if (search.$language) {
language = search.$language;
}
@@ -594,10 +556,7 @@ const buildWhereClause = ({
`bad $text: $caseSensitive not supported, please use $regex or create a separate lower case column.`
);
}
if (
search.$diacriticSensitive &&
typeof search.$diacriticSensitive !== 'boolean'
) {
if (search.$diacriticSensitive && typeof search.$diacriticSensitive !== 'boolean') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`bad $text: $diacriticSensitive, should be boolean`
@@ -609,9 +568,7 @@ const buildWhereClause = ({
);
}
patterns.push(
`to_tsvector($${index}, $${index + 1}:name) @@ to_tsquery($${
index + 2
}, $${index + 3})`
`to_tsvector($${index}, $${index + 1}:name) @@ to_tsquery($${index + 2}, $${index + 3})`
);
values.push(language, fieldName, language, search.$term);
index += 4;
@@ -716,10 +673,7 @@ const buildWhereClause = ({
return `(${point[0]}, ${point[1]})`;
}
if (typeof point !== 'object' || point.__type !== 'GeoPoint') {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
'bad $geoWithin value'
);
throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $geoWithin value');
} else {
Parse.GeoPoint._validate(point.latitude, point.longitude);
}
@@ -830,9 +784,7 @@ const buildWhereClause = ({
if (initialPatternsLength === patterns.length) {
throw new Parse.Error(
Parse.Error.OPERATION_FORBIDDEN,
`Postgres doesn't support this query type yet ${JSON.stringify(
fieldValue
)}`
`Postgres doesn't support this query type yet ${JSON.stringify(fieldValue)}`
);
}
}
@@ -903,12 +855,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
const self = this;
await this._client.task('set-class-level-permissions', async t => {
await self._ensureSchemaCollectionExists(t);
const values = [
className,
'schema',
'classLevelPermissions',
JSON.stringify(CLPs),
];
const values = [className, 'schema', 'classLevelPermissions', JSON.stringify(CLPs)];
await t.none(
`UPDATE "_SCHEMA" SET $2:name = json_object_set_key($2:name, $3::text, $4::jsonb) WHERE "className" = $1`,
values
@@ -936,10 +883,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
Object.keys(submittedIndexes).forEach(name => {
const field = submittedIndexes[name];
if (existingIndexes[name] && field.__op !== 'Delete') {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
`Index ${name} exists, cannot update.`
);
throw new Parse.Error(Parse.Error.INVALID_QUERY, `Index ${name} exists, cannot update.`);
}
if (!existingIndexes[name] && field.__op === 'Delete') {
throw new Parse.Error(
@@ -990,24 +934,12 @@ export class PostgresStorageAdapter implements StorageAdapter {
'INSERT INTO "_SCHEMA" ("className", "schema", "isParseClass") VALUES ($<className>, $<schema>, true)',
{ className, schema }
);
await this.setIndexesWithSchemaFormat(
className,
schema.indexes,
{},
schema.fields,
t
);
await this.setIndexesWithSchemaFormat(className, schema.indexes, {}, schema.fields, t);
return toParseSchema(schema);
})
.catch(err => {
if (
err.code === PostgresUniqueIndexViolationError &&
err.detail.includes(className)
) {
throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
`Class ${className} already exists.`
);
if (err.code === PostgresUniqueIndexViolationError && err.detail.includes(className)) {
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, `Class ${className} already exists.`);
}
throw err;
});
@@ -1093,24 +1025,14 @@ export class PostgresStorageAdapter implements StorageAdapter {
const newColumns = Object.keys(schema.fields)
.filter(item => columns.indexOf(item) === -1)
.map(fieldName =>
self.addFieldIfNotExists(
className,
fieldName,
schema.fields[fieldName],
t
)
self.addFieldIfNotExists(className, fieldName, schema.fields[fieldName], t)
);
await t.batch(newColumns);
});
}
async addFieldIfNotExists(
className: string,
fieldName: string,
type: any,
conn: any
) {
async addFieldIfNotExists(className: string, fieldName: string, type: any, conn: any) {
// TODO: Must be revised for invalid logic...
debug('addFieldIfNotExists', { className, fieldName, type });
conn = conn || this._client;
@@ -1128,11 +1050,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
);
} catch (error) {
if (error.code === PostgresRelationDoesNotExistError) {
return self.createClass(
className,
{ fields: { [fieldName]: type } },
t
);
return self.createClass(className, { fields: { [fieldName]: type } }, t);
}
if (error.code !== PostgresDuplicateColumnError) {
throw error;
@@ -1234,11 +1152,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
// may do so.
// Returns a Promise.
async deleteFields(
className: string,
schema: SchemaType,
fieldNames: string[]
): Promise<void> {
async deleteFields(className: string, schema: SchemaType, fieldNames: string[]): Promise<void> {
debug('deleteFields', className, fieldNames);
fieldNames = fieldNames.reduce((list: Array<string>, fieldName: string) => {
const field = schema.fields[fieldName];
@@ -1257,15 +1171,12 @@ export class PostgresStorageAdapter implements StorageAdapter {
.join(', DROP COLUMN');
await this._client.tx('delete-fields', async t => {
await t.none(
'UPDATE "_SCHEMA" SET "schema" = $<schema> WHERE "className" = $<className>',
{ schema, className }
);
await t.none('UPDATE "_SCHEMA" SET "schema" = $<schema> WHERE "className" = $<className>', {
schema,
className,
});
if (values.length > 1) {
await t.none(
`ALTER TABLE $1:name DROP COLUMN IF EXISTS ${columns}`,
values
);
await t.none(`ALTER TABLE $1:name DROP COLUMN IF EXISTS ${columns}`, values);
}
});
}
@@ -1412,10 +1323,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
const fieldName = columnsArray[index];
if (['_rperm', '_wperm'].indexOf(fieldName) >= 0) {
termination = '::text[]';
} else if (
schema.fields[fieldName] &&
schema.fields[fieldName].type === 'Array'
) {
} else if (schema.fields[fieldName] && schema.fields[fieldName].type === 'Array') {
termination = '::jsonb';
}
return `$${index + 2 + columnsArray.length}${termination}`;
@@ -1427,18 +1335,13 @@ export class PostgresStorageAdapter implements StorageAdapter {
return `POINT($${l}, $${l + 1})`;
});
const columnsPattern = columnsArray
.map((col, index) => `$${index + 2}:name`)
.join();
const columnsPattern = columnsArray.map((col, index) => `$${index + 2}:name`).join();
const valuesPattern = initialValues.concat(geoPointsInjects).join();
const qs = `INSERT INTO $1:name (${columnsPattern}) VALUES (${valuesPattern})`;
const values = [className, ...columnsArray, ...valuesArray];
debug(qs, values);
const promise = (transactionalSession
? transactionalSession.t
: this._client
)
const promise = (transactionalSession ? transactionalSession.t : this._client)
.none(qs, values)
.then(() => ({ ops: [object] }))
.catch(error => {
@@ -1488,17 +1391,11 @@ export class PostgresStorageAdapter implements StorageAdapter {
}
const qs = `WITH deleted AS (DELETE FROM $1:name WHERE ${where.pattern} RETURNING *) SELECT count(*) FROM deleted`;
debug(qs, values);
const promise = (transactionalSession
? transactionalSession.t
: this._client
)
const promise = (transactionalSession ? transactionalSession.t : this._client)
.one(qs, values, a => +a.count)
.then(count => {
if (count === 0) {
throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Object not found.'
);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
} else {
return count;
}
@@ -1523,13 +1420,9 @@ export class PostgresStorageAdapter implements StorageAdapter {
transactionalSession: ?any
): Promise<any> {
debug('findOneAndUpdate', className, query, update);
return this.updateObjectsByQuery(
className,
schema,
query,
update,
transactionalSession
).then(val => val[0]);
return this.updateObjectsByQuery(className, schema, query, update, transactionalSession).then(
val => val[0]
);
}
// Apply the update to all objects that match the given Parse Query.
@@ -1592,39 +1485,28 @@ export class PostgresStorageAdapter implements StorageAdapter {
const fieldNameIndex = index;
index += 1;
values.push(fieldName);
const update = Object.keys(fieldValue).reduce(
(lastKey: string, key: string) => {
const str = generate(
lastKey,
`$${index}::text`,
`$${index + 1}::jsonb`
);
index += 2;
let value = fieldValue[key];
if (value) {
if (value.__op === 'Delete') {
value = null;
} else {
value = JSON.stringify(value);
}
const update = Object.keys(fieldValue).reduce((lastKey: string, key: string) => {
const str = generate(lastKey, `$${index}::text`, `$${index + 1}::jsonb`);
index += 2;
let value = fieldValue[key];
if (value) {
if (value.__op === 'Delete') {
value = null;
} else {
value = JSON.stringify(value);
}
values.push(key, value);
return str;
},
lastKey
);
}
values.push(key, value);
return str;
}, lastKey);
updatePatterns.push(`$${fieldNameIndex}:name = ${update}`);
} else if (fieldValue.__op === 'Increment') {
updatePatterns.push(
`$${index}:name = COALESCE($${index}:name, 0) + $${index + 1}`
);
updatePatterns.push(`$${index}:name = COALESCE($${index}:name, 0) + $${index + 1}`);
values.push(fieldName, fieldValue.amount);
index += 2;
} else if (fieldValue.__op === 'Add') {
updatePatterns.push(
`$${index}:name = array_add(COALESCE($${index}:name, '[]'::jsonb), $${
index + 1
}::jsonb)`
`$${index}:name = array_add(COALESCE($${index}:name, '[]'::jsonb), $${index + 1}::jsonb)`
);
values.push(fieldName, JSON.stringify(fieldValue.objects));
index += 2;
@@ -1678,9 +1560,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
values.push(fieldName, toPostgresValue(fieldValue));
index += 2;
} else if (fieldValue.__type === 'GeoPoint') {
updatePatterns.push(
`$${index}:name = POINT($${index + 1}, $${index + 2})`
);
updatePatterns.push(`$${index}:name = POINT($${index + 1}, $${index + 2})`);
values.push(fieldName, fieldValue.longitude, fieldValue.latitude);
index += 3;
} else if (fieldValue.__type === 'Polygon') {
@@ -1745,12 +1625,9 @@ export class PostgresStorageAdapter implements StorageAdapter {
})
.map(k => k.split('.')[1]);
const deletePatterns = keysToDelete.reduce(
(p: string, c: string, i: number) => {
return p + ` - '$${index + 1 + i}:value'`;
},
''
);
const deletePatterns = keysToDelete.reduce((p: string, c: string, i: number) => {
return p + ` - '$${index + 1 + i}:value'`;
}, '');
// Override Object
let updateObject = "'{}'::jsonb";
@@ -1799,14 +1676,10 @@ export class PostgresStorageAdapter implements StorageAdapter {
});
values.push(...where.values);
const whereClause =
where.pattern.length > 0 ? `WHERE ${where.pattern}` : '';
const whereClause = where.pattern.length > 0 ? `WHERE ${where.pattern}` : '';
const qs = `UPDATE $1:name SET ${updatePatterns.join()} ${whereClause} RETURNING *`;
debug('update: ', qs, values);
const promise = (transactionalSession
? transactionalSession.t
: this._client
).any(qs, values);
const promise = (transactionalSession ? transactionalSession.t : this._client).any(qs, values);
if (transactionalSession) {
transactionalSession.batch.push(promise);
}
@@ -1823,23 +1696,12 @@ export class PostgresStorageAdapter implements StorageAdapter {
) {
debug('upsertOneObject', { className, query, update });
const createValue = Object.assign({}, query, update);
return this.createObject(
className,
schema,
createValue,
transactionalSession
).catch(error => {
return this.createObject(className, schema, createValue, transactionalSession).catch(error => {
// ignore duplicate value errors as it's upsert
if (error.code !== Parse.Error.DUPLICATE_VALUE) {
throw error;
}
return this.findOneAndUpdate(
className,
schema,
query,
update,
transactionalSession
);
return this.findOneAndUpdate(className, schema, query, update, transactionalSession);
});
}
@@ -1868,8 +1730,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
});
values.push(...where.values);
const wherePattern =
where.pattern.length > 0 ? `WHERE ${where.pattern}` : '';
const wherePattern = where.pattern.length > 0 ? `WHERE ${where.pattern}` : '';
const limitPattern = hasLimit ? `LIMIT $${values.length + 1}` : '';
if (hasLimit) {
values.push(limit);
@@ -1892,10 +1753,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
return `${transformKey} DESC`;
})
.join();
sortPattern =
sort !== undefined && Object.keys(sort).length > 0
? `ORDER BY ${sorting}`
: '';
sortPattern = sort !== undefined && Object.keys(sort).length > 0 ? `ORDER BY ${sorting}` : '';
}
if (where.sorts && Object.keys((where.sorts: any)).length > 0) {
sortPattern = `ORDER BY ${where.sorts.join()}`;
@@ -1926,9 +1784,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
}
const originalQuery = `SELECT ${columns} FROM $1:name ${wherePattern} ${sortPattern} ${limitPattern} ${skipPattern}`;
const qs = explain
? this.createExplainableQuery(originalQuery)
: originalQuery;
const qs = explain ? this.createExplainableQuery(originalQuery) : originalQuery;
debug(qs, values);
return this._client
.any(qs, values)
@@ -1943,9 +1799,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
if (explain) {
return results;
}
return results.map(object =>
this.postgresObjectToParseObject(className, object, schema)
);
return results.map(object => this.postgresObjectToParseObject(className, object, schema));
});
}
@@ -1977,10 +1831,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
let coords = object[fieldName];
coords = coords.substr(2, coords.length - 4).split('),(');
coords = coords.map(point => {
return [
parseFloat(point.split(',')[1]),
parseFloat(point.split(',')[0]),
];
return [parseFloat(point.split(',')[1]), parseFloat(point.split(',')[0])];
});
object[fieldName] = {
__type: 'Polygon',
@@ -2052,37 +1903,26 @@ export class PostgresStorageAdapter implements StorageAdapter {
// As such, we shouldn't expose this function to users of parse until we have an out-of-band
// Way of determining if a field is nullable. Undefined doesn't count against uniqueness,
// which is why we use sparse indexes.
async ensureUniqueness(
className: string,
schema: SchemaType,
fieldNames: string[]
) {
async ensureUniqueness(className: string, schema: SchemaType, fieldNames: string[]) {
const constraintName = `${className}_unique_${fieldNames.sort().join('_')}`;
const constraintPatterns = fieldNames.map(
(fieldName, index) => `$${index + 3}:name`
);
const constraintPatterns = fieldNames.map((fieldName, index) => `$${index + 3}:name`);
const qs = `CREATE UNIQUE INDEX IF NOT EXISTS $2:name ON $1:name(${constraintPatterns.join()})`;
return this._client
.none(qs, [className, constraintName, ...fieldNames])
.catch(error => {
if (
error.code === PostgresDuplicateRelationError &&
error.message.includes(constraintName)
) {
// Index already exists. Ignore error.
} else if (
error.code === PostgresUniqueIndexViolationError &&
error.message.includes(constraintName)
) {
// Cast the error into the proper parse error
throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'A duplicate value for a field with unique values was provided'
);
} else {
throw error;
}
});
return this._client.none(qs, [className, constraintName, ...fieldNames]).catch(error => {
if (error.code === PostgresDuplicateRelationError && error.message.includes(constraintName)) {
// Index already exists. Ignore error.
} else if (
error.code === PostgresUniqueIndexViolationError &&
error.message.includes(constraintName)
) {
// Cast the error into the proper parse error
throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'A duplicate value for a field with unique values was provided'
);
} else {
throw error;
}
});
}
// Executes a count.
@@ -2103,15 +1943,13 @@ export class PostgresStorageAdapter implements StorageAdapter {
});
values.push(...where.values);
const wherePattern =
where.pattern.length > 0 ? `WHERE ${where.pattern}` : '';
const wherePattern = where.pattern.length > 0 ? `WHERE ${where.pattern}` : '';
let qs = '';
if (where.pattern.length > 0 || !estimate) {
qs = `SELECT count(*) FROM $1:name ${wherePattern}`;
} else {
qs =
'SELECT reltuples AS approximate_row_count FROM pg_class WHERE relname = $1';
qs = 'SELECT reltuples AS approximate_row_count FROM pg_class WHERE relname = $1';
}
return this._client
@@ -2130,12 +1968,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
});
}
async distinct(
className: string,
schema: SchemaType,
query: QueryType,
fieldName: string
) {
async distinct(className: string, schema: SchemaType, query: QueryType, fieldName: string) {
debug('distinct', className, query);
let field = fieldName;
let column = fieldName;
@@ -2145,13 +1978,9 @@ export class PostgresStorageAdapter implements StorageAdapter {
column = fieldName.split('.')[0];
}
const isArrayField =
schema.fields &&
schema.fields[fieldName] &&
schema.fields[fieldName].type === 'Array';
schema.fields && schema.fields[fieldName] && schema.fields[fieldName].type === 'Array';
const isPointerField =
schema.fields &&
schema.fields[fieldName] &&
schema.fields[fieldName].type === 'Pointer';
schema.fields && schema.fields[fieldName] && schema.fields[fieldName].type === 'Pointer';
const values = [field, column, className];
const where = buildWhereClause({
schema,
@@ -2161,8 +1990,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
});
values.push(...where.values);
const wherePattern =
where.pattern.length > 0 ? `WHERE ${where.pattern}` : '';
const wherePattern = where.pattern.length > 0 ? `WHERE ${where.pattern}` : '';
const transformer = isArrayField ? 'jsonb_array_elements' : 'ON';
let qs = `SELECT DISTINCT ${transformer}($1:name) $2:name FROM $3:name ${wherePattern}`;
if (isNested) {
@@ -2195,9 +2023,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
return results.map(object => object[column][child]);
})
.then(results =>
results.map(object =>
this.postgresObjectToParseObject(className, object, schema)
)
results.map(object => this.postgresObjectToParseObject(className, object, schema))
);
}
@@ -2235,11 +2061,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
index += 1;
continue;
}
if (
field === '_id' &&
typeof value === 'object' &&
Object.keys(value).length !== 0
) {
if (field === '_id' && typeof value === 'object' && Object.keys(value).length !== 0) {
groupValues = value;
const groupByFields = [];
for (const alias in value) {
@@ -2261,9 +2083,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
columns.push(
`EXTRACT(${
mongoAggregateToPostgres[operation]
} FROM $${index}:name AT TIME ZONE 'UTC') AS $${
index + 1
}:name`
} FROM $${index}:name AT TIME ZONE 'UTC') AS $${index + 1}:name`
);
values.push(source, alias);
index += 2;
@@ -2323,10 +2143,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
}
if (stage.$match) {
const patterns = [];
const orOrAnd = Object.prototype.hasOwnProperty.call(
stage.$match,
'$or'
)
const orOrAnd = Object.prototype.hasOwnProperty.call(stage.$match, '$or')
? ' OR '
: ' AND ';
@@ -2345,9 +2162,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
Object.keys(ParseToPosgresComparator).forEach(cmp => {
if (value[cmp]) {
const pgComparator = ParseToPosgresComparator[cmp];
matchPatterns.push(
`$${index}:name ${pgComparator} $${index + 1}`
);
matchPatterns.push(`$${index}:name ${pgComparator} $${index + 1}`);
values.push(field, toPostgresValue(value[cmp]));
index += 2;
}
@@ -2355,18 +2170,13 @@ export class PostgresStorageAdapter implements StorageAdapter {
if (matchPatterns.length > 0) {
patterns.push(`(${matchPatterns.join(' AND ')})`);
}
if (
schema.fields[field] &&
schema.fields[field].type &&
matchPatterns.length === 0
) {
if (schema.fields[field] && schema.fields[field].type && matchPatterns.length === 0) {
patterns.push(`$${index}:name = $${index + 1}`);
values.push(field, value);
index += 2;
}
}
wherePattern =
patterns.length > 0 ? `WHERE ${patterns.join(` ${orOrAnd} `)}` : '';
wherePattern = patterns.length > 0 ? `WHERE ${patterns.join(` ${orOrAnd} `)}` : '';
}
if (stage.$limit) {
limitPattern = `LIMIT $${index}`;
@@ -2390,8 +2200,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
})
.join();
values.push(...keys);
sortPattern =
sort !== undefined && sorting.length > 0 ? `ORDER BY ${sorting}` : '';
sortPattern = sort !== undefined && sorting.length > 0 ? `ORDER BY ${sorting}` : '';
}
}
@@ -2406,17 +2215,13 @@ export class PostgresStorageAdapter implements StorageAdapter {
const originalQuery = `SELECT ${columns
.filter(Boolean)
.join()} FROM $1:name ${wherePattern} ${skipPattern} ${groupPattern} ${sortPattern} ${limitPattern}`;
const qs = explain
? this.createExplainableQuery(originalQuery)
: originalQuery;
const qs = explain ? this.createExplainableQuery(originalQuery) : originalQuery;
debug(qs, values);
return this._client.any(qs, values).then(a => {
if (explain) {
return a;
}
const results = a.map(object =>
this.postgresObjectToParseObject(className, object, schema)
);
const results = a.map(object => this.postgresObjectToParseObject(className, object, schema));
results.forEach(result => {
if (!Object.prototype.hasOwnProperty.call(result, 'objectId')) {
result.objectId = null;
@@ -2474,11 +2279,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
});
}
async createIndexes(
className: string,
indexes: any,
conn: ?any
): Promise<void> {
async createIndexes(className: string, indexes: any, conn: ?any): Promise<void> {
return (conn || this._client).tx(t =>
t.batch(
indexes.map(i => {
@@ -2498,9 +2299,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
type: any,
conn: ?any
): Promise<void> {
await (
conn || this._client
).none('CREATE INDEX IF NOT EXISTS $1:name ON $2:name ($3:name)', [
await (conn || this._client).none('CREATE INDEX IF NOT EXISTS $1:name ON $2:name ($3:name)', [
fieldName,
className,
type,
@@ -2512,9 +2311,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
query: 'DROP INDEX $1:name',
values: i,
}));
await (conn || this._client).tx(t =>
t.none(this._pgp.helpers.concat(queries))
);
await (conn || this._client).tx(t => t.none(this._pgp.helpers.concat(queries)));
}
async getIndexes(className: string) {
@@ -2547,18 +2344,14 @@ export class PostgresStorageAdapter implements StorageAdapter {
}
commitTransactionalSession(transactionalSession: any): Promise<void> {
transactionalSession.resolve(
transactionalSession.t.batch(transactionalSession.batch)
);
transactionalSession.resolve(transactionalSession.t.batch(transactionalSession.batch));
return transactionalSession.result;
}
abortTransactionalSession(transactionalSession: any): Promise<void> {
const result = transactionalSession.result.catch();
transactionalSession.batch.push(Promise.reject());
transactionalSession.resolve(
transactionalSession.t.batch(transactionalSession.batch)
);
transactionalSession.resolve(transactionalSession.t.batch(transactionalSession.batch));
return result;
}
@@ -2575,41 +2368,34 @@ export class PostgresStorageAdapter implements StorageAdapter {
const indexNameOptions: Object =
indexName != null ? { name: indexName } : { name: defaultIndexName };
const constraintPatterns = caseInsensitive
? fieldNames.map(
(fieldName, index) => `lower($${index + 3}:name) varchar_pattern_ops`
)
? fieldNames.map((fieldName, index) => `lower($${index + 3}:name) varchar_pattern_ops`)
: fieldNames.map((fieldName, index) => `$${index + 3}:name`);
const qs = `CREATE INDEX IF NOT EXISTS $1:name ON $2:name (${constraintPatterns.join()})`;
await conn
.none(qs, [indexNameOptions.name, className, ...fieldNames])
.catch(error => {
if (
error.code === PostgresDuplicateRelationError &&
error.message.includes(indexNameOptions.name)
) {
// Index already exists. Ignore error.
} else if (
error.code === PostgresUniqueIndexViolationError &&
error.message.includes(indexNameOptions.name)
) {
// Cast the error into the proper parse error
throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'A duplicate value for a field with unique values was provided'
);
} else {
throw error;
}
});
await conn.none(qs, [indexNameOptions.name, className, ...fieldNames]).catch(error => {
if (
error.code === PostgresDuplicateRelationError &&
error.message.includes(indexNameOptions.name)
) {
// Index already exists. Ignore error.
} else if (
error.code === PostgresUniqueIndexViolationError &&
error.message.includes(indexNameOptions.name)
) {
// Cast the error into the proper parse error
throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'A duplicate value for a field with unique values was provided'
);
} else {
throw error;
}
});
}
}
function convertPolygonToSQL(polygon) {
if (polygon.length < 3) {
throw new Parse.Error(
Parse.Error.INVALID_JSON,
`Polygon must have at least 3 values`
);
throw new Parse.Error(Parse.Error.INVALID_JSON, `Polygon must have at least 3 values`);
}
if (
polygon[0][0] !== polygon[polygon.length - 1][0] ||
@@ -2757,9 +2543,7 @@ function literalizeRegexPart(s: string) {
var GeoPointCoder = {
isValidJSON(value) {
return (
typeof value === 'object' && value !== null && value.__type === 'GeoPoint'
);
return typeof value === 'object' && value !== null && value.__type === 'GeoPoint';
},
};

View File

@@ -34,18 +34,10 @@ export interface StorageAdapter {
classExists(className: string): Promise<boolean>;
setClassLevelPermissions(className: string, clps: any): Promise<void>;
createClass(className: string, schema: SchemaType): Promise<void>;
addFieldIfNotExists(
className: string,
fieldName: string,
type: any
): Promise<void>;
addFieldIfNotExists(className: string, fieldName: string, type: any): Promise<void>;
deleteClass(className: string): Promise<void>;
deleteAllClasses(fast: boolean): Promise<void>;
deleteFields(
className: string,
schema: SchemaType,
fieldNames: Array<string>
): Promise<void>;
deleteFields(className: string, schema: SchemaType, fieldNames: Array<string>): Promise<void>;
getAllClasses(): Promise<StorageClass[]>;
getClass(className: string): Promise<StorageClass>;
createObject(
@@ -95,11 +87,7 @@ export interface StorageAdapter {
caseSensitive?: boolean,
options?: Object
): Promise<any>;
ensureUniqueness(
className: string,
schema: SchemaType,
fieldNames: Array<string>
): Promise<void>;
ensureUniqueness(className: string, schema: SchemaType, fieldNames: Array<string>): Promise<void>;
count(
className: string,
schema: SchemaType,