@hed-hog/api-pagination 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +9 -0
- package/.prettierrc.js +4 -0
- package/dist/constants/pagination.constants.d.ts +3 -0
- package/dist/constants/pagination.constants.d.ts.map +1 -0
- package/dist/constants/pagination.constants.js +6 -0
- package/dist/constants/pagination.constants.js.map +1 -0
- package/dist/databases/abstract.database.d.ts +61 -0
- package/dist/databases/abstract.database.d.ts.map +1 -0
- package/dist/databases/abstract.database.js +653 -0
- package/dist/databases/abstract.database.js.map +1 -0
- package/dist/databases/database.d.ts +5 -0
- package/dist/databases/database.d.ts.map +1 -0
- package/dist/databases/database.factory.d.ts +7 -0
- package/dist/databases/database.factory.d.ts.map +1 -0
- package/dist/databases/database.factory.js +20 -0
- package/dist/databases/database.factory.js.map +1 -0
- package/dist/databases/database.js +9 -0
- package/dist/databases/database.js.map +1 -0
- package/dist/databases/index.d.ts +4 -0
- package/dist/databases/index.d.ts.map +1 -0
- package/dist/databases/index.js +20 -0
- package/dist/databases/index.js.map +1 -0
- package/dist/databases/mysql.database.d.ts +10 -0
- package/dist/databases/mysql.database.d.ts.map +1 -0
- package/dist/databases/mysql.database.js +17 -0
- package/dist/databases/mysql.database.js.map +1 -0
- package/dist/databases/postgres.database.d.ts +10 -0
- package/dist/databases/postgres.database.d.ts.map +1 -0
- package/dist/databases/postgres.database.js +17 -0
- package/dist/databases/postgres.database.js.map +1 -0
- package/dist/decorator/pagination.decorator.d.ts +3 -0
- package/dist/decorator/pagination.decorator.d.ts.map +1 -0
- package/dist/decorator/pagination.decorator.js +54 -0
- package/dist/decorator/pagination.decorator.js.map +1 -0
- package/dist/dto/pagination.dto.d.ts +10 -0
- package/dist/dto/pagination.dto.d.ts.map +1 -0
- package/dist/dto/pagination.dto.js +52 -0
- package/dist/dto/pagination.dto.js.map +1 -0
- package/dist/enums/patination.enums.d.ts +12 -0
- package/dist/enums/patination.enums.d.ts.map +1 -0
- package/dist/enums/patination.enums.js +17 -0
- package/dist/enums/patination.enums.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/pagination.module.d.ts +3 -0
- package/dist/pagination.module.d.ts.map +1 -0
- package/dist/pagination.module.js +22 -0
- package/dist/pagination.module.js.map +1 -0
- package/dist/pagination.service.d.ts +23 -0
- package/dist/pagination.service.d.ts.map +1 -0
- package/dist/pagination.service.js +255 -0
- package/dist/pagination.service.js.map +1 -0
- package/dist/types/pagination.types.d.ts +34 -0
- package/dist/types/pagination.types.d.ts.map +1 -0
- package/dist/types/pagination.types.js +3 -0
- package/dist/types/pagination.types.js.map +1 -0
- package/dist/types/query-option.d.ts +5 -0
- package/dist/types/query-option.d.ts.map +1 -0
- package/dist/types/query-option.js +3 -0
- package/dist/types/query-option.js.map +1 -0
- package/dist/types/relation-n2n-result.d.ts +7 -0
- package/dist/types/relation-n2n-result.d.ts.map +1 -0
- package/dist/types/relation-n2n-result.js +3 -0
- package/dist/types/relation-n2n-result.js.map +1 -0
- package/dist/types/transaction-queries.d.ts +7 -0
- package/dist/types/transaction-queries.d.ts.map +1 -0
- package/dist/types/transaction-queries.js +3 -0
- package/dist/types/transaction-queries.js.map +1 -0
- package/package.json +31 -0
- package/src/constants/pagination.constants.ts +2 -0
- package/src/databases/abstract.database.ts +830 -0
- package/src/databases/database.factory.ts +25 -0
- package/src/databases/database.ts +4 -0
- package/src/databases/index.ts +3 -0
- package/src/databases/mysql.database.ts +14 -0
- package/src/databases/postgres.database.ts +14 -0
- package/src/decorator/pagination.decorator.ts +84 -0
- package/src/dto/pagination.dto.ts +31 -0
- package/src/enums/patination.enums.ts +12 -0
- package/src/index.ts +7 -0
- package/src/pagination.module.ts +9 -0
- package/src/pagination.service.ts +371 -0
- package/src/types/pagination.types.ts +44 -0
- package/src/types/query-option.ts +4 -0
- package/src/types/relation-n2n-result.ts +6 -0
- package/src/types/transaction-queries.ts +7 -0
- package/tsconfig.json +11 -0
- package/tsconfig.production.json +46 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Database } from './database';
|
|
2
|
+
import { PostgresDatabase } from './postgres.database';
|
|
3
|
+
import { MySQLDatabase } from './mysql.database';
|
|
4
|
+
|
|
5
|
+
export class DatabaseFactory {
|
|
6
|
+
public static create(
|
|
7
|
+
type: Database,
|
|
8
|
+
host: string,
|
|
9
|
+
user: string,
|
|
10
|
+
password: string,
|
|
11
|
+
database: string,
|
|
12
|
+
port: number,
|
|
13
|
+
) {
|
|
14
|
+
switch (type) {
|
|
15
|
+
case Database.POSTGRES:
|
|
16
|
+
return new PostgresDatabase(host, user, password, database, port);
|
|
17
|
+
|
|
18
|
+
case Database.MYSQL:
|
|
19
|
+
return new MySQLDatabase(host, user, password, database, port);
|
|
20
|
+
|
|
21
|
+
default:
|
|
22
|
+
console.warn(`[WARN] Unsupported Database: ${type}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AbstractDatabase } from './abstract.database';
|
|
2
|
+
import { Database } from './database';
|
|
3
|
+
|
|
4
|
+
export class MySQLDatabase extends AbstractDatabase {
|
|
5
|
+
constructor(
|
|
6
|
+
protected host: string,
|
|
7
|
+
protected user: string,
|
|
8
|
+
protected password: string,
|
|
9
|
+
protected database: string,
|
|
10
|
+
protected port: number,
|
|
11
|
+
) {
|
|
12
|
+
super(Database.MYSQL, host, user, password, database, port);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AbstractDatabase } from './abstract.database';
|
|
2
|
+
import { Database } from './database';
|
|
3
|
+
|
|
4
|
+
export class PostgresDatabase extends AbstractDatabase {
|
|
5
|
+
constructor(
|
|
6
|
+
protected host: string,
|
|
7
|
+
protected user: string,
|
|
8
|
+
protected password: string,
|
|
9
|
+
protected database: string,
|
|
10
|
+
protected port: number,
|
|
11
|
+
) {
|
|
12
|
+
super(Database.POSTGRES, host, user, password, database, port);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BadRequestException,
|
|
3
|
+
createParamDecorator,
|
|
4
|
+
ExecutionContext,
|
|
5
|
+
} from '@nestjs/common';
|
|
6
|
+
import { plainToClass } from 'class-transformer';
|
|
7
|
+
import { validateSync } from 'class-validator';
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_PAGE,
|
|
10
|
+
DEFAULT_PAGE_SIZE,
|
|
11
|
+
} from '../constants/pagination.constants';
|
|
12
|
+
import { PaginationDTO } from '../dto/pagination.dto';
|
|
13
|
+
import { PageOrderDirection, PaginationField } from '../enums/patination.enums';
|
|
14
|
+
import { PaginationType } from '../types/pagination.types';
|
|
15
|
+
|
|
16
|
+
export const Pagination = createParamDecorator(
|
|
17
|
+
(data: PaginationField, ctx: ExecutionContext): PaginationType => {
|
|
18
|
+
const request = ctx.switchToHttp().getRequest();
|
|
19
|
+
|
|
20
|
+
const defaultOptions: PaginationType = {
|
|
21
|
+
page: DEFAULT_PAGE,
|
|
22
|
+
pageSize: DEFAULT_PAGE_SIZE,
|
|
23
|
+
search: '',
|
|
24
|
+
sortField: 'id',
|
|
25
|
+
sortOrder: PageOrderDirection.Asc,
|
|
26
|
+
fields: '',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const requestData = {
|
|
30
|
+
...defaultOptions,
|
|
31
|
+
...(request.body || {}),
|
|
32
|
+
...(request.query || {}),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
page = defaultOptions.page,
|
|
37
|
+
pageSize = defaultOptions.pageSize,
|
|
38
|
+
search = defaultOptions.search,
|
|
39
|
+
sortField = defaultOptions.sortField,
|
|
40
|
+
sortOrder = defaultOptions.sortOrder,
|
|
41
|
+
fields = defaultOptions.fields,
|
|
42
|
+
} = requestData;
|
|
43
|
+
|
|
44
|
+
const validSortOrder = Object.values(PageOrderDirection).includes(sortOrder)
|
|
45
|
+
? sortOrder
|
|
46
|
+
: defaultOptions.sortOrder;
|
|
47
|
+
|
|
48
|
+
const finalData = {
|
|
49
|
+
page,
|
|
50
|
+
pageSize,
|
|
51
|
+
search,
|
|
52
|
+
sortField,
|
|
53
|
+
sortOrder: validSortOrder,
|
|
54
|
+
fields,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const paginationDtoInstance = plainToClass(PaginationDTO, finalData);
|
|
58
|
+
|
|
59
|
+
const errors = validateSync(paginationDtoInstance);
|
|
60
|
+
|
|
61
|
+
if (errors.length > 0) {
|
|
62
|
+
throw new BadRequestException(
|
|
63
|
+
'Pagination data is not valid according to PaginationDto: ' +
|
|
64
|
+
errors
|
|
65
|
+
.map((error) => Object.values(error.constraints).join(', '))
|
|
66
|
+
.join(', '),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (data) {
|
|
71
|
+
switch (data) {
|
|
72
|
+
case PaginationField.Page:
|
|
73
|
+
case PaginationField.PageSize:
|
|
74
|
+
return finalData[data] ? +finalData[data] : defaultOptions[data];
|
|
75
|
+
case PaginationField.SortOrder:
|
|
76
|
+
return validSortOrder || defaultOptions[data];
|
|
77
|
+
default:
|
|
78
|
+
return finalData[data];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return finalData;
|
|
83
|
+
},
|
|
84
|
+
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Transform } from 'class-transformer';
|
|
2
|
+
import { IsEnum, IsInt, IsOptional, IsString } from 'class-validator';
|
|
3
|
+
import { PageOrderDirection } from '../enums/patination.enums';
|
|
4
|
+
export class PaginationDTO {
|
|
5
|
+
@IsOptional()
|
|
6
|
+
@Transform(({ value }) => Number(value))
|
|
7
|
+
@IsInt({ message: 'page must be an integer' })
|
|
8
|
+
page: number;
|
|
9
|
+
|
|
10
|
+
@IsOptional()
|
|
11
|
+
@Transform(({ value }) => Number(value))
|
|
12
|
+
@IsInt({ message: 'pageSize must be an integer' })
|
|
13
|
+
pageSize: number;
|
|
14
|
+
|
|
15
|
+
@IsOptional()
|
|
16
|
+
@IsString({ message: 'search must be a string' })
|
|
17
|
+
search: string;
|
|
18
|
+
|
|
19
|
+
@IsOptional()
|
|
20
|
+
@IsString({ message: 'field must be a string' })
|
|
21
|
+
sortField: string;
|
|
22
|
+
|
|
23
|
+
@IsOptional()
|
|
24
|
+
@IsString({ message: 'sortOrder must be a string' })
|
|
25
|
+
@IsEnum(PageOrderDirection, { message: 'sortOrder is not valid' })
|
|
26
|
+
sortOrder: PageOrderDirection;
|
|
27
|
+
|
|
28
|
+
@IsOptional()
|
|
29
|
+
@IsString({ message: 'fields must be a string' })
|
|
30
|
+
fields: string;
|
|
31
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './constants/pagination.constants';
|
|
2
|
+
export * from './decorator/pagination.decorator';
|
|
3
|
+
export * from './dto/pagination.dto';
|
|
4
|
+
export * from './enums/patination.enums';
|
|
5
|
+
export * from './pagination.module';
|
|
6
|
+
export * from './pagination.service';
|
|
7
|
+
export type * from './types/pagination.types';
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { itemTranslations } from '@hed-hog/api';
|
|
2
|
+
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_PAGE,
|
|
5
|
+
DEFAULT_PAGE_SIZE,
|
|
6
|
+
} from './constants/pagination.constants';
|
|
7
|
+
import { AbstractDatabase, Database, DatabaseFactory } from './databases';
|
|
8
|
+
import { PageOrderDirection } from './enums/patination.enums';
|
|
9
|
+
import type { FindManyArgs, PaginationParams } from './types/pagination.types';
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class PaginationService {
|
|
13
|
+
private readonly logger = new Logger(PaginationService.name);
|
|
14
|
+
private db: any = null;
|
|
15
|
+
async paginate<T, M extends any>(
|
|
16
|
+
model: M,
|
|
17
|
+
paginationParams: PaginationParams,
|
|
18
|
+
customQuery?: FindManyArgs<M>,
|
|
19
|
+
translationKey?: string,
|
|
20
|
+
) /*: Promise<PaginatedResult<T>>*/ {
|
|
21
|
+
try {
|
|
22
|
+
if (!model) {
|
|
23
|
+
throw new BadRequestException('Model is required');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const page = Number(paginationParams.page || DEFAULT_PAGE);
|
|
27
|
+
const pageSize = Number(paginationParams.pageSize || DEFAULT_PAGE_SIZE);
|
|
28
|
+
const search = paginationParams.search || null;
|
|
29
|
+
const sortField = paginationParams.sortField || null;
|
|
30
|
+
const sortOrder = paginationParams.sortOrder || PageOrderDirection.Asc;
|
|
31
|
+
const fields = paginationParams.fields
|
|
32
|
+
? paginationParams.fields.split(',')
|
|
33
|
+
: null;
|
|
34
|
+
|
|
35
|
+
if (page < 1 || pageSize < 1) {
|
|
36
|
+
throw new BadRequestException(
|
|
37
|
+
'Page and pageSize must be greater than 0',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let selectCondition = undefined;
|
|
42
|
+
let sortOrderCondition: any = {
|
|
43
|
+
id: paginationParams.sortOrder || PageOrderDirection.Asc,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (sortField) {
|
|
47
|
+
const invalid = this.isInvalidField(sortField, model);
|
|
48
|
+
let localeInvalid = false;
|
|
49
|
+
if (invalid) {
|
|
50
|
+
localeInvalid = this.isInvalidLocaleField(sortField, model);
|
|
51
|
+
|
|
52
|
+
if (localeInvalid) {
|
|
53
|
+
this.logger.error(`Invalid field: ${sortField}`);
|
|
54
|
+
throw new BadRequestException(
|
|
55
|
+
`Invalid field: ${sortField}. Valid columns are: ${this.extractFieldNames(
|
|
56
|
+
model,
|
|
57
|
+
).join(', ')}`,
|
|
58
|
+
);
|
|
59
|
+
} else {
|
|
60
|
+
sortOrderCondition = {
|
|
61
|
+
[`${(model as any).name}_locale`]: { [sortField]: sortOrder },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
sortOrderCondition = { [sortField]: sortOrder };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (search) {
|
|
70
|
+
if (typeof search !== 'string') {
|
|
71
|
+
this.logger.error('Search must be a string');
|
|
72
|
+
throw new BadRequestException('Search must be a string');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (fields) {
|
|
77
|
+
const invalidFields = this.isInvalidFields(fields, model);
|
|
78
|
+
|
|
79
|
+
if (invalidFields) {
|
|
80
|
+
this.logger.error(
|
|
81
|
+
`Invalid fields: ${fields.join(', ')}. Valid columns are: ${this.extractFieldNames(
|
|
82
|
+
model,
|
|
83
|
+
).join(', ')}`,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
throw new BadRequestException(
|
|
87
|
+
`Invalid fields: ${fields.join(', ')}. Valid columns are: ${this.extractFieldNames(
|
|
88
|
+
model,
|
|
89
|
+
).join(', ')}`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
selectCondition = fields.reduce((acc, field) => {
|
|
94
|
+
acc[field] = true;
|
|
95
|
+
return acc;
|
|
96
|
+
}, {});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const skip = page > 0 ? pageSize * (page - 1) : 0;
|
|
100
|
+
|
|
101
|
+
if (
|
|
102
|
+
(customQuery as any).where &&
|
|
103
|
+
(customQuery as any).where.OR &&
|
|
104
|
+
(customQuery as any).where.OR.length === 0
|
|
105
|
+
) {
|
|
106
|
+
delete (customQuery as any).where.OR;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const query: any = {
|
|
110
|
+
select: selectCondition,
|
|
111
|
+
where: (customQuery as any)?.where || {},
|
|
112
|
+
orderBy: sortOrderCondition,
|
|
113
|
+
take: pageSize,
|
|
114
|
+
skip,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if ((customQuery as any)?.include) {
|
|
118
|
+
query.include = (customQuery as any)?.include;
|
|
119
|
+
delete query.select;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let [total, data] = await Promise.all([
|
|
123
|
+
(model as any).count({ where: (customQuery as any)?.where || {} }),
|
|
124
|
+
(model as any).findMany(query),
|
|
125
|
+
//this.query(model, query),
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
const lastPage = Math.ceil(total / pageSize);
|
|
129
|
+
|
|
130
|
+
if (translationKey) {
|
|
131
|
+
data = data.map((item: any) => {
|
|
132
|
+
return itemTranslations(translationKey, item);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
total,
|
|
138
|
+
lastPage,
|
|
139
|
+
page,
|
|
140
|
+
pageSize,
|
|
141
|
+
prev: page > 1 ? page - 1 : null,
|
|
142
|
+
next: page < lastPage ? page + 1 : null,
|
|
143
|
+
data,
|
|
144
|
+
};
|
|
145
|
+
} catch (error) {
|
|
146
|
+
this.logger.error('Pagination Error:', error);
|
|
147
|
+
|
|
148
|
+
if (error instanceof BadRequestException) {
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
throw new BadRequestException(`Failed to paginate: ${error}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
extractFieldNames(model: Record<string, any>): string[] {
|
|
157
|
+
const fieldNames: string[] = [];
|
|
158
|
+
|
|
159
|
+
const fields = model.fields;
|
|
160
|
+
|
|
161
|
+
for (const key in fields) {
|
|
162
|
+
if (fields && fields.hasOwnProperty(key)) {
|
|
163
|
+
fieldNames.push(key);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return fieldNames;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
isInvalidField(sortField: string, model: any): boolean {
|
|
171
|
+
return model && model.fields ? !model.fields[sortField] : true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
isInvalidLocaleField(sortField: string, model: any): boolean {
|
|
175
|
+
const fields = model['$parent'][`${model.name}_locale`].fields;
|
|
176
|
+
|
|
177
|
+
return model && fields ? !fields[sortField] : true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
isInvalidFields(fields: string[], model: any): boolean {
|
|
181
|
+
return !fields.every((field) =>
|
|
182
|
+
model.fields ? model && model.fields[field] : false,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
isInvalidLocaleFields(fields: string[], model: any): boolean {
|
|
187
|
+
const localeFields = model['$parent'][`${model.name}_locale`].fields;
|
|
188
|
+
|
|
189
|
+
return !fields.every((field) =>
|
|
190
|
+
localeFields ? !localeFields[field] : false,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async getDb(model: any): Promise<any> {
|
|
195
|
+
const {
|
|
196
|
+
DATABASE_URL,
|
|
197
|
+
DB_HOST,
|
|
198
|
+
DB_PORT,
|
|
199
|
+
DB_USERNAME,
|
|
200
|
+
DB_PASSWORD,
|
|
201
|
+
DB_DATABASE,
|
|
202
|
+
} = model['$parent']._engine.config.env;
|
|
203
|
+
|
|
204
|
+
const type = DATABASE_URL.split(':')[0];
|
|
205
|
+
|
|
206
|
+
this.db = DatabaseFactory.create(
|
|
207
|
+
type === 'mysql' ? Database.MYSQL : Database.POSTGRES,
|
|
208
|
+
DB_HOST,
|
|
209
|
+
DB_USERNAME,
|
|
210
|
+
DB_PASSWORD,
|
|
211
|
+
DB_DATABASE,
|
|
212
|
+
Number(DB_PORT),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return this.db;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async getBuilder(
|
|
219
|
+
model: any,
|
|
220
|
+
tableName: string,
|
|
221
|
+
query: any,
|
|
222
|
+
builder: any,
|
|
223
|
+
): Promise<any> {
|
|
224
|
+
const db = await this.getDb(model);
|
|
225
|
+
|
|
226
|
+
if (!builder) {
|
|
227
|
+
builder = {
|
|
228
|
+
joinTables: [],
|
|
229
|
+
select: [],
|
|
230
|
+
where: [],
|
|
231
|
+
order: [],
|
|
232
|
+
join: [],
|
|
233
|
+
from: db.getColumnNameWithScaping(tableName),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (query.orderBy) {
|
|
238
|
+
for (const key in query.orderBy) {
|
|
239
|
+
if (typeof query.orderBy[key] === 'object') {
|
|
240
|
+
if (!builder.joinTables.includes(key)) {
|
|
241
|
+
builder.joinTables.push(key);
|
|
242
|
+
const foreignKey = await db.getColumnNameFromRelation(
|
|
243
|
+
tableName,
|
|
244
|
+
`${tableName}_locale`,
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const primaryKeys = await db.getPrimaryKeys(tableName);
|
|
248
|
+
|
|
249
|
+
if (primaryKeys.length !== 1) {
|
|
250
|
+
throw new Error('Only single primary key is supported');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const primaryKey = primaryKeys[0];
|
|
254
|
+
|
|
255
|
+
builder.join.push(
|
|
256
|
+
`LEFT JOIN ${db.getColumnNameWithScaping(key)} ON ${db.getColumnNameWithScaping(key)}.${db.getColumnNameWithScaping(foreignKey)} = ${db.getColumnNameWithScaping(tableName)}.${db.getColumnNameWithScaping(primaryKey)}`,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
for (const k in query.orderBy[key]) {
|
|
260
|
+
builder.order.push(
|
|
261
|
+
`${db.getColumnNameWithScaping(key)}.${db.getColumnNameWithScaping(k)} ${query.orderBy[key][k]}`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (query.select) {
|
|
270
|
+
builder.select = [
|
|
271
|
+
...builder.select,
|
|
272
|
+
...Object.keys(query.select).map(
|
|
273
|
+
(key) =>
|
|
274
|
+
`${db.getColumnNameWithScaping(tableName)}.${db.getColumnNameWithScaping(key)}`,
|
|
275
|
+
),
|
|
276
|
+
];
|
|
277
|
+
for (const key in query.select) {
|
|
278
|
+
if (typeof query.select[key] === 'object') {
|
|
279
|
+
builder = await this.getBuilder(
|
|
280
|
+
model,
|
|
281
|
+
key,
|
|
282
|
+
query.select[key],
|
|
283
|
+
builder,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} else if (query.include) {
|
|
288
|
+
builder.select = [
|
|
289
|
+
...builder.select,
|
|
290
|
+
`${db.getColumnNameWithScaping(tableName)}.*`,
|
|
291
|
+
];
|
|
292
|
+
for (const key in query.include) {
|
|
293
|
+
if (typeof query.include[key] === 'object') {
|
|
294
|
+
if (!builder.joinTables.includes(key)) {
|
|
295
|
+
builder.joinTables.push(key);
|
|
296
|
+
|
|
297
|
+
const foreignKey = await db.getColumnNameFromRelation(
|
|
298
|
+
tableName,
|
|
299
|
+
key,
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const primaryKeys = await db.getPrimaryKeys(tableName);
|
|
303
|
+
|
|
304
|
+
const primaryKey = primaryKeys[0];
|
|
305
|
+
|
|
306
|
+
builder.join.push(
|
|
307
|
+
`LEFT JOIN ${db.getColumnNameWithScaping(key)} ON ${db.getColumnNameWithScaping(key)}.${db.getColumnNameWithScaping(foreignKey)} = ${db.getColumnNameWithScaping(tableName)}.${db.getColumnNameWithScaping(primaryKey)}`,
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
builder = await this.getBuilder(
|
|
311
|
+
model,
|
|
312
|
+
key,
|
|
313
|
+
query.include[key],
|
|
314
|
+
builder,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (query.where) {
|
|
322
|
+
for (const key in query.where) {
|
|
323
|
+
if (typeof query.where[key] === 'object') {
|
|
324
|
+
if (!builder.joinTables.includes(key)) {
|
|
325
|
+
builder.joinTables.push(key);
|
|
326
|
+
const foreignKey = await db.getColumnNameFromRelation(
|
|
327
|
+
key,
|
|
328
|
+
tableName,
|
|
329
|
+
);
|
|
330
|
+
const primaryKeys = await db.getPrimaryKeys(key);
|
|
331
|
+
|
|
332
|
+
builder.join.push(
|
|
333
|
+
`LEFT JOIN ${db.getColumnNameWithScaping(key)} ON ${db.getColumnNameWithScaping(key)}.${db.getColumnNameWithScaping(primaryKeys[0])} = ${db.getColumnNameWithScaping(tableName)}.${db.getColumnNameWithScaping(foreignKey)}`,
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
for (const k in query.where[key]) {
|
|
337
|
+
builder.where.push(
|
|
338
|
+
`${db.getColumnNameWithScaping(key)}.${db.getColumnNameWithScaping(k)} = ${AbstractDatabase.addSimpleQuotes(query.where[key][k])}`,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
builder.where.push(
|
|
344
|
+
`${db.getColumnNameWithScaping(tableName)}.${db.getColumnNameWithScaping(key)} = ${AbstractDatabase.addSimpleQuotes(query.where[key])}`,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return builder;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async query(model: any, query: any): Promise<any[]> {
|
|
354
|
+
const db = await this.getDb(model);
|
|
355
|
+
const builder = await this.getBuilder(model, model.name, query, null);
|
|
356
|
+
|
|
357
|
+
const sql = [];
|
|
358
|
+
|
|
359
|
+
sql.push(`SELECT ${builder.select.join(', ')}`);
|
|
360
|
+
sql.push(`FROM ${builder.from}`);
|
|
361
|
+
if (builder.join.length) sql.push(builder.join.join(' '));
|
|
362
|
+
if (builder.where.length) sql.push(`WHERE ${builder.where.join(' AND ')}`);
|
|
363
|
+
if (builder.order.length) sql.push(`ORDER BY ${builder.order.join(', ')}`);
|
|
364
|
+
if (query.take >= 0 && query.skip >= 0)
|
|
365
|
+
sql.push(db.getLimit(query.take, query.skip));
|
|
366
|
+
|
|
367
|
+
const result = await db.query(sql.join(' '));
|
|
368
|
+
|
|
369
|
+
return result;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { PageOrderDirection } from '../enums/patination.enums';
|
|
2
|
+
|
|
3
|
+
export type PaginatedResult<T> = {
|
|
4
|
+
total: number;
|
|
5
|
+
lastPage: number;
|
|
6
|
+
page: number;
|
|
7
|
+
pageSize: number;
|
|
8
|
+
prev: number | null;
|
|
9
|
+
next: number | null;
|
|
10
|
+
data: T[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type PaginationType = string | number | PaginationParams;
|
|
14
|
+
|
|
15
|
+
export type PaginateFunction = <K, T>(
|
|
16
|
+
model: any,
|
|
17
|
+
args?: K,
|
|
18
|
+
options?: PaginateOptions,
|
|
19
|
+
) => Promise<PaginatedResult<T>>;
|
|
20
|
+
|
|
21
|
+
export type PaginationParams = {
|
|
22
|
+
page?: number;
|
|
23
|
+
pageSize?: number;
|
|
24
|
+
search?: string;
|
|
25
|
+
sortField?: string;
|
|
26
|
+
sortOrder?: PageOrderDirection;
|
|
27
|
+
fields: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type PaginateOptions = {
|
|
31
|
+
page?: number | string;
|
|
32
|
+
pageSize?: number | string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type BaseModel = {
|
|
36
|
+
findMany: (args: any) => Promise<any[]>;
|
|
37
|
+
count: (args: any) => Promise<number>;
|
|
38
|
+
fields?: Record<string, any>;
|
|
39
|
+
name: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type FindManyArgs<M> = M extends { findMany: (args: infer A) => any }
|
|
43
|
+
? A
|
|
44
|
+
: never;
|