@oondemand/oon-core-back 0.3.74 → 0.3.76
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/package.json +1 -1
- package/src/api/access.routes.js +353 -0
- package/src/api/auth.routes.js +39 -34
- package/src/boot/createApp.js +2 -2
- package/src/core/define.js +5 -1
- package/src/core/errors/GenericError.js +3 -2
- package/src/core/factories/crudController.js +96 -27
- package/src/core/factories/crudService.js +101 -22
- package/src/core/factories/modelRouter.js +18 -15
- package/src/core/middlewares/auth.js +72 -4
- package/src/core/middlewares/permissions.js +37 -0
- package/src/core/middlewares/rbac.js +38 -10
- package/src/core/rbac/identityProvider.js +66 -0
- package/src/core/rbac/policy.js +168 -0
- package/src/core/rbac/storage.js +387 -0
- package/src/core/tenancy.js +200 -0
- package/src/index.js +19 -0
- package/src/manifest/appManifest.js +26 -104
- package/src/manifest/rbacManifest.js +89 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const Helpers = require("../utils/helpers");
|
|
2
2
|
const registry = require("../registry");
|
|
3
|
+
const { scopeMutation } = require("../tenancy");
|
|
3
4
|
const {
|
|
4
5
|
prepareProcessMutation,
|
|
5
6
|
assertAtomicInvariants,
|
|
@@ -38,13 +39,27 @@ function createCrudController(entry, service) {
|
|
|
38
39
|
if (validate) await validate(data, context);
|
|
39
40
|
};
|
|
40
41
|
|
|
41
|
-
const prepareCreate = async (
|
|
42
|
-
|
|
42
|
+
const prepareCreate = async (
|
|
43
|
+
data,
|
|
44
|
+
method = "post",
|
|
45
|
+
session = null,
|
|
46
|
+
accessContext = undefined,
|
|
47
|
+
) => {
|
|
48
|
+
const scopedData = scopeMutation(entry, data, accessContext);
|
|
49
|
+
const prepared = await prepareProcessMutation(entry, {}, scopedData, {
|
|
43
50
|
op: "create",
|
|
44
51
|
session,
|
|
52
|
+
accessContext,
|
|
45
53
|
});
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
// Reaplica o escopo depois das regras para impedir que fórmula/hook de
|
|
56
|
+
// domínio substitua tenantId/usuarioId confiável. O controller usa o
|
|
57
|
+
// contrato público de tenancy e não exige helpers extras em services
|
|
58
|
+
// customizados/legados.
|
|
59
|
+
prepared.changes = scopeMutation(entry, prepared.changes, accessContext);
|
|
60
|
+
prepared.consolidated = scopeMutation(entry, prepared.consolidated, accessContext);
|
|
61
|
+
|
|
62
|
+
await assertReferencePolicies(entry, prepared.consolidated, { session, accessContext });
|
|
48
63
|
await runCustomValidation(prepared.consolidated, {
|
|
49
64
|
op: "create",
|
|
50
65
|
method,
|
|
@@ -54,14 +69,24 @@ function createCrudController(entry, service) {
|
|
|
54
69
|
consolidated: prepared.consolidated,
|
|
55
70
|
changedFields: prepared.changedFields,
|
|
56
71
|
session,
|
|
72
|
+
accessContext,
|
|
57
73
|
});
|
|
58
74
|
return prepared;
|
|
59
75
|
};
|
|
60
76
|
|
|
61
|
-
const prepareUpdate = async (
|
|
77
|
+
const prepareUpdate = async (
|
|
78
|
+
id,
|
|
79
|
+
changes,
|
|
80
|
+
method,
|
|
81
|
+
session = null,
|
|
82
|
+
accessContext = undefined,
|
|
83
|
+
) => {
|
|
84
|
+
const scopedChanges = scopeMutation(entry, changes, accessContext);
|
|
62
85
|
const validate = registry.getValidation(entry.name);
|
|
63
86
|
if (!validate && !hasDomainRules(entry) && !hasProcessRules(entry)) {
|
|
64
|
-
const normalizedChanges =
|
|
87
|
+
const normalizedChanges = scopedChanges && typeof scopedChanges === "object"
|
|
88
|
+
? { ...scopedChanges }
|
|
89
|
+
: {};
|
|
65
90
|
return {
|
|
66
91
|
current: null,
|
|
67
92
|
requestedChanges: normalizedChanges,
|
|
@@ -71,14 +96,17 @@ function createCrudController(entry, service) {
|
|
|
71
96
|
};
|
|
72
97
|
}
|
|
73
98
|
|
|
74
|
-
const currentRecord = await service.getById(id, { session });
|
|
99
|
+
const currentRecord = await service.getById(id, { session, accessContext });
|
|
75
100
|
const current = toPlainRecord(currentRecord);
|
|
76
|
-
const prepared = await prepareProcessMutation(entry, current,
|
|
101
|
+
const prepared = await prepareProcessMutation(entry, current, scopedChanges, {
|
|
77
102
|
op: "update",
|
|
78
103
|
session,
|
|
104
|
+
accessContext,
|
|
79
105
|
});
|
|
106
|
+
prepared.changes = scopeMutation(entry, prepared.changes, accessContext);
|
|
107
|
+
prepared.consolidated = scopeMutation(entry, prepared.consolidated, accessContext);
|
|
80
108
|
|
|
81
|
-
await assertReferencePolicies(entry, prepared.consolidated, { session });
|
|
109
|
+
await assertReferencePolicies(entry, prepared.consolidated, { session, accessContext });
|
|
82
110
|
if (validate) {
|
|
83
111
|
await validate(prepared.consolidated, {
|
|
84
112
|
op: "update",
|
|
@@ -90,30 +118,33 @@ function createCrudController(entry, service) {
|
|
|
90
118
|
consolidated: prepared.consolidated,
|
|
91
119
|
changedFields: prepared.changedFields,
|
|
92
120
|
session,
|
|
121
|
+
accessContext,
|
|
93
122
|
});
|
|
94
123
|
}
|
|
95
124
|
|
|
96
125
|
return { current, ...prepared };
|
|
97
126
|
};
|
|
98
127
|
|
|
99
|
-
async function createOne(data, method, session) {
|
|
100
|
-
const prepared = await prepareCreate(data, method, session);
|
|
128
|
+
async function createOne(data, method, session, accessContext) {
|
|
129
|
+
const prepared = await prepareCreate(data, method, session, accessContext);
|
|
101
130
|
await assertAtomicInvariants(entry, prepared.consolidated, null, session);
|
|
102
|
-
const record = await service.create(prepared.changes, { session });
|
|
131
|
+
const record = await service.create(prepared.changes, { session, accessContext });
|
|
103
132
|
await recalculateDependents(entry, null, toPlainRecord(record), {
|
|
104
133
|
session,
|
|
105
134
|
changedFields: prepared.changedFields,
|
|
135
|
+
accessContext,
|
|
106
136
|
});
|
|
107
137
|
return record;
|
|
108
138
|
}
|
|
109
139
|
|
|
110
|
-
async function updateOne(id, changes, method, session) {
|
|
111
|
-
const prepared = await prepareUpdate(id, changes, method, session);
|
|
140
|
+
async function updateOne(id, changes, method, session, accessContext) {
|
|
141
|
+
const prepared = await prepareUpdate(id, changes, method, session, accessContext);
|
|
112
142
|
await assertAtomicInvariants(entry, prepared.consolidated, prepared.current, session);
|
|
113
|
-
const record = await service.update(id, prepared.changes, { session });
|
|
143
|
+
const record = await service.update(id, prepared.changes, { session, accessContext });
|
|
114
144
|
await recalculateDependents(entry, prepared.current, toPlainRecord(record), {
|
|
115
145
|
session,
|
|
116
146
|
changedFields: prepared.changedFields,
|
|
147
|
+
accessContext,
|
|
117
148
|
});
|
|
118
149
|
return record;
|
|
119
150
|
}
|
|
@@ -127,19 +158,27 @@ function createCrudController(entry, service) {
|
|
|
127
158
|
searchTerm,
|
|
128
159
|
sort,
|
|
129
160
|
filtros,
|
|
161
|
+
accessContext: req.accessContext,
|
|
130
162
|
});
|
|
131
163
|
Helpers.sendPaginatedResponse({ res, statusCode: 200, results, pagination });
|
|
132
164
|
},
|
|
133
165
|
|
|
134
166
|
async getById(req, res) {
|
|
135
|
-
const registro = await service.getById(req.params.id
|
|
167
|
+
const registro = await service.getById(req.params.id, {
|
|
168
|
+
accessContext: req.accessContext,
|
|
169
|
+
});
|
|
136
170
|
Helpers.sendResponse({ res, statusCode: 200, [entry.singular]: registro });
|
|
137
171
|
},
|
|
138
172
|
|
|
139
173
|
async create(req, res) {
|
|
140
174
|
const registro = await withProcessTransaction(
|
|
141
175
|
entry,
|
|
142
|
-
(session) => createOne(
|
|
176
|
+
(session) => createOne(
|
|
177
|
+
req.body ?? {},
|
|
178
|
+
"post",
|
|
179
|
+
session,
|
|
180
|
+
req.accessContext,
|
|
181
|
+
),
|
|
143
182
|
);
|
|
144
183
|
Helpers.sendResponse({ res, statusCode: 201, [entry.singular]: registro });
|
|
145
184
|
},
|
|
@@ -147,7 +186,13 @@ function createCrudController(entry, service) {
|
|
|
147
186
|
async update(req, res) {
|
|
148
187
|
const registro = await withProcessTransaction(
|
|
149
188
|
entry,
|
|
150
|
-
(session) => updateOne(
|
|
189
|
+
(session) => updateOne(
|
|
190
|
+
req.params.id,
|
|
191
|
+
req.body ?? {},
|
|
192
|
+
"put",
|
|
193
|
+
session,
|
|
194
|
+
req.accessContext,
|
|
195
|
+
),
|
|
151
196
|
);
|
|
152
197
|
Helpers.sendResponse({ res, statusCode: 200, [entry.singular]: registro });
|
|
153
198
|
},
|
|
@@ -155,20 +200,36 @@ function createCrudController(entry, service) {
|
|
|
155
200
|
async patch(req, res) {
|
|
156
201
|
const registro = await withProcessTransaction(
|
|
157
202
|
entry,
|
|
158
|
-
(session) => updateOne(
|
|
203
|
+
(session) => updateOne(
|
|
204
|
+
req.params.id,
|
|
205
|
+
req.body ?? {},
|
|
206
|
+
"patch",
|
|
207
|
+
session,
|
|
208
|
+
req.accessContext,
|
|
209
|
+
),
|
|
159
210
|
);
|
|
160
211
|
Helpers.sendResponse({ res, statusCode: 200, [entry.singular]: registro });
|
|
161
212
|
},
|
|
162
213
|
|
|
163
214
|
async remove(req, res) {
|
|
164
215
|
const registro = await withProcessTransaction(entry, async (session) => {
|
|
165
|
-
const currentRecord = await service.getById(req.params.id, {
|
|
216
|
+
const currentRecord = await service.getById(req.params.id, {
|
|
217
|
+
session,
|
|
218
|
+
accessContext: req.accessContext,
|
|
219
|
+
});
|
|
166
220
|
const current = toPlainRecord(currentRecord);
|
|
167
|
-
await assertDeleteAllowed(entry, req.params.id, {
|
|
168
|
-
|
|
221
|
+
await assertDeleteAllowed(entry, req.params.id, {
|
|
222
|
+
session,
|
|
223
|
+
accessContext: req.accessContext,
|
|
224
|
+
});
|
|
225
|
+
const removed = await service.remove(req.params.id, {
|
|
226
|
+
session,
|
|
227
|
+
accessContext: req.accessContext,
|
|
228
|
+
});
|
|
169
229
|
await recalculateDependents(entry, current, null, {
|
|
170
230
|
session,
|
|
171
231
|
changedFields: Object.keys(current),
|
|
232
|
+
accessContext: req.accessContext,
|
|
172
233
|
});
|
|
173
234
|
return removed;
|
|
174
235
|
});
|
|
@@ -178,16 +239,22 @@ function createCrudController(entry, service) {
|
|
|
178
239
|
async importMany(req, res) {
|
|
179
240
|
const rows = req.body?.rows || req.body;
|
|
180
241
|
if (!Array.isArray(rows)) {
|
|
181
|
-
const result = await service.importMany(rows
|
|
242
|
+
const result = await service.importMany(rows, {
|
|
243
|
+
accessContext: req.accessContext,
|
|
244
|
+
});
|
|
182
245
|
Helpers.sendResponse({ res, statusCode: 201, ...result });
|
|
183
246
|
return;
|
|
184
247
|
}
|
|
185
248
|
|
|
186
249
|
if (!hasProcessRules(entry)) {
|
|
187
250
|
const preparedRows = await Promise.all(
|
|
188
|
-
rows.map(async (row) => (
|
|
251
|
+
rows.map(async (row) => (
|
|
252
|
+
await prepareCreate(row, "import", null, req.accessContext)
|
|
253
|
+
).changes),
|
|
189
254
|
);
|
|
190
|
-
const result = await service.importMany(preparedRows
|
|
255
|
+
const result = await service.importMany(preparedRows, {
|
|
256
|
+
accessContext: req.accessContext,
|
|
257
|
+
});
|
|
191
258
|
Helpers.sendResponse({ res, statusCode: 201, ...result });
|
|
192
259
|
return;
|
|
193
260
|
}
|
|
@@ -195,7 +262,7 @@ function createCrudController(entry, service) {
|
|
|
195
262
|
let inserted = 0;
|
|
196
263
|
for (const row of rows) {
|
|
197
264
|
await withProcessTransaction(entry, async (session) => {
|
|
198
|
-
await createOne(row, "import", session);
|
|
265
|
+
await createOne(row, "import", session, req.accessContext);
|
|
199
266
|
inserted += 1;
|
|
200
267
|
});
|
|
201
268
|
}
|
|
@@ -203,7 +270,9 @@ function createCrudController(entry, service) {
|
|
|
203
270
|
},
|
|
204
271
|
|
|
205
272
|
async exportAll(req, res) {
|
|
206
|
-
const results = await service.exportAll(req.query
|
|
273
|
+
const results = await service.exportAll(req.query, {
|
|
274
|
+
accessContext: req.accessContext,
|
|
275
|
+
});
|
|
207
276
|
Helpers.sendResponse({ res, statusCode: 200, results });
|
|
208
277
|
},
|
|
209
278
|
};
|
|
@@ -214,4 +283,4 @@ module.exports = {
|
|
|
214
283
|
toPlainRecord,
|
|
215
284
|
hasDomainRules,
|
|
216
285
|
hasProcessRules,
|
|
217
|
-
};
|
|
286
|
+
};
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
const GenericError = require("../errors/GenericError");
|
|
2
2
|
const registry = require("../registry");
|
|
3
|
+
const {
|
|
4
|
+
dataScope,
|
|
5
|
+
scopeFilter,
|
|
6
|
+
mergeScopedFilter,
|
|
7
|
+
scopeMutation,
|
|
8
|
+
scopedIdFilter,
|
|
9
|
+
} = require("../tenancy");
|
|
3
10
|
|
|
4
11
|
const COMMON_REFERENCE_LABEL_FIELDS = [
|
|
5
12
|
"nome",
|
|
@@ -15,7 +22,7 @@ const COMMON_REFERENCE_LABEL_FIELDS = [
|
|
|
15
22
|
"sigla",
|
|
16
23
|
];
|
|
17
24
|
|
|
18
|
-
function referencePopulateSpecs(entry) {
|
|
25
|
+
function referencePopulateSpecs(entry, accessContext) {
|
|
19
26
|
const setting = entry.definition?.crud?.populateRefs;
|
|
20
27
|
if (!setting) return [];
|
|
21
28
|
|
|
@@ -35,10 +42,15 @@ function referencePopulateSpecs(entry) {
|
|
|
35
42
|
...COMMON_REFERENCE_LABEL_FIELDS,
|
|
36
43
|
...(referencedEntry?.searchableFields || []),
|
|
37
44
|
];
|
|
45
|
+
let match;
|
|
46
|
+
if (referencedEntry && dataScope(referencedEntry) !== "legacy") {
|
|
47
|
+
match = scopeFilter(referencedEntry, accessContext);
|
|
48
|
+
}
|
|
38
49
|
|
|
39
50
|
return {
|
|
40
51
|
path: field.name,
|
|
41
52
|
select: [...new Set(select)].join(" "),
|
|
53
|
+
...(match && Object.keys(match).length ? { match } : {}),
|
|
42
54
|
};
|
|
43
55
|
});
|
|
44
56
|
}
|
|
@@ -62,21 +74,47 @@ function withSession(query, session) {
|
|
|
62
74
|
return session && query && typeof query.session === "function" ? query.session(session) : query;
|
|
63
75
|
}
|
|
64
76
|
|
|
77
|
+
function scopeDenied() {
|
|
78
|
+
return new GenericError("Registro fora do contexto de acesso atual.", {
|
|
79
|
+
statusCode: 403,
|
|
80
|
+
code: "TENANT_SCOPE_DENIED",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function notFound() {
|
|
85
|
+
return new GenericError("Registro não encontrado.", { statusCode: 404 });
|
|
86
|
+
}
|
|
87
|
+
|
|
65
88
|
function createCrudService(entry) {
|
|
66
89
|
const { mongooseModel: Model, searchableFields } = entry;
|
|
67
|
-
const populateSpecs = referencePopulateSpecs(entry);
|
|
68
90
|
|
|
69
|
-
async function
|
|
91
|
+
async function assertScopedMiss(id, accessContext, session) {
|
|
92
|
+
const scope = dataScope(entry);
|
|
93
|
+
if (scope === "legacy" || scope === "system") throw notFound();
|
|
94
|
+
const exists = await withSession(Model.exists({ _id: id }), session);
|
|
95
|
+
if (exists) throw scopeDenied();
|
|
96
|
+
throw notFound();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function listPaginated({
|
|
100
|
+
pageIndex = 0,
|
|
101
|
+
pageSize = 20,
|
|
102
|
+
searchTerm,
|
|
103
|
+
sort,
|
|
104
|
+
filtros = {},
|
|
105
|
+
accessContext,
|
|
106
|
+
} = {}) {
|
|
70
107
|
const page = Math.max(0, Number(pageIndex) || 0);
|
|
71
108
|
const limit = Math.min(200, Math.max(1, Number(pageSize) || 20));
|
|
72
109
|
|
|
73
|
-
const query =
|
|
110
|
+
const query = mergeScopedFilter(entry, cleanFilters(filtros), accessContext);
|
|
74
111
|
if (searchTerm && searchableFields.length) {
|
|
75
112
|
const rx = new RegExp(escapeRegex(searchTerm), "i");
|
|
76
113
|
query.$or = searchableFields.map((f) => ({ [f]: rx }));
|
|
77
114
|
}
|
|
78
115
|
|
|
79
116
|
const sortSpec = parseSort(sort);
|
|
117
|
+
const populateSpecs = referencePopulateSpecs(entry, accessContext);
|
|
80
118
|
const listQuery = populateQuery(
|
|
81
119
|
Model.find(query).sort(sortSpec).skip(page * limit).limit(limit),
|
|
82
120
|
populateSpecs
|
|
@@ -98,32 +136,48 @@ function createCrudService(entry) {
|
|
|
98
136
|
}
|
|
99
137
|
|
|
100
138
|
async function getById(id, options = {}) {
|
|
101
|
-
const
|
|
139
|
+
const populateSpecs = referencePopulateSpecs(entry, options.accessContext);
|
|
140
|
+
const query = withSession(
|
|
141
|
+
Model.findOne(scopedIdFilter(entry, id, options.accessContext)),
|
|
142
|
+
options.session,
|
|
143
|
+
);
|
|
102
144
|
const doc = await resolvePopulated(query, populateSpecs);
|
|
103
|
-
if (!doc)
|
|
145
|
+
if (!doc) await assertScopedMiss(id, options.accessContext, options.session);
|
|
104
146
|
return doc;
|
|
105
147
|
}
|
|
106
148
|
|
|
107
149
|
async function create(data, options = {}) {
|
|
108
|
-
const
|
|
150
|
+
const scopedData = scopeMutation(entry, data, options.accessContext);
|
|
151
|
+
const doc = new Model(scopedData);
|
|
109
152
|
await doc.save({ session: options.session || undefined });
|
|
110
|
-
return resolvePopulated(doc,
|
|
153
|
+
return resolvePopulated(doc, referencePopulateSpecs(entry, options.accessContext));
|
|
111
154
|
}
|
|
112
155
|
|
|
113
156
|
async function update(id, data, options = {}) {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
157
|
+
const scopedData = scopeMutation(entry, data, options.accessContext);
|
|
158
|
+
const query = Model.findOneAndUpdate(
|
|
159
|
+
scopedIdFilter(entry, id, options.accessContext),
|
|
160
|
+
scopedData,
|
|
161
|
+
{
|
|
162
|
+
new: true,
|
|
163
|
+
runValidators: true,
|
|
164
|
+
session: options.session || undefined,
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
const doc = await resolvePopulated(
|
|
168
|
+
query,
|
|
169
|
+
referencePopulateSpecs(entry, options.accessContext),
|
|
170
|
+
);
|
|
171
|
+
if (!doc) await assertScopedMiss(id, options.accessContext, options.session);
|
|
121
172
|
return doc;
|
|
122
173
|
}
|
|
123
174
|
|
|
124
175
|
async function remove(id, options = {}) {
|
|
125
|
-
const doc = await withSession(
|
|
126
|
-
|
|
176
|
+
const doc = await withSession(
|
|
177
|
+
Model.findOneAndDelete(scopedIdFilter(entry, id, options.accessContext)),
|
|
178
|
+
options.session,
|
|
179
|
+
);
|
|
180
|
+
if (!doc) await assertScopedMiss(id, options.accessContext, options.session);
|
|
127
181
|
return doc;
|
|
128
182
|
}
|
|
129
183
|
|
|
@@ -131,18 +185,38 @@ function createCrudService(entry) {
|
|
|
131
185
|
if (!Array.isArray(rows)) {
|
|
132
186
|
throw new GenericError("Esperado um array de registros.", { statusCode: 400 });
|
|
133
187
|
}
|
|
134
|
-
const
|
|
188
|
+
const scopedRows = rows.map((row) => scopeMutation(entry, row, options.accessContext));
|
|
189
|
+
const docs = await Model.insertMany(scopedRows, {
|
|
135
190
|
ordered: false,
|
|
136
191
|
session: options.session || undefined,
|
|
137
192
|
});
|
|
138
193
|
return { inserted: docs.length };
|
|
139
194
|
}
|
|
140
195
|
|
|
141
|
-
async function exportAll(filtros = {}) {
|
|
142
|
-
|
|
196
|
+
async function exportAll(filtros = {}, options = {}) {
|
|
197
|
+
const query = mergeScopedFilter(entry, cleanFilters(filtros), options.accessContext);
|
|
198
|
+
return Model.find(query).lean();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function scopeQuery(filter = {}, accessContext) {
|
|
202
|
+
return mergeScopedFilter(entry, filter, accessContext);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function scopedData(data = {}, accessContext) {
|
|
206
|
+
return scopeMutation(entry, data, accessContext);
|
|
143
207
|
}
|
|
144
208
|
|
|
145
|
-
return {
|
|
209
|
+
return {
|
|
210
|
+
listPaginated,
|
|
211
|
+
getById,
|
|
212
|
+
create,
|
|
213
|
+
update,
|
|
214
|
+
remove,
|
|
215
|
+
importMany,
|
|
216
|
+
exportAll,
|
|
217
|
+
scopeQuery,
|
|
218
|
+
scopedData,
|
|
219
|
+
};
|
|
146
220
|
}
|
|
147
221
|
|
|
148
222
|
function cleanFilters(filtros) {
|
|
@@ -163,4 +237,9 @@ function escapeRegex(str) {
|
|
|
163
237
|
return String(str).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
164
238
|
}
|
|
165
239
|
|
|
166
|
-
module.exports = {
|
|
240
|
+
module.exports = {
|
|
241
|
+
createCrudService,
|
|
242
|
+
referencePopulateSpecs,
|
|
243
|
+
resolvePopulated,
|
|
244
|
+
cleanFilters,
|
|
245
|
+
};
|
|
@@ -2,16 +2,14 @@ const express = require("express");
|
|
|
2
2
|
const { asyncHandler } = require("../utils/helpers");
|
|
3
3
|
const auth = require("../middlewares/auth");
|
|
4
4
|
const { rbac } = require("../middlewares/rbac");
|
|
5
|
+
const { requirePermission } = require("../middlewares/permissions");
|
|
5
6
|
const { mutationAudit } = require("../middlewares/mutationAudit");
|
|
6
7
|
const { createCrudService } = require("./crudService");
|
|
7
8
|
const { createCrudController } = require("./crudController");
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
* Monta o router de CRUD de uma model.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Endpoints (Seção 2.9): GET list, GET :id, POST, PUT :id, PATCH :id,
|
|
14
|
-
* DELETE :id, POST /import, GET /export.
|
|
11
|
+
* Monta o router de CRUD de uma model. `crud.permissions` é o contrato
|
|
12
|
+
* canônico; `crud.roles` permanece como fallback para Centrais legadas.
|
|
15
13
|
*/
|
|
16
14
|
function buildModelRouter(entry) {
|
|
17
15
|
const router = express.Router();
|
|
@@ -20,43 +18,48 @@ function buildModelRouter(entry) {
|
|
|
20
18
|
|
|
21
19
|
const crud = entry.definition.crud || {};
|
|
22
20
|
const roles = crud.roles || {};
|
|
23
|
-
const
|
|
24
|
-
const
|
|
21
|
+
const permissions = crud.permissions || {};
|
|
22
|
+
const readGuard = permissions.read
|
|
23
|
+
? requirePermission(permissions.read)
|
|
24
|
+
: rbac(roles.read || []);
|
|
25
|
+
const writeGuard = permissions.write
|
|
26
|
+
? requirePermission(permissions.write)
|
|
27
|
+
: rbac(roles.write || []);
|
|
25
28
|
const entidade = entry.name;
|
|
26
29
|
|
|
27
30
|
router.use(auth);
|
|
28
31
|
|
|
29
|
-
router.get("/",
|
|
30
|
-
router.get("/export",
|
|
31
|
-
router.get("/:id",
|
|
32
|
+
router.get("/", readGuard, asyncHandler(controller.list));
|
|
33
|
+
router.get("/export", readGuard, asyncHandler(controller.exportAll));
|
|
34
|
+
router.get("/:id", readGuard, asyncHandler(controller.getById));
|
|
32
35
|
|
|
33
36
|
router.post(
|
|
34
37
|
"/import",
|
|
35
|
-
|
|
38
|
+
writeGuard,
|
|
36
39
|
mutationAudit({ entidade, acao: "adicionado" }),
|
|
37
40
|
asyncHandler(controller.importMany)
|
|
38
41
|
);
|
|
39
42
|
router.post(
|
|
40
43
|
"/",
|
|
41
|
-
|
|
44
|
+
writeGuard,
|
|
42
45
|
mutationAudit({ entidade, acao: "adicionado" }),
|
|
43
46
|
asyncHandler(controller.create)
|
|
44
47
|
);
|
|
45
48
|
router.put(
|
|
46
49
|
"/:id",
|
|
47
|
-
|
|
50
|
+
writeGuard,
|
|
48
51
|
mutationAudit({ entidade, acao: "alterado" }),
|
|
49
52
|
asyncHandler(controller.update)
|
|
50
53
|
);
|
|
51
54
|
router.patch(
|
|
52
55
|
"/:id",
|
|
53
|
-
|
|
56
|
+
writeGuard,
|
|
54
57
|
mutationAudit({ entidade, acao: "alterado" }),
|
|
55
58
|
asyncHandler(controller.patch)
|
|
56
59
|
);
|
|
57
60
|
router.delete(
|
|
58
61
|
"/:id",
|
|
59
|
-
|
|
62
|
+
writeGuard,
|
|
60
63
|
mutationAudit({ entidade, acao: "excluido" }),
|
|
61
64
|
asyncHandler(controller.remove)
|
|
62
65
|
);
|