Compare commits

..

8 Commits

12 changed files with 330 additions and 136 deletions

21
LICENSE
View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2021 The hapi-sequelize-crud Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -109,10 +109,17 @@ Getting related models is easy, just use a query parameter `include`.
```js
// returns all teams with their related City model
// GET /teams?include=city
// GET /teams?include=city or
// GET /teams?include={"model": "City"}
// results in a Sequelize query:
Team.findAll({include: City})
or if association defined with an alias
// GET /players?include={"model": "Master", "as": "Couch"}
// results in a Sequelize query:
Players.findAll({include: Master, as: 'Couch'})
```
If you want to get multiple related models, just pass multiple `include` parameters.
@ -133,6 +140,56 @@ For models that have a many-to-many relationship, you can also pass the plural v
Team.findAll({include: [Player]})
```
Filtering by related models property, you can pass **where** paremeter inside each **include** items object.
```js
// returns all team with their related City where City property name equals Healdsburg
// GET /teams?include={"model": "City", "where": {"name": "Healdsburg"}}
// results in a Sequelize query:
Team.findAll({include: {model: City, where: {name: 'Healdsburg'}}})
```
More complex example with nested include, association alias and association filtering.
```js
// returns all team with its players along with its couch of each player
// GET /cities?include[]={
// "model": "Team",
// "include": {
// "model": "Player",
// "where": {
// "name": "Pinot"
// },
// "include": {
// "model": "Master",
// "as": "Coach",
// "where": {
// "name": "Shifu"
// }
// }
// }
// }
// results in a Sequelize query:
City.findAll({
include: {
model: Team,
include: {
model: Player,
where: {
name: Pinot
},
include: {
model: Master,
as: 'Coach',
where: {
name: 'Shifu'
}
}
}
}
})
```
## `limit` and `offset` queries
Restricting list (`GET`) and scope queries to a restricted count can be done by passing `limit=<number>` and/or `offset=<number>`.

View File

@ -1,6 +1,6 @@
{
"name": "hapi-sequelize-crud",
"version": "2.9.3",
"version": "2.9.1",
"description": "Hapi plugin that automatically generates RESTful API for CRUD",
"main": "build/index.js",
"config": {

View File

@ -19,14 +19,14 @@ export default (server, a, b, names, options) => {
update(server, a, b, names);
};
export const get = (server, a, b, names) => {
export const get = async (server, a, b, names) => {
server.route({
method: 'GET',
path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const base = await a.findOne({
where: {
@ -51,14 +51,14 @@ export const get = (server, a, b, names) => {
});
};
export const list = (server, a, b, names) => {
export const list = async (server, a, b, names) => {
server.route({
method: 'GET',
path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}`,
path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}`,
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const base = await a.findOne({
@ -77,16 +77,16 @@ export const list = (server, a, b, names) => {
});
};
export const scope = (server, a, b, names) => {
export const scope = async (server, a, b, names) => {
const scopes = Object.keys(b.options.scopes);
server.route({
method: 'GET',
path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}/{scope}`,
path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}/{scope}`,
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const base = await a.findOne({
@ -116,7 +116,7 @@ export const scope = (server, a, b, names) => {
});
};
export const scopeScope = (server, a, b, names) => {
export const scopeScope = async (server, a, b, names) => {
const scopes = {
a: Object.keys(a.options.scopes),
b: Object.keys(b.options.scopes),
@ -124,11 +124,11 @@ export const scopeScope = (server, a, b, names) => {
server.route({
method: 'GET',
path: `${prefix}/${names.a.plural}/{scopea}/${names.b.plural}/{scopeb}`,
path: `${prefix}${names.a.plural}/{scopea}/${names.b.plural}/{scopeb}`,
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const list = await b.scope(request.params.scopeb).findAll({
@ -152,14 +152,14 @@ export const scopeScope = (server, a, b, names) => {
});
};
export const destroy = (server, a, b, names) => {
export const destroy = async (server, a, b, names) => {
server.route({
method: 'DELETE',
path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}`,
path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}`,
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const base = await a.findOne({
@ -179,16 +179,16 @@ export const destroy = (server, a, b, names) => {
});
};
export const destroyScope = (server, a, b, names) => {
export const destroyScope = async (server, a, b, names) => {
const scopes = Object.keys(b.options.scopes);
server.route({
method: 'DELETE',
path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}/{scope}`,
path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}/{scope}`,
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const base = await a.findOne({
@ -221,14 +221,14 @@ export const destroyScope = (server, a, b, names) => {
});
};
export const update = (server, a, b, names) => {
export const update = async (server, a, b, names) => {
server.route({
method: 'PUT',
path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}`,
path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}`,
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const base = await a.findOne({

View File

@ -14,14 +14,14 @@ export default (server, a, b, names, options) => {
update(server, a, b, names);
};
export const get = (server, a, b, names) => {
export const get = async (server, a, b, names) => {
server.route({
method: 'GET',
path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}`,
path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}`,
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const base = await a.findOne({
@ -47,7 +47,7 @@ export const get = (server, a, b, names) => {
export const create = (server, a, b, names) => {
server.route({
method: 'POST',
path: `${prefix}/${names.a.singular}/{id}/${names.b.singular}`,
path: `${prefix}${names.a.singular}/{id}/${names.b.singular}`,
@error
async handler(request, reply) {
@ -67,14 +67,14 @@ export const create = (server, a, b, names) => {
});
};
export const destroy = (server, a, b, names) => {
export const destroy = async (server, a, b, names) => {
server.route({
method: 'DELETE',
path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const base = await a.findOne({
@ -99,11 +99,11 @@ export const destroy = (server, a, b, names) => {
export const update = (server, a, b, names) => {
server.route({
method: 'PUT',
path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const base = await a.findOne({

View File

@ -6,7 +6,7 @@ const STATUS_OK = 200;
setup(test);
test('belongsTo /team?include=city', async (t) => {
test('belongsTo /team?include=city', async(t) => {
const { server, instances } = t.context;
const { team1, city1 } = instances;
const path = `/team/${team1.id}?include=city`;
@ -17,7 +17,7 @@ test('belongsTo /team?include=city', async (t) => {
t.is(result.City.id, city1.id);
});
test('belongsTo /team?include=cities', async (t) => {
test('belongsTo /team?include=cities', async(t) => {
const { server, instances } = t.context;
const { team1, city1 } = instances;
const path = `/team/${team1.id}?include=cities`;
@ -28,7 +28,7 @@ test('belongsTo /team?include=cities', async (t) => {
t.is(result.City.id, city1.id);
});
test('hasMany /team?include=player', async (t) => {
test('hasMany /team?include=player', async(t) => {
const { server, instances } = t.context;
const { team1, player1, player2 } = instances;
const path = `/team/${team1.id}?include=player`;
@ -42,7 +42,7 @@ test('hasMany /team?include=player', async (t) => {
t.truthy(playerIds.includes(player2.id));
});
test('hasMany /team?include=players', async (t) => {
test('hasMany /team?include=players', async(t) => {
const { server, instances } = t.context;
const { team1, player1, player2 } = instances;
const path = `/team/${team1.id}?include=players`;
@ -56,7 +56,18 @@ test('hasMany /team?include=players', async (t) => {
t.truthy(playerIds.includes(player2.id));
});
test('multiple includes /team?include=players&include=city', async (t) => {
test('belongsTo with alias /player?include={"model": "Master", "as": "Coach"}', async(t) => {
const { server, instances } = t.context;
const { team1, master1 } = instances;
const path = `/player/${team1.id}?include={"model": "Master", "as": "Coach"}`;
const { result, statusCode } = await server.inject(path);
t.is(statusCode, STATUS_OK);
t.is(result.id, team1.id);
t.is(result.Coach.id, master1.id);
});
test('multiple includes /team?include=players&include=city', async(t) => {
const { server, instances } = t.context;
const { team1, player1, player2, city1 } = instances;
const path = `/team/${team1.id}?include=players&include=city`;
@ -71,7 +82,7 @@ test('multiple includes /team?include=players&include=city', async (t) => {
t.is(result.City.id, city1.id);
});
test('multiple includes /team?include[]=players&include[]=city', async (t) => {
test('multiple includes /team?include[]=players&include[]=city', async(t) => {
const { server, instances } = t.context;
const { team1, player1, player2, city1 } = instances;
const path = `/team/${team1.id}?include[]=players&include[]=city`;
@ -85,3 +96,67 @@ test('multiple includes /team?include[]=players&include[]=city', async (t) => {
t.truthy(playerIds.includes(player2.id));
t.is(result.City.id, city1.id);
});
test('multiple includes /team?include[]=players&include[]={"model": "City"}', async(t) => {
const { server, instances } = t.context;
const { team1, player1, player2, city1 } = instances;
const path = `/team/${team1.id}?include[]=players&include[]={"model": "City"}`;
const { result, statusCode } = await server.inject(path);
t.is(statusCode, STATUS_OK);
t.is(result.id, team1.id);
const playerIds = result.Players.map(({ id }) => id);
t.truthy(playerIds.includes(player1.id));
t.truthy(playerIds.includes(player2.id));
t.is(result.City.id, city1.id);
});
test('include filter /teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}'
, async(t) => {
const { server } = t.context;
const url = '/teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}';
const method = 'GET';
const { statusCode } = await server.inject({ url, method });
t.is(statusCode, STATUS_OK);
});
test('nested include filter ' +
'/citiy?include[]=' +
'{"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}'
, async(t) => {
const { instances, server } = t.context;
const { city1, team1, team2 } = instances;
const url = '/city?include[]=' +
'{"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}';
const method = 'GET';
const { result, statusCode } = await server.inject({ url, method });
t.is(statusCode, STATUS_OK);
t.is(result.id, city1.id);
const teamIds = result.Teams.map(({ id }) => id);
t.truthy(teamIds.includes(team1.id));
t.truthy(teamIds.includes(team2.id));
});
test('complex include ' +
'/cities?include[]={"model":"Team", ' +
'"include":{ "model":"Player", "where":{"name": "Pinot"}, ' +
'"include":{ "model":"Master", "as":"Coach", "where":{"name": "Shifu"}}}}'
, async(t) => {
const { instances, server } = t.context;
const { city1, master1, player2, team1 } = instances;
const method = 'GET';
const url = '/cities?include[]={"model":"Team", ' +
'"include":{ "model":"Player", "where":{"name": "Pinot"}, ' +
'"include":{ "model":"Master", "as":"Coach", "where":{"name": "Shifu"}}}}';
const { result, statusCode } = await server.inject({ url, method });
t.is(statusCode, STATUS_OK);
t.is(result[0].id, city1.id);
t.is(result[0].Teams[0].id, team1.id);
t.is(result[0].Teams[0].Players[0].id, player2.id);
t.is(result[0].Teams[0].Players[0].Coach.id, master1.id);
});

View File

@ -5,16 +5,16 @@ import _ from 'lodash';
import { parseInclude, parseWhere, parseLimitAndOffset, parseOrder } from './utils';
import { notFound } from 'boom';
import * as associations from './associations/index';
import getConfigForMethod from './get-config-for-method.js';
import getConfigForMethod, { sequelizeOperators } from './get-config-for-method.js';
const createAll = ({
server,
model,
prefix,
config,
attributeValidation,
associationValidation,
scopes,
server,
model,
prefix,
config,
attributeValidation,
associationValidation,
scopes,
}) => {
Object.keys(methods).forEach((method) => {
methods[method]({
@ -35,27 +35,28 @@ const createAll = ({
export { associations };
/*
The `models` option, becomes `permissions`, and can look like:
The `models` option, becomes `permissions`, and can look like:
```
models: ['cat', 'dog']
```
```
models: ['cat', 'dog']
```
or
or
```
models: {
cat: ['list', 'get']
, dog: true // all
}
```
```
models: {
cat: ['list', 'get']
, dog: true // all
}
```
*/
*/
export default (server, model, { prefix, defaultConfig: config, models: permissions }) => {
const modelName = model._singular;
const modelAttributes = Object.keys(model.attributes);
const associatedModelNames = Object.keys(model.associations);
const associatedModelAliases = _.map(model.associations, (assoc => assoc.as));
const modelAssociations = [
...associatedModelNames,
..._.flatMap(associatedModelNames, (associationName) => {
@ -71,13 +72,28 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
return params;
}, {});
const validAssociations = modelAssociations.length
? joi.string().valid(...modelAssociations)
: joi.valid(null);
const modelsHasAssociations = modelAssociations && modelAssociations.length;
const validAssociationsString = modelsHasAssociations
? joi.string().valid(...modelAssociations)
: joi.valid(null);
const validAssociationsObject = modelsHasAssociations
? joi.object().keys({
model: joi.string().valid(...modelAssociations),
where: joi.object().keys({
...attributeValidation,
...sequelizeOperators,
}),
as: joi.string().valid(...associatedModelAliases),
include: joi.any(), // @Todo: should validate the same as associationValidation var below
})
: joi.valid(null);
const associationValidation = {
include: [joi.array().items(validAssociations), validAssociations],
include: [
joi.array().items(validAssociationsString, validAssociationsObject),
validAssociationsString,
validAssociationsObject,
],
};
const scopes = Object.keys(model.options.scopes);
// if we don't have any permissions set, just create all the methods
@ -91,11 +107,11 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
associationValidation,
scopes,
});
// if permissions are set, but we can't parse them, throw an error
// if permissions are set, but we can't parse them, throw an error
} else if (!Array.isArray(permissions)) {
throw new Error('hapi-sequelize-crud: `models` property must be an array');
// if permissions are set, but the only thing we've got is a model name, there
// are no permissions to be set, so just create all methods and move on
// if permissions are set, but the only thing we've got is a model name, there
// are no permissions to be set, so just create all methods and move on
} else if (permissions.includes(modelName)) {
createAll({
server,
@ -106,7 +122,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
associationValidation,
scopes,
});
// if we've gotten here, we have complex permissions and need to set them
// if we've gotten here, we have complex permissions and need to set them
} else {
const permissionOptions = permissions.filter((permission) => {
return permission.model === modelName;
@ -147,14 +163,14 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
}
};
export const list = ({ server, model, prefix = '/', config }) => {
export const list = async ({ server, model, prefix = '/', config }) => {
server.route({
method: 'GET',
path: path.join(prefix, model._plural),
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const { limit, offset } = parseLimitAndOffset(request);
const order = parseOrder(request);
@ -174,14 +190,14 @@ export const list = ({ server, model, prefix = '/', config }) => {
});
};
export const get = ({ server, model, prefix = '/', config }) => {
export const get = async ({ server, model, prefix = '/', config }) => {
server.route({
method: 'GET',
path: path.join(prefix, model._singular, '{id?}'),
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const { id } = request.params;
if (id) where[model.primaryKeyField] = id;
@ -198,14 +214,14 @@ export const get = ({ server, model, prefix = '/', config }) => {
});
};
export const scope = ({ server, model, prefix = '/', config }) => {
export const scope = async ({ server, model, prefix = '/', config }) => {
server.route({
method: 'GET',
path: path.join(prefix, model._plural, '{scope}'),
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
const { limit, offset } = parseLimitAndOffset(request);
const order = parseOrder(request);
@ -255,9 +271,9 @@ export const destroy = ({ server, model, prefix = '/', config }) => {
if (!list.length) {
return void reply(id
? notFound(`${id} not found.`)
: notFound('Nothing found.')
);
? notFound(`${id} not found.`)
: notFound('Nothing found.')
);
}
await Promise.all(list.map(instance => instance.destroy()));
@ -284,9 +300,9 @@ export const destroyAll = ({ server, model, prefix = '/', config }) => {
if (!list.length) {
return void reply(id
? notFound(`${id} not found.`)
: notFound('Nothing found.')
);
? notFound(`${id} not found.`)
: notFound('Nothing found.')
);
}
await Promise.all(list.map(instance => instance.destroy()));
@ -299,14 +315,14 @@ export const destroyAll = ({ server, model, prefix = '/', config }) => {
});
};
export const destroyScope = ({ server, model, prefix = '/', config }) => {
export const destroyScope = async ({ server, model, prefix = '/', config }) => {
server.route({
method: 'DELETE',
path: path.join(prefix, model._plural, '{scope}'),
@error
async handler(request, reply) {
const include = parseInclude(request);
const include = await parseInclude(request);
const where = parseWhere(request);
if (include instanceof Error) return void reply(include);

View File

@ -38,6 +38,7 @@ const register = (server, options = {}, next) => {
// Join tables
if (model.options.name.singular !== model.name) continue;
for (const key of Object.keys(model.associations)) {
const association = model.associations[key];
const { source, target } = association;
@ -94,15 +95,11 @@ const register = (server, options = {}, next) => {
// build the methods for each model now that we've defined all the
// associations
Object.keys(models).filter((modelName) => {
const model = models[modelName];
return model.options.name.singular === model.name;
}).forEach((modelName) => {
Object.keys(models).forEach((modelName) => {
const model = models[modelName];
crud(server, model, options);
});
next();
};

View File

@ -1,5 +1,7 @@
import { omit, identity, toNumber, isString, isUndefined } from 'lodash';
import { notImplemented } from 'boom';
import joi from 'joi';
import Promise from 'bluebird';
const sequelizeKeys = ['include', 'order', 'limit', 'offset'];
@ -8,7 +10,7 @@ const getModels = (request) => {
const noRequestModels = !request.models;
if (noGetDb && noRequestModels) {
return notImplemented('`request.getDb` or `request.models` are not defined.'
+ 'Be sure to load hapi-sequelize before hapi-sequelize-crud.');
+ 'Be sure to load hapi-sequelize before hapi-sequelize-crud.');
}
const { models } = noGetDb ? request : request.getDb();
@ -16,31 +18,68 @@ const getModels = (request) => {
return models;
};
export const parseInclude = request => {
const getModelInstance = (models, includeItem) => {
return new Promise(async(resolve) => {
if (includeItem) {
if (typeof includeItem !== 'object') {
const singluarOrPluralMatch = Object.keys(models).find((modelName) => {
const { _singular, _plural } = models[modelName];
return _singular === includeItem || _plural === includeItem;
});
if (singluarOrPluralMatch) {
return resolve(models[singluarOrPluralMatch]);
}
}
if (typeof includeItem === 'string' && models.hasOwnProperty(includeItem)) {
return resolve(models[includeItem]);
} else if (typeof includeItem === 'object') {
if (
typeof includeItem.model === 'string' &&
includeItem.model.length &&
models.hasOwnProperty(includeItem.model)
) {
includeItem.model = models[includeItem.model];
}
if (includeItem.hasOwnProperty('include')) {
includeItem.include = await getModelInstance(models, includeItem.include);
return resolve(includeItem);
} else {
return resolve(includeItem);
}
}
}
return resolve(includeItem);
});
};
export const parseInclude = async(request) => {
if (typeof request.query.include === 'undefined') return [];
const include = Array.isArray(request.query.include)
? request.query.include
: [request.query.include]
? request.query.include
: [request.query.include]
;
const models = getModels(request);
if (models.isBoom) return models;
return include.map(a => {
const singluarOrPluralMatch = Object.keys(models).find((modelName) => {
const { _singular, _plural } = models[modelName];
return _singular === a || _plural === a;
});
if (singluarOrPluralMatch) return models[singluarOrPluralMatch];
if (typeof a === 'string') return models[a];
if (a && typeof a.model === 'string' && a.model.length) {
a.model = models[a.model];
const jsonValidation = joi.string().regex(/^\{.*?"model":.*?\}$/);
const includes = include.map(async(b) => {
let a = b;
try {
if (!jsonValidation.validate(a).error) {
a = JSON.parse(b);
}
} catch (e) {
//
}
return a;
return getModelInstance(models, a);
}).filter(identity);
return await Promise.all(includes);
};
export const parseWhere = request => {

18
test/fixtures/models/master.js vendored Normal file
View File

@ -0,0 +1,18 @@
export default (sequelize, DataTypes) => {
return sequelize.define('Master', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: DataTypes.STRING,
}, {
classMethods: {
associate: (models) => {
models.Master.hasMany(models.Player, {
foreignKey: 'coachId'
});
},
},
});
};

View File

@ -14,6 +14,10 @@ export default (sequelize, DataTypes) => {
models.Player.belongsTo(models.Team, {
foreignKey: { name: 'teamId' },
});
models.Player.belongsTo(models.Master, {
foreignKey: 'coachId',
as: 'Coach',
});
},
},
scopes: {

View File

@ -14,15 +14,16 @@ const modelNames = [
{ Singluar: 'City', singular: 'city', Plural: 'Cities', plural: 'cities' },
{ Singluar: 'Team', singular: 'team', Plural: 'Teams', plural: 'teams' },
{ Singluar: 'Player', singular: 'player', Plural: 'Players', plural: 'players' },
{ Singluar: 'Master', singular: 'master', Plural: 'Masters', plural: 'masters' },
];
export default (test) => {
test.beforeEach('get an open port', async (t) => {
test.beforeEach('get an open port', async(t) => {
t.context.port = await getPort();
});
test.beforeEach('setup server', async (t) => {
test.beforeEach('setup server', async(t) => {
const sequelize = t.context.sequelize = new Sequelize({
dialect: 'sqlite',
logging: false,
@ -46,25 +47,33 @@ export default (test) => {
});
await server.register({
register: require('../src/index.js'),
options: {
name: dbName,
register: require('../src/index.js'),
options: {
name: dbName,
},
},
},
);
});
test.beforeEach('create data', async (t) => {
const { Player, Team, City } = t.context.sequelize.models;
test.beforeEach('create data', async(t) => {
const { Player, Master, Team, City } = t.context.sequelize.models;
const city1 = await City.create({ name: 'Healdsburg' });
const team1 = await Team.create({ name: 'Baseballs', cityId: city1.id });
const team2 = await Team.create({ name: 'Footballs', cityId: city1.id });
const master1 = await Master.create({ name: 'Shifu' });
const master2 = await Master.create({ name: 'Oogway' });
const player1 = await Player.create({
name: 'Cat', teamId: team1.id, active: true,
name: 'Cat', teamId: team1.id, active: true, coachId: master1.id
});
const player2 = await Player.create({ name: 'Pinot', teamId: team1.id });
const player3 = await Player.create({ name: 'Syrah', teamId: team2.id });
t.context.instances = { city1, team1, team2, player1, player2, player3 };
const player2 = await Player.create({
name: 'Pinot', teamId: team1.id, coachId: master1.id
});
const player3 = await Player.create({
name: 'Syrah', teamId: team2.id, coachId: master2.id
});
t.context.instances = {
city1, team1, team2, player1, player2, player3, master1, master2
};
});
// kill the server so that we can exit and don't leak memory