1
0
Fork 0

Implemented FoxxService

This commit is contained in:
Alan Plum 2015-09-22 13:49:29 +02:00
parent 309dcb2c17
commit 252cba5e9f
14 changed files with 305 additions and 564 deletions

View File

@ -57,7 +57,7 @@ publicController.get("/whoAmI", function(req, res) {
var uid = req.session && req.session.get("uid"); var uid = req.session && req.session.get("uid");
var user = null; var user = null;
if (uid) { if (uid) {
var users = Foxx.requireApp("_system/users").userStorage; var users = Foxx.getExports("_system/users").userStorage;
try { try {
user = users.get(uid).get("user"); user = users.get(uid).get("user");
} catch (e) { } catch (e) {
@ -82,11 +82,11 @@ publicController.post("/login", function (req, res) {
} else { } else {
req.session = publicController.sessions.getSessionStorage().create(); 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 credentials = req.parameters.credentials;
var user = users.resolve(credentials.get("username")); var user = users.resolve(credentials.get("username"));
if (!user) throw new UnauthorizedError(); 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")); var valid = auth.verifyPassword(user.get("authData").simple, credentials.get("password"));
if (!valid) throw new UnauthorizedError(); if (!valid) throw new UnauthorizedError();
req.session.setUser(user); req.session.setUser(user);

View File

@ -32,7 +32,7 @@
var FoxxController = require("org/arangodb/foxx").Controller, var FoxxController = require("org/arangodb/foxx").Controller,
UnauthorizedError = require("http-errors").Unauthorized, UnauthorizedError = require("http-errors").Unauthorized,
internal = require("internal"), internal = require("internal"),
Configuration = require("models/configuration").Model, Configuration = require("./models/configuration").Model,
controller = new FoxxController(applicationContext), controller = new FoxxController(applicationContext),
db = require("internal").db, db = require("internal").db,
FoxxManager = require("org/arangodb/foxx/manager"); FoxxManager = require("org/arangodb/foxx/manager");

View File

@ -55,7 +55,7 @@
) )
}; };
var fs = require("fs"); var fs = require("fs");
var defaultThumb = require("/lib/defaultThumbnail").defaultThumb; var defaultThumb = require("./lib/defaultThumbnail").defaultThumb;
controller.activateSessions({ controller.activateSessions({
autoCreateSession: false, autoCreateSession: false,
@ -212,7 +212,7 @@
var mount = validateMount(req); var mount = validateMount(req);
var app = FoxxManager.lookupApp(mount); var app = FoxxManager.lookupApp(mount);
if (app.hasOwnProperty("_thumbnail")) { if (app.hasOwnProperty("_thumbnail")) {
res.body = app._thumbnail; res.body = app.thumbnail;
} else { } else {
res.body = defaultThumb; res.body = defaultThumb;
} }
@ -345,7 +345,7 @@
controller.get("/download/zip", function(req, res) { controller.get("/download/zip", function(req, res) {
var mount = validateMount(req); var mount = validateMount(req);
var app = FoxxManager.lookupApp(mount); 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); var zipPath = fmUtils.zipDirectory(dir);
res.set("Content-Type", "application/octet-stream"); res.set("Content-Type", "application/octet-stream");
res.set("Content-Disposition", "attachment; filename=app.zip"); res.set("Content-Disposition", "attachment; filename=app.zip");

View File

@ -114,17 +114,18 @@ function Module(id, parent) {
} }
this.context = { this.context = {
module: this,
exports: this.exports,
require: createRequire(this),
print: internal.print, print: internal.print,
process: NATIVE_MODULES.process, process: NATIVE_MODULES.process,
console: NATIVE_MODULES.console, console: NATIVE_MODULES.console,
module: this,
exports: this.exports,
require: createRequire(this),
__filename: null, __filename: null,
__dirname: null __dirname: null
}; };
if (parent) { if (parent) {
Object.keys(parent.context).forEach(function (key) { Object.keys(parent.context).forEach(function (key) {
if (!hasOwnProperty(this.context, key)) { if (!hasOwnProperty(this.context, key)) {
this.context[key] = parent.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 // backwards compatibility
Module.Module = Module; Module.Module = Module;

View File

@ -2,7 +2,7 @@
const EventEmitter = require('events').EventEmitter; const EventEmitter = require('events').EventEmitter;
const internal = require('internal'); const internal = require('internal');
const path = require('path'); const fs = require('fs');
module.exports = exports = new EventEmitter(); module.exports = exports = new EventEmitter();
@ -15,7 +15,7 @@ exports.stdout = {
} }
}; };
exports.cwd = function () { exports.cwd = function () {
return path.resolve(internal.appPath); return fs.makeAbsolute('');
}; };
exports.nextTick = function (fn) { exports.nextTick = function (fn) {
fn(); fn();

View File

@ -1037,7 +1037,7 @@ function foxxRouting (req, res, options, next) {
try { try {
var app = foxxManager.lookupApp(mount); var app = foxxManager.lookupApp(mount);
var devel = app._isDevelopment; var devel = app.isDevelopment;
if (devel || ! options.hasOwnProperty('routing')) { if (devel || ! options.hasOwnProperty('routing')) {
delete options.error; delete options.error;
@ -1047,8 +1047,8 @@ function foxxRouting (req, res, options, next) {
app = foxxManager.lookupApp(mount); app = foxxManager.lookupApp(mount);
} }
if (app._isBroken) { if (app.isBroken) {
throw app._error; throw app.error;
} }
options.routing = flattenRoutingTree(buildRoutingTree([foxxManager.routes(mount)])); options.routing = flattenRoutingTree(buildRoutingTree([foxxManager.routes(mount)]));

View File

@ -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:

View File

@ -43,7 +43,7 @@ const semver = require('semver');
const utils = require('org/arangodb/foxx/manager-utils'); const utils = require('org/arangodb/foxx/manager-utils');
const store = require('org/arangodb/foxx/store'); const store = require('org/arangodb/foxx/store');
const deprecated = require('org/arangodb/deprecated'); 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 TemplateEngine = require('org/arangodb/foxx/templateEngine').Engine;
const routeApp = require('org/arangodb/foxx/routing').routeApp; const routeApp = require('org/arangodb/foxx/routing').routeApp;
const exportApp = require('org/arangodb/foxx/routing').exportApp; const exportApp = require('org/arangodb/foxx/routing').exportApp;
@ -61,7 +61,6 @@ const executeGlobalContextFunction = require('internal').executeGlobalContextFun
const actions = require('org/arangodb/actions'); const actions = require('org/arangodb/actions');
const plainServerVersion = require("org/arangodb").plainServerVersion; const plainServerVersion = require("org/arangodb").plainServerVersion;
const _ = require('underscore'); const _ = require('underscore');
const Module = require('module');
const throwDownloadError = arangodb.throwDownloadError; const throwDownloadError = arangodb.throwDownloadError;
const throwFileNotFound = arangodb.throwFileNotFound; const throwFileNotFound = arangodb.throwFileNotFound;
@ -263,8 +262,8 @@ function refillCaches(dbname) {
while (cursor.hasNext()) { while (cursor.hasNext()) {
var config = _.clone(cursor.next()); var config = _.clone(cursor.next());
var app = new ArangoApp(config); var app = new FoxxService(config);
var mount = app._mount; var mount = app.mount;
cache[mount] = app; cache[mount] = app;
routes.push(mount); routes.push(mount);
} }
@ -449,9 +448,9 @@ function isSystemMount(mount) {
function computeRootAppPath(mount) { function computeRootAppPath(mount) {
if (isSystemMount(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) { function executeAppScript(scriptName, app, argv) {
var readableName = utils.getReadableName(scriptName); var readableName = utils.getReadableName(scriptName);
var scripts = app._manifest.scripts; var scripts = app.manifest.scripts;
// Only run setup/teardown scripts if they exist // Only run setup/teardown scripts if they exist
if (scripts[scriptName] || (scriptName !== 'setup' && scriptName !== 'teardown')) { if (scripts[scriptName] || (scriptName !== 'setup' && scriptName !== 'teardown')) {
try { try {
return app.loadAppScript(scripts[scriptName], { return app.run(scripts[scriptName], {
appContext: { appContext: {
argv: argv ? (Array.isArray(argv) ? argv : [argv]) : [] argv: argv ? (Array.isArray(argv) ? argv : [argv]) : []
} }
@ -503,7 +502,7 @@ function executeAppScript(scriptName, app, argv) {
} catch (e) { } catch (e) {
if (!(e.cause || e).statusCode) { if (!(e.cause || e).statusCode) {
let details = String((e.cause || e).stack || e.cause || e); 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; throw e;
} }
@ -557,7 +556,7 @@ function appConfig(mount, options, activateDevelopment) {
function createApp(mount, options, activateDevelopment) { function createApp(mount, options, activateDevelopment) {
var dbname = arangodb.db._name(); var dbname = arangodb.db._name();
var config = appConfig(mount, options, activateDevelopment); var config = appConfig(mount, options, activateDevelopment);
var app = new ArangoApp(config); var app = new FoxxService(config);
appCache[dbname][mount] = app; appCache[dbname][mount] = app;
return app; return app;
} }
@ -821,10 +820,10 @@ function readme(mount) {
let app = lookupApp(mount); let app = lookupApp(mount);
let path, readmeText; 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); readmeText = fs.exists(path) && fs.read(path);
if (!readmeText) { 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); readmeText = fs.exists(path) && fs.read(path);
} }
return readmeText || null; return readmeText || null;
@ -933,7 +932,7 @@ function rescanFoxx(mount) {
if (definition !== null) { if (definition !== null) {
collection.remove(definition._key); 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); var tempPath = fs.getTempFile('apps', false);
try { try {
_buildAppInPath(appInfo, tempPath, {}); _buildAppInPath(appInfo, tempPath, {});
var tmp = new ArangoApp(fakeAppConfig(tempPath)); var tmp = new FoxxService(fakeAppConfig(tempPath));
if (!tmp.needsConfiguration()) { if (!tmp.needsConfiguration()) {
routeApp(tmp, true); routeApp(tmp, true);
exportApp(tmp); exportApp(tmp);
@ -1376,7 +1375,7 @@ function upgrade(appInfo, mount, options) {
options.configuration[key] = oldConf[key]; options.configuration[key] = oldConf[key];
} }
}); });
var oldDeps = oldApp._options.dependencies || {}; var oldDeps = oldApp.options.dependencies || {};
options.dependencies = options.dependencies || {}; options.dependencies = options.dependencies || {};
Object.keys(oldDeps).forEach(function (key) { Object.keys(oldDeps).forEach(function (key) {
if (!options.dependencies.hasOwnProperty(key)) { if (!options.dependencies.hasOwnProperty(key)) {
@ -1464,7 +1463,7 @@ function configure(mount, options) {
[ mount ] ); [ mount ] );
utils.validateMount(mount, true); utils.validateMount(mount, true);
var app = lookupApp(mount); var app = lookupApp(mount);
var invalid = app.configure(options.configuration || {}); var invalid = app.applyConfiguration(options.configuration || {});
if (invalid.length > 0) { if (invalid.length > 0) {
// TODO Error handling // TODO Error handling
console.log(invalid); console.log(invalid);
@ -1485,7 +1484,7 @@ function updateDeps(mount, options) {
[ mount ] ); [ mount ] );
utils.validateMount(mount, true); utils.validateMount(mount, true);
var app = lookupApp(mount); var app = lookupApp(mount);
var invalid = app.updateDeps(options.dependencies || {}); var invalid = app.applyDependencies(options.dependencies || {});
if (invalid.length > 0) { if (invalid.length > 0) {
// TODO Error handling // TODO Error handling
console.log(invalid); console.log(invalid);
@ -1547,7 +1546,7 @@ function syncWithFolder(options) {
options.replace = true; options.replace = true;
appCache = appCache || {}; appCache = appCache || {};
appCache[dbname] = {}; appCache[dbname] = {};
var folders = fs.listTree(Module._appPath).filter(filterAppRoots); var folders = fs.listTree(FoxxService._appPath).filter(filterAppRoots);
var collection = utils.getStorage(); var collection = utils.getStorage();
return folders.map(function (folder) { return folders.map(function (folder) {
var mount; var mount;

View File

@ -86,7 +86,7 @@ exports.run = function runMochaTests(app, reporterName) {
files.forEach(function (file) { files.forEach(function (file) {
var context = {}; var context = {};
suite.emit('pre-require', context, file, mocha); 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); suite.emit('post-require', global, file, mocha);
}); });
@ -111,11 +111,11 @@ function isNotPattern(pattern) {
} }
function findTestFiles(app) { function findTestFiles(app) {
var patterns = app._manifest.tests || []; var patterns = app.manifest.tests || [];
if (patterns.every(isNotPattern)) { if (patterns.every(isNotPattern)) {
return patterns.slice(); 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 paths = fs.listTree(basePath);
var matchers = patterns.map(function (pattern) { var matchers = patterns.map(function (pattern) {
if (pattern.charAt(0) === '/') { if (pattern.charAt(0) === '/') {

View File

@ -193,7 +193,7 @@ var buildAssetRoute = function (app, path, basePath, asset) {
var installAssets = function (app, routes) { var installAssets = function (app, routes) {
var path; var path;
var desc = app._manifest; var desc = app.manifest;
if (! desc) { if (! desc) {
throw new Error("Invalid application manifest"); throw new Error("Invalid application manifest");
@ -206,7 +206,7 @@ var installAssets = function (app, routes) {
for (path in desc.assets) { for (path in desc.assets) {
if (desc.assets.hasOwnProperty(path)) { if (desc.assets.hasOwnProperty(path)) {
var asset = desc.assets[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')) { if (asset.hasOwnProperty('basePath')) {
basePath = asset.basePath; basePath = asset.basePath;
@ -247,8 +247,8 @@ var installAssets = function (app, routes) {
action: { action: {
"do": "org/arangodb/actions/pathHandler", "do": "org/arangodb/actions/pathHandler",
"options": { "options": {
root: app._root, root: app.root,
path: fs.join(app._path, directory), path: fs.join(app.path, directory),
gzip: gzip gzip: gzip
} }
} }
@ -444,26 +444,26 @@ var exportApp = function (app) {
errorMessage: errors.ERROR_APP_NEEDS_CONFIGURATION.message errorMessage: errors.ERROR_APP_NEEDS_CONFIGURATION.message
}); });
} }
if (!app._isDevelopment && isExported(app._mount)) { if (!app.isDevelopment && isExported(app.mount)) {
return app._exports; return app.main.exports;
} }
app._exports = {}; app.main.exports = {};
// mount all exports // mount all exports
if (app._manifest.hasOwnProperty("exports")) { if (app.manifest.hasOwnProperty("exports")) {
var appExports = app._manifest.exports; var appExports = app.manifest.exports;
if (typeof appExports === "string") { if (typeof appExports === "string") {
app._exports = app.loadAppScript(appExports); app.main.exports = app.run(appExports);
} else if (appExports) { } else if (appExports) {
Object.keys(appExports).forEach(function (key) { Object.keys(appExports).forEach(function (key) {
app._exports[key] = app.loadAppScript(appExports[key]); app.main.exports[key] = app.run(appExports[key]);
}); });
} }
} }
setIsExported(app._mount); setIsExported(app.mount);
return app._exports; return app.exports;
}; };
@ -512,7 +512,7 @@ var routeNeedsConfigurationApp = function(app) {
return { return {
urlPrefix: "", urlPrefix: "",
name: 'foxx("' + app._mount + '")', name: 'foxx("' + app.mount + '")',
routes: [{ routes: [{
"internal": true, "internal": true,
"url" : { "url" : {
@ -523,7 +523,7 @@ var routeNeedsConfigurationApp = function(app) {
"callback": function(req, res) { "callback": function(req, res) {
res.responseCode = actions.HTTP_SERVICE_UNAVAILABLE; res.responseCode = actions.HTTP_SERVICE_UNAVAILABLE;
res.contentType = "text/html; charset=utf-8"; res.contentType = "text/html; charset=utf-8";
if (app._isDevelopment) { if (app.isDevelopment) {
res.body = "<html><head><title>Service Unavailable</title></head><body><p>" + res.body = "<html><head><title>Service Unavailable</title></head><body><p>" +
"This service is not configured.</p>"; "This service is not configured.</p>";
res.body += "<h3>Configuration Information</h3>"; res.body += "<h3>Configuration Information</h3>";
@ -558,7 +558,7 @@ var routeBrokenApp = function(app, err) {
return { return {
urlPrefix: "", urlPrefix: "",
name: 'foxx("' + app._mount + '")', name: 'foxx("' + app.mount + '")',
routes: [{ routes: [{
"internal": true, "internal": true,
"url" : { "url" : {
@ -569,7 +569,7 @@ var routeBrokenApp = function(app, err) {
"callback": function(req, res) { "callback": function(req, res) {
res.responseCode = actions.HTTP_SERVICE_UNAVAILABLE; res.responseCode = actions.HTTP_SERVICE_UNAVAILABLE;
res.contentType = "text/html; charset=utf-8"; res.contentType = "text/html; charset=utf-8";
if (app._isDevelopment) { if (app.isDevelopment) {
var errToPrint = err.cause ? err.cause : err; var errToPrint = err.cause ? err.cause : err;
res.body = "<html><head><title>" + escapeHTML(String(errToPrint)) + res.body = "<html><head><title>" + escapeHTML(String(errToPrint)) +
"</title></head><body><pre>" + escapeHTML(String(errToPrint.stack)) + "</pre></body></html>"; "</title></head><body><pre>" + escapeHTML(String(errToPrint.stack)) + "</pre></body></html>";
@ -602,12 +602,12 @@ var routeApp = function (app, isInstallProcess) {
return routeNeedsConfigurationApp(app); return routeNeedsConfigurationApp(app);
} }
var defaultDocument = app._manifest.defaultDocument; var defaultDocument = app.manifest.defaultDocument;
// setup the routes // setup the routes
var routes = { var routes = {
urlPrefix: "", urlPrefix: "",
name: 'foxx("' + app._mount + '")', name: 'foxx("' + app.mount + '")',
routes: [], routes: [],
middleware: [], middleware: [],
context: {}, context: {},
@ -617,18 +617,18 @@ var routeApp = function (app, isInstallProcess) {
appContext: { appContext: {
app: app, 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 // only add redirection if src and target are not the same
routes.routes.push({ routes.routes.push({
"url" : { match: "/" }, "url" : { match: "/" },
"action" : { "action" : {
"do" : "org/arangodb/actions/redirectRequest", "do" : "org/arangodb/actions/redirectRequest",
"options" : { "options" : {
"permanently" : !app._isDevelopment, "permanently" : !app.isDevelopment,
"destination" : defaultDocument, "destination" : defaultDocument,
"relative" : true "relative" : true
} }
@ -637,7 +637,7 @@ var routeApp = function (app, isInstallProcess) {
} }
// mount all controllers // mount all controllers
var controllers = app._manifest.controllers; var controllers = app.manifest.controllers;
try { try {
if (controllers) { if (controllers) {
@ -664,19 +664,16 @@ var routeApp = function (app, isInstallProcess) {
return null; return null;
}; };
var mountController = function (app, routes, mountPoint, file) { var mountController = function (service, routes, mount, filename) {
validateRoute(mountPoint); validateRoute(mount);
// set up a context for the application start function // set up a context for the application start function
var tmpContext = { var tmpContext = {
prefix: arangodb.normalizeURL("/" + mountPoint), // app mount prefix: arangodb.normalizeURL(`/${mount}`), // app mount
foxxes: [] foxxes: []
}; };
app.loadAppScript(file, { service.run(filename, {appContext: tmpContext});
transform: transformScript(file),
appContext: tmpContext
});
// ............................................................................. // .............................................................................
// routingInfo // routingInfo
@ -689,11 +686,11 @@ var mountController = function (app, routes, mountPoint, file) {
_.extend(routes.models, foxx.models); _.extend(routes.models, foxx.models);
if (ri.hasOwnProperty("middleware")) { if (ri.hasOwnProperty('middleware')) {
createMiddlewareMatchers(ri.middleware, routes, mountPoint, ri.urlPrefix); createMiddlewareMatchers(ri.middleware, routes, mount, ri.urlPrefix);
} }
if (ri.hasOwnProperty("routes")) { if (ri.hasOwnProperty('routes')) {
transformRoutes(ri.routes, routes, mountPoint, ri.urlPrefix, app._isDevelopment); transformRoutes(ri.routes, routes, mount, ri.urlPrefix, service._isDevelopment);
} }
} }
}; };

View File

@ -1,11 +1,14 @@
'use strict'; 'use strict';
const _ = require('underscore'); const _ = require('underscore');
const ArangoError = require('org/arangodb').ArangoError;
const errors = require('org/arangodb').errors;
const internal = require('internal'); const internal = require('internal');
const assert = require('assert'); const assert = require('assert');
const Module = require('module'); const Module = require('module');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const parameterTypes = require('org/arangodb/foxx/manager-utils').parameterTypes; 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 getExports = require('org/arangodb/foxx').getExports;
const APP_PATH = internal.appPath ? path.resolve(internal.appPath) : undefined; 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; const DEV_APP_PATH = internal.devAppPath ? path.resolve(internal.devAppPath) : undefined;
class AppContext { 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) { function createConfiguration(definitions) {
const config = {}; const config = {};
Object.keys(definitions).forEach(function (name) { Object.keys(definitions).forEach(function (name) {
@ -33,7 +128,7 @@ function createDependencies(definitions, options) {
configurable: true, configurable: true,
enumerable: true, enumerable: true,
get() { get() {
const mount = options.dependencies[name]; const mount = options[name];
return mount ? getExports(mount) : undefined; return mount ? getExports(mount) : undefined;
} }
}); });
@ -96,6 +191,7 @@ class FoxxService {
this.main = new Module(`foxx:${data.mount}`); this.main = new Module(`foxx:${data.mount}`);
this.main.filename = path.resolve(this.root, this.path, lib, '.foxx'); this.main.filename = path.resolve(this.root, this.path, lib, '.foxx');
this.main.context.applicationContext = new AppContext(this); this.main.context.applicationContext = new AppContext(this);
this.main.context.console = require('org/arangodb/foxx/console')(this.mount);
} }
applyConfiguration(config) { applyConfiguration(config) {
@ -161,6 +257,121 @@ class FoxxService {
return warnings; 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() { get exports() {
return this.main.exports; return this.main.exports;
} }
@ -188,7 +399,7 @@ class FoxxService {
if (this.isSystem) { if (this.isSystem) {
return ''; return '';
} }
return this.mount.substr(1).replace(/-/g, '_').replace(/\//g, '_') + '_'; return this.mount.substr(1).replace(/[-.:/]/g, '_') + '_';
} }
static get _startupPath() { static get _startupPath() {
@ -203,7 +414,7 @@ class FoxxService {
static get _systemAppPath() { static get _systemAppPath() {
return APP_PATH ? ( return APP_PATH ? (
path.join(this._appPath, 'apps', 'system') path.join(STARTUP_PATH, 'apps', 'system')
) : undefined; ) : undefined;
} }

View File

@ -213,7 +213,7 @@ class Sessions {
if (typeof this.configuration.sessionStorage !== 'string') { if (typeof this.configuration.sessionStorage !== 'string') {
return this.configuration.sessionStorage; return this.configuration.sessionStorage;
} }
return Foxx.requireApp(this.configuration.sessionStorage).sessionStorage; return Foxx.getExports(this.configuration.sessionStorage).sessionStorage;
} }
} }

View File

@ -29,6 +29,7 @@
var _ = require('underscore'); var _ = require('underscore');
var fs = require('fs'); var fs = require('fs');
var internal = require('internal');
var ArangoError = require('org/arangodb').ArangoError; var ArangoError = require('org/arangodb').ArangoError;
var errors = require('org/arangodb').errors; var errors = require('org/arangodb').errors;
var resultNotFound = require('org/arangodb/actions').resultNotFound; var resultNotFound = require('org/arangodb/actions').resultNotFound;
@ -96,7 +97,7 @@ function swaggerPath(path, basePath) {
return path; return path;
} }
if (!basePath) { if (!basePath) {
basePath = fs.join(module.startupPath(), 'server', 'assets', 'swagger'); basePath = fs.join(internal.startupPath, 'server', 'assets', 'swagger');
} }
return fs.safeJoin(basePath, path); return fs.safeJoin(basePath, path);
} }
@ -117,12 +118,12 @@ function swaggerJson(req, res, opts) {
res.json({ res.json({
swagger: '2.0', swagger: '2.0',
info: { info: {
description: app && app._manifest.description, description: app && app.manifest.description,
version: app && app._manifest.version, version: app && app.manifest.version,
title: app && app._manifest.name, title: app && app.manifest.name,
license: app && app._manifest.license && {name: app._manifest.license} 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], schemes: [req.protocol],
paths: swagger.paths, paths: swagger.paths,
// securityDefinitions: {}, // securityDefinitions: {},

View File

@ -29,7 +29,7 @@
var db = require("org/arangodb").db; var db = require("org/arangodb").db;
var internal = require("internal"); var internal = require("internal");
var Module = require('module'); var FoxxService = require('org/arangodb/foxx/service');
var jsunity = require("jsunity"); var jsunity = require("jsunity");
var fs = require("fs"); var fs = require("fs");
@ -37,7 +37,7 @@ function runSetup () {
'use strict'; 'use strict';
internal.debugClearFailAt(); internal.debugClearFailAt();
var appPath = fs.join(Module._appPath, ".."); var appPath = fs.join(FoxxService._appPath, "..");
try { try {
db._dropDatabase("UnitTestsRecovery1"); db._dropDatabase("UnitTestsRecovery1");
@ -84,7 +84,7 @@ function recoverySuite () {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
testFoxxDirectories : function () { testFoxxDirectories : function () {
var appPath = fs.join(Module._appPath, ".."); var appPath = fs.join(FoxxService._appPath, "..");
assertTrue(fs.isDirectory(fs.join(appPath, "UnitTestsRecovery1"))); assertTrue(fs.isDirectory(fs.join(appPath, "UnitTestsRecovery1")));
assertTrue(fs.isFile(fs.join(appPath, "UnitTestsRecovery1", "foo.json"))); assertTrue(fs.isFile(fs.join(appPath, "UnitTestsRecovery1", "foo.json")));