@themost/web
Version:
MOST Web Framework 2.0 - Web Server Module
908 lines (891 loc) • 39.1 kB
JavaScript
// @themost-framework 2.0 Codename Blueshift Copyright (c) 2017-2025, THEMOST LP All rights reserved
var sprintf = require('sprintf').sprintf;
var HttpController = require('../mvc').HttpController;
var _ = require('lodash');
var pluralize = require('pluralize');
var TraceUtils = require('@themost/common').TraceUtils;
var LangUtils = require('@themost/common').LangUtils;
var HttpError = require('@themost/common').HttpError;
var HttpServerError = require('@themost/common').HttpServerError;
var HttpMethodNotAllowedError = require('@themost/common').HttpMethodNotAllowedError;
var HttpBadRequestError = require('@themost/common').HttpBadRequestError;
var HttpNotFoundError = require('@themost/common').HttpNotFoundError;
/**
* @classdesc HttpDataController class describes a common MOST Web Framework data controller.
* This controller is inherited by default from all data models. It offers a set of basic actions for CRUD operations against data objects
* and allows filtering, paging, sorting and grouping data objects with options similar to [OData]{@link http://www.odata.org/}.
<h2>Basic Features</h2>
<h3>Data Filtering ($filter query option)</h3>
<p>Logical Operators</p>
<p>The following table contains the logical operators supported in the query language:</p>
<table class="table-flat">
<thead><tr><th>Operator</th><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>eq</td><td>Equal</td><td>/Order/index.json?$filter=customer eq 353</td></tr>
<tr><td>ne</td><td>Not Equal</td><td>/Order/index.json?$filter=orderStatus/alternateName ne 'OrderDelivered'</td></tr>
<tr><td>gt</td><td>Greater than</td><td>/Order/index.json?$filter=orderedItem/price gt 1000</td></tr>
<tr><td>ge</td><td>Greater than or equal</td><td>/Order/index.json?$filter=orderedItem/price ge 500</td></tr>
<tr><td>lt</td><td>Lower than</td><td>/Order/index.json?$filter=orderedItem/price lt 500</td></tr>
<tr><td>le</td><td>Lower than or equal</td><td>/Order/index.json?$filter=orderedItem/price le 1000</td></tr>
<tr><td>and</td><td>Logical and</td><td>/Order/index.json?$filter=orderedItem/price gt 1000 and orderStatus/alternateName eq 'OrderPickup'</td></tr>
<tr><td>or</td><td>Logical or</td><td>/Order/index.json?$filter=orderStatus/alternateName eq 'OrderPickup' or orderStatus/alternateName eq 'OrderProcessing'</td></tr>
</tbody>
</table>
<p>Arithmetic Operators</p>
<p>The following table contains the arithmetic operators supported in the query language:</p>
<table class="table-flat">
<thead><tr><th>Operator</th><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>add</td><td>Addition</td><td>/Order/index.json?$filter=(orderedItem/price add 10) gt 1560</td></tr>
<tr><td>sub</td><td>Subtraction</td><td>/Order/index.json?$filter=(orderedItem/price sub 10) gt 1540</td></tr>
<tr><td>mul</td><td>Multiplication</td><td>/Order/index.json?$filter=(orderedItem/price mul 1.20) gt 1000</td></tr>
<tr><td>div</td><td>Division</td><td>/Order/index.json?$filter=(orderedItem/price div 2) le 500</td></tr>
<tr><td>mod</td><td>Modulo</td><td>/Order/index.json?$filter=(orderedItem/price mod 2) eq 0</td></tr>
</tbody>
</table>
<p>Functions</p>
<p>A set of functions are also defined for use in $filter query option:</p>
<table class="table-flat">
<thead><tr><th>Function</th><th>Example</th></tr></thead>
<tbody>
<tr><td colspan="2"><b>String Functions</b></td></tr>
<tr><td>startswith(field,string)</td><td>/Product/index.json?$filter=startswith(name,'Apple') eq true</td></tr>
<tr><td>endswith(field,string)</td><td>/Product/index.json?$filter=endswith(name,'Workstation') eq true</td></tr>
<tr><td>contains(field,string)</td><td>/Product/index.json?$filter=contains(name,'MacBook') eq true</td></tr>
<tr><td>length(field)</td><td>/Product/index.json?$filter=length(name) gt 40</td></tr>
<tr><td>indexof(field,string)</td><td>/Product/index.json?$filter=indexof(name,'Air') gt 1</td></tr>
<tr><td>substring(field,number)</td><td>/Product/index.json?$filter=substring(category,1) eq 'aptops'</td></tr>
<tr><td>substring(field,number,number)</td><td>/Product/index.json?$filter=substring(category,1,2) eq 'ap'</td></tr>
<tr><td>tolower(field)</td><td>/Product/index.json?$filter=tolower(category) eq 'laptops'</td></tr>
<tr><td>toupper(field)</td><td>/Product/index.json?$filter=toupper(category) eq 'LAPTOPS'</td></tr>
<tr><td>trim(field)</td><td>/Product/index.json?$filter=trim(category) eq 'Laptops'</td></tr>
<tr><td colspan="2"><b>Date Functions</b></td></tr>
<tr><td>day(field)</td><td>/Order/index.json?$filter=day(orderDate) eq 4</td></tr>
<tr><td>month(field)</td><td>/Order/index.json?$filter=month(orderDate) eq 6</td></tr>
<tr><td>year(field)</td><td>/Order/index.json?$filter=year(orderDate) ge 2014</td></tr>
<tr><td>hour(field)</td><td>/Order/index.json?$filter=hour(orderDate) ge 12 and hour(orderDate) lt 14</td></tr>
<tr><td>minute(field)</td><td>/Order/index.json?$filter=minute(orderDate) gt 15 and minute(orderDate) le 30</td></tr>
<tr><td>second(field)</td><td>/Order/index.json?$filter=second(orderDate) ge 0 and second(orderDate) le 45</td></tr>
<tr><td>date(field)</td><td>/Order/index.json?$filter=date(orderDate) eq '2015-03-20'</td></tr>
<tr><td colspan="2"><b>Math Functions</b></td></tr>
<tr><td>round(field)</td><td>/Product/index.json?$filter=round(price) le 389</td></tr>
<tr><td>floor(field)</td><td>/Product/index.json?$filter=floor(price) eq 389</td></tr>
<tr><td>ceiling(field)</td><td>/Product/index.json?$filter=ceiling(price) eq 390</td></tr>
</tbody>
</table>
<h3>Attribute Selection ($select query option)</h3>
<p>The following table contains attribute selection expressions supported in the query language:</p>
<table class="table-flat">
<thead><tr><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>Select attribute</td><td>/Order/index.json?$select=id,customer,orderStatus</td></tr>
<tr><td>Select attribute with alias</td><td>/Order/index.json?$select=id,customer/description as customerName,orderStatus/name as orderStatusName</td></tr>
<tr><td>Select attribute with aggregation</td><td>/Order/index.json?$select=count(id) as totalCount&$filter=orderStatus/alternateName eq 'OrderProcessing'</td></tr>
<tr><td> </td><td>/Product/index.json?$select=max(price) as maxPrice&$filter=category eq 'Laptops'</td></tr>
<tr><td> </td><td>/Product/index.json?$select=min(price) as minPrice&$filter=category eq 'Laptops'</td></tr>
</tbody>
</table>
<h3>Data Sorting ($orderby or $order query options)</h3>
<table class="table-flat">
<thead><tr><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>Ascending order</td><td>/Product/index.json?$orderby=name</td></tr>
<tr><td>Descending order</td><td>/Product/index.json?$orderby=category desc,name desc</td></tr>
</tbody>
</table>
<h3>Data Paging ($top, $skip and $inlinecount query options)</h3>
<p>The $top query option allows developers to apply paging in the result-set by giving the max number of records for each page. The default value is 25.
The $skip query option provides a way to skip a number of records. The default value is 0.
The $inlinecount query option includes in the result-set the total number of records of the query expression provided:
<pre class="prettyprint"><code>
{
"total": 94,
"value": [ ... ]
}
</code></pre>
<p>The default value is false.</p>
</p>
<table class="table-flat">
<thead><tr><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>Limit records</td><td>/Product/index.json?$top=5</td></tr>
<tr><td>Skip records</td><td>/Product/index.json?$top=5&$skip=5</td></tr>
<tr><td>Paged records</td><td>/Product/index.json?$top=5&$skip=5&$inlinecount=true</td></tr>
</tbody>
</table>
<h3>Data Grouping ($groupby or $group query options)</h3>
<p>The $groupby query option allows developers to group the result-set by one or more attributes</p>
<table class="table-flat">
<thead><tr><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>group</td><td>/Product/index.json?$select=count(id) as totalCount,category&$groupby=category</td></tr>
<tr><td>group and sort</td><td>/Product/index.json?$select=count(id) as totalCount,category&$groupby=category&$orderby=count(id) desc</td></tr>
</tbody>
</table>
<h3>Data Expanding ($expand)</h3>
<p>The $expand query option forces response to include associated objects which are not marked as expandable by default.</p>
<table class="table-flat">
<thead><tr><th>Description</th><th>Example</th></tr></thead>
<tbody>
<tr><td>expand</td><td>/Order/index.json?$filter=orderStatus/alternateName eq 'OrderProcessing'&$expand=customer</td></tr>
</tbody>
</table>
<p>The $expand option is optional for a <a href="https://docs.themost.io/most-data/DataField.html">DataField</a> marked as expandable.</p>
* @class
* @constructor
* @augments HttpController
* @property {DataModel} model - Gets or sets the current data model.
*/
function HttpDataController()
{
var model_;
var self = this;
Object.defineProperty(this, 'model', {
get: function() {
if (model_)
return model_;
model_ = self.context.model(self.name);
return model_;
},
set: function(value) {
model_ = value;
},
configurable:true,
enumerable:false
});
}
LangUtils.inherits(HttpDataController, HttpController);
/**
* Handles data object creation (e.g. /user/1/new.html, /user/1/new.json etc)
* @param {Function} callback
*/
HttpDataController.prototype.new = function (callback) {
try {
var self = this,
context = self.context;
context.handle(['GET'],function() {
callback(null, self.result());
}).handle(['POST', 'PUT'],function() {
var target = self.model.convert(context.params[self.model.name] || context.params.data, true);
self.model.save(target, function(err)
{
if (err) {
callback(HttpError.create(err));
}
else {
if (context.params.attr('returnUrl'))
callback(null, context.params.attr('returnUrl'));
callback(null, self.result(target));
}
});
}).unhandle(function() {
callback(new HttpMethodNotAllowedError());
});
}
catch (e) {
callback(HttpError.create(e));
}
};
/**
* Handles data object edit (e.g. /user/1/edit.html, /user/1/edit.json etc)
* @param {Function} callback
*/
HttpDataController.prototype.edit = function (callback) {
try {
var self = this,
context = self.context;
context.handle(['POST', 'PUT'], function() {
//get context param
var target = self.model.convert(context.params[self.model.name] || context.params.data, true);
if (target) {
self.model.save(target, function(err)
{
if (err) {
TraceUtils.log(err);
TraceUtils.log(err.stack);
callback(HttpError.create(err));
}
else {
if (context.params.attr('returnUrl'))
callback(null, context.params.attr('returnUrl'));
callback(null, self.result(target));
}
});
}
else {
callback(new HttpBadRequestError());
}
}).handle('DELETE', function() {
//get context param
var target = context.params[self.model.name] || context.params.data;
if (target) {
self.model.remove(target, function(err)
{
if (err) {
callback(HttpError.create(err));
}
else {
if (context.params.attr('returnUrl'))
callback(null, context.params.attr('returnUrl'));
callback(null, self.result(null));
}
});
}
else {
callback(new HttpBadRequestError());
}
}).handle('GET', function() {
if (context.request.route) {
if (context.request.route.static) {
callback(null, self.result());
return;
}
}
//get context param (id)
var filter = null, id = context.params.attr('id');
if (id) {
//create the equivalent open data filter
return self.model.where(self.model.primaryKey).equal(id).getItem().then(function(result) {
if (_.isNil(result)) {
return callback(null, self.result());
}
return callback(null, self.result(result));
}).catch(function(err) {
return callback(err);
});
}
else {
//get the requested open data filter
filter = context.params.attr('$filter');
}
if (filter) {
self.model.filter(filter, function(err, q) {
if (err) {
callback(HttpError.create(err));
return;
}
q.take(1, function (err, result) {
try {
if (err) {
callback(err);
}
else {
if (result.length>0)
callback(null, self.result(result));
else
callback(null, self.result(null));
}
}
catch (e) {
callback(HttpError.create(e));
}
});
});
}
else {
callback(new HttpBadRequestError());
}
}).unhandle(function() {
callback(new HttpMethodNotAllowedError());
});
}
catch (e) {
callback(HttpError.create(e));
}
};
HttpDataController.prototype.schema = function (callback) {
var self = this, context = self.context;
context.handle('GET', function() {
if (self.model) {
//prepare client model
var clone = JSON.parse(JSON.stringify(self.model));
var m = _.assign({}, clone);
//delete private properties
var keys = Object.keys(m);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (key.indexOf("_") === 0)
delete m[key];
}
//delete other server properties
delete m.view;
delete m.source;
delete m.fields;
delete m.privileges;
delete m.constraints;
delete m.eventListeners;
//set fields equal attributes
m.attributes = JSON.parse(JSON.stringify(self.model.attributes));
m.attributes.forEach(function(x) {
var mapping = self.model.inferMapping(x.name);
if (mapping)
x.mapping = JSON.parse(JSON.stringify(mapping));
//delete private properties
delete x.value;
delete x.calculation;
});
//prepare views and view fields
if (m.views) {
m.views.forEach(function(view) {
if (view.fields) {
view.fields.forEach(function(field) {
if (/\./.test(field.name)===false) {
//extend view field
var name = field.name;
var mField = m.attributes.filter(function(y) {
return (y.name===name);
})[0];
if (mField) {
for (var key in mField) {
if (mField.hasOwnProperty(key) && !field.hasOwnProperty(key)) {
field[key] = mField[key];
}
}
}
}
});
}
});
}
callback(null, self.result(m));
}
else {
callback(new HttpNotFoundError());
}
}).unhandle(function() {
callback(new HttpMethodNotAllowedError());
});
};
/**
* Handles data object display (e.g. /user/1/show.html, /user/1/show.json etc)
* @param {Function} callback
*/
HttpDataController.prototype.show = function (callback) {
try {
var self = this, context = self.context;
context.handle('GET', function() {
if (context.request.route) {
if (context.request.route.static) {
callback(null, self.result());
return;
}
}
var filter = null, id = context.params.attr('id');
if (id) {
//create the equivalent open data filter
filter = sprintf('%s eq %s',self.model.primaryKey,id);
}
else {
//get the requested open data filter
filter = context.params.attr('$filter');
}
self.model.filter(filter, function(err, q) {
if (err) {
callback(HttpError.create(err));
return;
}
q.take(1, function (err, result) {
try {
if (err) {
callback(HttpError.create(err));
}
else {
if (result.length>0)
callback(null, self.result(result[0]));
else
callback(new HttpNotFoundError('Item Not Found'));
}
}
catch (e) {
callback(HttpError.create(e));
}
});
});
}).unhandle(function() {
callback(new HttpMethodNotAllowedError());
});
}
catch (e) {
callback(e);
}
};
/**
* Handles data object deletion (e.g. /user/1/remove.html, /user/1/remove.json etc)
* @param {Function} callback
*/
HttpDataController.prototype.remove = function (callback) {
try {
var self = this, context = self.context;
context.handle(['POST','DELETE'], function() {
var target = context.params[self.model.name] || context.params.data;
if (target) {
self.model.remove(target, function(err)
{
if (err) {
callback(HttpError.create(err));
}
else {
if (context.params.attr('returnUrl'))
callback(null, context.params.attr('returnUrl'));
callback(null, self.result(target));
}
});
}
else {
callback(new HttpBadRequestError());
}
}).unhandle(function() {
callback(new HttpMethodNotAllowedError());
});
}
catch (e) {
callback(HttpError.create(e))
}
};
/**
* @param {Function} callback
* @private
*/
HttpDataController.prototype.filter = function (callback) {
var self = this, params = self.context.params;
if (typeof self.model !== 'object' || self.model === null) {
callback(new Error('Model is of the wrong type or undefined.'));
return;
}
var filter = params.$filter,
select = params.$select,
search = params.$search,
skip = params.$skip || 0,
levels = parseInt(params.$levels),
orderBy = params.$order || params.$orderby,
groupBy = params.$group || params.$groupby,
expand = params.$expand;
self.model.filter(filter,
/**
* @param {Error} err
* @param {DataQueryable} q
*/
function (err, q) {
try {
if (err) {
return callback(err);
}
else {
if ((typeof search === 'string') && (search.length>0)) {
q.search(search);
}
//set $groupby
if (groupBy) {
q.groupBy.apply(q, groupBy.split(',').map(function(x) {
return x.replace(/^\s+|\s+$/g, '');
}));
}
//set $select
if (select) {
q.select.apply(q, select.split(',').map(function(x) {
return x.replace(/^\s+|\s+$/g, '');
}));
}
//set $skip
if (!/^\d+$/.test(skip)) {
return callback(new HttpBadRequestError("Skip may be a non-negative integer."))
}
//set expandable levels
if (!isNaN(levels)) {
q.levels(levels);
}
q.skip(skip);
//set $orderby
if (orderBy) {
orderBy.split(',').map(function(x) {
return x.replace(/^\s+|\s+$/g, '');
}).forEach(function(x) {
if (/\s+desc$/i.test(x)) {
q.orderByDescending(x.replace(/\s+desc$/i, ''));
}
else if (/\s+asc/i.test(x)) {
q.orderBy(x.replace(/\s+asc/i, ''));
}
else {
q.orderBy(x);
}
});
}
if (expand) {
var resolver = require("@themost/data/data-expand-resolver");
var matches = resolver.testExpandExpression(expand);
if (matches && matches.length>0) {
q.expand.apply(q, matches);
}
}
//return
callback(null, q);
}
}
catch (e) {
callback(e);
}
});
};
/**
*
* @param {Function} callback
*/
HttpDataController.prototype.index = function(callback)
{
try {
var self = this, context = self.context,
top = parseInt(context.params.attr('$top')),
take = top > 0 ? top : (top === -1 ? top : 25);
var count = /^true$/ig.test(context.params.attr('$inlinecount')) || /^true$/ig.test(context.params.attr('$count')) || false;
var first = /^true$/ig.test(context.params.attr('$first')) || false;
var asArray = /^true$/ig.test(context.params.attr('$array')) || false;
TraceUtils.debug(context.request.url);
context.handle('GET', function() {
if (context.request.route) {
if (context.request.route.static) {
callback(null, self.result([]));
return;
}
}
self.filter(
/**
* @param {Error} err
* @param {DataQueryable=} q
*/
function(err, q) {
try {
if (err) {
return callback(HttpError.create(err));
}
//apply as array parameter
q.asArray(asArray);
if (first) {
return q.first().then(function(result) {
return callback(null, self.result(result));
}).catch(function(err) {
return callback(HttpError.create(err));
});
}
if (take<0) {
return q.all().then(function(result) {
if (count) {
return callback(null, self.result({
value:result,
total:result.length
}));
}
else {
return callback(null, self.result(result));
}
}).catch(function(err) {
return callback(HttpError.create(err));
});
}
else {
if (count) {
return q.take(take).list().then(function(result) {
return callback(null, self.result(result));
}).catch(function(err) {
return callback(HttpError.create(err));
});
}
else {
return q.take(take).getItems().then(function(result) {
return callback(null, self.result(result));
}).catch(function(err) {
return callback(HttpError.create(err));
});
}
}
}
catch (e) {
return callback(e);
}
});
}).handle(['POST', 'PUT'], function() {
var target;
try {
target = self.model.convert(context.params[self.model.name] || context.params.data, true);
}
catch(err) {
TraceUtils.log(err);
var er = new HttpError(422, "An error occured while converting data objects.", err.message);
er.code = 'EDATA';
return callback(er);
}
if (target) {
self.model.save(target, function(err)
{
if (err) {
TraceUtils.log(err);
callback(HttpError.create(err));
}
else {
callback(null, self.result(target));
}
});
}
else {
return callback(new HttpBadRequestError());
}
}).handle('DELETE', function() {
//get data
var target;
try {
target = self.model.convert(context.params[self.model.name] || context.params.data, true);
}
catch(err) {
TraceUtils.log(err);
var er = new HttpError(422, "An error occurred while converting data objects.", err.message);
er.code = 'EDATA';
return callback(er);
}
if (target) {
self.model.remove(target, function(err)
{
if (err) {
callback(HttpError.create(err));
}
else {
callback(null, self.result(target));
}
});
}
else {
return callback(new HttpBadRequestError());
}
}).unhandle(function() {
return callback(new HttpMethodNotAllowedError());
});
}
catch (e) {
callback(HttpError.create(e));
}
};
/**
* Returns an instance of HttpResult class which contains a collection of items based on the specified association.
* This association should be a one-to-many association or many-many association.
* A routing for this action may be:
<pre class="prettyprint"><code>
{ "url":"/:controller/:parent/:model/index.json", "mime":"application/json", "action":"association" }
</code></pre>
<p>
or
</p>
<pre class="prettyprint"><code>
{ "url":"/:controller/:parent/:model/index.html", "mime":"text/html", "action":"association" }
</code></pre>
<pre class="prettyprint"><code>
//get orders in JSON format
/GET /Party/353/Order/index.json
</code></pre>
<p>
This action supports common query options like $filter, $order, $top, $skip etc.
The result will be a result-set with associated items:
</p>
<pre class="prettyprint"><code>
//JSON Results:
{
"total": 8,
"skip": 0,
"value": [
{
"id": 37,
"customer": 353,
"orderDate": "2015-05-05 01:19:34.000+03:00",
"orderedItem": {
"id": 407,
"additionalType": "Product",
"category": "PC Components",
"price": 1625.49,
"model": "HR5845",
"releaseDate": "2015-09-20 03:35:33.000+03:00",
"name": "Nvidia GeForce GTX 650 Ti Boost",
"dateCreated": "2015-11-23 14:53:04.884+02:00",
"dateModified": "2015-11-23 14:53:04.887+02:00"
},
"orderNumber": "OFV804",
"orderStatus": {
"id": 1,
"name": "Delivered",
"alternateName": "OrderDelivered",
"description": "Representing the successful delivery of an order."
},
"paymentDue": "2015-05-25 01:19:34.000+03:00",
"paymentMethod": {
"id": 6,
"name": "Direct Debit",
"alternateName": "DirectDebit",
"description": "Payment by direct debit"
},
"additionalType": "Order",
"dateCreated": "2015-11-23 21:00:18.264+02:00",
"dateModified": "2015-11-23 21:00:18.266+02:00"
}
...]
...
}
</code></pre>
* @param {Function} callback - A callback function where the first argument will contain the Error object if an error occured, or null otherwise.
*/
HttpDataController.prototype.association = function(callback) {
try {
var self = this,
parent = self.context.params.parent,
model = self.context.params.model;
if (_.isNil(parent) || _.isNil(model)) {
return callback(new HttpBadRequestError());
}
return self.model.where(self.model.primaryKey).equal(parent).select(self.model.primaryKey).getTypedItem()
.then(function(obj) {
if (_.isNil(obj)) {
return callback(new HttpNotFoundError());
}
//get primary key
var key = obj[self.model.primaryKey];
//get mapping
var mapping = self.model.inferMapping(model);
//get count parameter
var count = LangUtils.parseBoolean(self.context.params.$inlinecount);
if (_.isNil(mapping)) {
//try to find associated model
//get singular model name
var otherModelName = pluralize.singular(model);
//search for model with this name
var otherModel = self.context.model(otherModelName);
if (otherModel) {
var otherFields = _.filter(otherModel.attributes, function(x) {
return x.type === self.model.name;
});
if (otherFields.length>1) {
return callback(new HttpMethodNotAllowedError("Multiple associations found"));
}
else if (otherFields.length === 1) {
var otherField = otherFields[0];
mapping = otherModel.inferMapping(otherField.name);
if (mapping && mapping.associationType === 'junction') {
var attr;
//search model for attribute that has an association of type junction with child model
if (mapping.parentModel === otherModel.name) {
attr = _.find(otherModel.attributes, function(x) {
return x.name === otherField.name;
});
}
else {
attr = _.find(self.model.attributes, function(x) {
return x.type === otherModel.name;
});
}
if (_.isNil(attr)) {
return callback(new HttpNotFoundError("Association not found"));
}
if (attr) {
model = attr.name;
mapping = self.model.inferMapping(attr.name);
}
}
}
}
if (_.isNil(mapping)) {
return callback(new HttpNotFoundError("Association not found"));
}
}
if (mapping.associationType === 'junction') {
/**
* @type {DataQueryable}
*/
var junction = obj.property(model);
return junction.model.filter(self.context.params, function (err, q) {
if (err) {
callback(err);
}
else {
//merge properties
if (q.query.$select) {
junction.query.$select = q.query.$select;
}
if (q.$expand) {
junction.$expand = q.$expand;
}
if (q.query.$group) {
junction.query.$group = q.query.$group;
}
if (q.query.$order) {
junction.query.$order = q.query.$order;
}
if (q.query.$prepared) {
junction.query.$where = q.query.$prepared;
}
if (q.query.$skip) {
junction.query.$skip = q.query.$skip;
}
if (q.query.$take) {
junction.query.$take = q.query.$take;
}
if (count) {
junction.list(function (err, result) {
if (err) {
return callback(err);
}
return callback(null, self.result(result));
});
}
else {
junction.getItems().then(function (result) {
return callback(null, self.result(result));
}).catch(function (err) {
return callback(err);
});
}
}
});
}
else if (mapping.parentModel === self.model.name && mapping.associationType === 'association') {
//get associated model
var associatedModel = self.context.model(mapping.childModel);
if (_.isNil(associatedModel)) {
return callback(new HttpNotFoundError("Associated model not found"));
}
return associatedModel.filter(self.context.params,
/**
* @param {Error} err
* @param {DataQueryable} q
* @returns {*}
*/
function (err, q) {
if (err) {
return callback(err);
}
if (count) {
q.where(mapping.childField).equal(key).list(function (err, result) {
if (err) {
return callback(err);
}
return callback(null, self.result(result));
});
}
else {
q.where(mapping.childField).equal(key).getItems().then(function (result) {
return callback(null, self.result(result));
}).catch(function (err) {
return callback(err);
});
}
});
}
else {
return callback(new HttpNotFoundError());
}
}).catch(function (err) {
return callback(err);
});
}
catch(err) {
TraceUtils.log(err);
callback(err, new HttpServerError());
}
};
if (typeof module !== 'undefined') {
module.exports = HttpDataController;
}