Add feature to allow nested include and filtering relationships/associations #36
@@ -19,14 +19,14 @@ export default (server, a, b, names, options) => {
 | 
				
			|||||||
  update(server, a, b, names);
 | 
					  update(server, a, b, names);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const get = (server, a, b, names) => {
 | 
					export const get = async (server, a, b, names) => {
 | 
				
			||||||
  server.route({
 | 
					  server.route({
 | 
				
			||||||
    method: 'GET',
 | 
					    method: 'GET',
 | 
				
			||||||
    path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
 | 
					    path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const base = await a.findOne({
 | 
					      const base = await a.findOne({
 | 
				
			||||||
        where: {
 | 
					        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({
 | 
					  server.route({
 | 
				
			||||||
    method: 'GET',
 | 
					    method: 'GET',
 | 
				
			||||||
    path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}`,
 | 
					    path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const base = await a.findOne({
 | 
					      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);
 | 
					  const scopes = Object.keys(b.options.scopes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  server.route({
 | 
					  server.route({
 | 
				
			||||||
    method: 'GET',
 | 
					    method: 'GET',
 | 
				
			||||||
    path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}/{scope}`,
 | 
					    path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}/{scope}`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const base = await a.findOne({
 | 
					      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 = {
 | 
					  const scopes = {
 | 
				
			||||||
    a: Object.keys(a.options.scopes),
 | 
					    a: Object.keys(a.options.scopes),
 | 
				
			||||||
    b: Object.keys(b.options.scopes),
 | 
					    b: Object.keys(b.options.scopes),
 | 
				
			||||||
@@ -124,11 +124,11 @@ export const scopeScope = (server, a, b, names) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  server.route({
 | 
					  server.route({
 | 
				
			||||||
    method: 'GET',
 | 
					    method: 'GET',
 | 
				
			||||||
    path: `${prefix}/${names.a.plural}/{scopea}/${names.b.plural}/{scopeb}`,
 | 
					    path: `${prefix}${names.a.plural}/{scopea}/${names.b.plural}/{scopeb}`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const list = await b.scope(request.params.scopeb).findAll({
 | 
					      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({
 | 
					  server.route({
 | 
				
			||||||
    method: 'DELETE',
 | 
					    method: 'DELETE',
 | 
				
			||||||
    path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}`,
 | 
					    path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const base = await a.findOne({
 | 
					      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);
 | 
					  const scopes = Object.keys(b.options.scopes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  server.route({
 | 
					  server.route({
 | 
				
			||||||
    method: 'DELETE',
 | 
					    method: 'DELETE',
 | 
				
			||||||
    path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}/{scope}`,
 | 
					    path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}/{scope}`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const base = await a.findOne({
 | 
					      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({
 | 
					  server.route({
 | 
				
			||||||
    method: 'PUT',
 | 
					    method: 'PUT',
 | 
				
			||||||
    path: `${prefix}/${names.a.singular}/{aid}/${names.b.plural}`,
 | 
					    path: `${prefix}${names.a.singular}/{aid}/${names.b.plural}`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const base = await a.findOne({
 | 
					      const base = await a.findOne({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,14 +14,14 @@ export default (server, a, b, names, options) => {
 | 
				
			|||||||
  update(server, a, b, names);
 | 
					  update(server, a, b, names);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const get = (server, a, b, names) => {
 | 
					export const get = async (server, a, b, names) => {
 | 
				
			||||||
  server.route({
 | 
					  server.route({
 | 
				
			||||||
    method: 'GET',
 | 
					    method: 'GET',
 | 
				
			||||||
    path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}`,
 | 
					    path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const base = await a.findOne({
 | 
					      const base = await a.findOne({
 | 
				
			||||||
@@ -47,7 +47,7 @@ export const get = (server, a, b, names) => {
 | 
				
			|||||||
export const create = (server, a, b, names) => {
 | 
					export const create = (server, a, b, names) => {
 | 
				
			||||||
  server.route({
 | 
					  server.route({
 | 
				
			||||||
    method: 'POST',
 | 
					    method: 'POST',
 | 
				
			||||||
    path: `${prefix}/${names.a.singular}/{id}/${names.b.singular}`,
 | 
					    path: `${prefix}${names.a.singular}/{id}/${names.b.singular}`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    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({
 | 
					  server.route({
 | 
				
			||||||
    method: 'DELETE',
 | 
					    method: 'DELETE',
 | 
				
			||||||
    path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
 | 
					    path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const base = await a.findOne({
 | 
					      const base = await a.findOne({
 | 
				
			||||||
@@ -99,11 +99,11 @@ export const destroy = (server, a, b, names) => {
 | 
				
			|||||||
export const update = (server, a, b, names) => {
 | 
					export const update = (server, a, b, names) => {
 | 
				
			||||||
  server.route({
 | 
					  server.route({
 | 
				
			||||||
    method: 'PUT',
 | 
					    method: 'PUT',
 | 
				
			||||||
    path: `${prefix}/${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
 | 
					    path: `${prefix}${names.a.singular}/{aid}/${names.b.singular}/{bid}`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const base = await a.findOne({
 | 
					      const base = await a.findOne({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ const STATUS_OK = 200;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
setup(test);
 | 
					setup(test);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test('belongsTo /team?include=city', async (t) => {
 | 
					test('belongsTo /team?include=city', async(t) => {
 | 
				
			||||||
  const { server, instances } = t.context;
 | 
					  const { server, instances } = t.context;
 | 
				
			||||||
| 
						
							
	
	
	
	
	
	
	
	 | 
				|||||||
  const { team1, city1 } = instances;
 | 
					  const { team1, city1 } = instances;
 | 
				
			||||||
  const path = `/team/${team1.id}?include=city`;
 | 
					  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);
 | 
					  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 { server, instances } = t.context;
 | 
				
			||||||
  const { team1, city1 } = instances;
 | 
					  const { team1, city1 } = instances;
 | 
				
			||||||
  const path = `/team/${team1.id}?include=cities`;
 | 
					  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);
 | 
					  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 { server, instances } = t.context;
 | 
				
			||||||
  const { team1, player1, player2 } = instances;
 | 
					  const { team1, player1, player2 } = instances;
 | 
				
			||||||
  const path = `/team/${team1.id}?include=player`;
 | 
					  const path = `/team/${team1.id}?include=player`;
 | 
				
			||||||
@@ -42,7 +42,7 @@ test('hasMany /team?include=player', async (t) => {
 | 
				
			|||||||
  t.truthy(playerIds.includes(player2.id));
 | 
					  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 { server, instances } = t.context;
 | 
				
			||||||
  const { team1, player1, player2 } = instances;
 | 
					  const { team1, player1, player2 } = instances;
 | 
				
			||||||
  const path = `/team/${team1.id}?include=players`;
 | 
					  const path = `/team/${team1.id}?include=players`;
 | 
				
			||||||
@@ -56,7 +56,18 @@ test('hasMany /team?include=players', async (t) => {
 | 
				
			|||||||
  t.truthy(playerIds.includes(player2.id));
 | 
					  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 { server, instances } = t.context;
 | 
				
			||||||
  const { team1, player1, player2, city1 } = instances;
 | 
					  const { team1, player1, player2, city1 } = instances;
 | 
				
			||||||
  const path = `/team/${team1.id}?include=players&include=city`;
 | 
					  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);
 | 
					  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 { server, instances } = t.context;
 | 
				
			||||||
  const { team1, player1, player2, city1 } = instances;
 | 
					  const { team1, player1, player2, city1 } = instances;
 | 
				
			||||||
  const path = `/team/${team1.id}?include[]=players&include[]=city`;
 | 
					  const path = `/team/${team1.id}?include[]=players&include[]=city`;
 | 
				
			||||||
@@ -86,8 +97,23 @@ test('multiple includes /team?include[]=players&include[]=city', async (t) => {
 | 
				
			|||||||
  t.is(result.City.id, city1.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('inlcude filter /teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}'
 | 
					test('inlcude filter /teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}'
 | 
				
			||||||
  , async (t) => {
 | 
					  , async(t) => {
 | 
				
			||||||
    const { server } = t.context;
 | 
					    const { server } = t.context;
 | 
				
			||||||
    const url = '/teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}';
 | 
					    const url = '/teams?include[]={"model": "City", "where": {"name": "Healdsburg"}}';
 | 
				
			||||||
    const method = 'GET';
 | 
					    const method = 'GET';
 | 
				
			||||||
@@ -95,3 +121,37 @@ test('inlcude filter /teams?include[]={"model": "City", "where": {"name": "Heald
 | 
				
			|||||||
    const { statusCode } = await server.inject({ url, method });
 | 
					    const { statusCode } = await server.inject({ url, method });
 | 
				
			||||||
    t.is(statusCode, STATUS_OK);
 | 
					    t.is(statusCode, STATUS_OK);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('nested inlcude filter ' +
 | 
				
			||||||
 | 
					  '/city?include[]={"model": "Team", "include": {"model": "Player", "where": {"name": "Pinot"}}}'
 | 
				
			||||||
 | 
					  , async(t) => {
 | 
				
			||||||
 | 
					  const { instances, server } = t.context;
 | 
				
			||||||
 | 
					  const { city1, team1, player2 } = instances;
 | 
				
			||||||
 | 
					    // eslint-disable-next-line max-len
 | 
				
			||||||
 | 
					  const url = '/city?include[]={"model": "Team", "include": {"model": "Player", "where": {"name": "Pinot"}}}';
 | 
				
			||||||
 | 
					  const method = 'GET';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { result, statusCode } = await server.inject({ url, method });
 | 
				
			||||||
 | 
					  t.is(statusCode, STATUS_OK);
 | 
				
			||||||
 | 
					  t.is(result.id, city1.id);
 | 
				
			||||||
 | 
					  t.is(result.Teams[0].id, team1.id);
 | 
				
			||||||
 | 
					  t.is(result.Teams[0].Players[0].id, player2.id);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('nested inlcude filter ' +
 | 
				
			||||||
 | 
					  '/city?include[]={"model": "Team", "include": {"model": "City", "where": {"name": "Healdsburg"}}}'
 | 
				
			||||||
 | 
					  , async(t) => {
 | 
				
			||||||
 | 
					  const { instances, server } = t.context;
 | 
				
			||||||
 | 
					  const { city1, team1, team2 } = instances;
 | 
				
			||||||
 | 
					  // eslint-disable-next-line max-len
 | 
				
			||||||
 | 
					  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));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										22
									
								
								src/crud.js
									
									
									
									
									
								
							
							
						
						@@ -56,6 +56,7 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
 | 
				
			|||||||
  const modelName = model._singular;
 | 
					  const modelName = model._singular;
 | 
				
			||||||
  const modelAttributes = Object.keys(model.attributes);
 | 
					  const modelAttributes = Object.keys(model.attributes);
 | 
				
			||||||
  const associatedModelNames = Object.keys(model.associations);
 | 
					  const associatedModelNames = Object.keys(model.associations);
 | 
				
			||||||
 | 
					  const associatedModelAliases = _.map(model.associations, (assoc => assoc.as));
 | 
				
			||||||
  const modelAssociations = [
 | 
					  const modelAssociations = [
 | 
				
			||||||
    ...associatedModelNames,
 | 
					    ...associatedModelNames,
 | 
				
			||||||
    ..._.flatMap(associatedModelNames, (associationName) => {
 | 
					    ..._.flatMap(associatedModelNames, (associationName) => {
 | 
				
			||||||
@@ -82,12 +83,13 @@ export default (server, model, { prefix, defaultConfig: config, models: permissi
 | 
				
			|||||||
          ...attributeValidation,
 | 
					          ...attributeValidation,
 | 
				
			||||||
          ...sequelizeOperators,
 | 
					          ...sequelizeOperators,
 | 
				
			||||||
        }),
 | 
					        }),
 | 
				
			||||||
 | 
					        as: joi.string().valid(...associatedModelAliases),
 | 
				
			||||||
 | 
					        include: joi.any(), // @Todo: should validate the same as associationValidation var below
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      : joi.valid(null);
 | 
					      : joi.valid(null);
 | 
				
			||||||
  const associationValidation = {
 | 
					  const associationValidation = {
 | 
				
			||||||
    include: [
 | 
					    include: [
 | 
				
			||||||
      joi.array().items(validAssociationsString),
 | 
					      joi.array().items(validAssociationsString, validAssociationsObject),
 | 
				
			||||||
      joi.array().items(validAssociationsObject),
 | 
					 | 
				
			||||||
      validAssociationsString,
 | 
					      validAssociationsString,
 | 
				
			||||||
      validAssociationsObject,
 | 
					      validAssociationsObject,
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
@@ -161,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({
 | 
					  server.route({
 | 
				
			||||||
    method: 'GET',
 | 
					    method: 'GET',
 | 
				
			||||||
    path: path.join(prefix, model._plural),
 | 
					    path: path.join(prefix, model._plural),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
      const { limit, offset } = parseLimitAndOffset(request);
 | 
					      const { limit, offset } = parseLimitAndOffset(request);
 | 
				
			||||||
      const order = parseOrder(request);
 | 
					      const order = parseOrder(request);
 | 
				
			||||||
@@ -188,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({
 | 
					  server.route({
 | 
				
			||||||
    method: 'GET',
 | 
					    method: 'GET',
 | 
				
			||||||
    path: path.join(prefix, model._singular, '{id?}'),
 | 
					    path: path.join(prefix, model._singular, '{id?}'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
      const { id } = request.params;
 | 
					      const { id } = request.params;
 | 
				
			||||||
      if (id) where[model.primaryKeyField] = id;
 | 
					      if (id) where[model.primaryKeyField] = id;
 | 
				
			||||||
@@ -212,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({
 | 
					  server.route({
 | 
				
			||||||
    method: 'GET',
 | 
					    method: 'GET',
 | 
				
			||||||
    path: path.join(prefix, model._plural, '{scope}'),
 | 
					    path: path.join(prefix, model._plural, '{scope}'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
      const { limit, offset } = parseLimitAndOffset(request);
 | 
					      const { limit, offset } = parseLimitAndOffset(request);
 | 
				
			||||||
      const order = parseOrder(request);
 | 
					      const order = parseOrder(request);
 | 
				
			||||||
@@ -313,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({
 | 
					  server.route({
 | 
				
			||||||
    method: 'DELETE',
 | 
					    method: 'DELETE',
 | 
				
			||||||
    path: path.join(prefix, model._plural, '{scope}'),
 | 
					    path: path.join(prefix, model._plural, '{scope}'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @error
 | 
					    @error
 | 
				
			||||||
    async handler(request, reply) {
 | 
					    async handler(request, reply) {
 | 
				
			||||||
      const include = parseInclude(request);
 | 
					      const include = await parseInclude(request);
 | 
				
			||||||
      const where = parseWhere(request);
 | 
					      const where = parseWhere(request);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (include instanceof Error) return void reply(include);
 | 
					      if (include instanceof Error) return void reply(include);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										65
									
								
								src/utils.js
									
									
									
									
									
								
							
							
						
						@@ -1,6 +1,7 @@
 | 
				
			|||||||
import { omit, identity, toNumber, isString, isUndefined } from 'lodash';
 | 
					import { omit, identity, toNumber, isString, isUndefined } from 'lodash';
 | 
				
			||||||
import { notImplemented } from 'boom';
 | 
					import { notImplemented } from 'boom';
 | 
				
			||||||
import joi from 'joi';
 | 
					import joi from 'joi';
 | 
				
			||||||
 | 
					import Promise from 'bluebird';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sequelizeKeys = ['include', 'order', 'limit', 'offset'];
 | 
					const sequelizeKeys = ['include', 'order', 'limit', 'offset'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,7 +18,9 @@ const getModels = (request) => {
 | 
				
			|||||||
  return models;
 | 
					  return models;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const parseInclude = request => {
 | 
					export const parseInclude = async(request) => {
 | 
				
			||||||
 | 
					  if (typeof request.query.include === 'undefined') return [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const include = Array.isArray(request.query.include)
 | 
					  const include = Array.isArray(request.query.include)
 | 
				
			||||||
| 
						
							
	
	
	
	
	
	
	
	 
 `JSON.parse` will throw if not handed valid JSON. We should use a try/catch here.
 
			
			
		what code inside the catch do you prefer? what code inside the catch do you prefer?
 
			
			
		
 https://github.com/mdibaiee/hapi-sequelize-crud/blob/5ba9d7d26100f1a1a82367097342a8c35d43a26f/src/utils.js#L50-L54 and https://github.com/mdibaiee/hapi-sequelize-crud/blob/5ba9d7d26100f1a1a82367097342a8c35d43a26f/src/utils.js#L78-L83 are both options. Thanks!
 
			
			
		 | 
				|||||||
      ? request.query.include
 | 
					      ? request.query.include
 | 
				
			||||||
| 
						
							
	
	
	
	
	
	
	
	 maybe like this @joeybaker? maybe like this @joeybaker?
 
			
			
		Kinda! The try/catch part is right, but the joi check should be a part of the validation. What you have here will always pass the  What do you think of something like this in  Kinda! The try/catch part is right, but the joi check should be a part of the validation. What you have here will always pass the `if` condition, because `joi.string()` always returns a truthy value.
What do you think of something like this in `crud.js` on line 77?
``` js
  const assocationJSONvaliation = joi.string().regex(/^\{.*?"model":.*?\}$/);
  const associationValidation = {
    include: [
      joi.array().items(validAssociations),
      joi.array().items(assocationJSONvaliation),
      validAssociations,
      assocationJSONvaliation,
    ],
  };
```
 
			
			
		but with this code, it won't validate the "model" name and the "where" object isn't it? but with this code, it won't validate the "**model**" name and the "**where**" object isn't it?
if the JSON string doesn't parse good so it caught as a string, but later it won't pass because crud.js that i pushed has something like `joi.string().valid(...modelAssociations)` 
 
			
			
		currently my version of crud.js has this to validate its association object: currently my version of crud.js has this to validate its association object:
```
joi.object().keys({
        model: joi.string().valid(...modelAssociations),
        where: joi.object().keys({
          ...attributeValidation,
          ...sequelizeOperators,
        }),
      })
```
 
			
			
		I get it. I was opting for: "if you pass JSON, we're going to skip validating it … it's too complex to do correctly". Here's the problem I see with: It ensures you get a valid JS object to validate. But you won't. You'll get a string that contains JSON. That's why I was opting for the regex of a string. Did I get confused there? I get it. I was opting for: "if you pass JSON, we're going to skip validating it … it's too complex to do correctly". 
Here's the problem I see with:
``` js
joi.object().keys({
        model: joi.string().valid(...modelAssociations),
        where: joi.object().keys({
          ...attributeValidation,
          ...sequelizeOperators,
        }),
      })
```
It ensures you get a valid JS object to validate. But you won't. You'll get a string that contains JSON. That's why I was opting for the regex of a string. Did I get confused there?
 
			
			
		sorry @joeybaker, but i still didn't get it, what the main purpose of adding the json validation you want? because i have tried several cases of using include parameter and i didn't find any include parameter validation problem sorry @joeybaker, but i still didn't get it, what the main purpose of adding the json validation you want? because i have tried several cases of using include parameter and i didn't find any include parameter validation problem
 
			
			
		 | 
				|||||||
      : [request.query.include]
 | 
					      : [request.query.include]
 | 
				
			||||||
@@ -26,32 +29,58 @@ export const parseInclude = request => {
 | 
				
			|||||||
  const models = getModels(request);
 | 
					  const models = getModels(request);
 | 
				
			||||||
  if (models.isBoom) return models;
 | 
					  if (models.isBoom) return models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return include.map(b => {
 | 
					  const getModelInstance = 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(includeItem.include);
 | 
				
			||||||
 | 
					            return resolve(includeItem);
 | 
				
			||||||
 | 
					          } else {
 | 
				
			||||||
 | 
					            return resolve(includeItem);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return resolve(includeItem);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const jsonValidation = joi.string().regex(/^\{.*?"model":.*?\}$/);
 | 
				
			||||||
 | 
					  const includes = include.map(async(b) => {
 | 
				
			||||||
    let a = b;
 | 
					    let a = b;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      if (joi.string().regex(/^\{.*?"model":.*?\}$/)) {
 | 
					      if (!jsonValidation.validate(a).error) {
 | 
				
			||||||
        a = JSON.parse(b);
 | 
					        a = JSON.parse(b);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      //
 | 
					      //
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (typeof a !== 'object') {
 | 
					 | 
				
			||||||
      const singluarOrPluralMatch = Object.keys(models).find((modelName) => {
 | 
					 | 
				
			||||||
        const { _singular, _plural } = models[modelName];
 | 
					 | 
				
			||||||
        return _singular === a || _plural === a;
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (singluarOrPluralMatch) return models[singluarOrPluralMatch];
 | 
					    return getModelInstance(a);
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (typeof a === 'string') return models[a];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (a && typeof a.model === 'string' && a.model.length) {
 | 
					 | 
				
			||||||
      a.model = models[a.model];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return a;
 | 
					 | 
				
			||||||
  }).filter(identity);
 | 
					  }).filter(identity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return await Promise.all(includes);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const parseWhere = request => {
 | 
					export const parseWhere = request => {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								test/fixtures/models/master.js
									
									
									
									
										vendored
									
									
										Normal 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'
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										4
									
								
								test/fixtures/models/player.js
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -14,6 +14,10 @@ export default (sequelize, DataTypes) => {
 | 
				
			|||||||
        models.Player.belongsTo(models.Team, {
 | 
					        models.Player.belongsTo(models.Team, {
 | 
				
			||||||
          foreignKey: { name: 'teamId' },
 | 
					          foreignKey: { name: 'teamId' },
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        models.Player.belongsTo(models.Master, {
 | 
				
			||||||
 | 
					          foreignKey: 'coachId',
 | 
				
			||||||
 | 
					          as: 'Coach',
 | 
				
			||||||
| 
						
							
	
	
	
	
	
	
	
	 needed for association/relationship alias test needed for association/relationship alias test
 
			
			
		Can you not use Teams and Players? Just so we don't get an overly-complex mock schema? Can you not use Teams and Players? Just so we don't get an overly-complex mock schema?
 
			
			
		Ah! I see. Makes sense. Ah! I see. Makes sense.
 
			
			
		is it ok then? is it ok then?
 
			
			
		Yup! Yup!
 
			
			
		 | 
				|||||||
 | 
					        });
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    scopes: {
 | 
					    scopes: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,15 +14,16 @@ const modelNames = [
 | 
				
			|||||||
  { Singluar: 'City', singular: 'city', Plural: 'Cities', plural: 'cities' },
 | 
					  { Singluar: 'City', singular: 'city', Plural: 'Cities', plural: 'cities' },
 | 
				
			||||||
  { Singluar: 'Team', singular: 'team', Plural: 'Teams', plural: 'teams' },
 | 
					  { Singluar: 'Team', singular: 'team', Plural: 'Teams', plural: 'teams' },
 | 
				
			||||||
  { Singluar: 'Player', singular: 'player', Plural: 'Players', plural: 'players' },
 | 
					  { Singluar: 'Player', singular: 'player', Plural: 'Players', plural: 'players' },
 | 
				
			||||||
 | 
					  { Singluar: 'Master', singular: 'master', Plural: 'Masters', plural: 'masters' },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default (test) => {
 | 
					export default (test) => {
 | 
				
			||||||
  test.beforeEach('get an open port', async (t) => {
 | 
					  test.beforeEach('get an open port', async(t) => {
 | 
				
			||||||
    t.context.port = await getPort();
 | 
					    t.context.port = await getPort();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test.beforeEach('setup server', async (t) => {
 | 
					  test.beforeEach('setup server', async(t) => {
 | 
				
			||||||
    const sequelize = t.context.sequelize = new Sequelize({
 | 
					    const sequelize = t.context.sequelize = new Sequelize({
 | 
				
			||||||
      dialect: 'sqlite',
 | 
					      dialect: 'sqlite',
 | 
				
			||||||
      logging: false,
 | 
					      logging: false,
 | 
				
			||||||
@@ -54,17 +55,25 @@ export default (test) => {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test.beforeEach('create data', async (t) => {
 | 
					  test.beforeEach('create data', async(t) => {
 | 
				
			||||||
    const { Player, Team, City } = t.context.sequelize.models;
 | 
					    const { Player, Master, Team, City } = t.context.sequelize.models;
 | 
				
			||||||
    const city1 = await City.create({ name: 'Healdsburg' });
 | 
					    const city1 = await City.create({ name: 'Healdsburg' });
 | 
				
			||||||
    const team1 = await Team.create({ name: 'Baseballs', cityId: city1.id });
 | 
					    const team1 = await Team.create({ name: 'Baseballs', cityId: city1.id });
 | 
				
			||||||
    const team2 = await Team.create({ name: 'Footballs', 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({
 | 
					    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 player2 = await Player.create({
 | 
				
			||||||
    const player3 = await Player.create({ name: 'Syrah', teamId: team2.id });
 | 
					      name: 'Pinot', teamId: team1.id, coachId: master1.id
 | 
				
			||||||
    t.context.instances = { city1, team1, team2, player1, player2, player3 };
 | 
					    });
 | 
				
			||||||
 | 
					    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
 | 
					  // kill the server so that we can exit and don't leak memory
 | 
				
			||||||
 
 | 
				
			|||||||
Why remove all these spaces?
sorry this is from my IDE settings, but i don't see any eslint error