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 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);

View File

@ -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");

View File

@ -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");

View File

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

View File

@ -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();

View File

@ -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)]));

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 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;

View File

@ -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) === '/') {

View File

@ -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 = "<html><head><title>Service Unavailable</title></head><body><p>" +
"This service is not configured.</p>";
res.body += "<h3>Configuration Information</h3>";
@ -558,7 +558,7 @@ var routeBrokenApp = function(app, err) {
return {
urlPrefix: "",
name: 'foxx("' + app._mount + '")',
name: 'foxx("' + app.mount + '")',
routes: [{
"internal": true,
"url" : {
@ -569,7 +569,7 @@ var routeBrokenApp = function(app, err) {
"callback": function(req, res) {
res.responseCode = actions.HTTP_SERVICE_UNAVAILABLE;
res.contentType = "text/html; charset=utf-8";
if (app._isDevelopment) {
if (app.isDevelopment) {
var errToPrint = err.cause ? err.cause : err;
res.body = "<html><head><title>" + escapeHTML(String(errToPrint)) +
"</title></head><body><pre>" + escapeHTML(String(errToPrint.stack)) + "</pre></body></html>";
@ -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);
}
}
};

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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: {},

View File

@ -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")));