Skip to content

Commit c496175

Browse files
authored
Merge pull request kurierjs#7 from Martinarbez/main
Filters for included manyToMany relationships
2 parents 91661f5 + 3f6d164 commit c496175

File tree

3 files changed

+155
-3
lines changed

3 files changed

+155
-3
lines changed

src/processors/knex-processor.ts

+30-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import pick from "../utils/pick";
1818
import promiseHashMap from "../utils/promise-hash-map";
1919
import OperationProcessor from "./operation-processor";
2020
import { KnexOperators as operators } from "../utils/operators";
21-
import { pluralize } from "../utils/string";
21+
import { underscore, pluralize } from "../utils/string";
2222

2323
const getWhereMethod = (value: string, operator: string) => {
2424
if (value !== "null") {
@@ -155,10 +155,37 @@ export default class KnexProcessor<ResourceT extends Resource> extends Operation
155155
const primaryKey = this.resourceClass.schema.primaryKeyName || DEFAULT_PRIMARY_KEY;
156156
const filters = params ? { [primaryKey]: id, ...(params.filter || {}) } : { [primaryKey]: id };
157157

158-
const records: KnexRecord[] = await this.getQuery()
158+
let query = this.getQuery();
159+
160+
let columns = this.getColumns(this.appInstance.app.serializer, (params || {}).fields);
161+
162+
if(params?.include){
163+
let isIncluded = false;
164+
for (const include of params.include) {
165+
if(include.includes('.')){
166+
continue;
167+
}
168+
const relationship = this.resourceClass.schema.relationships[include];
169+
if (relationship.manyToMany && relationship.foreignKeyName&& relationship.intermediateTable && filters.hasOwnProperty(relationship.foreignKeyName)) {
170+
if(!isIncluded){
171+
columns = columns.map(column => {
172+
if(typeof column !== 'string' && columns.toString().includes('count')){
173+
return column
174+
}
175+
return `${this.tableName}.${column}`})
176+
isIncluded = true
177+
}
178+
const intermediateTableColumn = `${underscore(op.ref.type)}_id`
179+
query.innerJoin(relationship.intermediateTable, `${this.tableName}.${primaryKey}`, `${relationship.intermediateTable}.${intermediateTableColumn}`)
180+
columns.push(`${relationship.intermediateTable}.${relationship.foreignKeyName} as ${relationship.foreignKeyName}`)
181+
}
182+
}
183+
}
184+
185+
const records: KnexRecord[] = await query
159186
.where((queryBuilder) => this.filtersToKnex(queryBuilder, filters))
160187
.modify((queryBuilder) => this.optionsBuilder(queryBuilder, params || {}))
161-
.select(this.getColumns(this.appInstance.app.serializer, (params || {}).fields));
188+
.select(columns);
162189

163190
if (!records.length && id) {
164191
throw JsonApiErrors.RecordNotExists();

tests/test-suite/acceptance/article.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ describe.each(transportLayers)("Transport Layer: %s", (transportLayer) => {
4242
expect(result.body).toEqual({ data: [articles.toGet.response[0]] });
4343
});
4444

45+
it("Search Articles with included tags, filter by tagId", async () => {
46+
const articleCreationResult = await request.post(`/articles`).send(articles.forCreation.requests.jsonapi);
47+
expect(articleCreationResult.status).toEqual(201);
48+
const firstArticleFilteredByFirstTag = await request.get(`/articles?include=tags&filter[tag_id]=1`);
49+
expect(firstArticleFilteredByFirstTag.status).toEqual(200);
50+
expect(firstArticleFilteredByFirstTag.body.data.length).toEqual(2);
51+
const firstArticleFilteredBySecondTag = await request.get(`/articles?include=tags&filter[tag_id]=2`);
52+
expect(firstArticleFilteredBySecondTag.status).toEqual(200);
53+
expect(firstArticleFilteredBySecondTag.body.data.length).toEqual(1);
54+
expect(firstArticleFilteredBySecondTag.body.data[0].relationships.tags.data).toEqual(articles.forUpdate.requests.jsonapi.data.relationships.tags.data);
55+
});
56+
4557
it("Authenticated - Get an specific article with it's votes and author - Multiple types include", async () => {
4658
const authData = await getAuthenticationData();
4759
const result = await request.get("/articles/1?include=author,votes").set("Authorization", authData.token);

tests/test-suite/acceptance/factories/article.ts

+113
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,119 @@ export default {
249249
},
250250
],
251251
},
252+
multipleArticlesIncludedTags: {
253+
data: [
254+
{
255+
id: 1,
256+
type: "article",
257+
attributes: {
258+
body: "this is test 1",
259+
voteCount: 2,
260+
},
261+
relationships: {
262+
votes: {
263+
data: [
264+
{
265+
id: 1,
266+
type: "vote",
267+
},
268+
{
269+
id: 2,
270+
type: "vote",
271+
},
272+
],
273+
},
274+
author: {
275+
data: {
276+
id: 1,
277+
type: "user",
278+
},
279+
},
280+
},
281+
meta: {
282+
hello: "world",
283+
},
284+
},
285+
286+
],
287+
included: [
288+
{
289+
id: 1,
290+
type: "vote",
291+
attributes: {
292+
points: 10,
293+
createdOn: null,
294+
updatedOn: null,
295+
updatedBy: null,
296+
createdBy: null,
297+
},
298+
relationships: {
299+
user: {
300+
data: {
301+
id: 1,
302+
type: "user",
303+
},
304+
},
305+
article: {
306+
data: {
307+
id: 1,
308+
type: "article",
309+
},
310+
},
311+
},
312+
},
313+
{
314+
id: 2,
315+
type: "vote",
316+
attributes: {
317+
points: 2,
318+
createdOn: null,
319+
updatedOn: null,
320+
updatedBy: null,
321+
createdBy: null,
322+
},
323+
relationships: {
324+
user: {
325+
data: {
326+
id: 1,
327+
type: "user",
328+
},
329+
},
330+
article: {
331+
data: {
332+
id: 1,
333+
type: "article",
334+
},
335+
},
336+
},
337+
},
338+
{
339+
id: 3,
340+
type: "vote",
341+
attributes: {
342+
points: 8,
343+
createdOn: null,
344+
updatedOn: null,
345+
updatedBy: null,
346+
createdBy: null,
347+
},
348+
relationships: {
349+
user: {
350+
data: {
351+
id: 3,
352+
type: "user",
353+
},
354+
},
355+
article: {
356+
data: {
357+
id: 3,
358+
type: "article",
359+
},
360+
},
361+
},
362+
},
363+
],
364+
},
252365
multipleArticlesIncludedVotes: {
253366
data: [
254367
{

0 commit comments

Comments
 (0)