@devstroupe/devkit-nest 1.0.0
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/README.md +72 -0
- package/dist/database/base.repository.d.ts +16 -0
- package/dist/database/base.repository.js +63 -0
- package/dist/database/factory-where.d.ts +32 -0
- package/dist/database/factory-where.js +174 -0
- package/dist/database/factory-where.test.d.ts +1 -0
- package/dist/database/factory-where.test.js +111 -0
- package/dist/database/typeorm-query.builder.d.ts +11 -0
- package/dist/database/typeorm-query.builder.js +192 -0
- package/dist/database/typeorm-query.builder.test.d.ts +1 -0
- package/dist/database/typeorm-query.builder.test.js +29 -0
- package/dist/http/base-crud.controller.d.ts +9 -0
- package/dist/http/base-crud.controller.js +82 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @devstroupe/devkit-nest
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@devstroupe/devkit-nest)
|
|
4
|
+
[](../../LICENSE)
|
|
5
|
+
|
|
6
|
+
Classes base para **NestJS + TypeORM** do DT-DevKit. Provê repositórios genéricos paginados e controladores CRUD padronizados.
|
|
7
|
+
|
|
8
|
+
## Instalação
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @devstroupe/devkit-nest
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Peer Dependencies
|
|
15
|
+
```bash
|
|
16
|
+
npm install @nestjs/common @nestjs/core @nestjs/swagger typeorm reflect-metadata
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Classes disponíveis
|
|
20
|
+
|
|
21
|
+
### `BaseRepository<Entity>`
|
|
22
|
+
|
|
23
|
+
Repositório TypeORM com parser automático de paginação e filtros. Todos os repositórios gerados pelo `devstroupe generate crud` herdam desta classe.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { BaseRepository } from '@devstroupe/devkit-nest';
|
|
27
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
28
|
+
import { Repository } from 'typeorm';
|
|
29
|
+
import { Cabin } from '../domain/entities/cabin.entity';
|
|
30
|
+
|
|
31
|
+
@Injectable()
|
|
32
|
+
export class CabinRepository extends BaseRepository<Cabin> {
|
|
33
|
+
constructor(
|
|
34
|
+
@InjectRepository(Cabin)
|
|
35
|
+
private readonly repo: Repository<Cabin>
|
|
36
|
+
) {
|
|
37
|
+
super(repo);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Métodos herdados:
|
|
43
|
+
- `findMany(query)` — paginação automática com filtros
|
|
44
|
+
- `findById(id)` — busca por ID com validação
|
|
45
|
+
|
|
46
|
+
### `BaseCrudController<Entity, CreateDto, UpdateDto>`
|
|
47
|
+
|
|
48
|
+
Controller NestJS com endpoints CRUD padronizados e documentação Swagger automática.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { BaseCrudController } from '@devstroupe/devkit-nest';
|
|
52
|
+
|
|
53
|
+
@Controller('cabins')
|
|
54
|
+
export class CabinController extends BaseCrudController(
|
|
55
|
+
Cabin, CreateCabinDto, UpdateCabinDto
|
|
56
|
+
) {}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Endpoints gerados automaticamente:
|
|
60
|
+
- `GET /` — listagem paginada com filtros via query params
|
|
61
|
+
- `GET /:id` — busca por ID
|
|
62
|
+
- `POST /` — criação
|
|
63
|
+
- `PATCH /:id` — atualização parcial
|
|
64
|
+
- `DELETE /:id` — remoção
|
|
65
|
+
|
|
66
|
+
## Regras de governança
|
|
67
|
+
|
|
68
|
+
A regra `use-standard-devkit-queries` do linter exige que:
|
|
69
|
+
- Todos os **Controllers CRUD** herdem de `BaseCrudController`
|
|
70
|
+
- Todos os **Repositories** herdem de `BaseRepository`
|
|
71
|
+
|
|
72
|
+
Execute `devstroupe lint:rules` para verificar conformidade.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Repository, SelectQueryBuilder, ObjectLiteral } from 'typeorm';
|
|
2
|
+
import { IPaginator, IFilterPagination } from '@devstroupe/devkit-core';
|
|
3
|
+
import { IQueryBuilderOptions } from './typeorm-query.builder';
|
|
4
|
+
export declare abstract class BaseRepository<Entity extends ObjectLiteral, FilterDto extends IFilterPagination = IFilterPagination> {
|
|
5
|
+
protected readonly repository: Repository<Entity>;
|
|
6
|
+
protected allowedFilters?: string[];
|
|
7
|
+
protected allowedSorts?: string[];
|
|
8
|
+
protected searchableFields?: string[];
|
|
9
|
+
constructor(repository: Repository<Entity>);
|
|
10
|
+
create(data: any): Promise<Entity>;
|
|
11
|
+
update(id: any, data: any): Promise<Entity>;
|
|
12
|
+
delete(id: any): Promise<void>;
|
|
13
|
+
findOne(id: any): Promise<Entity | null>;
|
|
14
|
+
findMany(filter: FilterDto): Promise<IPaginator<Entity>>;
|
|
15
|
+
protected paginate(qb: SelectQueryBuilder<Entity>, filter: any, options?: IQueryBuilderOptions): Promise<IPaginator<Entity>>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseRepository = void 0;
|
|
4
|
+
const typeorm_query_builder_1 = require("./typeorm-query.builder");
|
|
5
|
+
class BaseRepository {
|
|
6
|
+
repository;
|
|
7
|
+
allowedFilters;
|
|
8
|
+
allowedSorts;
|
|
9
|
+
searchableFields;
|
|
10
|
+
constructor(repository) {
|
|
11
|
+
this.repository = repository;
|
|
12
|
+
}
|
|
13
|
+
async create(data) {
|
|
14
|
+
const entity = this.repository.create(data);
|
|
15
|
+
return await this.repository.save(entity);
|
|
16
|
+
}
|
|
17
|
+
async update(id, data) {
|
|
18
|
+
await this.repository.save({ id, ...data });
|
|
19
|
+
const updated = await this.findOne(id);
|
|
20
|
+
if (!updated) {
|
|
21
|
+
throw new Error(`Entity with ID ${id} not found after update`);
|
|
22
|
+
}
|
|
23
|
+
return updated;
|
|
24
|
+
}
|
|
25
|
+
async delete(id) {
|
|
26
|
+
await this.repository.softDelete(id);
|
|
27
|
+
}
|
|
28
|
+
async findOne(id) {
|
|
29
|
+
return await this.repository.findOne({ where: { id } });
|
|
30
|
+
}
|
|
31
|
+
async findMany(filter) {
|
|
32
|
+
const alias = this.repository.metadata.name.toLowerCase();
|
|
33
|
+
const queryBuilder = this.repository.createQueryBuilder(alias);
|
|
34
|
+
return await this.paginate(queryBuilder, filter, {
|
|
35
|
+
allowedFilters: this.allowedFilters,
|
|
36
|
+
allowedSorts: this.allowedSorts,
|
|
37
|
+
searchableFields: this.searchableFields,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async paginate(qb, filter, options) {
|
|
41
|
+
const queryBuilder = typeorm_query_builder_1.TypeOrmQueryBuilder.apply(qb, filter, options);
|
|
42
|
+
const [items, totalItems] = await queryBuilder.getManyAndCount();
|
|
43
|
+
const page = Number(filter.page) || 1;
|
|
44
|
+
let limit = 10;
|
|
45
|
+
if (filter.limit === 'todos' || filter.limit === 'all') {
|
|
46
|
+
limit = totalItems;
|
|
47
|
+
}
|
|
48
|
+
else if (filter.limit) {
|
|
49
|
+
limit = Number(filter.limit);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
items,
|
|
53
|
+
meta: {
|
|
54
|
+
totalItems,
|
|
55
|
+
currentPage: page,
|
|
56
|
+
itemsPerPage: limit === 0 ? totalItems : limit,
|
|
57
|
+
itemCount: items.length,
|
|
58
|
+
totalPage: limit === 0 ? 1 : Math.ceil(totalItems / limit),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
exports.BaseRepository = BaseRepository;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SelectQueryBuilder, ObjectLiteral } from 'typeorm';
|
|
2
|
+
export declare const CONDITION_SUFFIXES: {
|
|
3
|
+
ARRAY: string;
|
|
4
|
+
DATE_START: string;
|
|
5
|
+
DATE_END: string;
|
|
6
|
+
IN: string;
|
|
7
|
+
NOT_IN: string;
|
|
8
|
+
BOOL: string;
|
|
9
|
+
GT: string;
|
|
10
|
+
GTE: string;
|
|
11
|
+
LT: string;
|
|
12
|
+
LTE: string;
|
|
13
|
+
NULL: string;
|
|
14
|
+
LIKE: string;
|
|
15
|
+
ILIKE: string;
|
|
16
|
+
NOT_LIKE: string;
|
|
17
|
+
NOT_ILIKE: string;
|
|
18
|
+
};
|
|
19
|
+
export declare class ConditionHandlers {
|
|
20
|
+
static handleArray<QR extends ObjectLiteral>(qb: SelectQueryBuilder<QR>, key: string, value: any, tableName: string): SelectQueryBuilder<QR>;
|
|
21
|
+
static handleDate<QR extends ObjectLiteral>(qb: SelectQueryBuilder<QR>, key: string, value: any, tableName: string, isEndDate: boolean): SelectQueryBuilder<QR>;
|
|
22
|
+
static handleInCondition<QR extends ObjectLiteral>(qb: SelectQueryBuilder<QR>, key: string, value: any, tableName: string, isNotIn?: boolean): SelectQueryBuilder<QR>;
|
|
23
|
+
static handleBoolean<QR extends ObjectLiteral>(qb: SelectQueryBuilder<QR>, key: string, value: any, tableName: string): SelectQueryBuilder<QR>;
|
|
24
|
+
static handleComparison<QR extends ObjectLiteral>(qb: SelectQueryBuilder<QR>, key: string, value: any, tableName: string, operator: '>' | '>=' | '<' | '<='): SelectQueryBuilder<QR>;
|
|
25
|
+
static handleNull<QR extends ObjectLiteral>(qb: SelectQueryBuilder<QR>, key: string, value: any, tableName: string): SelectQueryBuilder<QR>;
|
|
26
|
+
static handleDefault<QR extends ObjectLiteral>(qb: SelectQueryBuilder<QR>, key: string, value: any, tableName: string): SelectQueryBuilder<QR>;
|
|
27
|
+
static handleLike<QR extends ObjectLiteral>(qb: SelectQueryBuilder<QR>, key: string, value: any, tableName: string, options?: {
|
|
28
|
+
caseSensitive?: boolean;
|
|
29
|
+
not?: boolean;
|
|
30
|
+
}): SelectQueryBuilder<QR>;
|
|
31
|
+
}
|
|
32
|
+
export declare const factoryWhere: <QR extends ObjectLiteral, T>(queryBuilder: SelectQueryBuilder<QR>, where: T) => SelectQueryBuilder<QR>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.factoryWhere = exports.ConditionHandlers = exports.CONDITION_SUFFIXES = void 0;
|
|
4
|
+
exports.CONDITION_SUFFIXES = {
|
|
5
|
+
ARRAY: '_$array',
|
|
6
|
+
DATE_START: '_$date_start',
|
|
7
|
+
DATE_END: '_$date_end',
|
|
8
|
+
IN: '_$in',
|
|
9
|
+
NOT_IN: '_$not_in',
|
|
10
|
+
BOOL: '_$bool',
|
|
11
|
+
GT: '_$gt',
|
|
12
|
+
GTE: '_$gte',
|
|
13
|
+
LT: '_$lt',
|
|
14
|
+
LTE: '_$lte',
|
|
15
|
+
NULL: '_$null',
|
|
16
|
+
LIKE: '_$like',
|
|
17
|
+
ILIKE: '_$ilike',
|
|
18
|
+
NOT_LIKE: '_$not_like',
|
|
19
|
+
NOT_ILIKE: '_$not_ilike',
|
|
20
|
+
};
|
|
21
|
+
function createParamName(key) {
|
|
22
|
+
return key.replace(/\./g, '_').replace(/[^a-zA-Z0-9_]/g, '') + Math.random().toString(36).slice(2, 6);
|
|
23
|
+
}
|
|
24
|
+
function StringToArray(value) {
|
|
25
|
+
if (Array.isArray(value))
|
|
26
|
+
return value;
|
|
27
|
+
if (typeof value === 'string') {
|
|
28
|
+
return value.split(',').map((v) => v.trim());
|
|
29
|
+
}
|
|
30
|
+
return [value];
|
|
31
|
+
}
|
|
32
|
+
function BooleanString(value) {
|
|
33
|
+
if (typeof value === 'boolean')
|
|
34
|
+
return value;
|
|
35
|
+
if (value === 'true' || value === '1' || value === 1)
|
|
36
|
+
return true;
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function formatDateTime(value, isEndDate) {
|
|
40
|
+
const dateStr = typeof value === 'object' && value instanceof Date ? value.toISOString() : String(value);
|
|
41
|
+
if (dateStr.includes('T')) {
|
|
42
|
+
return dateStr;
|
|
43
|
+
}
|
|
44
|
+
// Se for apenas data YYYY-MM-DD
|
|
45
|
+
return isEndDate ? `${dateStr} 23:59:59` : `${dateStr} 00:00:00`;
|
|
46
|
+
}
|
|
47
|
+
class ConditionHandlers {
|
|
48
|
+
static handleArray(qb, key, value, tableName) {
|
|
49
|
+
const originalKey = key.replace(exports.CONDITION_SUFFIXES.ARRAY, '');
|
|
50
|
+
const paramName = `${createParamName(key)}_array`;
|
|
51
|
+
const prefix = key.includes('.') ? '' : `${tableName}.`;
|
|
52
|
+
const condition = `${prefix}${originalKey} IN (:${paramName})`;
|
|
53
|
+
return qb.andWhere(condition, {
|
|
54
|
+
[paramName]: StringToArray(value),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
static handleDate(qb, key, value, tableName, isEndDate) {
|
|
58
|
+
const originalKey = key.replace(exports.CONDITION_SUFFIXES.DATE_END, '').replace(exports.CONDITION_SUFFIXES.DATE_START, '');
|
|
59
|
+
const paramName = `${createParamName(key)}_date`;
|
|
60
|
+
const operator = isEndDate ? '<=' : '>=';
|
|
61
|
+
const formattedDate = formatDateTime(value, isEndDate);
|
|
62
|
+
const prefix = key.includes('.') ? '' : `${tableName}.`;
|
|
63
|
+
const condition = `${prefix}${originalKey} ${operator} :${paramName}`;
|
|
64
|
+
return qb.andWhere(condition, { [paramName]: formattedDate });
|
|
65
|
+
}
|
|
66
|
+
static handleInCondition(qb, key, value, tableName, isNotIn = false) {
|
|
67
|
+
const originalKey = key.replace(isNotIn ? exports.CONDITION_SUFFIXES.NOT_IN : exports.CONDITION_SUFFIXES.IN, '');
|
|
68
|
+
const paramName = createParamName(key);
|
|
69
|
+
const prefix = key.includes('.') ? '' : `${tableName}.`;
|
|
70
|
+
const arr = StringToArray(value).map((v) => (typeof v === 'string' && /^\d+$/.test(v) ? Number(v) : v));
|
|
71
|
+
const condition = `${prefix}${originalKey} ${isNotIn ? 'NOT IN' : 'IN'} (:...${paramName})`;
|
|
72
|
+
return qb.andWhere(condition, { [paramName]: arr });
|
|
73
|
+
}
|
|
74
|
+
static handleBoolean(qb, key, value, tableName) {
|
|
75
|
+
const originalKey = key.replace(exports.CONDITION_SUFFIXES.BOOL, '');
|
|
76
|
+
const paramName = createParamName(key);
|
|
77
|
+
const prefix = key.includes('.') ? '' : `${tableName}.`;
|
|
78
|
+
const condition = `${prefix}${originalKey} = :${paramName}`;
|
|
79
|
+
return qb.andWhere(condition, { [paramName]: BooleanString(value) });
|
|
80
|
+
}
|
|
81
|
+
static handleComparison(qb, key, value, tableName, operator) {
|
|
82
|
+
const suffixMap = {
|
|
83
|
+
'>': exports.CONDITION_SUFFIXES.GT,
|
|
84
|
+
'>=': exports.CONDITION_SUFFIXES.GTE,
|
|
85
|
+
'<': exports.CONDITION_SUFFIXES.LT,
|
|
86
|
+
'<=': exports.CONDITION_SUFFIXES.LTE,
|
|
87
|
+
};
|
|
88
|
+
const originalKey = key.replace(suffixMap[operator], '');
|
|
89
|
+
const paramName = createParamName(key);
|
|
90
|
+
const prefix = key.includes('.') ? '' : `${tableName}.`;
|
|
91
|
+
const condition = `${prefix}${originalKey} ${operator} :${paramName}`;
|
|
92
|
+
return qb.andWhere(condition, { [paramName]: value });
|
|
93
|
+
}
|
|
94
|
+
static handleNull(qb, key, value, tableName) {
|
|
95
|
+
const originalKey = key.replace(exports.CONDITION_SUFFIXES.NULL, '');
|
|
96
|
+
const isNull = BooleanString(value);
|
|
97
|
+
const prefix = key.includes('.') ? '' : `${tableName}.`;
|
|
98
|
+
const condition = `${prefix}${originalKey} IS ${isNull ? 'NULL' : 'NOT NULL'}`;
|
|
99
|
+
return qb.andWhere(condition);
|
|
100
|
+
}
|
|
101
|
+
static handleDefault(qb, key, value, tableName) {
|
|
102
|
+
const paramName = createParamName(key);
|
|
103
|
+
if (key.includes('.')) {
|
|
104
|
+
return qb.andWhere(`${key} = :${paramName}`, { [paramName]: value });
|
|
105
|
+
}
|
|
106
|
+
return qb.andWhere(`${tableName}.${key} = :${paramName}`, { [paramName]: value });
|
|
107
|
+
}
|
|
108
|
+
static handleLike(qb, key, value, tableName, options = {}) {
|
|
109
|
+
const { caseSensitive = true, not = false } = options;
|
|
110
|
+
const originalKey = key
|
|
111
|
+
.replace(exports.CONDITION_SUFFIXES.LIKE, '')
|
|
112
|
+
.replace(exports.CONDITION_SUFFIXES.ILIKE, '')
|
|
113
|
+
.replace(exports.CONDITION_SUFFIXES.NOT_LIKE, '')
|
|
114
|
+
.replace(exports.CONDITION_SUFFIXES.NOT_ILIKE, '');
|
|
115
|
+
const paramName = createParamName(key);
|
|
116
|
+
const dbType = qb.connection.options.type;
|
|
117
|
+
const isMySQL = dbType === 'mysql' || dbType === 'mariadb';
|
|
118
|
+
const prefix = key.includes('.') ? '' : `${tableName}.`;
|
|
119
|
+
let condition;
|
|
120
|
+
let paramValue;
|
|
121
|
+
if (!caseSensitive && isMySQL) {
|
|
122
|
+
const operator = not ? 'NOT LIKE' : 'LIKE';
|
|
123
|
+
condition = `LOWER(${prefix}${originalKey}) ${operator} :${paramName}`;
|
|
124
|
+
paramValue = `%${String(value).toLowerCase()}%`;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const operator = caseSensitive ? (not ? 'NOT LIKE' : 'LIKE') : not ? 'NOT ILIKE' : 'ILIKE';
|
|
128
|
+
condition = `${prefix}${originalKey} ${operator} :${paramName}`;
|
|
129
|
+
paramValue = `%${value}%`;
|
|
130
|
+
}
|
|
131
|
+
return qb.andWhere(condition, { [paramName]: paramValue });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.ConditionHandlers = ConditionHandlers;
|
|
135
|
+
const CONDITION_HANDLERS = [
|
|
136
|
+
{ suffix: exports.CONDITION_SUFFIXES.ARRAY, handle: (qb, k, v, t) => ConditionHandlers.handleArray(qb, k, v, t) },
|
|
137
|
+
{ suffix: exports.CONDITION_SUFFIXES.DATE_END, handle: (qb, k, v, t) => ConditionHandlers.handleDate(qb, k, v, t, true) },
|
|
138
|
+
{ suffix: exports.CONDITION_SUFFIXES.DATE_START, handle: (qb, k, v, t) => ConditionHandlers.handleDate(qb, k, v, t, false) },
|
|
139
|
+
{ suffix: exports.CONDITION_SUFFIXES.IN, handle: (qb, k, v, t) => ConditionHandlers.handleInCondition(qb, k, v, t, false) },
|
|
140
|
+
{ suffix: exports.CONDITION_SUFFIXES.NOT_IN, handle: (qb, k, v, t) => ConditionHandlers.handleInCondition(qb, k, v, t, true) },
|
|
141
|
+
{ suffix: exports.CONDITION_SUFFIXES.BOOL, handle: (qb, k, v, t) => ConditionHandlers.handleBoolean(qb, k, v, t) },
|
|
142
|
+
{ suffix: exports.CONDITION_SUFFIXES.GT, handle: (qb, k, v, t) => ConditionHandlers.handleComparison(qb, k, v, t, '>') },
|
|
143
|
+
{ suffix: exports.CONDITION_SUFFIXES.GTE, handle: (qb, k, v, t) => ConditionHandlers.handleComparison(qb, k, v, t, '>=') },
|
|
144
|
+
{ suffix: exports.CONDITION_SUFFIXES.LT, handle: (qb, k, v, t) => ConditionHandlers.handleComparison(qb, k, v, t, '<') },
|
|
145
|
+
{ suffix: exports.CONDITION_SUFFIXES.LTE, handle: (qb, k, v, t) => ConditionHandlers.handleComparison(qb, k, v, t, '<=') },
|
|
146
|
+
{ suffix: exports.CONDITION_SUFFIXES.NULL, handle: (qb, k, v, t) => ConditionHandlers.handleNull(qb, k, v, t) },
|
|
147
|
+
{ suffix: exports.CONDITION_SUFFIXES.LIKE, handle: (qb, k, v, t) => ConditionHandlers.handleLike(qb, k, v, t, { caseSensitive: true, not: false }) },
|
|
148
|
+
{ suffix: exports.CONDITION_SUFFIXES.ILIKE, handle: (qb, k, v, t) => ConditionHandlers.handleLike(qb, k, v, t, { caseSensitive: false, not: false }) },
|
|
149
|
+
{ suffix: exports.CONDITION_SUFFIXES.NOT_LIKE, handle: (qb, k, v, t) => ConditionHandlers.handleLike(qb, k, v, t, { caseSensitive: true, not: true }) },
|
|
150
|
+
{ suffix: exports.CONDITION_SUFFIXES.NOT_ILIKE, handle: (qb, k, v, t) => ConditionHandlers.handleLike(qb, k, v, t, { caseSensitive: false, not: true }) },
|
|
151
|
+
];
|
|
152
|
+
const factoryWhere = (queryBuilder, where) => {
|
|
153
|
+
if (!where || typeof where !== 'object')
|
|
154
|
+
return queryBuilder;
|
|
155
|
+
const tableName = queryBuilder.alias;
|
|
156
|
+
for (const [key, value] of Object.entries(where)) {
|
|
157
|
+
if (value === undefined || value === null || value === '')
|
|
158
|
+
continue;
|
|
159
|
+
try {
|
|
160
|
+
const handler = CONDITION_HANDLERS.find((h) => key.endsWith(h.suffix));
|
|
161
|
+
if (handler) {
|
|
162
|
+
queryBuilder = handler.handle(queryBuilder, key, value, tableName);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
queryBuilder = ConditionHandlers.handleDefault(queryBuilder, key, value, tableName);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.warn(`Erro ao processar condição para a chave "${key}":`, error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return queryBuilder;
|
|
173
|
+
};
|
|
174
|
+
exports.factoryWhere = factoryWhere;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const node_test_1 = require("node:test");
|
|
37
|
+
const assert = __importStar(require("node:assert"));
|
|
38
|
+
const factory_where_1 = require("./factory-where");
|
|
39
|
+
// Mock de SelectQueryBuilder
|
|
40
|
+
function createMockQueryBuilder(alias, dbType = 'postgres') {
|
|
41
|
+
const calls = [];
|
|
42
|
+
const qb = {
|
|
43
|
+
alias,
|
|
44
|
+
connection: {
|
|
45
|
+
options: {
|
|
46
|
+
type: dbType
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
andWhere(condition, params) {
|
|
50
|
+
calls.push({ condition, params });
|
|
51
|
+
return this;
|
|
52
|
+
},
|
|
53
|
+
getCalls() {
|
|
54
|
+
return calls;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return qb;
|
|
58
|
+
}
|
|
59
|
+
(0, node_test_1.test)('factoryWhere should process simple equality filters', () => {
|
|
60
|
+
const qb = createMockQueryBuilder('cabin');
|
|
61
|
+
const filter = {
|
|
62
|
+
company_id: 5,
|
|
63
|
+
name: 'Cabine A'
|
|
64
|
+
};
|
|
65
|
+
const result = (0, factory_where_1.factoryWhere)(qb, filter);
|
|
66
|
+
const calls = result.getCalls();
|
|
67
|
+
assert.strictEqual(calls.length, 2);
|
|
68
|
+
// Verifica primeiro filtro: company_id
|
|
69
|
+
assert.ok(calls[0].condition.startsWith('cabin.company_id = :company_id'));
|
|
70
|
+
assert.strictEqual(calls[0].params[Object.keys(calls[0].params)[0]], 5);
|
|
71
|
+
// Verifica segundo filtro: name
|
|
72
|
+
assert.ok(calls[1].condition.startsWith('cabin.name = :name'));
|
|
73
|
+
assert.strictEqual(calls[1].params[Object.keys(calls[1].params)[0]], 'Cabine A');
|
|
74
|
+
});
|
|
75
|
+
(0, node_test_1.test)('factoryWhere should process suffixes correctly', () => {
|
|
76
|
+
const qb = createMockQueryBuilder('cabin');
|
|
77
|
+
const filter = {
|
|
78
|
+
'is_active_$bool': 'true',
|
|
79
|
+
'code_$like': 'TX',
|
|
80
|
+
'company_id_$in': '1,2,3',
|
|
81
|
+
'id_$gt': 10
|
|
82
|
+
};
|
|
83
|
+
const result = (0, factory_where_1.factoryWhere)(qb, filter);
|
|
84
|
+
const calls = result.getCalls();
|
|
85
|
+
assert.strictEqual(calls.length, 4);
|
|
86
|
+
// 1. is_active_$bool
|
|
87
|
+
assert.ok(calls[0].condition.startsWith('cabin.is_active = :is_active_bool'));
|
|
88
|
+
assert.strictEqual(calls[0].params[Object.keys(calls[0].params)[0]], true);
|
|
89
|
+
// 2. code_$like
|
|
90
|
+
assert.ok(calls[1].condition.startsWith('cabin.code LIKE :code_like'));
|
|
91
|
+
assert.strictEqual(calls[1].params[Object.keys(calls[1].params)[0]], '%TX%');
|
|
92
|
+
// 3. company_id_$in
|
|
93
|
+
assert.ok(calls[2].condition.startsWith('cabin.company_id IN (:...company_id_in'));
|
|
94
|
+
assert.deepStrictEqual(calls[2].params[Object.keys(calls[2].params)[0]], [1, 2, 3]);
|
|
95
|
+
// 4. id_$gt
|
|
96
|
+
assert.ok(calls[3].condition.startsWith('cabin.id > :id_gt'));
|
|
97
|
+
assert.strictEqual(calls[3].params[Object.keys(calls[3].params)[0]], 10);
|
|
98
|
+
});
|
|
99
|
+
(0, node_test_1.test)('factoryWhere should ignore empty, null, and undefined values', () => {
|
|
100
|
+
const qb = createMockQueryBuilder('cabin');
|
|
101
|
+
const filter = {
|
|
102
|
+
name: '',
|
|
103
|
+
code: null,
|
|
104
|
+
company_id: undefined,
|
|
105
|
+
is_active: true
|
|
106
|
+
};
|
|
107
|
+
const result = (0, factory_where_1.factoryWhere)(qb, filter);
|
|
108
|
+
const calls = result.getCalls();
|
|
109
|
+
assert.strictEqual(calls.length, 1);
|
|
110
|
+
assert.ok(calls[0].condition.startsWith('cabin.is_active = :is_active'));
|
|
111
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SelectQueryBuilder, ObjectLiteral } from 'typeorm';
|
|
2
|
+
import { IFilterPagination } from '@devstroupe/devkit-core';
|
|
3
|
+
export interface IQueryBuilderOptions {
|
|
4
|
+
allowedFilters?: string[];
|
|
5
|
+
allowedSorts?: string[];
|
|
6
|
+
searchableFields?: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare class TypeOrmQueryBuilder {
|
|
9
|
+
static apply<Entity extends ObjectLiteral>(qb: SelectQueryBuilder<Entity>, filter: IFilterPagination, options?: IQueryBuilderOptions): SelectQueryBuilder<Entity>;
|
|
10
|
+
private static parseStructuredValue;
|
|
11
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TypeOrmQueryBuilder = void 0;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
5
|
+
class TypeOrmQueryBuilder {
|
|
6
|
+
static apply(qb, filter, options = {}) {
|
|
7
|
+
const alias = qb.alias;
|
|
8
|
+
const { allowedFilters, allowedSorts, searchableFields } = options;
|
|
9
|
+
const structuredFilter = this.parseStructuredValue(filter.filter);
|
|
10
|
+
const structuredSort = this.parseStructuredValue(filter.sort);
|
|
11
|
+
// 1. Paginação e Limite
|
|
12
|
+
const page = Number(filter.page) || 1;
|
|
13
|
+
let limit = 10;
|
|
14
|
+
if (filter.limit === 'todos' || filter.limit === 'all') {
|
|
15
|
+
limit = 0;
|
|
16
|
+
}
|
|
17
|
+
else if (filter.limit) {
|
|
18
|
+
limit = Number(filter.limit);
|
|
19
|
+
}
|
|
20
|
+
if (limit > 0) {
|
|
21
|
+
qb.take(limit).skip((page - 1) * limit);
|
|
22
|
+
}
|
|
23
|
+
// 2. Busca Global (search)
|
|
24
|
+
if (filter.search && searchableFields?.length) {
|
|
25
|
+
qb.andWhere(new typeorm_1.Brackets((subQb) => {
|
|
26
|
+
searchableFields.forEach((field, index) => {
|
|
27
|
+
const paramName = `search_param_${index}_${Math.random().toString(36).slice(2, 6)}`;
|
|
28
|
+
const fullField = field.includes('.') ? field : `${alias}.${field}`;
|
|
29
|
+
subQb.orWhere(`LOWER(${fullField}) LIKE :${paramName}`, {
|
|
30
|
+
[paramName]: `%${String(filter.search).toLowerCase()}%`,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
// 3. Filtros Estruturados (filter[field][operator]=value ou filter[field]=value)
|
|
36
|
+
if (structuredFilter) {
|
|
37
|
+
for (const [rawField, value] of Object.entries(structuredFilter)) {
|
|
38
|
+
// Tratar sufixos legados para retrocompatibilidade
|
|
39
|
+
let field = rawField;
|
|
40
|
+
let inferredOperator = null;
|
|
41
|
+
if (field.endsWith('_$bool')) {
|
|
42
|
+
field = field.replace('_$bool', '');
|
|
43
|
+
inferredOperator = 'eq';
|
|
44
|
+
}
|
|
45
|
+
else if (field.endsWith('_$like') || field.endsWith('_$ilike')) {
|
|
46
|
+
field = field.replace(/_\$i?like/, '');
|
|
47
|
+
inferredOperator = 'contains';
|
|
48
|
+
}
|
|
49
|
+
else if (field.endsWith('_$date_start') || field.endsWith('_$gte')) {
|
|
50
|
+
field = field.replace(/_\$(date_start|gte)/, '');
|
|
51
|
+
inferredOperator = 'gte';
|
|
52
|
+
}
|
|
53
|
+
else if (field.endsWith('_$date_end') || field.endsWith('_$lte')) {
|
|
54
|
+
field = field.replace(/_\$(date_end|lte)/, '');
|
|
55
|
+
inferredOperator = 'lte';
|
|
56
|
+
}
|
|
57
|
+
else if (field.endsWith('_$in')) {
|
|
58
|
+
field = field.replace('_$in', '');
|
|
59
|
+
inferredOperator = 'in';
|
|
60
|
+
}
|
|
61
|
+
else if (field.endsWith('_$not_in')) {
|
|
62
|
+
field = field.replace('_$not_in', '');
|
|
63
|
+
inferredOperator = 'notIn';
|
|
64
|
+
}
|
|
65
|
+
else if (field.endsWith('_$null')) {
|
|
66
|
+
field = field.replace('_$null', '');
|
|
67
|
+
inferredOperator = 'isNull';
|
|
68
|
+
}
|
|
69
|
+
// Validação de campos permitidos
|
|
70
|
+
if (allowedFilters && !allowedFilters.includes(field)) {
|
|
71
|
+
console.warn(`Filter on field "${field}" is not allowed.`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const prefix = field.includes('.') ? '' : `${alias}.`;
|
|
75
|
+
const fullField = `${prefix}${field}`;
|
|
76
|
+
const applyCondition = (op, val) => {
|
|
77
|
+
if (val === undefined || val === null || val === '')
|
|
78
|
+
return;
|
|
79
|
+
const paramName = `${field.replace(/\./g, '_')}_${op}_${Math.random().toString(36).slice(2, 6)}`;
|
|
80
|
+
switch (op) {
|
|
81
|
+
case 'eq':
|
|
82
|
+
qb.andWhere(`${fullField} = :${paramName}`, { [paramName]: val });
|
|
83
|
+
break;
|
|
84
|
+
case 'neq':
|
|
85
|
+
qb.andWhere(`${fullField} != :${paramName}`, { [paramName]: val });
|
|
86
|
+
break;
|
|
87
|
+
case 'contains':
|
|
88
|
+
qb.andWhere(`LOWER(${fullField}) LIKE :${paramName}`, { [paramName]: `%${String(val).toLowerCase()}%` });
|
|
89
|
+
break;
|
|
90
|
+
case 'startsWith':
|
|
91
|
+
qb.andWhere(`LOWER(${fullField}) LIKE :${paramName}`, { [paramName]: `${String(val).toLowerCase()}%` });
|
|
92
|
+
break;
|
|
93
|
+
case 'endsWith':
|
|
94
|
+
qb.andWhere(`LOWER(${fullField}) LIKE :${paramName}`, { [paramName]: `%${String(val).toLowerCase()}` });
|
|
95
|
+
break;
|
|
96
|
+
case 'gt':
|
|
97
|
+
qb.andWhere(`${fullField} > :${paramName}`, { [paramName]: val });
|
|
98
|
+
break;
|
|
99
|
+
case 'gte':
|
|
100
|
+
qb.andWhere(`${fullField} >= :${paramName}`, { [paramName]: val });
|
|
101
|
+
break;
|
|
102
|
+
case 'lt':
|
|
103
|
+
qb.andWhere(`${fullField} < :${paramName}`, { [paramName]: val });
|
|
104
|
+
break;
|
|
105
|
+
case 'lte':
|
|
106
|
+
qb.andWhere(`${fullField} <= :${paramName}`, { [paramName]: val });
|
|
107
|
+
break;
|
|
108
|
+
case 'in':
|
|
109
|
+
const inArr = Array.isArray(val) ? val : String(val).split(',').map((s) => s.trim());
|
|
110
|
+
if (inArr.length > 0) {
|
|
111
|
+
qb.andWhere(`${fullField} IN (:...${paramName})`, { [paramName]: inArr });
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case 'notIn':
|
|
115
|
+
const notInArr = Array.isArray(val) ? val : String(val).split(',').map((s) => s.trim());
|
|
116
|
+
if (notInArr.length > 0) {
|
|
117
|
+
qb.andWhere(`${fullField} NOT IN (:...${paramName})`, { [paramName]: notInArr });
|
|
118
|
+
}
|
|
119
|
+
break;
|
|
120
|
+
case 'between':
|
|
121
|
+
const betweenArr = Array.isArray(val) ? val : String(val).split(',').map((s) => s.trim());
|
|
122
|
+
if (betweenArr.length === 2) {
|
|
123
|
+
const p1 = `${paramName}_1`;
|
|
124
|
+
const p2 = `${paramName}_2`;
|
|
125
|
+
qb.andWhere(`${fullField} BETWEEN :${p1} AND :${p2}`, { [p1]: betweenArr[0], [p2]: betweenArr[1] });
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
case 'isNull':
|
|
129
|
+
const isNullVal = typeof val === 'boolean' ? val : val === 'true' || val === '1';
|
|
130
|
+
qb.andWhere(`${fullField} IS ${isNullVal ? 'NULL' : 'NOT NULL'}`);
|
|
131
|
+
break;
|
|
132
|
+
case 'isNotNull':
|
|
133
|
+
const isNotNullVal = typeof val === 'boolean' ? val : val === 'true' || val === '1';
|
|
134
|
+
qb.andWhere(`${fullField} IS ${isNotNullVal ? 'NOT NULL' : 'NULL'}`);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
if (inferredOperator) {
|
|
139
|
+
applyCondition(inferredOperator, value);
|
|
140
|
+
}
|
|
141
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)) {
|
|
142
|
+
for (const [op, val] of Object.entries(value)) {
|
|
143
|
+
applyCondition(op, val);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// Valor direto, mapeia para 'eq'
|
|
148
|
+
applyCondition('eq', value);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// 4. Ordenação Estruturada (sort[field]=asc)
|
|
153
|
+
if (structuredSort) {
|
|
154
|
+
for (const [field, direction] of Object.entries(structuredSort)) {
|
|
155
|
+
if (allowedSorts && !allowedSorts.includes(field)) {
|
|
156
|
+
console.warn(`Sorting on field "${field}" is not allowed.`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const orderDirection = String(direction).toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
|
|
160
|
+
const fullField = field.includes('.') ? field : `${alias}.${field}`;
|
|
161
|
+
qb.addOrderBy(fullField, orderDirection);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else if (filter.sort && typeof filter.sort === 'string' && filter.direction) {
|
|
165
|
+
// Retrocompatibilidade com query params planos (sort=name&direction=desc)
|
|
166
|
+
const field = filter.sort;
|
|
167
|
+
if (!allowedSorts || allowedSorts.includes(field)) {
|
|
168
|
+
const orderDirection = String(filter.direction).toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
|
|
169
|
+
const fullField = field.includes('.') ? field : `${alias}.${field}`;
|
|
170
|
+
qb.addOrderBy(fullField, orderDirection);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return qb;
|
|
174
|
+
}
|
|
175
|
+
static parseStructuredValue(value) {
|
|
176
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
if (typeof value !== 'string' || !value.trim().startsWith('{'))
|
|
180
|
+
return undefined;
|
|
181
|
+
try {
|
|
182
|
+
const parsed = JSON.parse(value);
|
|
183
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
184
|
+
? parsed
|
|
185
|
+
: undefined;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
exports.TypeOrmQueryBuilder = TypeOrmQueryBuilder;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const typeorm_query_builder_1 = require("./typeorm-query.builder");
|
|
9
|
+
function createQueryBuilder() {
|
|
10
|
+
const conditions = [];
|
|
11
|
+
return {
|
|
12
|
+
alias: 'cabin',
|
|
13
|
+
take() { return this; },
|
|
14
|
+
skip() { return this; },
|
|
15
|
+
andWhere(condition, params) {
|
|
16
|
+
conditions.push({ condition, params });
|
|
17
|
+
return this;
|
|
18
|
+
},
|
|
19
|
+
addOrderBy() { return this; },
|
|
20
|
+
conditions,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
(0, node_test_1.default)('TypeOrmQueryBuilder parses JSON relationship filters from HTTP query params', () => {
|
|
24
|
+
const queryBuilder = createQueryBuilder();
|
|
25
|
+
typeorm_query_builder_1.TypeOrmQueryBuilder.apply(queryBuilder, { filter: JSON.stringify({ company_id: 42 }) }, { allowedFilters: ['company_id'] });
|
|
26
|
+
strict_1.default.equal(queryBuilder.conditions.length, 1);
|
|
27
|
+
strict_1.default.match(queryBuilder.conditions[0].condition, /^cabin\.company_id = :company_id_eq_/);
|
|
28
|
+
strict_1.default.equal(Object.values(queryBuilder.conditions[0].params)[0], 42);
|
|
29
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare abstract class BaseCrudController<Entity, FilterDto = any> {
|
|
2
|
+
protected readonly service: any;
|
|
3
|
+
constructor(service: any);
|
|
4
|
+
create(body: any): Promise<Entity>;
|
|
5
|
+
update(id: string, body: any): Promise<Entity>;
|
|
6
|
+
findMany(filter: FilterDto): Promise<any>;
|
|
7
|
+
findById(id: string): Promise<Entity>;
|
|
8
|
+
delete(id: string): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.BaseCrudController = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
18
|
+
class BaseCrudController {
|
|
19
|
+
service;
|
|
20
|
+
constructor(service) {
|
|
21
|
+
this.service = service;
|
|
22
|
+
}
|
|
23
|
+
async create(body) {
|
|
24
|
+
return await this.service.create(body);
|
|
25
|
+
}
|
|
26
|
+
async update(id, body) {
|
|
27
|
+
return await this.service.update(+id, body);
|
|
28
|
+
}
|
|
29
|
+
async findMany(filter) {
|
|
30
|
+
return await this.service.findMany(filter);
|
|
31
|
+
}
|
|
32
|
+
async findById(id) {
|
|
33
|
+
return await this.service.findOne(+id);
|
|
34
|
+
}
|
|
35
|
+
async delete(id) {
|
|
36
|
+
return await this.service.delete(+id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.BaseCrudController = BaseCrudController;
|
|
40
|
+
__decorate([
|
|
41
|
+
(0, common_1.Post)(),
|
|
42
|
+
(0, swagger_1.ApiOperation)({ summary: 'Create entity' }),
|
|
43
|
+
(0, swagger_1.ApiResponse)({ status: 201, description: 'Created successfully' }),
|
|
44
|
+
__param(0, (0, common_1.Body)()),
|
|
45
|
+
__metadata("design:type", Function),
|
|
46
|
+
__metadata("design:paramtypes", [Object]),
|
|
47
|
+
__metadata("design:returntype", Promise)
|
|
48
|
+
], BaseCrudController.prototype, "create", null);
|
|
49
|
+
__decorate([
|
|
50
|
+
(0, common_1.Put)(':id'),
|
|
51
|
+
(0, swagger_1.ApiOperation)({ summary: 'Update entity' }),
|
|
52
|
+
(0, swagger_1.ApiResponse)({ status: 200, description: 'Updated successfully' }),
|
|
53
|
+
__param(0, (0, common_1.Param)('id')),
|
|
54
|
+
__param(1, (0, common_1.Body)()),
|
|
55
|
+
__metadata("design:type", Function),
|
|
56
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
57
|
+
__metadata("design:returntype", Promise)
|
|
58
|
+
], BaseCrudController.prototype, "update", null);
|
|
59
|
+
__decorate([
|
|
60
|
+
(0, common_1.Get)(),
|
|
61
|
+
(0, swagger_1.ApiOperation)({ summary: 'List entities with filter and pagination' }),
|
|
62
|
+
__param(0, (0, common_1.Query)()),
|
|
63
|
+
__metadata("design:type", Function),
|
|
64
|
+
__metadata("design:paramtypes", [Object]),
|
|
65
|
+
__metadata("design:returntype", Promise)
|
|
66
|
+
], BaseCrudController.prototype, "findMany", null);
|
|
67
|
+
__decorate([
|
|
68
|
+
(0, common_1.Get)(':id'),
|
|
69
|
+
(0, swagger_1.ApiOperation)({ summary: 'Find entity by ID' }),
|
|
70
|
+
__param(0, (0, common_1.Param)('id')),
|
|
71
|
+
__metadata("design:type", Function),
|
|
72
|
+
__metadata("design:paramtypes", [String]),
|
|
73
|
+
__metadata("design:returntype", Promise)
|
|
74
|
+
], BaseCrudController.prototype, "findById", null);
|
|
75
|
+
__decorate([
|
|
76
|
+
(0, common_1.Delete)(':id'),
|
|
77
|
+
(0, swagger_1.ApiOperation)({ summary: 'Delete entity' }),
|
|
78
|
+
__param(0, (0, common_1.Param)('id')),
|
|
79
|
+
__metadata("design:type", Function),
|
|
80
|
+
__metadata("design:paramtypes", [String]),
|
|
81
|
+
__metadata("design:returntype", Promise)
|
|
82
|
+
], BaseCrudController.prototype, "delete", null);
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./database/factory-where"), exports);
|
|
18
|
+
__exportStar(require("./database/base.repository"), exports);
|
|
19
|
+
__exportStar(require("./database/typeorm-query.builder"), exports);
|
|
20
|
+
__exportStar(require("./http/base-crud.controller"), exports);
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devstroupe/devkit-nest",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "DevsTroupe Development Kit — NestJS base classes: BaseRepository, BaseCrudController and query parsers",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"devstroupe",
|
|
8
|
+
"devkit",
|
|
9
|
+
"nestjs",
|
|
10
|
+
"typeorm",
|
|
11
|
+
"crud",
|
|
12
|
+
"repository",
|
|
13
|
+
"base-controller",
|
|
14
|
+
"typescript"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/devstroupe/devkit#readme",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/devstroupe/devkit.git",
|
|
20
|
+
"directory": "packages/devkit-nest"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"main": "dist/index.js",
|
|
30
|
+
"types": "dist/index.d.ts",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@devstroupe/devkit-core": "^1.0.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
36
|
+
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
|
37
|
+
"@nestjs/swagger": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0",
|
|
38
|
+
"typeorm": "^0.3.0",
|
|
39
|
+
"reflect-metadata": "^0.1.13 || ^0.2.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@nestjs/common": "^10.3.8",
|
|
43
|
+
"@nestjs/core": "^10.3.8",
|
|
44
|
+
"@nestjs/swagger": "^7.3.1",
|
|
45
|
+
"@types/node": "^20.11.0",
|
|
46
|
+
"typeorm": "^0.3.17",
|
|
47
|
+
"reflect-metadata": "^0.1.13",
|
|
48
|
+
"typescript": "^5.4.5"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc",
|
|
52
|
+
"test": "tsc && node --test dist/**/*.test.js"
|
|
53
|
+
}
|
|
54
|
+
}
|