diff --git a/js/apps/system/_admin/aardvark/APP/aardvark.js b/js/apps/system/_admin/aardvark/APP/aardvark.js index 921a82579b..8ae813850e 100644 --- a/js/apps/system/_admin/aardvark/APP/aardvark.js +++ b/js/apps/system/_admin/aardvark/APP/aardvark.js @@ -57,7 +57,7 @@ publicController.get("/whoAmI", function(req, res) { var uid = req.session && req.session.get("uid"); var user = null; if (uid) { - var users = Foxx.requireApp("_system/users").userStorage; + var users = Foxx.getExports("_system/users").userStorage; try { user = users.get(uid).get("user"); } catch (e) { @@ -82,11 +82,11 @@ publicController.post("/login", function (req, res) { } else { req.session = publicController.sessions.getSessionStorage().create(); } - var users = Foxx.requireApp("_system/users").userStorage; + var users = Foxx.getExports("_system/users").userStorage; var credentials = req.parameters.credentials; var user = users.resolve(credentials.get("username")); if (!user) throw new UnauthorizedError(); - var auth = Foxx.requireApp("_system/simple-auth").auth; + var auth = Foxx.getExports("_system/simple-auth").auth; var valid = auth.verifyPassword(user.get("authData").simple, credentials.get("password")); if (!valid) throw new UnauthorizedError(); req.session.setUser(user); diff --git a/js/apps/system/_admin/aardvark/APP/foxxTemplates.js b/js/apps/system/_admin/aardvark/APP/foxxTemplates.js index 1f2a27e89d..d792fba599 100644 --- a/js/apps/system/_admin/aardvark/APP/foxxTemplates.js +++ b/js/apps/system/_admin/aardvark/APP/foxxTemplates.js @@ -32,7 +32,7 @@ var FoxxController = require("org/arangodb/foxx").Controller, UnauthorizedError = require("http-errors").Unauthorized, internal = require("internal"), - Configuration = require("models/configuration").Model, + Configuration = require("./models/configuration").Model, controller = new FoxxController(applicationContext), db = require("internal").db, FoxxManager = require("org/arangodb/foxx/manager"); diff --git a/js/apps/system/_admin/aardvark/APP/foxxes.js b/js/apps/system/_admin/aardvark/APP/foxxes.js index 8e7a2f3d4c..9429a675a1 100644 --- a/js/apps/system/_admin/aardvark/APP/foxxes.js +++ b/js/apps/system/_admin/aardvark/APP/foxxes.js @@ -55,7 +55,7 @@ ) }; var fs = require("fs"); - var defaultThumb = require("/lib/defaultThumbnail").defaultThumb; + var defaultThumb = require("./lib/defaultThumbnail").defaultThumb; controller.activateSessions({ autoCreateSession: false, @@ -212,7 +212,7 @@ var mount = validateMount(req); var app = FoxxManager.lookupApp(mount); if (app.hasOwnProperty("_thumbnail")) { - res.body = app._thumbnail; + res.body = app.thumbnail; } else { res.body = defaultThumb; } @@ -345,7 +345,7 @@ controller.get("/download/zip", function(req, res) { var mount = validateMount(req); var app = FoxxManager.lookupApp(mount); - var dir = fs.join(fs.makeAbsolute(app._root), app._path); + var dir = fs.join(fs.makeAbsolute(app.root), app.path); var zipPath = fmUtils.zipDirectory(dir); res.set("Content-Type", "application/octet-stream"); res.set("Content-Disposition", "attachment; filename=app.zip"); diff --git a/js/common/bootstrap/modules.js b/js/common/bootstrap/modules.js index 4a63cda689..71c69a0944 100644 --- a/js/common/bootstrap/modules.js +++ b/js/common/bootstrap/modules.js @@ -114,17 +114,18 @@ function Module(id, parent) { } this.context = { - module: this, - exports: this.exports, - require: createRequire(this), print: internal.print, process: NATIVE_MODULES.process, console: NATIVE_MODULES.console, + module: this, + exports: this.exports, + require: createRequire(this), __filename: null, __dirname: null }; + if (parent) { - Object.keys(parent.context).forEach(function (key) { + Object.keys(parent.context).forEach(function (key) { if (!hasOwnProperty(this.context, key)) { this.context[key] = parent.context[key]; } @@ -563,6 +564,18 @@ Module._extensions['.json'] = function(module, filename) { }; +Module._extensions['.coffee'] = function(module, filename) { + require('org/arangodb/deprecated')( + '2.8', + 'CoffeeScript support is deprecated,' + + ' please pre-compile CoffeeScript modules to JavaScript using external build tools.' + ); + var content = fs.readFileSync(filename, 'utf8'); + var cs = require('coffee-script'); + module._compile(cs.compile(stripBOM(content), {bare: true}), filename); +}; + + // backwards compatibility Module.Module = Module; diff --git a/js/node/process.js b/js/node/process.js index 6e8de2eade..5a1431c5ee 100644 --- a/js/node/process.js +++ b/js/node/process.js @@ -2,7 +2,7 @@ const EventEmitter = require('events').EventEmitter; const internal = require('internal'); -const path = require('path'); +const fs = require('fs'); module.exports = exports = new EventEmitter(); @@ -15,7 +15,7 @@ exports.stdout = { } }; exports.cwd = function () { - return path.resolve(internal.appPath); + return fs.makeAbsolute(''); }; exports.nextTick = function (fn) { fn(); diff --git a/js/server/modules/org/arangodb/actions.js b/js/server/modules/org/arangodb/actions.js index 3c6ae7703a..b51d23b22b 100644 --- a/js/server/modules/org/arangodb/actions.js +++ b/js/server/modules/org/arangodb/actions.js @@ -1037,7 +1037,7 @@ function foxxRouting (req, res, options, next) { try { var app = foxxManager.lookupApp(mount); - var devel = app._isDevelopment; + var devel = app.isDevelopment; if (devel || ! options.hasOwnProperty('routing')) { delete options.error; @@ -1047,8 +1047,8 @@ function foxxRouting (req, res, options, next) { app = foxxManager.lookupApp(mount); } - if (app._isBroken) { - throw app._error; + if (app.isBroken) { + throw app.error; } options.routing = flattenRoutingTree(buildRoutingTree([foxxManager.routes(mount)])); diff --git a/js/server/modules/org/arangodb/foxx/arangoApp.js b/js/server/modules/org/arangodb/foxx/arangoApp.js deleted file mode 100644 index 584210f288..0000000000 --- a/js/server/modules/org/arangodb/foxx/arangoApp.js +++ /dev/null @@ -1,480 +0,0 @@ -'use strict'; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief Foxx application module -/// -/// @file -/// -/// DISCLAIMER -/// -/// Copyright 2013 triagens GmbH, Cologne, Germany -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -/// -/// Copyright holder is triAGENS GmbH, Cologne, Germany -/// -/// @author Dr. Frank Celler -/// @author Michael Hackstein -/// @author Copyright 2013, triAGENS GmbH, Cologne, Germany -//////////////////////////////////////////////////////////////////////////////// - -// ----------------------------------------------------------------------------- -// --SECTION-- imports -// ----------------------------------------------------------------------------- - -var fs = require("fs"); -var internal = require("internal"); -var Module = require('module'); -var db = internal.db; -var joi = require("joi"); -var _= require("underscore"); -var utils = require("org/arangodb/foxx/manager-utils"); -var console = require("console"); -var arangodb = require("org/arangodb"); -var ArangoError = arangodb.ArangoError; -var errors = arangodb.errors; -var throwFileNotFound = arangodb.throwFileNotFound; - - -// ----------------------------------------------------------------------------- -// --SECTION-- private functions -// ----------------------------------------------------------------------------- - -function applyDefaultConfig(config, parse) { - var res = {}; - if (config !== undefined) { - Object.keys(config).forEach(function (key) { - if (config[key].default !== undefined) { - res[key] = config[key].default; - if (!parse && config[key].type === 'json') { - res[key] = JSON.stringify(res[key]); - } - } - }); - } - return res; -} - -// ----------------------------------------------------------------------------- -// --SECTION-- constructors and destructors -// ----------------------------------------------------------------------------- - -// ----------------------------------------------------------------------------- -// --SECTION-- AppContext -// ----------------------------------------------------------------------------- - -function AppContext(app) { - var prefix = fs.safeJoin(app._root, app._path); - - this._prefix = prefix; - this.comments = []; - this.name = app._name; - this.version = app._version; - this.mount = app._mount; - this.collectionPrefix = app._collectionPrefix; - this.options = app._options; - this.configuration = app._configuration; - this.dependencies = app._dependencies; - this.basePath = prefix; - this.baseUrl = '/_db/' + encodeURIComponent(db._name()) + app._mount; - this.isDevelopment = app._isDevelopment; - this.isProduction = ! app._isDevelopment; - this.manifest = app._manifest; -} - -AppContext.prototype.foxxFilename = function (path) { - return fs.safeJoin(this._prefix, path); -}; - -AppContext.prototype.collectionName = function (name) { - var replaced = this.collectionPrefix.replace(/[:\.]+/g, '_') + - name.replace(/[^a-zA-Z0-9]/g, '_').replace(/(^_+|_+$)/g, '').substr(0, 64); - - if (replaced.length === 0) { - throw new Error("Cannot derive collection name from '" + name + "'"); - } - - return replaced; -}; - -AppContext.prototype.collection = function (name) { - return db._collection(this.collectionName(name)); -}; - -AppContext.prototype.path = function (name) { - return fs.join(this._prefix, name); -}; - - -AppContext.prototype.comment = function (str) { - this.comments.push(str); -}; - -AppContext.prototype.clearComments = function () { - this.comments = []; -}; - -// ----------------------------------------------------------------------------- -// --SECTION-- ArangoApp -// ----------------------------------------------------------------------------- - -//////////////////////////////////////////////////////////////////////////////// -/// @brief Checks if the mountpoint is reserved for system apps -//////////////////////////////////////////////////////////////////////////////// - -function isSystemMount(mount) { -return (/^\/_/).test(mount); -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief returns the root path for application. Knows about system apps -//////////////////////////////////////////////////////////////////////////////// - -function computeRootAppPath(mount, isValidation) { -if (isValidation) { - return ""; -} -if (isSystemMount(mount)) { - return Module._systemAppPath; -} -return Module._appPath; -} - -//////////////////////////////////////////////////////////////////////////////// -/// @brief ArangoApp constructor -//////////////////////////////////////////////////////////////////////////////// - -function ArangoApp(config) { - if (config.error) { - this._error = config.error; - this._isBroken = true; - } - this._manifest = config.manifest || { - name: "unknown", - version: "error" - }; - if (!this._manifest.configuration) { - this._manifest.configuration = {}; - } - if (!this._manifest.dependencies) { - this._manifest.dependencies = {}; - } - this._name = this._manifest.name; - this._version = this._manifest.version; - this._root = computeRootAppPath(config.mount, config.id === "__internal"); - this._path = config.path; - this._options = config.options; - this._mount = config.mount; - this._isSystem = config.isSystem || false; - this._isDevelopment = config.isDevelopment || false; - this._exports = {}; - this._collectionPrefix = this._mount.substr(1).replace(/-/g, "_").replace(/\//g, "_") + "_"; - - // Apply the default configuration and ignore all missing options - var cfg = config.options.configuration; - this._options.configuration = applyDefaultConfig(this._manifest.configuration); - this._configuration = applyDefaultConfig(this._manifest.configuration, true); - this.configure(cfg); - - var deps = config.options.dependencies; - this._options.dependencies = {}; - this._dependencies = {}; - this.updateDeps(deps); - - this._context = new AppContext(this); - this._context.appPackage = module.createAppPackage(this); - this._context.appModule = this._context.appPackage.createAppModule(this); - - if (! this._manifest.hasOwnProperty("defaultDocument")) { - this._manifest.defaultDocument = "index.html"; - } - if (this._manifest.hasOwnProperty("thumbnail")) { - var thumbfile = fs.join(this._root, this._path, this._manifest.thumbnail); - try { - this._thumbnail = fs.read64(thumbfile); - } catch (err) { - console.warnLines( - "Cannot read thumbnail '%s' : %s", thumbfile, err); - } - } -} - -// ----------------------------------------------------------------------------- -// --SECTION-- private methods -// ----------------------------------------------------------------------------- - -//////////////////////////////////////////////////////////////////////////////// -/// @brief prints a package -//////////////////////////////////////////////////////////////////////////////// - -ArangoApp.prototype._PRINT = function (context) { - context.output += '[app "' + this._name + '" (' + this._version + ')]'; -}; - -// ----------------------------------------------------------------------------- -// --SECTION-- public methods -// ----------------------------------------------------------------------------- - -//////////////////////////////////////////////////////////////////////////////// -/// @brief creates a Json representation of itself to be persisted -//////////////////////////////////////////////////////////////////////////////// - -ArangoApp.prototype.toJSON = function () { - var json = { - manifest: this._manifest, - name: this._name, - version: this._version, - path: this._path, - options: this._options, - mount: this._mount, - root: this._root, - isSystem: this._isSystem, - isDevelopment: this._isDevelopment - }; - if (this.hasOwnProperty("_error")) { - json.error = this._error; - } - if (this._manifest.hasOwnProperty("author")) { - json.author = this._manifest.author; - } - if (this._manifest.hasOwnProperty("description")) { - json.description = this._manifest.description; - } - if (this.hasOwnProperty("_thumbnail")) { - json.thumbnail = this._thumbnail; - } - - return json; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief creates a reduced Json representation of itself for output -//////////////////////////////////////////////////////////////////////////////// - -ArangoApp.prototype.simpleJSON = function () { - var json = { - name: this._name, - version: this._version, - mount: this._mount - }; - return json; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief toggles development mode -//////////////////////////////////////////////////////////////////////////////// - -ArangoApp.prototype.development = function(activate) { - this._isDevelopment = activate; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief set app dependencies -//////////////////////////////////////////////////////////////////////////////// - -ArangoApp.prototype.updateDeps = function (deps) { - var expected = this._manifest.dependencies; - var invalid = []; - - _.each(deps, function (mount, name) { - if (!expected[name]) { - invalid.push("Unexpected dependency " + name); - } - this._options.dependencies[name] = mount || undefined; - }, this); - - _.each(this._options.dependencies, function (mount, name) { - if (mount) { - Object.defineProperty(this._dependencies, name, { - configurable: true, - enumerable: true, - get: function () { - return require("org/arangodb/foxx").requireApp(mount); - } - }); - } - }, this); - - return invalid; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief set app configuration -//////////////////////////////////////////////////////////////////////////////// - -ArangoApp.prototype.configure = function(config) { - var expected = this._manifest.configuration; - var invalid = []; - this._options.configuration = this._options.configuration || {}; - - _.each(config, function (rawValue, name) { - if (!expected[name]) { - invalid.push("Unexpected Option " + name); - } else { - var value = rawValue; - var type = expected[name].type; - var schema = utils.parameterTypes[type]; - var error; - var result; - if (expected[name].required !== false) { - result = joi.any().required().validate(value); - if (result.error) { - error = result.error.message.replace(/^"value"/, '"' + name + '"'); - } - } - if (!error) { - if (schema.isJoi) { - schema = schema.optional().allow(null); - if (expected[name].default !== undefined) { - schema = schema.default(expected[name].default); - } - result = schema.validate(value); - if (result.error) { - error = result.error.message.replace(/^"value"/, '"' + name + '"'); - } - value = result.value; - } else { - try { - value = schema(value); - } catch (e) { - error = '"' + name + '": ' + e.message; - } - } - } - if (error) { - invalid.push(error); - } else { - this._options.configuration[name] = rawValue; - this._configuration[name] = value; - } - } - }, this); - - return invalid; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief get app configuration -//////////////////////////////////////////////////////////////////////////////// - -ArangoApp.prototype.getConfiguration = function (simple) { - var config = {}; - var definitions = this._manifest.configuration; - var options = this._options.configuration; - _.each(definitions, function (dfn, name) { - var value = options[name] === undefined ? dfn.default : options[name]; - config[name] = simple ? value : _.extend(_.clone(dfn), { - title: utils.getReadableName(name), - current: value - }); - }); - return config; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief get app dependencies -//////////////////////////////////////////////////////////////////////////////// - -ArangoApp.prototype.getDependencies = function(simple) { - var deps = {}; - var definitions = this._manifest.dependencies; - var options = this._options.dependencies; - _.each(definitions, function (dfn, name) { - deps[name] = simple ? options[name] : { - definition: dfn, - title: utils.getReadableName(name), - current: options[name] - }; - }); - return deps; -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief App needs to be configured -//////////////////////////////////////////////////////////////////////////////// - -ArangoApp.prototype.needsConfiguration = function() { - var config = this.getConfiguration(); - var deps = this.getDependencies(); - return _.any(config, function (cfg) { - return cfg.current === undefined && cfg.required !== false; - }) || _.any(deps, function (dep) { - return dep.current === undefined && dep.definition.required !== false; - }); -}; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief loadAppScript -//////////////////////////////////////////////////////////////////////////////// - -ArangoApp.prototype.loadAppScript = function (filename, options) { - options = options || {}; - - var appContext = _.extend({}, options.appContext, this._context); - var full = fs.join(this._root, this._path, filename); - - if (!fs.exists(full)) { - throwFileNotFound(full); - } - - var fileContent = fs.read(full); - - if (options.transform) { - fileContent = options.transform(fileContent); - } else if (/\.coffee$/.test(filename)) { - var cs = require("coffee-script"); - fileContent = cs.compile(fileContent, {bare: true}); - } - - var context = {}; - - if (options.context) { - Object.keys(options.context).forEach(function (key) { - context[key] = options.context[key]; - }); - } - - var localModule = appContext.appPackage.createAppModule(this, filename); - - context.__filename = full; - context.__dirname = module.normalizeModuleName(full + "/.."); - context.console = require("org/arangodb/foxx/console")(this._mount); - context.applicationContext = appContext; - - try { - return localModule.run(fileContent, context); - } catch(e) { - if (e instanceof ArangoError) { - throw e; - } - var err = new ArangoError({ - errorNum: errors.ERROR_FAILED_TO_EXECUTE_SCRIPT.code, - errorMessage: errors.ERROR_FAILED_TO_EXECUTE_SCRIPT.message - + "\nFile: " + filename - }); - err.stack = e.stack; - err.cause = e; - throw err; - } -}; - -exports.ArangoApp = ArangoApp; - -// ----------------------------------------------------------------------------- -// --SECTION-- END-OF-FILE -// ----------------------------------------------------------------------------- - -// Local Variables: -// mode: outline-minor -// outline-regexp: "/// @brief\\|/// @addtogroup\\|/// @page\\|// --SECTION--\\|/// @\\}\\|/\\*jslint" -// End: diff --git a/js/server/modules/org/arangodb/foxx/manager.js b/js/server/modules/org/arangodb/foxx/manager.js index c5a65a9386..e93298e4b6 100644 --- a/js/server/modules/org/arangodb/foxx/manager.js +++ b/js/server/modules/org/arangodb/foxx/manager.js @@ -43,7 +43,7 @@ const semver = require('semver'); const utils = require('org/arangodb/foxx/manager-utils'); const store = require('org/arangodb/foxx/store'); const deprecated = require('org/arangodb/deprecated'); -const ArangoApp = require('org/arangodb/foxx/arangoApp').ArangoApp; +const FoxxService = require('org/arangodb/foxx/service'); const TemplateEngine = require('org/arangodb/foxx/templateEngine').Engine; const routeApp = require('org/arangodb/foxx/routing').routeApp; const exportApp = require('org/arangodb/foxx/routing').exportApp; @@ -61,7 +61,6 @@ const executeGlobalContextFunction = require('internal').executeGlobalContextFun const actions = require('org/arangodb/actions'); const plainServerVersion = require("org/arangodb").plainServerVersion; const _ = require('underscore'); -const Module = require('module'); const throwDownloadError = arangodb.throwDownloadError; const throwFileNotFound = arangodb.throwFileNotFound; @@ -263,8 +262,8 @@ function refillCaches(dbname) { while (cursor.hasNext()) { var config = _.clone(cursor.next()); - var app = new ArangoApp(config); - var mount = app._mount; + var app = new FoxxService(config); + var mount = app.mount; cache[mount] = app; routes.push(mount); } @@ -449,9 +448,9 @@ function isSystemMount(mount) { function computeRootAppPath(mount) { if (isSystemMount(mount)) { - return Module._systemAppPath; + return FoxxService._systemAppPath; } - return Module._appPath; + return FoxxService._appPath; } //////////////////////////////////////////////////////////////////////////////// @@ -490,12 +489,12 @@ function computeAppPath(mount) { function executeAppScript(scriptName, app, argv) { var readableName = utils.getReadableName(scriptName); - var scripts = app._manifest.scripts; + var scripts = app.manifest.scripts; // Only run setup/teardown scripts if they exist if (scripts[scriptName] || (scriptName !== 'setup' && scriptName !== 'teardown')) { try { - return app.loadAppScript(scripts[scriptName], { + return app.run(scripts[scriptName], { appContext: { argv: argv ? (Array.isArray(argv) ? argv : [argv]) : [] } @@ -503,7 +502,7 @@ function executeAppScript(scriptName, app, argv) { } catch (e) { if (!(e.cause || e).statusCode) { let details = String((e.cause || e).stack || e.cause || e); - console.errorLines(`Running script "${readableName}" not possible for mount "${app._mount}":\n${details}`); + console.errorLines(`Running script "${readableName}" not possible for mount "${app.mount}":\n${details}`); } throw e; } @@ -557,7 +556,7 @@ function appConfig(mount, options, activateDevelopment) { function createApp(mount, options, activateDevelopment) { var dbname = arangodb.db._name(); var config = appConfig(mount, options, activateDevelopment); - var app = new ArangoApp(config); + var app = new FoxxService(config); appCache[dbname][mount] = app; return app; } @@ -821,10 +820,10 @@ function readme(mount) { let app = lookupApp(mount); let path, readmeText; - path = fs.join(app._root, app._path, 'README.md'); + path = fs.join(app.root, app.path, 'README.md'); readmeText = fs.exists(path) && fs.read(path); if (!readmeText) { - path = fs.join(app._root, app._path, 'README'); + path = fs.join(app.root, app.path, 'README'); readmeText = fs.exists(path) && fs.read(path); } return readmeText || null; @@ -933,7 +932,7 @@ function rescanFoxx(mount) { if (definition !== null) { collection.remove(definition._key); } - _scanFoxx(mount, old._options, old._isDevelopment); + _scanFoxx(mount, old.options, old.isDevelopment); } }); } @@ -978,7 +977,7 @@ function _validateApp(appInfo) { var tempPath = fs.getTempFile('apps', false); try { _buildAppInPath(appInfo, tempPath, {}); - var tmp = new ArangoApp(fakeAppConfig(tempPath)); + var tmp = new FoxxService(fakeAppConfig(tempPath)); if (!tmp.needsConfiguration()) { routeApp(tmp, true); exportApp(tmp); @@ -1376,7 +1375,7 @@ function upgrade(appInfo, mount, options) { options.configuration[key] = oldConf[key]; } }); - var oldDeps = oldApp._options.dependencies || {}; + var oldDeps = oldApp.options.dependencies || {}; options.dependencies = options.dependencies || {}; Object.keys(oldDeps).forEach(function (key) { if (!options.dependencies.hasOwnProperty(key)) { @@ -1464,7 +1463,7 @@ function configure(mount, options) { [ mount ] ); utils.validateMount(mount, true); var app = lookupApp(mount); - var invalid = app.configure(options.configuration || {}); + var invalid = app.applyConfiguration(options.configuration || {}); if (invalid.length > 0) { // TODO Error handling console.log(invalid); @@ -1485,7 +1484,7 @@ function updateDeps(mount, options) { [ mount ] ); utils.validateMount(mount, true); var app = lookupApp(mount); - var invalid = app.updateDeps(options.dependencies || {}); + var invalid = app.applyDependencies(options.dependencies || {}); if (invalid.length > 0) { // TODO Error handling console.log(invalid); @@ -1547,7 +1546,7 @@ function syncWithFolder(options) { options.replace = true; appCache = appCache || {}; appCache[dbname] = {}; - var folders = fs.listTree(Module._appPath).filter(filterAppRoots); + var folders = fs.listTree(FoxxService._appPath).filter(filterAppRoots); var collection = utils.getStorage(); return folders.map(function (folder) { var mount; diff --git a/js/server/modules/org/arangodb/foxx/mocha.js b/js/server/modules/org/arangodb/foxx/mocha.js index 5d98498865..62f01b24b6 100644 --- a/js/server/modules/org/arangodb/foxx/mocha.js +++ b/js/server/modules/org/arangodb/foxx/mocha.js @@ -86,7 +86,7 @@ exports.run = function runMochaTests(app, reporterName) { files.forEach(function (file) { var context = {}; suite.emit('pre-require', context, file, mocha); - suite.emit('require', app.loadAppScript(file, {context: context}), file, mocha); + suite.emit('require', app.run(file, {context: context}), file, mocha); suite.emit('post-require', global, file, mocha); }); @@ -111,11 +111,11 @@ function isNotPattern(pattern) { } function findTestFiles(app) { - var patterns = app._manifest.tests || []; + var patterns = app.manifest.tests || []; if (patterns.every(isNotPattern)) { return patterns.slice(); } - var basePath = fs.join(app._root, app._path); + var basePath = fs.join(app.root, app.path); var paths = fs.listTree(basePath); var matchers = patterns.map(function (pattern) { if (pattern.charAt(0) === '/') { diff --git a/js/server/modules/org/arangodb/foxx/routing.js b/js/server/modules/org/arangodb/foxx/routing.js index 8a24fb654c..f1f6bfcb9f 100644 --- a/js/server/modules/org/arangodb/foxx/routing.js +++ b/js/server/modules/org/arangodb/foxx/routing.js @@ -193,7 +193,7 @@ var buildAssetRoute = function (app, path, basePath, asset) { var installAssets = function (app, routes) { var path; - var desc = app._manifest; + var desc = app.manifest; if (! desc) { throw new Error("Invalid application manifest"); @@ -206,7 +206,7 @@ var installAssets = function (app, routes) { for (path in desc.assets) { if (desc.assets.hasOwnProperty(path)) { var asset = desc.assets[path]; - var basePath = fs.join(app._root, app._path); + var basePath = fs.join(app.root, app.path); if (asset.hasOwnProperty('basePath')) { basePath = asset.basePath; @@ -247,8 +247,8 @@ var installAssets = function (app, routes) { action: { "do": "org/arangodb/actions/pathHandler", "options": { - root: app._root, - path: fs.join(app._path, directory), + root: app.root, + path: fs.join(app.path, directory), gzip: gzip } } @@ -444,26 +444,26 @@ var exportApp = function (app) { errorMessage: errors.ERROR_APP_NEEDS_CONFIGURATION.message }); } - if (!app._isDevelopment && isExported(app._mount)) { - return app._exports; + if (!app.isDevelopment && isExported(app.mount)) { + return app.main.exports; } - app._exports = {}; + app.main.exports = {}; // mount all exports - if (app._manifest.hasOwnProperty("exports")) { - var appExports = app._manifest.exports; + if (app.manifest.hasOwnProperty("exports")) { + var appExports = app.manifest.exports; if (typeof appExports === "string") { - app._exports = app.loadAppScript(appExports); + app.main.exports = app.run(appExports); } else if (appExports) { Object.keys(appExports).forEach(function (key) { - app._exports[key] = app.loadAppScript(appExports[key]); + app.main.exports[key] = app.run(appExports[key]); }); } } - setIsExported(app._mount); - return app._exports; + setIsExported(app.mount); + return app.exports; }; @@ -512,7 +512,7 @@ var routeNeedsConfigurationApp = function(app) { return { urlPrefix: "", - name: 'foxx("' + app._mount + '")', + name: 'foxx("' + app.mount + '")', routes: [{ "internal": true, "url" : { @@ -523,7 +523,7 @@ var routeNeedsConfigurationApp = function(app) { "callback": function(req, res) { res.responseCode = actions.HTTP_SERVICE_UNAVAILABLE; res.contentType = "text/html; charset=utf-8"; - if (app._isDevelopment) { + if (app.isDevelopment) { res.body = "
" + "This service is not configured.
"; res.body += "" + escapeHTML(String(errToPrint.stack)) + ""; @@ -602,12 +602,12 @@ var routeApp = function (app, isInstallProcess) { return routeNeedsConfigurationApp(app); } - var defaultDocument = app._manifest.defaultDocument; + var defaultDocument = app.manifest.defaultDocument; // setup the routes var routes = { urlPrefix: "", - name: 'foxx("' + app._mount + '")', + name: 'foxx("' + app.mount + '")', routes: [], middleware: [], context: {}, @@ -617,18 +617,18 @@ var routeApp = function (app, isInstallProcess) { appContext: { app: app, - module: app._context.appModule + module: app.main } }; - if ((app._mount + defaultDocument) !== app._mount) { + if ((app.mount + defaultDocument) !== app.mount) { // only add redirection if src and target are not the same routes.routes.push({ "url" : { match: "/" }, "action" : { "do" : "org/arangodb/actions/redirectRequest", "options" : { - "permanently" : !app._isDevelopment, + "permanently" : !app.isDevelopment, "destination" : defaultDocument, "relative" : true } @@ -637,7 +637,7 @@ var routeApp = function (app, isInstallProcess) { } // mount all controllers - var controllers = app._manifest.controllers; + var controllers = app.manifest.controllers; try { if (controllers) { @@ -664,19 +664,16 @@ var routeApp = function (app, isInstallProcess) { return null; }; -var mountController = function (app, routes, mountPoint, file) { - validateRoute(mountPoint); +var mountController = function (service, routes, mount, filename) { + validateRoute(mount); // set up a context for the application start function var tmpContext = { - prefix: arangodb.normalizeURL("/" + mountPoint), // app mount + prefix: arangodb.normalizeURL(`/${mount}`), // app mount foxxes: [] }; - app.loadAppScript(file, { - transform: transformScript(file), - appContext: tmpContext - }); + service.run(filename, {appContext: tmpContext}); // ............................................................................. // routingInfo @@ -689,11 +686,11 @@ var mountController = function (app, routes, mountPoint, file) { _.extend(routes.models, foxx.models); - if (ri.hasOwnProperty("middleware")) { - createMiddlewareMatchers(ri.middleware, routes, mountPoint, ri.urlPrefix); + if (ri.hasOwnProperty('middleware')) { + createMiddlewareMatchers(ri.middleware, routes, mount, ri.urlPrefix); } - if (ri.hasOwnProperty("routes")) { - transformRoutes(ri.routes, routes, mountPoint, ri.urlPrefix, app._isDevelopment); + if (ri.hasOwnProperty('routes')) { + transformRoutes(ri.routes, routes, mount, ri.urlPrefix, service._isDevelopment); } } }; diff --git a/js/server/modules/org/arangodb/foxx/service.js b/js/server/modules/org/arangodb/foxx/service.js index 65a67ff379..880596971d 100644 --- a/js/server/modules/org/arangodb/foxx/service.js +++ b/js/server/modules/org/arangodb/foxx/service.js @@ -1,11 +1,14 @@ 'use strict'; const _ = require('underscore'); +const ArangoError = require('org/arangodb').ArangoError; +const errors = require('org/arangodb').errors; const internal = require('internal'); const assert = require('assert'); const Module = require('module'); const path = require('path'); const fs = require('fs'); const parameterTypes = require('org/arangodb/foxx/manager-utils').parameterTypes; +const getReadableName = require('org/arangodb/foxx/manager-utils').getReadableName; const getExports = require('org/arangodb/foxx').getExports; const APP_PATH = internal.appPath ? path.resolve(internal.appPath) : undefined; @@ -13,8 +16,100 @@ const STARTUP_PATH = internal.startupPath ? path.resolve(internal.startupPath) : const DEV_APP_PATH = internal.devAppPath ? path.resolve(internal.devAppPath) : undefined; class AppContext { + constructor(service) { + this.basePath = path.resolve(service.root, service.path); + this.comments = []; + Object.defineProperty(this, '_service', { + get() { + return service; + } + }); + } + + foxxFilename(filename) { + return fs.safeJoin(this._prefix, filename); + } + + path(name) { + return path.join(this._prefix, name); + } + + collectionName(name) { + let fqn = ( + this.collectionPrefix + + name.replace(/[^a-z0-9]/ig, '_').replace(/(^_+|_+$)/g, '').substr(0, 64) + ); + if (!fqn.length) { + throw new Error(`Cannot derive collection name from "${name}"`); + } + } + + collection(name) { + return internal.db._collection(this.collectionName(name)); + } + + get _prefix() { + return this.basePath; + } + + get baseUrl() { + return `/_db/${encodeURIComponent(internal.db._name())}/${this._service.mount.slice(1)}`; + } + + get collectionPrefix() { + return this._service.collectionPrefix; + } + + get mount() { + return this._service.mount; + } + + get name() { + return this._service.name; + } + + get version() { + return this._service.version; + } + + get manifest() { + return this._service.manifest; + } + + get isDevelopment() { + return this._service.isDevelopment; + } + + get isProduction() { + return !this.isDevelopment; + } + + get options() { + return this._service.options; + } + + get configuration() { + return this._service.configuration; + } + + get dependencies() { + return this._service.dependencies; + } } +Object.defineProperties(AppContext.prototype, { + comment: { + value: function (str) { + this.comments.push(str); + } + }, + clearComments: { + value: function () { + this.comments.splice(0, this.comments.length); + } + } +}); + function createConfiguration(definitions) { const config = {}; Object.keys(definitions).forEach(function (name) { @@ -33,7 +128,7 @@ function createDependencies(definitions, options) { configurable: true, enumerable: true, get() { - const mount = options.dependencies[name]; + const mount = options[name]; return mount ? getExports(mount) : undefined; } }); @@ -96,6 +191,7 @@ class FoxxService { this.main = new Module(`foxx:${data.mount}`); this.main.filename = path.resolve(this.root, this.path, lib, '.foxx'); this.main.context.applicationContext = new AppContext(this); + this.main.context.console = require('org/arangodb/foxx/console')(this.mount); } applyConfiguration(config) { @@ -161,6 +257,121 @@ class FoxxService { return warnings; } + _PRINT(context) { + context.output += `[FoxxService "${this.name}" (${this.version}) on ${this.mount}]`; + } + + toJSON() { + const result = { + name: this.name, + version: this.version, + manifest: this.manifest, + path: this.path, + options: this.options, + mount: this.mount, + isSystem: this.isSystem, + isDevelopment: this.isDevelopment + }; + if (this.error) { + result.error = this.error; + } + if (this.manifest.author) { + result.author = this.manifest.author; + } + if (this.manifest.description) { + result.description = this.manifest.description; + } + if (this.thumbnail) { + result.thumbnail = this.thumbnail; + } + return result; + } + + simpleJSON() { + return { + name: this.name, + version: this.version, + mount: this.mount + }; + } + + development(isDevelopment) { + this.isDevelopment = isDevelopment; + } + + getConfiguration(simple) { + var config = {}; + var definitions = this.manifest.configuration; + var options = this.options.configuration; + _.each(definitions, function (dfn, name) { + var value = options[name] === undefined ? dfn.default : options[name]; + config[name] = simple ? value : _.extend(_.clone(dfn), { + title: getReadableName(name), + current: value + }); + }); + return config; + } + + getDependencies(simple) { + var deps = {}; + var definitions = this.manifest.dependencies; + var options = this.options.dependencies; + _.each(definitions, function (dfn, name) { + deps[name] = simple ? options[name] : { + definition: dfn, + title: getReadableName(name), + current: options[name] + }; + }); + return deps; + } + + needsConfiguration() { + var config = this.getConfiguration(); + var deps = this.getDependencies(); + return _.any(config, function (cfg) { + return cfg.current === undefined && cfg.required !== false; + }) || _.any(deps, function (dep) { + return dep.current === undefined && dep.definition.required !== false; + }); + } + + run(filename, options) { + options = options || {}; + filename = path.resolve(this.main.context.__dirname, filename); + + var module = new Module(filename, this.main); + module.context.applicationContext = _.extend( + new AppContext(this.main.context.applicationContext._service), + this.main.context.applicationContext, + options.appContext + ); + + if (options.context) { + Object.keys(options.context).forEach(function (key) { + module.context[key] = options.context[key]; + }); + } + + try { + module.load(filename); + return module.exports; + } catch(e) { + if (e instanceof ArangoError) { + throw e; + } + var err = new ArangoError({ + errorNum: errors.ERROR_FAILED_TO_EXECUTE_SCRIPT.code, + errorMessage: errors.ERROR_FAILED_TO_EXECUTE_SCRIPT.message + + '\nFile: ' + filename + }); + err.stack = e.stack; + err.cause = e; + throw err; + } + } + get exports() { return this.main.exports; } @@ -188,7 +399,7 @@ class FoxxService { if (this.isSystem) { return ''; } - return this.mount.substr(1).replace(/-/g, '_').replace(/\//g, '_') + '_'; + return this.mount.substr(1).replace(/[-.:/]/g, '_') + '_'; } static get _startupPath() { @@ -203,7 +414,7 @@ class FoxxService { static get _systemAppPath() { return APP_PATH ? ( - path.join(this._appPath, 'apps', 'system') + path.join(STARTUP_PATH, 'apps', 'system') ) : undefined; } diff --git a/js/server/modules/org/arangodb/foxx/sessions.js b/js/server/modules/org/arangodb/foxx/sessions.js index 4b6e25b1bb..3e0533ce18 100644 --- a/js/server/modules/org/arangodb/foxx/sessions.js +++ b/js/server/modules/org/arangodb/foxx/sessions.js @@ -213,7 +213,7 @@ class Sessions { if (typeof this.configuration.sessionStorage !== 'string') { return this.configuration.sessionStorage; } - return Foxx.requireApp(this.configuration.sessionStorage).sessionStorage; + return Foxx.getExports(this.configuration.sessionStorage).sessionStorage; } } diff --git a/js/server/modules/org/arangodb/foxx/swagger.js b/js/server/modules/org/arangodb/foxx/swagger.js index b521f2d0d2..8ed023fcd3 100644 --- a/js/server/modules/org/arangodb/foxx/swagger.js +++ b/js/server/modules/org/arangodb/foxx/swagger.js @@ -29,6 +29,7 @@ var _ = require('underscore'); var fs = require('fs'); +var internal = require('internal'); var ArangoError = require('org/arangodb').ArangoError; var errors = require('org/arangodb').errors; var resultNotFound = require('org/arangodb/actions').resultNotFound; @@ -96,7 +97,7 @@ function swaggerPath(path, basePath) { return path; } if (!basePath) { - basePath = fs.join(module.startupPath(), 'server', 'assets', 'swagger'); + basePath = fs.join(internal.startupPath, 'server', 'assets', 'swagger'); } return fs.safeJoin(basePath, path); } @@ -117,12 +118,12 @@ function swaggerJson(req, res, opts) { res.json({ swagger: '2.0', info: { - description: app && app._manifest.description, - version: app && app._manifest.version, - title: app && app._manifest.name, - license: app && app._manifest.license && {name: app._manifest.license} + description: app && app.manifest.description, + version: app && app.manifest.version, + title: app && app.manifest.name, + license: app && app.manifest.license && {name: app.manifest.license} }, - basePath: '/_db/' + encodeURIComponent(req.database) + (app ? app._mount : opts.appPath), + basePath: '/_db/' + encodeURIComponent(req.database) + (app ? app.mount : opts.appPath), schemes: [req.protocol], paths: swagger.paths, // securityDefinitions: {}, diff --git a/js/server/tests/recovery/foxx-directories.js b/js/server/tests/recovery/foxx-directories.js index 63879546b3..bf68fc1efc 100644 --- a/js/server/tests/recovery/foxx-directories.js +++ b/js/server/tests/recovery/foxx-directories.js @@ -29,7 +29,7 @@ var db = require("org/arangodb").db; var internal = require("internal"); -var Module = require('module'); +var FoxxService = require('org/arangodb/foxx/service'); var jsunity = require("jsunity"); var fs = require("fs"); @@ -37,7 +37,7 @@ function runSetup () { 'use strict'; internal.debugClearFailAt(); - var appPath = fs.join(Module._appPath, ".."); + var appPath = fs.join(FoxxService._appPath, ".."); try { db._dropDatabase("UnitTestsRecovery1"); @@ -84,7 +84,7 @@ function recoverySuite () { //////////////////////////////////////////////////////////////////////////////// testFoxxDirectories : function () { - var appPath = fs.join(Module._appPath, ".."); + var appPath = fs.join(FoxxService._appPath, ".."); assertTrue(fs.isDirectory(fs.join(appPath, "UnitTestsRecovery1"))); assertTrue(fs.isFile(fs.join(appPath, "UnitTestsRecovery1", "foo.json")));