'use strict'; exports = module.exports = { get: get, getByContainerId: getByContainerId, add: add, exists: exists, del: del, update: update, getAll: getAll, getPortBindings: getPortBindings, delPortBinding: delPortBinding, setAddonConfig: setAddonConfig, getAddonConfig: getAddonConfig, getAddonConfigByAppId: getAddonConfigByAppId, getAddonConfigByName: getAddonConfigByName, unsetAddonConfig: unsetAddonConfig, unsetAddonConfigByAppId: unsetAddonConfigByAppId, getAppIdByAddonConfigValue: getAppIdByAddonConfigValue, setHealth: setHealth, setTask: setTask, getAppStoreIds: getAppStoreIds, // subdomain table types SUBDOMAIN_TYPE_PRIMARY: 'primary', SUBDOMAIN_TYPE_REDIRECT: 'redirect', _clear: clear }; var assert = require('assert'), async = require('async'), BoxError = require('./boxerror.js'), database = require('./database.js'), safe = require('safetydance'), util = require('util'); var APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationState', 'apps.errorJson', 'apps.runState', 'apps.health', 'apps.containerId', 'apps.manifestJson', 'subdomains.subdomain AS location', 'subdomains.domain', 'apps.accessRestrictionJson', 'apps.memoryLimit', 'apps.cpuShares', 'apps.label', 'apps.tagsJson', 'apps.taskId', 'apps.reverseProxyConfigJson', 'apps.servicesConfigJson', 'apps.sso', 'apps.debugModeJson', 'apps.enableBackup', 'apps.proxyAuth', 'apps.creationTime', 'apps.updateTime', 'apps.mailboxName', 'apps.mailboxDomain', 'apps.enableAutomaticUpdate', 'apps.dataDir', 'apps.ts', 'apps.healthTime' ].join(','); var PORT_BINDINGS_FIELDS = [ 'hostPort', 'type', 'environmentVariable', 'appId' ].join(','); const SUBDOMAIN_FIELDS = [ 'appId', 'domain', 'subdomain', 'type' ].join(','); function postProcess(result) { assert.strictEqual(typeof result, 'object'); assert(result.manifestJson === null || typeof result.manifestJson === 'string'); result.manifest = safe.JSON.parse(result.manifestJson); delete result.manifestJson; assert(result.tagsJson === null || typeof result.tagsJson === 'string'); result.tags = safe.JSON.parse(result.tagsJson) || []; delete result.tagsJson; assert(result.reverseProxyConfigJson === null || typeof result.reverseProxyConfigJson === 'string'); result.reverseProxyConfig = safe.JSON.parse(result.reverseProxyConfigJson) || {}; delete result.reverseProxyConfigJson; assert(result.hostPorts === null || typeof result.hostPorts === 'string'); assert(result.environmentVariables === null || typeof result.environmentVariables === 'string'); result.portBindings = { }; let hostPorts = result.hostPorts === null ? [ ] : result.hostPorts.split(','); let environmentVariables = result.environmentVariables === null ? [ ] : result.environmentVariables.split(','); let portTypes = result.portTypes === null ? [ ] : result.portTypes.split(','); delete result.hostPorts; delete result.environmentVariables; delete result.portTypes; for (let i = 0; i < environmentVariables.length; i++) { result.portBindings[environmentVariables[i]] = { hostPort: parseInt(hostPorts[i], 10), type: portTypes[i] }; } assert(result.accessRestrictionJson === null || typeof result.accessRestrictionJson === 'string'); result.accessRestriction = safe.JSON.parse(result.accessRestrictionJson); if (result.accessRestriction && !result.accessRestriction.users) result.accessRestriction.users = []; delete result.accessRestrictionJson; result.sso = !!result.sso; // make it bool result.enableBackup = !!result.enableBackup; // make it bool result.enableAutomaticUpdate = !!result.enableAutomaticUpdate; // make it bool result.proxyAuth = !!result.proxyAuth; assert(result.debugModeJson === null || typeof result.debugModeJson === 'string'); result.debugMode = safe.JSON.parse(result.debugModeJson); delete result.debugModeJson; assert(result.servicesConfigJson === null || typeof result.servicesConfigJson === 'string'); result.servicesConfig = safe.JSON.parse(result.servicesConfigJson) || {}; delete result.servicesConfigJson; result.alternateDomains = result.alternateDomains || []; result.alternateDomains.forEach(function (d) { delete d.appId; delete d.type; }); let envNames = JSON.parse(result.envNames), envValues = JSON.parse(result.envValues); delete result.envNames; delete result.envValues; result.env = {}; for (let i = 0; i < envNames.length; i++) { // NOTE: envNames is [ null ] when env of an app is empty if (envNames[i]) result.env[envNames[i]] = envValues[i]; } let volumeIds = JSON.parse(result.volumeIds); delete result.volumeIds; let volumeReadOnlys = JSON.parse(result.volumeReadOnlys); delete result.volumeReadOnlys; result.mounts = volumeIds[0] === null ? [] : volumeIds.map((v, idx) => { return { volumeId: v, readOnly: !!volumeReadOnlys[idx] }; }); // NOTE: volumeIds is [null] when volumes of an app is empty result.error = safe.JSON.parse(result.errorJson); delete result.errorJson; result.taskId = result.taskId ? String(result.taskId) : null; } function get(id, callback) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof callback, 'function'); database.query('SELECT ' + APPS_FIELDS_PREFIXED + ',' + 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables, GROUP_CONCAT(appPortBindings.type) AS portTypes, ' + 'JSON_ARRAYAGG(appEnvVars.name) AS envNames, JSON_ARRAYAGG(appEnvVars.value) AS envValues,' + 'JSON_ARRAYAGG(appMounts.volumeId) AS volumeIds, JSON_ARRAYAGG(appMounts.readOnly) AS volumeReadOnlys ' + ' FROM apps' + ' LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId' + ' LEFT OUTER JOIN appEnvVars ON apps.id = appEnvVars.appId' + ' LEFT OUTER JOIN subdomains ON apps.id = subdomains.appId AND subdomains.type = ?' + ' LEFT OUTER JOIN appMounts ON apps.id = appMounts.appId' + ' WHERE apps.id = ? GROUP BY apps.id', [ exports.SUBDOMAIN_TYPE_PRIMARY, id ], function (error, result) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'App not found')); database.query('SELECT ' + SUBDOMAIN_FIELDS + ' FROM subdomains WHERE appId = ? AND type = ?', [ id, exports.SUBDOMAIN_TYPE_REDIRECT ], function (error, alternateDomains) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); result[0].alternateDomains = alternateDomains; postProcess(result[0]); callback(null, result[0]); }); }); } function getByContainerId(containerId, callback) { assert.strictEqual(typeof containerId, 'string'); assert.strictEqual(typeof callback, 'function'); database.query('SELECT ' + APPS_FIELDS_PREFIXED + ',' + 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables, GROUP_CONCAT(appPortBindings.type) AS portTypes,' + 'JSON_ARRAYAGG(appEnvVars.name) AS envNames, JSON_ARRAYAGG(appEnvVars.value) AS envValues,' + 'JSON_ARRAYAGG(appMounts.volumeId) AS volumeIds, JSON_ARRAYAGG(appMounts.readOnly) AS volumeReadOnlys ' + ' FROM apps' + ' LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId' + ' LEFT OUTER JOIN appEnvVars ON apps.id = appEnvVars.appId' + ' LEFT OUTER JOIN subdomains ON apps.id = subdomains.appId AND subdomains.type = ?' + ' LEFT OUTER JOIN appMounts ON apps.id = appMounts.appId' + ' WHERE containerId = ? GROUP BY apps.id', [ exports.SUBDOMAIN_TYPE_PRIMARY, containerId ], function (error, result) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'App not found')); database.query('SELECT ' + SUBDOMAIN_FIELDS + ' FROM subdomains WHERE appId = ? AND type = ?', [ result[0].id, exports.SUBDOMAIN_TYPE_REDIRECT ], function (error, alternateDomains) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); result[0].alternateDomains = alternateDomains; postProcess(result[0]); callback(null, result[0]); }); }); } function getAll(callback) { assert.strictEqual(typeof callback, 'function'); database.query('SELECT ' + APPS_FIELDS_PREFIXED + ',' + 'GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables, GROUP_CONCAT(appPortBindings.type) AS portTypes,' + 'JSON_ARRAYAGG(appEnvVars.name) AS envNames, JSON_ARRAYAGG(appEnvVars.value) AS envValues,' + 'JSON_ARRAYAGG(appMounts.volumeId) AS volumeIds, JSON_ARRAYAGG(appMounts.readOnly) AS volumeReadOnlys ' + ' FROM apps' + ' LEFT OUTER JOIN appPortBindings ON apps.id = appPortBindings.appId' + ' LEFT OUTER JOIN appEnvVars ON apps.id = appEnvVars.appId' + ' LEFT OUTER JOIN subdomains ON apps.id = subdomains.appId AND subdomains.type = ?' + ' LEFT OUTER JOIN appMounts ON apps.id = appMounts.appId' + ' GROUP BY apps.id ORDER BY apps.id', [ exports.SUBDOMAIN_TYPE_PRIMARY ], function (error, results) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); database.query('SELECT ' + SUBDOMAIN_FIELDS + ' FROM subdomains WHERE type = ?', [ exports.SUBDOMAIN_TYPE_REDIRECT ], function (error, alternateDomains) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); alternateDomains.forEach(function (d) { var domain = results.find(function (a) { return d.appId === a.id; }); if (!domain) return; domain.alternateDomains = domain.alternateDomains || []; domain.alternateDomains.push(d); }); results.forEach(postProcess); callback(null, results); }); }); } function add(id, appStoreId, manifest, location, domain, portBindings, data, callback) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof appStoreId, 'string'); assert(manifest && typeof manifest === 'object'); assert.strictEqual(typeof manifest.version, 'string'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof portBindings, 'object'); assert(data && typeof data === 'object'); assert.strictEqual(typeof callback, 'function'); portBindings = portBindings || { }; var manifestJson = JSON.stringify(manifest); const accessRestriction = data.accessRestriction || null; const accessRestrictionJson = JSON.stringify(accessRestriction); const memoryLimit = data.memoryLimit || 0; const cpuShares = data.cpuShares || 512; const installationState = data.installationState; const runState = data.runState; const sso = 'sso' in data ? data.sso : null; const debugModeJson = data.debugMode ? JSON.stringify(data.debugMode) : null; const env = data.env || {}; const label = data.label || null; const tagsJson = data.tags ? JSON.stringify(data.tags) : null; const mailboxName = data.mailboxName || null; const mailboxDomain = data.mailboxDomain || null; const reverseProxyConfigJson = data.reverseProxyConfig ? JSON.stringify(data.reverseProxyConfig) : null; var queries = []; queries.push({ query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, runState, accessRestrictionJson, memoryLimit, cpuShares, ' + 'sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson) ' + ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', args: [ id, appStoreId, manifestJson, installationState, runState, accessRestrictionJson, memoryLimit, cpuShares, sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson ] }); queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, domain, location, exports.SUBDOMAIN_TYPE_PRIMARY ] }); Object.keys(portBindings).forEach(function (env) { queries.push({ query: 'INSERT INTO appPortBindings (environmentVariable, hostPort, type, appId) VALUES (?, ?, ?, ?)', args: [ env, portBindings[env].hostPort, portBindings[env].type, id ] }); }); Object.keys(env).forEach(function (name) { queries.push({ query: 'INSERT INTO appEnvVars (appId, name, value) VALUES (?, ?, ?)', args: [ id, name, env[name] ] }); }); if (data.alternateDomains) { data.alternateDomains.forEach(function (d) { queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, exports.SUBDOMAIN_TYPE_REDIRECT ] }); }); } database.transaction(queries, function (error) { if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS, error.message)); if (error && error.code === 'ER_NO_REFERENCED_ROW_2') return callback(new BoxError(BoxError.NOT_FOUND, 'no such domain')); if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); callback(null); }); } function exists(id, callback) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof callback, 'function'); database.query('SELECT 1 FROM apps WHERE id=?', [ id ], function (error, result) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); return callback(null, result.length !== 0); }); } function getPortBindings(id, callback) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof callback, 'function'); database.query('SELECT ' + PORT_BINDINGS_FIELDS + ' FROM appPortBindings WHERE appId = ?', [ id ], function (error, results) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); var portBindings = { }; for (var i = 0; i < results.length; i++) { portBindings[results[i].environmentVariable] = { hostPort: results[i].hostPort, type: results[i].type }; } callback(null, portBindings); }); } function delPortBinding(hostPort, type, callback) { assert.strictEqual(typeof hostPort, 'number'); assert.strictEqual(typeof type, 'string'); assert.strictEqual(typeof callback, 'function'); database.query('DELETE FROM appPortBindings WHERE hostPort=? AND type=?', [ hostPort, type ], function (error, result) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'App not found')); callback(null); }); } function del(id, callback) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof callback, 'function'); var queries = [ { query: 'DELETE FROM subdomains WHERE appId = ?', args: [ id ] }, { query: 'DELETE FROM appPortBindings WHERE appId = ?', args: [ id ] }, { query: 'DELETE FROM appEnvVars WHERE appId = ?', args: [ id ] }, { query: 'DELETE FROM appPasswords WHERE identifier = ?', args: [ id ] }, { query: 'DELETE FROM appMounts WHERE appId = ?', args: [ id ] }, { query: 'DELETE FROM apps WHERE id = ?', args: [ id ] } ]; database.transaction(queries, function (error, results) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); if (results[5].affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'App not found')); callback(null); }); } function clear(callback) { assert.strictEqual(typeof callback, 'function'); async.series([ database.query.bind(null, 'DELETE FROM subdomains'), database.query.bind(null, 'DELETE FROM appPortBindings'), database.query.bind(null, 'DELETE FROM appAddonConfigs'), database.query.bind(null, 'DELETE FROM appEnvVars'), database.query.bind(null, 'DELETE FROM apps') ], function (error) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); return callback(null); }); } function update(id, app, callback) { updateWithConstraints(id, app, '', callback); } function updateWithConstraints(id, app, constraints, callback) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof constraints, 'string'); assert.strictEqual(typeof callback, 'function'); assert(!('portBindings' in app) || typeof app.portBindings === 'object'); assert(!('accessRestriction' in app) || typeof app.accessRestriction === 'object' || app.accessRestriction === ''); assert(!('alternateDomains' in app) || Array.isArray(app.alternateDomains)); assert(!('tags' in app) || Array.isArray(app.tags)); assert(!('env' in app) || typeof app.env === 'object'); var queries = [ ]; if ('portBindings' in app) { var portBindings = app.portBindings || { }; // replace entries by app id queries.push({ query: 'DELETE FROM appPortBindings WHERE appId = ?', args: [ id ] }); Object.keys(portBindings).forEach(function (env) { var values = [ portBindings[env].hostPort, portBindings[env].type, env, id ]; queries.push({ query: 'INSERT INTO appPortBindings (hostPort, type, environmentVariable, appId) VALUES(?, ?, ?, ?)', args: values }); }); } if ('env' in app) { queries.push({ query: 'DELETE FROM appEnvVars WHERE appId = ?', args: [ id ] }); Object.keys(app.env).forEach(function (name) { queries.push({ query: 'INSERT INTO appEnvVars (appId, name, value) VALUES (?, ?, ?)', args: [ id, name, app.env[name] ] }); }); } if ('location' in app && 'domain' in app) { // must be updated together as they are unique together queries.push({ query: 'DELETE FROM subdomains WHERE appId = ?', args: [ id ]}); // all locations of an app must be updated together queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, app.domain, app.location, exports.SUBDOMAIN_TYPE_PRIMARY ]}); if ('alternateDomains' in app) { app.alternateDomains.forEach(function (d) { queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, exports.SUBDOMAIN_TYPE_REDIRECT ]}); }); } } if ('mounts' in app) { queries.push({ query: 'DELETE FROM appMounts WHERE appId = ?', args: [ id ]}); app.mounts.forEach(function (m) { queries.push({ query: 'INSERT INTO appMounts (appId, volumeId, readOnly) VALUES (?, ?, ?)', args: [ id, m.volumeId, m.readOnly ]}); }); } var fields = [ ], values = [ ]; for (var p in app) { if (p === 'manifest' || p === 'tags' || p === 'accessRestriction' || p === 'debugMode' || p === 'error' || p === 'reverseProxyConfig' || p === 'servicesConfig') { fields.push(`${p}Json = ?`); values.push(JSON.stringify(app[p])); } else if (p !== 'portBindings' && p !== 'location' && p !== 'domain' && p !== 'alternateDomains' && p !== 'env' && p !== 'mounts') { fields.push(p + ' = ?'); values.push(app[p]); } } if (values.length !== 0) { values.push(id); queries.push({ query: 'UPDATE apps SET ' + fields.join(', ') + ' WHERE id = ? ' + constraints, args: values }); } database.transaction(queries, function (error, results) { if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS, error.message)); if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); if (results[results.length - 1].affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'App not found')); return callback(null); }); } function setHealth(appId, health, healthTime, callback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof health, 'string'); assert(util.isDate(healthTime)); assert.strictEqual(typeof callback, 'function'); var values = { health, healthTime }; updateWithConstraints(appId, values, '', callback); } function setTask(appId, values, options, callback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof values, 'object'); assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof callback, 'function'); if (!options.requireNullTaskId) return updateWithConstraints(appId, values, '', callback); if (options.requiredState === null) { updateWithConstraints(appId, values, 'AND taskId IS NULL', callback); } else { updateWithConstraints(appId, values, `AND taskId IS NULL AND installationState = "${options.requiredState}"`, callback); } } function getAppStoreIds(callback) { assert.strictEqual(typeof callback, 'function'); database.query('SELECT id, appStoreId FROM apps', function (error, results) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); callback(null, results); }); } function setAddonConfig(appId, addonId, env, callback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof addonId, 'string'); assert(util.isArray(env)); assert.strictEqual(typeof callback, 'function'); unsetAddonConfig(appId, addonId, function (error) { if (error) return callback(error); if (env.length === 0) return callback(null); var query = 'INSERT INTO appAddonConfigs(appId, addonId, name, value) VALUES '; var args = [ ], queryArgs = [ ]; for (var i = 0; i < env.length; i++) { args.push(appId, addonId, env[i].name, env[i].value); queryArgs.push('(?, ?, ?, ?)'); } database.query(query + queryArgs.join(','), args, function (error) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); return callback(null); }); }); } function unsetAddonConfig(appId, addonId, callback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof addonId, 'string'); assert.strictEqual(typeof callback, 'function'); database.query('DELETE FROM appAddonConfigs WHERE appId = ? AND addonId = ?', [ appId, addonId ], function (error) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); callback(null); }); } function unsetAddonConfigByAppId(appId, callback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof callback, 'function'); database.query('DELETE FROM appAddonConfigs WHERE appId = ?', [ appId ], function (error) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); callback(null); }); } function getAddonConfig(appId, addonId, callback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof addonId, 'string'); assert.strictEqual(typeof callback, 'function'); database.query('SELECT name, value FROM appAddonConfigs WHERE appId = ? AND addonId = ?', [ appId, addonId ], function (error, results) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); callback(null, results); }); } function getAddonConfigByAppId(appId, callback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof callback, 'function'); database.query('SELECT name, value FROM appAddonConfigs WHERE appId = ?', [ appId ], function (error, results) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); callback(null, results); }); } function getAppIdByAddonConfigValue(addonId, namePattern, value, callback) { assert.strictEqual(typeof addonId, 'string'); assert.strictEqual(typeof namePattern, 'string'); assert.strictEqual(typeof value, 'string'); assert.strictEqual(typeof callback, 'function'); database.query('SELECT appId FROM appAddonConfigs WHERE addonId = ? AND name LIKE ? AND value = ?', [ addonId, namePattern, value ], function (error, results) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); if (results.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'App not found')); callback(null, results[0].appId); }); } function getAddonConfigByName(appId, addonId, namePattern, callback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof addonId, 'string'); assert.strictEqual(typeof namePattern, 'string'); assert.strictEqual(typeof callback, 'function'); database.query('SELECT value FROM appAddonConfigs WHERE appId = ? AND addonId = ? AND name LIKE ?', [ appId, addonId, namePattern ], function (error, results) { if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error)); if (results.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'App not found')); callback(null, results[0].value); }); }