@adminforth/crud-approve-plugin 1.0.13 → 1.0.15
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/.woodpecker/buildSlackNotify.sh +3 -1
- package/.woodpecker/release.yml +27 -9
- package/05-CRUDApprove.md +0 -130
- package/build.log +15 -0
- package/custom/ListPageDiffView.vue +1 -1
- package/custom/pnpm-lock.yaml +308 -0
- package/dist/custom/ListPageDiffView.vue +86 -0
- package/dist/custom/ShowPageDiffView.vue +95 -0
- package/dist/custom/package-lock.json +420 -0
- package/dist/custom/package.json +6 -1
- package/dist/custom/pnpm-lock.yaml +308 -0
- package/dist/index.js +371 -19
- package/dist/types.js +10 -3
- package/index.ts +20 -1
- package/package.json +5 -5
- package/pnpm-workspace.yaml +4 -0
package/dist/index.js
CHANGED
|
@@ -7,11 +7,242 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { AdminForthPlugin } from "adminforth";
|
|
11
|
-
import {
|
|
10
|
+
import { ActionCheckSource, AdminForthPlugin, AdminForthSortDirections, Filters } from "adminforth";
|
|
11
|
+
import { AdminForthDataTypes, AllowedActionsEnum } from "adminforth";
|
|
12
|
+
import { AllowedForReviewActionsEnum, ApprovalStatusEnum } from './types.js';
|
|
13
|
+
import dayjs from "dayjs";
|
|
14
|
+
import utc from "dayjs/plugin/utc.js";
|
|
15
|
+
import { randomUUID } from "crypto";
|
|
16
|
+
dayjs.extend(utc);
|
|
12
17
|
export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
13
18
|
constructor(options) {
|
|
14
19
|
super(options, import.meta.url);
|
|
20
|
+
this.createApprovalRequest = (_a) => __awaiter(this, [_a], void 0, function* ({ resource, action, data, user, oldRecord, updates, extra, record }) {
|
|
21
|
+
var _b;
|
|
22
|
+
//@ts-ignore
|
|
23
|
+
if (extra && extra.adminforth_plugin_crud_approve && extra.adminforth_plugin_crud_approve.callingFromApprovalPlugin) {
|
|
24
|
+
return { ok: true, error: 'Approval request creation aborted to avoid infinite loop' };
|
|
25
|
+
}
|
|
26
|
+
const recordIdFieldName = (_b = resource.columns.find((c) => c.primaryKey === true)) === null || _b === void 0 ? void 0 : _b.name;
|
|
27
|
+
const recordId = (data === null || data === void 0 ? void 0 : data[recordIdFieldName]) || (oldRecord === null || oldRecord === void 0 ? void 0 : oldRecord[recordIdFieldName]);
|
|
28
|
+
let newRecord = {};
|
|
29
|
+
const connector = this.adminforth.connectors[resource.dataSource];
|
|
30
|
+
if (action === AllowedForReviewActionsEnum.create) {
|
|
31
|
+
oldRecord = {};
|
|
32
|
+
newRecord = updates || record;
|
|
33
|
+
}
|
|
34
|
+
else if (action === AllowedForReviewActionsEnum.edit) {
|
|
35
|
+
newRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
|
|
36
|
+
for (const key in updates) {
|
|
37
|
+
newRecord[key] = updates[key];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (action === AllowedForReviewActionsEnum.delete) {
|
|
41
|
+
oldRecord = yield connector.getRecordByPrimaryKey(resource, recordId);
|
|
42
|
+
}
|
|
43
|
+
const checks = yield Promise.all(resource.columns.map((c) => __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
var _a;
|
|
45
|
+
if (typeof c.backendOnly === "function") {
|
|
46
|
+
const result = yield c.backendOnly({
|
|
47
|
+
adminUser: user,
|
|
48
|
+
resource,
|
|
49
|
+
meta: {},
|
|
50
|
+
source: ActionCheckSource.ShowRequest,
|
|
51
|
+
adminforth: this.adminforth,
|
|
52
|
+
});
|
|
53
|
+
return { col: c, result };
|
|
54
|
+
}
|
|
55
|
+
return { col: c, result: (_a = c.backendOnly) !== null && _a !== void 0 ? _a : false };
|
|
56
|
+
})));
|
|
57
|
+
const backendOnlyColumns = checks
|
|
58
|
+
.filter(({ result }) => result === true)
|
|
59
|
+
.map(({ col }) => col);
|
|
60
|
+
backendOnlyColumns.forEach((c) => {
|
|
61
|
+
if (JSON.stringify(oldRecord[c.name]) != JSON.stringify(newRecord[c.name])) {
|
|
62
|
+
if (action !== AllowedForReviewActionsEnum.delete) {
|
|
63
|
+
newRecord[c.name] = '<hidden value after>';
|
|
64
|
+
}
|
|
65
|
+
if (action !== AllowedForReviewActionsEnum.create) {
|
|
66
|
+
oldRecord[c.name] = '<hidden value before>';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
delete oldRecord[c.name];
|
|
71
|
+
delete newRecord[c.name];
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
if (action === AllowedForReviewActionsEnum.edit) {
|
|
75
|
+
for (const key in oldRecord) {
|
|
76
|
+
if (JSON.stringify(oldRecord[key]) === JSON.stringify(newRecord[key])) {
|
|
77
|
+
delete oldRecord[key];
|
|
78
|
+
delete newRecord[key];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const createdAt = dayjs.utc().format();
|
|
83
|
+
const diffRecord = {
|
|
84
|
+
[this.options.resourceColumns.idColumnName]: randomUUID(),
|
|
85
|
+
[this.options.resourceColumns.resourceIdColumnName]: resource.resourceId,
|
|
86
|
+
[this.options.resourceColumns.actionColumnName]: action,
|
|
87
|
+
[this.options.resourceColumns.statusColumnName]: ApprovalStatusEnum.pending,
|
|
88
|
+
[this.options.resourceColumns.dataColumnName]: { 'oldRecord': oldRecord, 'newRecord': newRecord },
|
|
89
|
+
[this.options.resourceColumns.userIdColumnName]: user.pk,
|
|
90
|
+
[this.options.resourceColumns.recordIdColumnName]: recordId,
|
|
91
|
+
[this.options.resourceColumns.createdAtColumnName]: createdAt,
|
|
92
|
+
[this.options.resourceColumns.extraColumnName]: extra || {},
|
|
93
|
+
};
|
|
94
|
+
const diffResource = this.adminforth.config.resources.find((r) => r.resourceId === this.diffResource.resourceId);
|
|
95
|
+
yield this.adminforth.createResourceRecord({ resource: diffResource, record: diffRecord, adminUser: user });
|
|
96
|
+
return { ok: true, error: null };
|
|
97
|
+
});
|
|
98
|
+
this.callBeforeSaveHooks = (
|
|
99
|
+
// resource: AdminForthResource, action: AllowedActionsEnum, record: any,
|
|
100
|
+
// adminUser: AdminUser, recordId?: any, extra?: HttpExtra
|
|
101
|
+
resource, action, record, adminUser, recordId, updates, oldRecord, adminforth, extra) => __awaiter(this, void 0, void 0, function* () {
|
|
102
|
+
let hooks = [];
|
|
103
|
+
if (action === AllowedActionsEnum.create) {
|
|
104
|
+
hooks = resource.hooks.create.beforeSave;
|
|
105
|
+
}
|
|
106
|
+
else if (action === AllowedActionsEnum.edit) {
|
|
107
|
+
hooks = resource.hooks.edit.beforeSave;
|
|
108
|
+
}
|
|
109
|
+
else if (action === AllowedActionsEnum.delete) {
|
|
110
|
+
hooks = resource.hooks.delete.beforeSave;
|
|
111
|
+
}
|
|
112
|
+
if (extra === undefined) {
|
|
113
|
+
extra = {};
|
|
114
|
+
}
|
|
115
|
+
//@ts-ignore
|
|
116
|
+
extra.adminforth_plugin_crud_approve = {
|
|
117
|
+
//@ts-ignore
|
|
118
|
+
callingFromApprovalPlugin: true
|
|
119
|
+
//@ts-ignore
|
|
120
|
+
};
|
|
121
|
+
for (const hook of hooks) {
|
|
122
|
+
const resp = yield hook({
|
|
123
|
+
resource,
|
|
124
|
+
record,
|
|
125
|
+
adminUser,
|
|
126
|
+
recordId,
|
|
127
|
+
adminforth: this.adminforth,
|
|
128
|
+
extra,
|
|
129
|
+
updates,
|
|
130
|
+
oldRecord,
|
|
131
|
+
});
|
|
132
|
+
if (!resp || (typeof resp.ok !== 'boolean' && (!resp.error && !resp.newRecordId))) {
|
|
133
|
+
throw new Error(`Invalid return value from beforeSave hook. Expected: { ok: boolean, error?: string | null, newRecordId?: any }.\n` +
|
|
134
|
+
`Note: Return { ok: false, error: null, newRecordId } to stop creation and redirect to an existing record.`);
|
|
135
|
+
}
|
|
136
|
+
if (resp.ok === false && !resp.error) {
|
|
137
|
+
const { error, ok, newRecordId } = resp;
|
|
138
|
+
return {
|
|
139
|
+
error: error !== null && error !== void 0 ? error : 'Operation aborted by hook',
|
|
140
|
+
newRecordId: newRecordId
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (resp.error) {
|
|
144
|
+
return { error: resp.error };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { ok: true, error: null };
|
|
148
|
+
});
|
|
149
|
+
this.callAfterSaveHooks = (
|
|
150
|
+
// resource: AdminForthResource, action: AllowedActionsEnum, record: any,
|
|
151
|
+
// adminUser: AdminUser, recordId: any, extra?: HttpExtra
|
|
152
|
+
resource, action, record, adminUser, recordId, updates, oldRecord, adminforth, extra) => __awaiter(this, void 0, void 0, function* () {
|
|
153
|
+
let hooks = [];
|
|
154
|
+
if (action === AllowedActionsEnum.create) {
|
|
155
|
+
hooks = resource.hooks.create.afterSave;
|
|
156
|
+
}
|
|
157
|
+
else if (action === AllowedActionsEnum.edit) {
|
|
158
|
+
hooks = resource.hooks.edit.afterSave;
|
|
159
|
+
}
|
|
160
|
+
else if (action === AllowedActionsEnum.delete) {
|
|
161
|
+
hooks = resource.hooks.delete.afterSave;
|
|
162
|
+
}
|
|
163
|
+
for (const hook of hooks) {
|
|
164
|
+
const resp = yield hook({
|
|
165
|
+
resource,
|
|
166
|
+
record,
|
|
167
|
+
adminUser,
|
|
168
|
+
recordId,
|
|
169
|
+
adminforth: this.adminforth,
|
|
170
|
+
extra,
|
|
171
|
+
updates,
|
|
172
|
+
oldRecord,
|
|
173
|
+
});
|
|
174
|
+
if (!resp || (!resp.ok && !resp.error)) {
|
|
175
|
+
throw new Error(`Hook afterSave must return object with {ok: true} or { error: 'Error' } `);
|
|
176
|
+
}
|
|
177
|
+
if (resp.error) {
|
|
178
|
+
return { error: resp.error };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
this.verifyAuth = (cookies) => __awaiter(this, void 0, void 0, function* () {
|
|
183
|
+
let authCookie;
|
|
184
|
+
for (const i in cookies) {
|
|
185
|
+
if (cookies[i].key === `adminforth_${this.adminforth.config.customization.brandNameSlug}_jwt`) {
|
|
186
|
+
authCookie = cookies[i].value;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const authRes = yield this.adminforth.auth.verify(authCookie, 'auth', true);
|
|
190
|
+
if ('error' in authRes) {
|
|
191
|
+
return { error: authRes.error, authRes: null };
|
|
192
|
+
}
|
|
193
|
+
return { error: null, authRes: authRes };
|
|
194
|
+
});
|
|
195
|
+
this.createRecord = (resource, diffData, adminUser) => __awaiter(this, void 0, void 0, function* () {
|
|
196
|
+
const connector = this.adminforth.connectors[resource.dataSource];
|
|
197
|
+
//@ts-ignore
|
|
198
|
+
const err = this.adminforth.validateRecordValues(resource, diffData['newRecord'], 'create');
|
|
199
|
+
if (err) {
|
|
200
|
+
return { ok: false, error: err, createdRecord: null };
|
|
201
|
+
}
|
|
202
|
+
// remove virtual columns from record
|
|
203
|
+
for (const column of resource.columns.filter((col) => col.virtual)) {
|
|
204
|
+
if (diffData['newRecord'][column.name]) {
|
|
205
|
+
delete diffData['newRecord'][column.name];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return yield connector.createRecord({ resource, record: diffData['newRecord'], adminUser });
|
|
209
|
+
});
|
|
210
|
+
this.editRecord = (resource, diffData, targetRecordId, connector) => __awaiter(this, void 0, void 0, function* () {
|
|
211
|
+
let oldRecord;
|
|
212
|
+
const dataToUse = diffData['newRecord'];
|
|
213
|
+
//@ts-ignore
|
|
214
|
+
const err = this.adminforth.validateRecordValues(resource, dataToUse, 'edit', targetRecordId);
|
|
215
|
+
if (err) {
|
|
216
|
+
return { error: err };
|
|
217
|
+
}
|
|
218
|
+
// remove editReadonly columns from record
|
|
219
|
+
oldRecord = yield connector.getRecordByPrimaryKey(resource, targetRecordId);
|
|
220
|
+
for (const column of resource.columns.filter((col) => col.editReadonly)) {
|
|
221
|
+
if (column.name in dataToUse)
|
|
222
|
+
delete dataToUse[column.name];
|
|
223
|
+
}
|
|
224
|
+
const newValues = {};
|
|
225
|
+
for (const recordField in dataToUse) {
|
|
226
|
+
if (dataToUse[recordField] !== oldRecord[recordField]) {
|
|
227
|
+
// leave only changed fields to reduce data transfer/modifications in db
|
|
228
|
+
const column = resource.columns.find((col) => col.name === recordField);
|
|
229
|
+
if (!column || !column.virtual) {
|
|
230
|
+
// exclude virtual columns
|
|
231
|
+
newValues[recordField] = dataToUse[recordField];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (Object.keys(newValues).length > 0) {
|
|
236
|
+
const { error } = yield connector.updateRecord({ resource, recordId: targetRecordId, newValues });
|
|
237
|
+
if (error) {
|
|
238
|
+
return { ok: false, error };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return { ok: true, error: null };
|
|
242
|
+
});
|
|
243
|
+
this.deleteRecord = (resource, targetRecordId, connector) => __awaiter(this, void 0, void 0, function* () {
|
|
244
|
+
return yield connector.deleteRecord({ resource, recordId: targetRecordId });
|
|
245
|
+
});
|
|
15
246
|
this.options = options;
|
|
16
247
|
}
|
|
17
248
|
modifyResourceConfig(adminforth, resourceConfig) {
|
|
@@ -19,25 +250,31 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
19
250
|
modifyResourceConfig: { get: () => super.modifyResourceConfig }
|
|
20
251
|
});
|
|
21
252
|
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
-
_super.modifyResourceConfig.call(this, adminforth, resourceConfig);
|
|
23
253
|
// simply modify resourceConfig or adminforth.config. You can get access to plugin options via this.options;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
254
|
+
_super.modifyResourceConfig.call(this, adminforth, resourceConfig);
|
|
255
|
+
this.adminforth = adminforth;
|
|
256
|
+
this.diffResource = this.resourceConfig;
|
|
257
|
+
let diffColumn = resourceConfig.columns.find((c) => c.name === this.options.resourceColumns.dataColumnName);
|
|
258
|
+
if (!diffColumn) {
|
|
259
|
+
throw new Error(`Column ${this.options.resourceColumns.dataColumnName} not found in ${resourceConfig.label}`);
|
|
260
|
+
}
|
|
261
|
+
if (diffColumn.type !== AdminForthDataTypes.JSON) {
|
|
262
|
+
// throw new Error(`Column ${this.options.resourceColumns.dataColumnName} must be of type 'json'`)
|
|
263
|
+
}
|
|
264
|
+
diffColumn.components = {
|
|
265
|
+
show: {
|
|
266
|
+
file: this.componentPath('ShowPageDiffView.vue'),
|
|
267
|
+
meta: Object.assign(Object.assign({}, this.options), { pluginInstanceId: this.pluginInstanceId })
|
|
268
|
+
},
|
|
269
|
+
list: {
|
|
270
|
+
file: this.componentPath('ListPageDiffView.vue'),
|
|
271
|
+
meta: Object.assign(Object.assign({}, this.options), { pluginInstanceId: this.pluginInstanceId })
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
resourceConfig.options.defaultSort = {
|
|
275
|
+
columnName: this.options.resourceColumns.createdAtColumnName,
|
|
276
|
+
direction: AdminForthSortDirections.desc
|
|
38
277
|
};
|
|
39
|
-
const approvalResource = this.adminforth.config.resources.find(r => r.resourceId === this.options.diffTableName);
|
|
40
|
-
yield this.adminforth.createResourceRecord({ resource: approvalResource, record, adminUser: user });
|
|
41
278
|
});
|
|
42
279
|
}
|
|
43
280
|
validateConfigAfterDiscover(adminforth, resourceConfig) {
|
|
@@ -48,4 +285,119 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
48
285
|
// Needed if plugin can have multiple instances on one resource
|
|
49
286
|
return `single`;
|
|
50
287
|
}
|
|
288
|
+
setupEndpoints(server) {
|
|
289
|
+
server.endpoint({
|
|
290
|
+
method: 'POST',
|
|
291
|
+
path: `/plugin/crud-approve/update-status`,
|
|
292
|
+
noAuth: true,
|
|
293
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response, cookies }) {
|
|
294
|
+
const authRes = yield this.verifyAuth(cookies);
|
|
295
|
+
if (authRes.error) {
|
|
296
|
+
//@ts-ignore
|
|
297
|
+
response.status = 403;
|
|
298
|
+
return { error: authRes.error };
|
|
299
|
+
}
|
|
300
|
+
const adminUser = authRes.authRes;
|
|
301
|
+
const { resourceId, diffId, recordId, action, approved, code } = body;
|
|
302
|
+
const diffRecord = yield this.adminforth.resource(this.diffResource.resourceId).get(Filters.EQ(this.options.resourceColumns.idColumnName, diffId));
|
|
303
|
+
if (!diffRecord) {
|
|
304
|
+
//@ts-ignore
|
|
305
|
+
response.status = 404;
|
|
306
|
+
return { error: 'Diff record not found' };
|
|
307
|
+
}
|
|
308
|
+
if (diffRecord[this.options.resourceColumns.statusColumnName] !== ApprovalStatusEnum.pending) {
|
|
309
|
+
//@ts-ignore
|
|
310
|
+
response.status = 400;
|
|
311
|
+
return { error: 'Diff record is not pending' };
|
|
312
|
+
}
|
|
313
|
+
let beforeSaveResp;
|
|
314
|
+
if (approved === true) {
|
|
315
|
+
const resource = this.adminforth.config.resources.find((res) => res.resourceId == diffRecord[this.options.resourceColumns.resourceIdColumnName]);
|
|
316
|
+
const diffData = diffRecord[this.options.resourceColumns.dataColumnName];
|
|
317
|
+
const extra = diffRecord[this.options.resourceColumns.extraColumnName] || {};
|
|
318
|
+
extra.body = body;
|
|
319
|
+
extra.cookies = cookies;
|
|
320
|
+
let oldRecord = undefined;
|
|
321
|
+
if (action !== AllowedActionsEnum.create) {
|
|
322
|
+
oldRecord = yield this.adminforth.connectors[resource.dataSource].getRecordByPrimaryKey(resource, diffRecord[this.options.resourceColumns.recordIdColumnName]);
|
|
323
|
+
}
|
|
324
|
+
beforeSaveResp = yield this.callBeforeSaveHooks(resource, action, diffData['newRecord'], adminUser, diffRecord[this.options.resourceColumns.recordIdColumnName], diffData['newRecord'], oldRecord, this.adminforth, extra);
|
|
325
|
+
if (beforeSaveResp.error && beforeSaveResp.error !== 'Operation aborted by hook') {
|
|
326
|
+
//@ts-ignore
|
|
327
|
+
response.status = 500;
|
|
328
|
+
return { error: `Failed to apply approved changes: ${beforeSaveResp.error}` };
|
|
329
|
+
}
|
|
330
|
+
if (beforeSaveResp.error !== 'Operation aborted by hook') {
|
|
331
|
+
let recordUpdateResult;
|
|
332
|
+
const connector = this.adminforth.connectors[resource.dataSource];
|
|
333
|
+
if (action === AllowedActionsEnum.create) {
|
|
334
|
+
recordUpdateResult = yield this.createRecord(resource, diffData, adminUser);
|
|
335
|
+
}
|
|
336
|
+
else if (action === AllowedActionsEnum.edit) {
|
|
337
|
+
recordUpdateResult = yield this.editRecord(resource, diffData, diffRecord[this.options.resourceColumns.recordIdColumnName], connector);
|
|
338
|
+
}
|
|
339
|
+
else if (action === AllowedActionsEnum.delete) {
|
|
340
|
+
recordUpdateResult = yield this.deleteRecord(resource, diffRecord[this.options.resourceColumns.recordIdColumnName], connector);
|
|
341
|
+
}
|
|
342
|
+
if (recordUpdateResult === null || recordUpdateResult === void 0 ? void 0 : recordUpdateResult.error) {
|
|
343
|
+
//@ts-ignore
|
|
344
|
+
response.status = 500;
|
|
345
|
+
console.error('Error applying approved changes:', recordUpdateResult);
|
|
346
|
+
return { error: `Failed to apply approved changes: ${recordUpdateResult.error}` };
|
|
347
|
+
}
|
|
348
|
+
let afterSaveResp;
|
|
349
|
+
if (action === AllowedActionsEnum.create) {
|
|
350
|
+
const newRecord = recordUpdateResult.createdRecord;
|
|
351
|
+
afterSaveResp = yield this.callAfterSaveHooks(resource, action, newRecord, adminUser, diffRecord[this.options.resourceColumns.recordIdColumnName],
|
|
352
|
+
//@ts-ignore
|
|
353
|
+
newRecord, {}, this.adminforth, { body });
|
|
354
|
+
}
|
|
355
|
+
else if (action === AllowedActionsEnum.edit) {
|
|
356
|
+
const newRecord = diffData['newRecord'];
|
|
357
|
+
const oldRecord = yield this.adminforth.connectors[resource.dataSource].getRecordByPrimaryKey(resource, diffRecord[this.options.resourceColumns.recordIdColumnName]);
|
|
358
|
+
afterSaveResp = yield this.callAfterSaveHooks(resource, action, newRecord, adminUser,
|
|
359
|
+
//@ts-ignore
|
|
360
|
+
recordId, newRecord, oldRecord, this.adminforth, { body });
|
|
361
|
+
}
|
|
362
|
+
else if (action === AllowedActionsEnum.delete) {
|
|
363
|
+
const newRecord = diffData['newRecord'];
|
|
364
|
+
afterSaveResp = yield this.callAfterSaveHooks(resource, action, newRecord, adminUser, diffRecord[this.options.resourceColumns.recordIdColumnName],
|
|
365
|
+
//@ts-ignore
|
|
366
|
+
{}, diffData['oldRecord'], this.adminforth, { body });
|
|
367
|
+
}
|
|
368
|
+
if (afterSaveResp === null || afterSaveResp === void 0 ? void 0 : afterSaveResp.error) {
|
|
369
|
+
//@ts-ignore
|
|
370
|
+
response.status = 500;
|
|
371
|
+
return { error: `Failed to apply approved changes: ${afterSaveResp.error}` };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const r = yield this.adminforth.updateResourceRecord({
|
|
376
|
+
resource: this.diffResource, recordId: diffId,
|
|
377
|
+
adminUser: adminUser, oldRecord: diffRecord,
|
|
378
|
+
updates: {
|
|
379
|
+
[this.options.resourceColumns.statusColumnName]: approved ? ApprovalStatusEnum.approved : ApprovalStatusEnum.rejected,
|
|
380
|
+
[this.options.resourceColumns.responserIdColumnName]: authRes.authRes.pk,
|
|
381
|
+
},
|
|
382
|
+
extra: {
|
|
383
|
+
//@ts-ignore
|
|
384
|
+
adminforth_plugin_crud_approve: {
|
|
385
|
+
//@ts-ignore
|
|
386
|
+
callingFromApprovalPlugin: true
|
|
387
|
+
//@ts-ignore
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
if (r.error) {
|
|
392
|
+
//@ts-ignore
|
|
393
|
+
response.status = 500;
|
|
394
|
+
return { error: `Failed to update diff record status: ${r.error}` };
|
|
395
|
+
}
|
|
396
|
+
if ((beforeSaveResp === null || beforeSaveResp === void 0 ? void 0 : beforeSaveResp.error) === 'Operation aborted by hook') {
|
|
397
|
+
return beforeSaveResp;
|
|
398
|
+
}
|
|
399
|
+
return { ok: true };
|
|
400
|
+
})
|
|
401
|
+
});
|
|
402
|
+
}
|
|
51
403
|
}
|
package/dist/types.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
export var AllowedForReviewActionsEnum;
|
|
2
|
+
(function (AllowedForReviewActionsEnum) {
|
|
3
|
+
AllowedForReviewActionsEnum["create"] = "create";
|
|
4
|
+
AllowedForReviewActionsEnum["edit"] = "edit";
|
|
5
|
+
AllowedForReviewActionsEnum["delete"] = "delete";
|
|
6
|
+
AllowedForReviewActionsEnum["custom"] = "custom";
|
|
7
|
+
})(AllowedForReviewActionsEnum || (AllowedForReviewActionsEnum = {}));
|
|
1
8
|
export var ApprovalStatusEnum;
|
|
2
9
|
(function (ApprovalStatusEnum) {
|
|
3
|
-
ApprovalStatusEnum["
|
|
4
|
-
ApprovalStatusEnum["
|
|
5
|
-
ApprovalStatusEnum["
|
|
10
|
+
ApprovalStatusEnum[ApprovalStatusEnum["pending"] = 1] = "pending";
|
|
11
|
+
ApprovalStatusEnum[ApprovalStatusEnum["approved"] = 2] = "approved";
|
|
12
|
+
ApprovalStatusEnum[ApprovalStatusEnum["rejected"] = 3] = "rejected";
|
|
6
13
|
})(ApprovalStatusEnum || (ApprovalStatusEnum = {}));
|
package/index.ts
CHANGED
|
@@ -58,6 +58,7 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
58
58
|
{resource, action, data, user, oldRecord, updates, extra, record}:
|
|
59
59
|
{resource: AdminForthResource, action: AllowedForReviewActionsEnum, data: Object, user: AdminUser, record?: Object, oldRecord?: Object, updates?: Object, extra?: HttpExtra}
|
|
60
60
|
) => {
|
|
61
|
+
//@ts-ignore
|
|
61
62
|
if (extra && extra.adminforth_plugin_crud_approve && extra.adminforth_plugin_crud_approve.callingFromApprovalPlugin) {
|
|
62
63
|
return { ok: true, error: 'Approval request creation aborted to avoid infinite loop' };
|
|
63
64
|
}
|
|
@@ -168,8 +169,11 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
168
169
|
if (extra === undefined) {
|
|
169
170
|
extra = {};
|
|
170
171
|
}
|
|
172
|
+
//@ts-ignore
|
|
171
173
|
extra.adminforth_plugin_crud_approve = {
|
|
174
|
+
//@ts-ignore
|
|
172
175
|
callingFromApprovalPlugin: true
|
|
176
|
+
//@ts-ignore
|
|
173
177
|
}
|
|
174
178
|
for (const hook of hooks) {
|
|
175
179
|
const resp = await hook({
|
|
@@ -263,6 +267,7 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
263
267
|
|
|
264
268
|
createRecord = async (resource: AdminForthResource, diffData: any, adminUser: AdminUser) => {
|
|
265
269
|
const connector = this.adminforth.connectors[resource.dataSource];
|
|
270
|
+
//@ts-ignore
|
|
266
271
|
const err = this.adminforth.validateRecordValues(resource, diffData['newRecord'], 'create');
|
|
267
272
|
if (err) {
|
|
268
273
|
return { ok: false, error: err, createdRecord: null };
|
|
@@ -280,6 +285,7 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
280
285
|
editRecord = async (resource: AdminForthResource, diffData: any, targetRecordId: any, connector: IAdminForthDataSourceConnectorBase) => {
|
|
281
286
|
let oldRecord;
|
|
282
287
|
const dataToUse = diffData['newRecord'];
|
|
288
|
+
//@ts-ignore
|
|
283
289
|
const err = this.adminforth.validateRecordValues(resource, dataToUse, 'edit', targetRecordId);
|
|
284
290
|
if (err) {
|
|
285
291
|
return { error: err };
|
|
@@ -323,6 +329,7 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
323
329
|
handler: async ({ body, response, cookies }) => {
|
|
324
330
|
const authRes = await this.verifyAuth(cookies);
|
|
325
331
|
if (authRes.error) {
|
|
332
|
+
//@ts-ignore
|
|
326
333
|
response.status = 403;
|
|
327
334
|
return { error: authRes.error };
|
|
328
335
|
}
|
|
@@ -333,11 +340,13 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
333
340
|
Filters.EQ(this.options.resourceColumns.idColumnName, diffId),
|
|
334
341
|
)
|
|
335
342
|
if (!diffRecord) {
|
|
343
|
+
//@ts-ignore
|
|
336
344
|
response.status = 404;
|
|
337
345
|
return { error: 'Diff record not found' };
|
|
338
346
|
}
|
|
339
347
|
|
|
340
348
|
if (diffRecord[this.options.resourceColumns.statusColumnName] !== ApprovalStatusEnum.pending) {
|
|
349
|
+
//@ts-ignore
|
|
341
350
|
response.status = 400;
|
|
342
351
|
return { error: 'Diff record is not pending' };
|
|
343
352
|
}
|
|
@@ -362,6 +371,7 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
362
371
|
diffData['newRecord'], oldRecord, this.adminforth, extra
|
|
363
372
|
);
|
|
364
373
|
if (beforeSaveResp.error && beforeSaveResp.error !== 'Operation aborted by hook') {
|
|
374
|
+
//@ts-ignore
|
|
365
375
|
response.status = 500;
|
|
366
376
|
return { error: `Failed to apply approved changes: ${beforeSaveResp.error}` };
|
|
367
377
|
}
|
|
@@ -382,6 +392,7 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
382
392
|
);
|
|
383
393
|
}
|
|
384
394
|
if (recordUpdateResult?.error) {
|
|
395
|
+
//@ts-ignore
|
|
385
396
|
response.status = 500;
|
|
386
397
|
console.error('Error applying approved changes:', recordUpdateResult);
|
|
387
398
|
return { error: `Failed to apply approved changes: ${recordUpdateResult.error}` };
|
|
@@ -393,6 +404,7 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
393
404
|
afterSaveResp = await this.callAfterSaveHooks(
|
|
394
405
|
resource, action as AllowedActionsEnum, newRecord, adminUser,
|
|
395
406
|
diffRecord[this.options.resourceColumns.recordIdColumnName],
|
|
407
|
+
//@ts-ignore
|
|
396
408
|
newRecord, {}, this.adminforth, { body }
|
|
397
409
|
);
|
|
398
410
|
} else if (action === AllowedActionsEnum.edit) {
|
|
@@ -402,6 +414,7 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
402
414
|
);
|
|
403
415
|
afterSaveResp = await this.callAfterSaveHooks(
|
|
404
416
|
resource, action as AllowedActionsEnum, newRecord, adminUser,
|
|
417
|
+
//@ts-ignore
|
|
405
418
|
recordId, newRecord, oldRecord, this.adminforth, { body }
|
|
406
419
|
);
|
|
407
420
|
} else if (action === AllowedActionsEnum.delete) {
|
|
@@ -409,11 +422,13 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
409
422
|
afterSaveResp = await this.callAfterSaveHooks(
|
|
410
423
|
resource, action as AllowedActionsEnum, newRecord, adminUser,
|
|
411
424
|
diffRecord[this.options.resourceColumns.recordIdColumnName],
|
|
425
|
+
//@ts-ignore
|
|
412
426
|
{}, diffData['oldRecord'], this.adminforth, { body }
|
|
413
427
|
);
|
|
414
428
|
}
|
|
415
429
|
|
|
416
430
|
if (afterSaveResp?.error) {
|
|
431
|
+
//@ts-ignore
|
|
417
432
|
response.status = 500;
|
|
418
433
|
return { error: `Failed to apply approved changes: ${afterSaveResp.error}` };
|
|
419
434
|
}
|
|
@@ -427,12 +442,16 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
427
442
|
[this.options.resourceColumns.responserIdColumnName]: authRes.authRes.pk,
|
|
428
443
|
},
|
|
429
444
|
extra: {
|
|
445
|
+
//@ts-ignore
|
|
430
446
|
adminforth_plugin_crud_approve: {
|
|
447
|
+
//@ts-ignore
|
|
431
448
|
callingFromApprovalPlugin: true
|
|
449
|
+
//@ts-ignore
|
|
432
450
|
}
|
|
433
451
|
}
|
|
434
452
|
});
|
|
435
453
|
if (r.error) {
|
|
454
|
+
//@ts-ignore
|
|
436
455
|
response.status = 500;
|
|
437
456
|
return { error: `Failed to update diff record status: ${r.error}` };
|
|
438
457
|
}
|
|
@@ -443,4 +462,4 @@ export default class CRUDApprovePlugin extends AdminForthPlugin {
|
|
|
443
462
|
}
|
|
444
463
|
})
|
|
445
464
|
}
|
|
446
|
-
}
|
|
465
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/crud-approve-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -14,18 +14,16 @@
|
|
|
14
14
|
"url": "https://github.com/devforth/af-crud-approve-plugin.git"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
|
|
18
|
-
"prepare": "npm link adminforth"
|
|
17
|
+
"build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
|
|
19
18
|
},
|
|
20
19
|
"author": "devforth",
|
|
21
20
|
"license": "ISC",
|
|
22
21
|
"dependencies": {
|
|
23
22
|
"@adminforth/two-factors-auth": "^2.10.4",
|
|
24
|
-
"adminforth": "next",
|
|
25
23
|
"dayjs": "^1.11.11"
|
|
26
24
|
},
|
|
27
25
|
"peerDependencies": {
|
|
28
|
-
"adminforth": "
|
|
26
|
+
"adminforth": "^3.1.2"
|
|
29
27
|
},
|
|
30
28
|
"release": {
|
|
31
29
|
"plugins": [
|
|
@@ -36,6 +34,7 @@
|
|
|
36
34
|
[
|
|
37
35
|
"semantic-release-slack-bot",
|
|
38
36
|
{
|
|
37
|
+
"packageName": "@adminforth/crud-approve-plugin",
|
|
39
38
|
"notifyOnSuccess": true,
|
|
40
39
|
"notifyOnFail": true,
|
|
41
40
|
"slackIcon": ":package:",
|
|
@@ -53,6 +52,7 @@
|
|
|
53
52
|
},
|
|
54
53
|
"devDependencies": {
|
|
55
54
|
"@types/node": "^22.10.7",
|
|
55
|
+
"adminforth": "^3.1.2",
|
|
56
56
|
"semantic-release": "^24.2.1",
|
|
57
57
|
"semantic-release-slack-bot": "^4.0.2",
|
|
58
58
|
"typescript": "^5.7.3"
|