Skip to content

Commit 884dcc1

Browse files
authored
feat: add aggregates operations
Merge pull request #42 from StemateF/main
2 parents 8d69902 + 30ffa18 commit 884dcc1

File tree

7 files changed

+271
-61
lines changed

7 files changed

+271
-61
lines changed

src/drivers/default/builders/queryBuilder.ts

+166-30
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1-
import {HttpMethod} from '../enums/httpMethod';
2-
import {Model} from '../../../model';
3-
import {ModelConstructor} from '../../../contracts/modelConstructor';
4-
import {Scope} from '../scope';
5-
import {Filter} from '../filter';
6-
import {FilterOperator} from '../enums/filterOperator';
7-
import {FilterType} from '../enums/filterType';
8-
import {Sorter} from '../sorter';
9-
import {SortDirection} from '../enums/sortDirection';
10-
import {UrlBuilder} from '../../../builders/urlBuilder';
11-
import {ExtractModelAttributesType} from '../../../types/extractModelAttributesType';
1+
import { HttpMethod } from '../enums/httpMethod';
2+
import { Model } from '../../../model';
3+
import { ModelConstructor } from '../../../contracts/modelConstructor';
4+
import { Scope } from '../scope';
5+
import { Filter } from '../filter';
6+
import { FilterOperator } from '../enums/filterOperator';
7+
import { FilterType } from '../enums/filterType';
8+
import { Sorter } from '../sorter';
9+
import { SortDirection } from '../enums/sortDirection';
10+
import { UrlBuilder } from '../../../builders/urlBuilder';
11+
import { ExtractModelAttributesType } from '../../../types/extractModelAttributesType';
1212
import {
1313
ExtractModelPersistedAttributesType
1414
} from '../../../types/extractModelPersistedAttributesType';
15-
import {ExtractModelRelationsType} from '../../../types/extractModelRelationsType';
16-
import {HttpClient} from '../../../httpClient';
17-
import {AxiosResponse} from 'axios';
18-
import {Orion} from '../../../orion';
19-
import {ExtractModelKeyType} from '../../../types/extractModelKeyType';
15+
import { ExtractModelRelationsType } from '../../../types/extractModelRelationsType';
16+
import { HttpClient } from '../../../httpClient';
17+
import { AxiosResponse } from 'axios';
18+
import { Orion } from '../../../orion';
19+
import { ExtractModelKeyType } from '../../../types/extractModelKeyType';
20+
import { AggregateItem } from '../../../types/AggregateItem';
21+
import { ModelRelations } from '../../../types/ModelRelations';
2022

2123
export class QueryBuilder<
2224
M extends Model,
@@ -33,6 +35,12 @@ export class QueryBuilder<
3335
protected includes: string[] = [];
3436
protected fetchTrashed: boolean = false;
3537
protected fetchOnlyTrashed: boolean = false;
38+
protected withCountRelations: ModelRelations<Relations>[] = [];
39+
protected withExistsRelations: ModelRelations<Relations>[] = [];
40+
protected withAvgRelations: AggregateItem<Relations>[] = [];
41+
protected withSumRelations: AggregateItem<Relations>[] = [];
42+
protected withMinRelations: AggregateItem<Relations>[] = [];
43+
protected withMaxRelations: AggregateItem<Relations>[] = [];
3644

3745
protected scopes: Array<Scope> = [];
3846
protected filters: Array<Filter> = [];
@@ -57,7 +65,7 @@ export class QueryBuilder<
5765
const response = await this.httpClient.request<{ data: Array<AllAttributes & Relations> }>(
5866
'',
5967
HttpMethod.GET,
60-
this.prepareQueryParams({limit, page})
68+
this.prepareQueryParams({ limit, page })
6169
);
6270

6371
return response.data.data.map((attributes: AllAttributes & Relations) => {
@@ -69,12 +77,12 @@ export class QueryBuilder<
6977
const response = await this.httpClient.request<{ data: Array<AllAttributes & Relations> }>(
7078
'/search',
7179
HttpMethod.POST,
72-
this.prepareQueryParams({limit, page}),
80+
this.prepareQueryParams({ limit, page }),
7381
{
7482
scopes: this.scopes,
7583
filters: this.filters,
76-
search: {value: this.searchValue},
77-
sort: this.sorters,
84+
search: { value: this.searchValue },
85+
sort: this.sorters
7886
}
7987
);
8088

@@ -109,7 +117,7 @@ export class QueryBuilder<
109117
resources: items.map(x => x.$attributes)
110118
};
111119

112-
const response = await this.httpClient.request<{data: Array<AllAttributes & Relations> }>(
120+
const response = await this.httpClient.request<{ data: Array<AllAttributes & Relations> }>(
113121
`/batch`,
114122
HttpMethod.POST,
115123
null,
@@ -118,7 +126,7 @@ export class QueryBuilder<
118126

119127
return response.data.data.map((attributes) => {
120128
return this.hydrate(attributes, response);
121-
})
129+
});
122130
}
123131

124132
public async update(key: Key, attributes: Attributes): Promise<M> {
@@ -138,12 +146,12 @@ export class QueryBuilder<
138146
};
139147
items.forEach((v) => data.resources[v.$getKey()] = v.$attributes);
140148

141-
const response = await this.httpClient.request<{ data: Array< AllAttributes & Relations > }>(
149+
const response = await this.httpClient.request<{ data: Array<AllAttributes & Relations> }>(
142150
`batch`,
143151
HttpMethod.PATCH,
144152
null,
145153
data
146-
)
154+
);
147155

148156
return response.data.data.map((attributes: AllAttributes & Relations) => {
149157
return this.hydrate(attributes, response);
@@ -154,22 +162,21 @@ export class QueryBuilder<
154162
const response = await this.httpClient.request<{ data: AllAttributes & Relations }>(
155163
`/${key}`,
156164
HttpMethod.DELETE,
157-
this.prepareQueryParams({force})
165+
this.prepareQueryParams({ force })
158166
);
159167

160168
return this.hydrate(response.data.data, response);
161169
}
162170

163-
public async batchDelete(items: Key[]): Promise<M[]>
164-
{
171+
public async batchDelete(items: Key[]): Promise<M[]> {
165172
if (!items.length)
166173
return [];
167174

168175
const data = {
169176
resources: items
170177
};
171178

172-
const response = await this.httpClient.request<{ data: Array< AllAttributes & Relations > }>(
179+
const response = await this.httpClient.request<{ data: Array<AllAttributes & Relations> }>(
173180
`/batch`,
174181
HttpMethod.DELETE,
175182
null,
@@ -196,7 +203,7 @@ export class QueryBuilder<
196203
resources: items
197204
};
198205

199-
const response = await this.httpClient.request<{ data: Array< AllAttributes & Relations > }>(
206+
const response = await this.httpClient.request<{ data: Array<AllAttributes & Relations> }>(
200207
`/batch/restore`,
201208
HttpMethod.POST,
202209
null,
@@ -209,7 +216,7 @@ export class QueryBuilder<
209216
}
210217

211218

212-
public with(relations: string[]): this {
219+
public with(relations: ModelRelations<Relations>[]): this {
213220
this.includes = relations;
214221

215222
return this;
@@ -281,6 +288,101 @@ export class QueryBuilder<
281288
return model;
282289
}
283290

291+
/**
292+
* Include the count of the specified relations.
293+
* The relations need to be whitelisted in the controller.
294+
* @link https://tailflow.github.io/laravel-orion-docs/v2.x/guide/search.html#aggregates
295+
*/
296+
public withCount(relations: ModelRelations<Relations>[] | ModelRelations<Relations>): this {
297+
if (!Array.isArray(relations)) {
298+
relations = [relations];
299+
}
300+
301+
this.withCountRelations.push(...relations);
302+
303+
return this;
304+
}
305+
306+
/**
307+
* Include the exists of the specified relations.
308+
* The relations need to be whitelisted in the controller.
309+
* @link https://tailflow.github.io/laravel-orion-docs/v2.x/guide/search.html#aggregates
310+
* @param relations
311+
*/
312+
public withExists(relations: ModelRelations<Relations>[] | ModelRelations<Relations>): this {
313+
if (!Array.isArray(relations)) {
314+
relations = [relations];
315+
}
316+
317+
this.withExistsRelations.push(...relations);
318+
319+
return this;
320+
}
321+
322+
/**
323+
* Include the avg of the specified relations.
324+
* The relations need to be whitelisted in the controller.
325+
* @link https://tailflow.github.io/laravel-orion-docs/v2.x/guide/search.html#aggregates
326+
* @param relations
327+
*/
328+
public withAvg(relations: AggregateItem<Relations>[] | AggregateItem<Relations>): this {
329+
if (!Array.isArray(relations)) {
330+
relations = [relations];
331+
}
332+
333+
this.withAvgRelations.push(...relations);
334+
335+
return this;
336+
}
337+
338+
/**
339+
* Include the sum of the specified relations.
340+
* The relations need to be whitelisted in the controller.
341+
* @link https://tailflow.github.io/laravel-orion-docs/v2.x/guide/search.html#aggregates
342+
* @param relations
343+
*/
344+
public withSum(relations: AggregateItem<Relations>[] | AggregateItem<Relations>): this {
345+
if (!Array.isArray(relations)) {
346+
relations = [relations];
347+
}
348+
349+
this.withSumRelations.push(...relations);
350+
351+
return this;
352+
}
353+
354+
/**
355+
* Include the min of the specified relations.
356+
* The relations need to be whitelisted in the controller.
357+
* @link https://tailflow.github.io/laravel-orion-docs/v2.x/guide/search.html#aggregates
358+
* @param relations
359+
*/
360+
public withMin(relations: AggregateItem<Relations>[] | AggregateItem<Relations>): this {
361+
if (!Array.isArray(relations)) {
362+
relations = [relations];
363+
}
364+
365+
this.withMinRelations.push(...relations);
366+
367+
return this;
368+
}
369+
370+
/**
371+
* Include the max of the specified relations.
372+
* The relations need to be whitelisted in the controller.
373+
* @link https://tailflow.github.io/laravel-orion-docs/v2.x/guide/search.html#aggregates
374+
* @param relations
375+
*/
376+
public withMax(relations: AggregateItem<Relations>[] | AggregateItem<Relations>): this {
377+
if (!Array.isArray(relations)) {
378+
relations = [relations];
379+
}
380+
381+
this.withMaxRelations.push(...relations);
382+
383+
return this;
384+
}
385+
284386
public getHttpClient(): HttpClient {
285387
return this.httpClient;
286388
}
@@ -298,6 +400,40 @@ export class QueryBuilder<
298400
operationParams.include = this.includes.join(',');
299401
}
300402

403+
if (this.withCountRelations.length > 0) {
404+
operationParams.with_count = this.withCountRelations.join(',');
405+
}
406+
407+
if (this.withExistsRelations.length > 0) {
408+
operationParams.with_exists = this.withExistsRelations.join(',');
409+
}
410+
411+
if (this.withAvgRelations.length > 0) {
412+
operationParams.with_avg = this.withAvgRelations.map((item) => {
413+
return `${item.relation}.${item.column}`;
414+
}).join(',');
415+
}
416+
417+
if (this.withSumRelations.length > 0) {
418+
operationParams.with_sum = this.withSumRelations.map((item) => {
419+
return `${item.relation}.${item.column}`;
420+
}).join(',');
421+
}
422+
423+
if (this.withMinRelations.length > 0) {
424+
operationParams.with_min = this.withMinRelations.map((item) => {
425+
item.relation;
426+
return `${item.relation}.${item.column}`;
427+
}).join(',');
428+
}
429+
430+
if (this.withMaxRelations.length > 0) {
431+
operationParams.with_max = this.withMaxRelations.map((item) => {
432+
return `${item.relation}.${item.column}`;
433+
}).join(',');
434+
}
435+
436+
301437
return operationParams;
302438
}
303439
}

src/types/AggregateItem.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ModelRelations } from './ModelRelations';
2+
3+
export type AggregateItem<Relations> = {
4+
relation: ModelRelations<Relations>;
5+
column: string;
6+
}

src/types/ModelRelations.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { ExtractModelRelationsType } from './extractModelRelationsType';
2+
3+
export type DirectModelRelations<Relations> = string & (keyof Relations)
4+
export type ChildModelRelations<Relations> = `${DirectModelRelations<Relations>}.${DirectModelRelations<ExtractModelRelationsType<Relations[keyof Relations]>>}`
5+
6+
7+
export type ModelRelations<Relations> =
8+
DirectModelRelations<Relations>
9+
| ChildModelRelations<Relations>

0 commit comments

Comments
 (0)