Merge remote-tracking branch 'upstream/master'
* upstream/master: Sort endpoints by letter. Add syntax highlighting styles & highlight threshold. Add contribution guidelines Bump version Fix how the array of models is iterated Bump version Make sure nested/referenced models in array are mapped to swagger Make sure nested/referenced models are mapped to swagger Bump version Newest Swagger UI requires application/x-www-form-urlencoded. Use `dist` property from swagger-ui package. Fixed undefined modelClass when using polymorphic relations Bump version Fix the type name for a property if model class is used Conflicts: lib/model-helper.js lib/swagger.js public/css/loopbackStyles.css
This commit is contained in:
commit
8563dd0463
|
@ -0,0 +1,151 @@
|
||||||
|
### Contributing ###
|
||||||
|
|
||||||
|
Thank you for your interest in `loopback-explorer`, an open source project
|
||||||
|
administered by StrongLoop.
|
||||||
|
|
||||||
|
Contributing to `loopback-explorer` is easy. In a few simple steps:
|
||||||
|
|
||||||
|
* Ensure that your effort is aligned with the project's roadmap by
|
||||||
|
talking to the maintainers, especially if you are going to spend a
|
||||||
|
lot of time on it.
|
||||||
|
|
||||||
|
* Make something better or fix a bug.
|
||||||
|
|
||||||
|
* Adhere to code style outlined in the [Google C++ Style Guide][] and
|
||||||
|
[Google Javascript Style Guide][].
|
||||||
|
|
||||||
|
* Sign the [Contributor License Agreement](https://cla.strongloop.com/strongloop/loopback-explorer)
|
||||||
|
|
||||||
|
* Submit a pull request through Github.
|
||||||
|
|
||||||
|
|
||||||
|
### Contributor License Agreement ###
|
||||||
|
|
||||||
|
```
|
||||||
|
Individual Contributor License Agreement
|
||||||
|
|
||||||
|
By signing this Individual Contributor License Agreement
|
||||||
|
("Agreement"), and making a Contribution (as defined below) to
|
||||||
|
StrongLoop, Inc. ("StrongLoop"), You (as defined below) accept and
|
||||||
|
agree to the following terms and conditions for Your present and
|
||||||
|
future Contributions submitted to StrongLoop. Except for the license
|
||||||
|
granted in this Agreement to StrongLoop and recipients of software
|
||||||
|
distributed by StrongLoop, You reserve all right, title, and interest
|
||||||
|
in and to Your Contributions.
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
"You" or "Your" shall mean the copyright owner or the individual
|
||||||
|
authorized by the copyright owner that is entering into this
|
||||||
|
Agreement with StrongLoop.
|
||||||
|
|
||||||
|
"Contribution" shall mean any original work of authorship,
|
||||||
|
including any modifications or additions to an existing work, that
|
||||||
|
is intentionally submitted by You to StrongLoop for inclusion in,
|
||||||
|
or documentation of, any of the products owned or managed by
|
||||||
|
StrongLoop ("Work"). For purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication
|
||||||
|
sent to StrongLoop or its representatives, including but not
|
||||||
|
limited to communication or electronic mailing lists, source code
|
||||||
|
control systems, and issue tracking systems that are managed by,
|
||||||
|
or on behalf of, StrongLoop for the purpose of discussing and
|
||||||
|
improving the Work, but excluding communication that is
|
||||||
|
conspicuously marked or otherwise designated in writing by You as
|
||||||
|
"Not a Contribution."
|
||||||
|
|
||||||
|
2. You Grant a Copyright License to StrongLoop
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this Agreement, You hereby
|
||||||
|
grant to StrongLoop and recipients of software distributed by
|
||||||
|
StrongLoop, a perpetual, worldwide, non-exclusive, no-charge,
|
||||||
|
royalty-free, irrevocable copyright license to reproduce, prepare
|
||||||
|
derivative works of, publicly display, publicly perform,
|
||||||
|
sublicense, and distribute Your Contributions and such derivative
|
||||||
|
works under any license and without any restrictions.
|
||||||
|
|
||||||
|
3. You Grant a Patent License to StrongLoop
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this Agreement, You hereby
|
||||||
|
grant to StrongLoop and to recipients of software distributed by
|
||||||
|
StrongLoop a perpetual, worldwide, non-exclusive, no-charge,
|
||||||
|
royalty-free, irrevocable (except as stated in this Section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell,
|
||||||
|
import, and otherwise transfer the Work under any license and
|
||||||
|
without any restrictions. The patent license You grant to
|
||||||
|
StrongLoop under this Section applies only to those patent claims
|
||||||
|
licensable by You that are necessarily infringed by Your
|
||||||
|
Contributions(s) alone or by combination of Your Contributions(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If any
|
||||||
|
entity institutes a patent litigation against You or any other
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit)
|
||||||
|
alleging that Your Contribution, or the Work to which You have
|
||||||
|
contributed, constitutes direct or contributory patent
|
||||||
|
infringement, any patent licenses granted to that entity under
|
||||||
|
this Agreement for that Contribution or Work shall terminate as
|
||||||
|
of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. You Have the Right to Grant Licenses to StrongLoop
|
||||||
|
|
||||||
|
You represent that You are legally entitled to grant the licenses
|
||||||
|
in this Agreement.
|
||||||
|
|
||||||
|
If Your employer(s) has rights to intellectual property that You
|
||||||
|
create, You represent that You have received permission to make
|
||||||
|
the Contributions on behalf of that employer, that Your employer
|
||||||
|
has waived such rights for Your Contributions, or that Your
|
||||||
|
employer has executed a separate Corporate Contributor License
|
||||||
|
Agreement with StrongLoop.
|
||||||
|
|
||||||
|
5. The Contributions Are Your Original Work
|
||||||
|
|
||||||
|
You represent that each of Your Contributions are Your original
|
||||||
|
works of authorship (see Section 8 (Submissions on Behalf of
|
||||||
|
Others) for submission on behalf of others). You represent that to
|
||||||
|
Your knowledge, no other person claims, or has the right to claim,
|
||||||
|
any right in any intellectual property right related to Your
|
||||||
|
Contributions.
|
||||||
|
|
||||||
|
You also represent that You are not legally obligated, whether by
|
||||||
|
entering into an agreement or otherwise, in any way that conflicts
|
||||||
|
with the terms of this Agreement.
|
||||||
|
|
||||||
|
You represent that Your Contribution submissions include complete
|
||||||
|
details of any third-party license or other restriction (including,
|
||||||
|
but not limited to, related patents and trademarks) of which You
|
||||||
|
are personally aware and which are associated with any part of
|
||||||
|
Your Contributions.
|
||||||
|
|
||||||
|
6. You Don't Have an Obligation to Provide Support for Your Contributions
|
||||||
|
|
||||||
|
You are not expected to provide support for Your Contributions,
|
||||||
|
except to the extent You desire to provide support. You may provide
|
||||||
|
support for free, for a fee, or not at all.
|
||||||
|
|
||||||
|
6. No Warranties or Conditions
|
||||||
|
|
||||||
|
StrongLoop acknowledges that unless required by applicable law or
|
||||||
|
agreed to in writing, You provide Your Contributions on an "AS IS"
|
||||||
|
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES
|
||||||
|
OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
7. Submission on Behalf of Others
|
||||||
|
|
||||||
|
If You wish to submit work that is not Your original creation, You
|
||||||
|
may submit it to StrongLoop separately from any Contribution,
|
||||||
|
identifying the complete details of its source and of any license
|
||||||
|
or other restriction (including, but not limited to, related
|
||||||
|
patents, trademarks, and license agreements) of which You are
|
||||||
|
personally aware, and conspicuously marking the work as
|
||||||
|
"Submitted on Behalf of a Third-Party: [named here]".
|
||||||
|
|
||||||
|
8. Agree to Notify of Change of Circumstances
|
||||||
|
|
||||||
|
You agree to notify StrongLoop of any facts or circumstances of
|
||||||
|
which You become aware that would make these representations
|
||||||
|
inaccurate in any respect. Email us at callback@strongloop.com.
|
||||||
|
```
|
||||||
|
|
||||||
|
[Google C++ Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
|
||||||
|
[Google Javascript Style Guide]: https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
|
3
index.js
3
index.js
|
@ -8,8 +8,7 @@ var urlJoin = require('./lib/url-join');
|
||||||
var _defaults = require('lodash.defaults');
|
var _defaults = require('lodash.defaults');
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var swagger = require('./lib/swagger');
|
var swagger = require('./lib/swagger');
|
||||||
var SWAGGER_UI_ROOT = path.join(__dirname, 'node_modules',
|
var SWAGGER_UI_ROOT = require('swagger-ui').dist;
|
||||||
'swagger-ui', 'dist');
|
|
||||||
var STATIC_ROOT = path.join(__dirname, 'public');
|
var STATIC_ROOT = path.join(__dirname, 'public');
|
||||||
|
|
||||||
module.exports = explorer;
|
module.exports = explorer;
|
||||||
|
|
|
@ -18,14 +18,16 @@ var modelHelper = module.exports = {
|
||||||
* @return {Object} Associated model definition.
|
* @return {Object} Associated model definition.
|
||||||
*/
|
*/
|
||||||
generateModelDefinition: function generateModelDefinition(modelClass, definitions) {
|
generateModelDefinition: function generateModelDefinition(modelClass, definitions) {
|
||||||
var processType = function(app, modelName, out) {
|
var processType = function(app, modelName, referencedModels) {
|
||||||
if (modelName) {
|
if (modelName) {
|
||||||
if (modelName.indexOf('[') == 0) {
|
if (modelName.indexOf('[') == 0) {
|
||||||
modelName = modelName.replace(/[\[\]]/g, '');
|
modelName = modelName.replace(/[\[\]]/g, '');
|
||||||
}
|
}
|
||||||
var model = app.models[modelName];
|
var model = app.models[modelName];
|
||||||
if (model) {
|
if (model) {
|
||||||
generateModelDefinition(model, out);
|
if (referencedModels.indexOf(model) === -1) {
|
||||||
|
referencedModels.push(model);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +43,19 @@ var modelHelper = module.exports = {
|
||||||
// Don't modify original properties.
|
// Don't modify original properties.
|
||||||
var properties = _cloneDeep(def.rawProperties);
|
var properties = _cloneDeep(def.rawProperties);
|
||||||
|
|
||||||
|
var referencedModels = [];
|
||||||
|
// Add models from settings
|
||||||
|
if (def.settings && def.settings.models) {
|
||||||
|
for (var m in def.settings.models) {
|
||||||
|
var model = modelClass[m];
|
||||||
|
if (typeof model === 'function' && model.modelName) {
|
||||||
|
if (referencedModels.indexOf(model) === -1) {
|
||||||
|
referencedModels.push(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate through each property in the model definition.
|
// Iterate through each property in the model definition.
|
||||||
// Types may be defined as constructors (e.g. String, Date, etc.),
|
// Types may be defined as constructors (e.g. String, Date, etc.),
|
||||||
// or as strings; getPropType() will take care of the conversion.
|
// or as strings; getPropType() will take care of the conversion.
|
||||||
|
@ -72,6 +87,21 @@ var modelHelper = module.exports = {
|
||||||
|
|
||||||
// Assign this back to the properties object.
|
// Assign this back to the properties object.
|
||||||
properties[key] = prop;
|
properties[key] = prop;
|
||||||
|
|
||||||
|
var propType = def.properties[key].type;
|
||||||
|
if (typeof propType === 'function' && propType.modelName) {
|
||||||
|
if (referencedModels.indexOf(propType) === -1) {
|
||||||
|
referencedModels.push(propType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Array.isArray(propType) && propType.length) {
|
||||||
|
var itemType = propType[0];
|
||||||
|
if (typeof itemType === 'function' && itemType.modelName) {
|
||||||
|
if (referencedModels.indexOf(itemType) === -1) {
|
||||||
|
referencedModels.push(itemType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
out[name] = {
|
out[name] = {
|
||||||
|
@ -83,9 +113,15 @@ var modelHelper = module.exports = {
|
||||||
// Generate model definitions for related models
|
// Generate model definitions for related models
|
||||||
for (var r in modelClass.relations) {
|
for (var r in modelClass.relations) {
|
||||||
var rel = modelClass.relations[r];
|
var rel = modelClass.relations[r];
|
||||||
generateModelDefinition(rel.modelTo, out);
|
if (rel.modelTo){
|
||||||
|
if (referencedModels.indexOf(rel.modelTo) === -1) {
|
||||||
|
referencedModels.push(rel.modelTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (rel.modelThrough) {
|
if (rel.modelThrough) {
|
||||||
generateModelDefinition(rel.modelThrough, out);
|
if (referencedModels.indexOf(rel.modelThrough) === -1) {
|
||||||
|
referencedModels.push(rel.modelThrough);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,22 +131,26 @@ var modelHelper = module.exports = {
|
||||||
var accepts = remote.accepts;
|
var accepts = remote.accepts;
|
||||||
if (accepts) {
|
if (accepts) {
|
||||||
for (var acceptIdx in accepts) {
|
for (var acceptIdx in accepts) {
|
||||||
processType(modelClass.app, accepts[acceptIdx].type, out);
|
processType(modelClass.app, accepts[acceptIdx].type, referencedModels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var returns = remote.returns;
|
var returns = remote.returns;
|
||||||
if (returns) {
|
if (returns) {
|
||||||
for (var returnIdx in returns) {
|
for (var returnIdx in returns) {
|
||||||
processType(modelClass.app, returns[returnIdx].type, out);
|
processType(modelClass.app, returns[returnIdx].type, referencedModels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var errors = remote.errors;
|
var errors = remote.errors;
|
||||||
if (errors) {
|
if (errors) {
|
||||||
for (var errorIdx in errors) {
|
for (var errorIdx in errors) {
|
||||||
processType(modelClass.app, errors[errorIdx].responseModel, out);
|
processType(modelClass.app, errors[errorIdx].responseModel, referencedModels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (var i = 0, n = referencedModels.length; i < n; i++) {
|
||||||
|
generateModelDefinition(referencedModels[i], out);
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -122,7 +162,9 @@ var modelHelper = module.exports = {
|
||||||
*/
|
*/
|
||||||
getPropType: function getPropType(propType) {
|
getPropType: function getPropType(propType) {
|
||||||
if (typeof propType === 'function') {
|
if (typeof propType === 'function') {
|
||||||
propType = propType.name.toLowerCase();
|
// See https://github.com/strongloop/loopback-explorer/issues/32
|
||||||
|
// The type can be a model class
|
||||||
|
propType = propType.modelName || propType.name.toLowerCase();
|
||||||
} else if(Array.isArray(propType)) {
|
} else if(Array.isArray(propType)) {
|
||||||
propType = 'array';
|
propType = 'array';
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,8 @@ function Swagger(loopbackApplication, swaggerApp, opts) {
|
||||||
swaggerVersion: '1.2',
|
swaggerVersion: '1.2',
|
||||||
basePath: loopbackApplication.get('restApiRoot') || '/api',
|
basePath: loopbackApplication.get('restApiRoot') || '/api',
|
||||||
resourcePath: 'resources',
|
resourcePath: 'resources',
|
||||||
// Default consumes/produces to application/json
|
// Default consumes/produces
|
||||||
consumes: ['application/json', 'application/xml', 'text/xml'],
|
consumes: ['application/json', 'application/x-www-form-urlencoded', 'application/xml', 'text/xml'],
|
||||||
produces: ['application/json', 'application/javascript', 'application/xml', 'text/javascript', 'text/xml'],
|
produces: ['application/json', 'application/javascript', 'application/xml', 'text/javascript', 'text/xml'],
|
||||||
version: getVersion()
|
version: getVersion()
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "loopback-explorer",
|
"name": "loopback-explorer",
|
||||||
"version": "1.2.7",
|
"version": "1.2.11",
|
||||||
"description": "Browse and test your LoopBack app's APIs",
|
"description": "Browse and test your LoopBack app's APIs",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -21,9 +21,22 @@
|
||||||
bottom: -30px;
|
bottom: -30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* JSON syntax highlighting */
|
||||||
|
.json, .json .attribute {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json .value .string {
|
||||||
|
color: #800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json .value .number, .json .value .literal {
|
||||||
|
color: #080;
|
||||||
|
}
|
||||||
|
|
||||||
.contentWell {
|
.contentWell {
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -26,7 +26,9 @@ $(function() {
|
||||||
log('Unable to Load SwaggerUI');
|
log('Unable to Load SwaggerUI');
|
||||||
log(data);
|
log(data);
|
||||||
},
|
},
|
||||||
docExpansion: 'none'
|
docExpansion: 'none',
|
||||||
|
highlightSizeThreshold: 16384,
|
||||||
|
sorter: 'alpha'
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#explore').click(setAccessToken);
|
$('#explore').click(setAccessToken);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var modelHelper = require('../lib/model-helper');
|
var modelHelper = require('../lib/model-helper');
|
||||||
|
var loopback = require('loopback');
|
||||||
var expect = require('chai').expect;
|
var expect = require('chai').expect;
|
||||||
|
|
||||||
describe('model-helper', function() {
|
describe('model-helper', function() {
|
||||||
|
@ -109,17 +110,74 @@ describe('model-helper', function() {
|
||||||
var prop = def.properties.array;
|
var prop = def.properties.array;
|
||||||
expect(prop).to.eql({ type: 'array', items: { type: 'any' } });
|
expect(prop).to.eql({ type: 'array', items: { type: 'any' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('converts Model type', function() {
|
||||||
|
var Address = loopback.createModel('Address', {street: String});
|
||||||
|
var def = buildSwaggerModels({
|
||||||
|
str: String,
|
||||||
|
address: Address
|
||||||
|
});
|
||||||
|
var prop = def.properties.address;
|
||||||
|
expect(prop).to.eql({ type: 'Address' });
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('related models', function() {
|
describe('related models', function() {
|
||||||
it('should include related models', function() {
|
it('should include related models', function () {
|
||||||
var defs = buildSwaggerModelsWithRelations({
|
var defs = buildSwaggerModelsWithRelations({
|
||||||
str: String // 'string'
|
str: String // 'string'
|
||||||
});
|
});
|
||||||
expect(defs).has.property('testModel');
|
expect(defs).has.property('testModel');
|
||||||
expect(defs).has.property('relatedModel');
|
expect(defs).has.property('relatedModel');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should include nesting models', function() {
|
||||||
|
var Model2 = loopback.createModel('Model2', {street: String});
|
||||||
|
var Model1 = loopback.createModel('Model1', {
|
||||||
|
str: String, // 'string'
|
||||||
|
address: Model2
|
||||||
|
});
|
||||||
|
var defs = modelHelper.generateModelDefinition(Model1, {});
|
||||||
|
expect(defs).has.property('Model1');
|
||||||
|
expect(defs).has.property('Model2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include used models', function() {
|
||||||
|
var Model4 = loopback.createModel('Model4', {street: String});
|
||||||
|
var Model3 = loopback.createModel('Model3', {
|
||||||
|
str: String, // 'string'
|
||||||
|
}, {models: {model4: 'Model4'}});
|
||||||
|
var defs = modelHelper.generateModelDefinition(Model3, {});
|
||||||
|
expect(defs).has.property('Model3');
|
||||||
|
expect(defs).has.property('Model4');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include nesting models in array', function() {
|
||||||
|
var Model6 = loopback.createModel('Model6', {street: String});
|
||||||
|
var Model5 = loopback.createModel('Model5', {
|
||||||
|
str: String, // 'string'
|
||||||
|
addresses: [Model6]
|
||||||
|
});
|
||||||
|
var defs = modelHelper.generateModelDefinition(Model5, {});
|
||||||
|
expect(defs).has.property('Model5');
|
||||||
|
expect(defs).has.property('Model6');
|
||||||
|
});
|
||||||
|
|
||||||
|
// https://github.com/strongloop/loopback-explorer/issues/49
|
||||||
|
it('should work if Array class is extended and no related models are found',
|
||||||
|
function() {
|
||||||
|
var Model7 = loopback.createModel('Model7', {street: String});
|
||||||
|
Array.prototype.customFunc = function() {
|
||||||
|
};
|
||||||
|
var defs = modelHelper.generateModelDefinition(Model7, {});
|
||||||
|
expect(defs).has.property('Model7');
|
||||||
|
expect(Object.keys(defs)).has.property('length', 1);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hidden properties', function() {
|
describe('hidden properties', function() {
|
||||||
it('should hide properties marked as "hidden"', function() {
|
it('should hide properties marked as "hidden"', function() {
|
||||||
var aClass = createModelCtor({
|
var aClass = createModelCtor({
|
||||||
|
|
Loading…
Reference in New Issue