@drax/ai-back 3.43.0 → 3.45.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/dist/controllers/TTSVoiceController.js +18 -0
- package/dist/factory/services/TTSVoiceServiceFactory.js +28 -0
- package/dist/index.js +14 -4
- package/dist/interfaces/ITTSVoice.js +1 -0
- package/dist/interfaces/ITTSVoiceRepository.js +1 -0
- package/dist/models/TTSVoiceModel.js +25 -0
- package/dist/permissions/TTSVoicePermissions.js +10 -0
- package/dist/repository/mongo/TTSVoiceMongoRepository.js +13 -0
- package/dist/repository/sqlite/TTSVoiceSqliteRepository.js +27 -0
- package/dist/routes/TTSVoiceRoutes.js +21 -0
- package/dist/schemas/TTSVoiceSchema.js +19 -0
- package/dist/services/TTSVoiceService.js +9 -0
- package/dist/tools/BuildContextTool.js +267 -0
- package/package.json +3 -3
- package/src/controllers/TTSVoiceController.ts +27 -0
- package/src/factory/services/TTSVoiceServiceFactory.ts +36 -0
- package/src/index.ts +30 -0
- package/src/interfaces/ITTSVoice.ts +20 -0
- package/src/interfaces/ITTSVoiceRepository.ts +8 -0
- package/src/models/TTSVoiceModel.ts +37 -0
- package/src/permissions/TTSVoicePermissions.ts +12 -0
- package/src/repository/mongo/TTSVoiceMongoRepository.ts +19 -0
- package/src/repository/sqlite/TTSVoiceSqliteRepository.ts +33 -0
- package/src/routes/TTSVoiceRoutes.ts +26 -0
- package/src/schemas/TTSVoiceSchema.ts +23 -0
- package/src/services/TTSVoiceService.ts +16 -0
- package/src/tools/BuildContextTool.ts +388 -0
- package/test/BuildContextTool.test.ts +183 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/types/controllers/TTSVoiceController.d.ts +8 -0
- package/types/controllers/TTSVoiceController.d.ts.map +1 -0
- package/types/factory/services/TTSVoiceServiceFactory.d.ts +8 -0
- package/types/factory/services/TTSVoiceServiceFactory.d.ts.map +1 -0
- package/types/index.d.ts +14 -2
- package/types/index.d.ts.map +1 -1
- package/types/interfaces/ITTSVoice.d.ts +16 -0
- package/types/interfaces/ITTSVoice.d.ts.map +1 -0
- package/types/interfaces/ITTSVoiceRepository.d.ts +6 -0
- package/types/interfaces/ITTSVoiceRepository.d.ts.map +1 -0
- package/types/models/TTSVoiceModel.d.ts +15 -0
- package/types/models/TTSVoiceModel.d.ts.map +1 -0
- package/types/permissions/TTSVoicePermissions.d.ts +10 -0
- package/types/permissions/TTSVoicePermissions.d.ts.map +1 -0
- package/types/repository/mongo/TTSVoiceMongoRepository.d.ts +9 -0
- package/types/repository/mongo/TTSVoiceMongoRepository.d.ts.map +1 -0
- package/types/repository/sqlite/TTSVoiceSqliteRepository.d.ts +23 -0
- package/types/repository/sqlite/TTSVoiceSqliteRepository.d.ts.map +1 -0
- package/types/routes/TTSVoiceRoutes.d.ts +4 -0
- package/types/routes/TTSVoiceRoutes.d.ts.map +1 -0
- package/types/schemas/TTSVoiceSchema.d.ts +36 -0
- package/types/schemas/TTSVoiceSchema.d.ts.map +1 -0
- package/types/services/TTSVoiceService.d.ts +10 -0
- package/types/services/TTSVoiceService.d.ts.map +1 -0
- package/types/tools/BuildContextTool.d.ts +33 -0
- package/types/tools/BuildContextTool.d.ts.map +1 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import {setNestedValue, UnauthorizedError} from "@drax/common-back";
|
|
2
|
+
import type {IDraxFieldFilter, IDraxPermission} from "@drax/crud-share";
|
|
3
|
+
import {BuilderTool} from "./BuilderTool.js";
|
|
4
|
+
import type {DraxAgentPromptContext} from "../interfaces/IDraxAgent.js";
|
|
5
|
+
import type {ToolBuilderOptions, ToolBuilderService} from "../interfaces/IBuilderTool.js";
|
|
6
|
+
|
|
7
|
+
interface BuildContextToolContext {
|
|
8
|
+
userId?: string | null;
|
|
9
|
+
tenantId?: string | null;
|
|
10
|
+
permissions?: string[];
|
|
11
|
+
hasSomePermission?: (permissions: string[]) => boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface BuildContextToolOptions<T = any, C = any, U = any> extends ToolBuilderOptions<T, C, U> {
|
|
15
|
+
context?: BuildContextToolContext;
|
|
16
|
+
permission?: Partial<IDraxPermission>;
|
|
17
|
+
tenantField?: string;
|
|
18
|
+
userField?: string;
|
|
19
|
+
tenantFilter?: boolean;
|
|
20
|
+
tenantSetter?: boolean;
|
|
21
|
+
tenantAssert?: boolean;
|
|
22
|
+
userFilter?: boolean;
|
|
23
|
+
userSetter?: boolean;
|
|
24
|
+
userAssert?: boolean;
|
|
25
|
+
defaultLimit?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type BuildContextToolOptionsInput<T = any, C = any, U = any> =
|
|
29
|
+
Omit<BuildContextToolOptions<T, C, U>, "context">;
|
|
30
|
+
|
|
31
|
+
class BuildContextTool<T = any, C = any, U = any> extends BuilderTool<T, C, U> {
|
|
32
|
+
constructor(options: BuildContextToolOptions<T, C, U>) {
|
|
33
|
+
super({
|
|
34
|
+
...options,
|
|
35
|
+
service: BuildContextTool.createContextService(options),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static fromPromptContext<T = any, C = any, U = any>(
|
|
40
|
+
options: BuildContextToolOptionsInput<T, C, U>,
|
|
41
|
+
promptContext: DraxAgentPromptContext,
|
|
42
|
+
): BuildContextTool<T, C, U> {
|
|
43
|
+
return new BuildContextTool({
|
|
44
|
+
...options,
|
|
45
|
+
context: {
|
|
46
|
+
userId: promptContext.input?.userId ?? promptContext.session.userId ?? null,
|
|
47
|
+
tenantId: promptContext.input?.tenantId ?? promptContext.session.tenantId ?? null,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected static createContextService<T = any, C = any, U = any>(
|
|
53
|
+
options: BuildContextToolOptions<T, C, U>,
|
|
54
|
+
): ToolBuilderService<T, C, U> {
|
|
55
|
+
const service = options.service;
|
|
56
|
+
const helper = new BuildContextToolServiceHelper(options);
|
|
57
|
+
const contextService: ToolBuilderService<T, C, U> = {...service};
|
|
58
|
+
|
|
59
|
+
if (typeof service.create === "function") {
|
|
60
|
+
contextService.create = async (data: any) => service.create?.(helper.applySetters(helper.clonePayload(data)));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (typeof service.update === "function") {
|
|
64
|
+
contextService.update = async (id: string, data: any) => {
|
|
65
|
+
const preItem = await service.findById?.(id);
|
|
66
|
+
helper.assertWritable(preItem, "update");
|
|
67
|
+
const payload = helper.removeSetterFields(helper.clonePayload(data));
|
|
68
|
+
return service.update?.(id, payload);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof service.updatePartial === "function") {
|
|
73
|
+
contextService.updatePartial = async (id: string, data: any) => {
|
|
74
|
+
const preItem = await service.findById?.(id);
|
|
75
|
+
helper.assertWritable(preItem, "update");
|
|
76
|
+
const payload = helper.removeSetterFields(helper.clonePayload(data));
|
|
77
|
+
return service.updatePartial?.(id, payload);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (typeof service.delete === "function") {
|
|
82
|
+
contextService.delete = async (id: string) => {
|
|
83
|
+
const item = await service.findById?.(id);
|
|
84
|
+
helper.assertWritable(item, "delete");
|
|
85
|
+
return service.delete?.(id);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (typeof service.findById === "function") {
|
|
90
|
+
contextService.findById = async (id: string) => {
|
|
91
|
+
const item = await service.findById?.(id);
|
|
92
|
+
helper.assertReadable(item);
|
|
93
|
+
return item;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof service.findByIds === "function") {
|
|
98
|
+
contextService.findByIds = async (ids: string[]) => {
|
|
99
|
+
const items = await service.findByIds?.(ids);
|
|
100
|
+
helper.assertReadableItems(items);
|
|
101
|
+
return items;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (typeof service.findOneBy === "function") {
|
|
106
|
+
contextService.findOneBy = async (field: string, value: any, filters: IDraxFieldFilter[] = []) =>
|
|
107
|
+
service.findOneBy?.(field, value, helper.applyReadFilters(filters));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (typeof service.findOne === "function") {
|
|
111
|
+
contextService.findOne = async (findOptions: any) =>
|
|
112
|
+
service.findOne?.({
|
|
113
|
+
...findOptions,
|
|
114
|
+
filters: helper.applyReadFilters(findOptions?.filters ?? []),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (typeof service.findBy === "function") {
|
|
119
|
+
contextService.findBy = async (field: string, value: any, limit: number = helper.defaultLimit, filters: IDraxFieldFilter[] = []) =>
|
|
120
|
+
service.findBy?.(field, value, limit, helper.applyReadFilters(filters));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (typeof service.fetchAll === "function") {
|
|
124
|
+
contextService.fetchAll = async () => {
|
|
125
|
+
const filters = helper.applyReadFilters([]);
|
|
126
|
+
|
|
127
|
+
if (filters.length > 0 && typeof service.find === "function") {
|
|
128
|
+
return service.find({
|
|
129
|
+
search: "",
|
|
130
|
+
filters,
|
|
131
|
+
order: false,
|
|
132
|
+
orderBy: "",
|
|
133
|
+
limit: helper.defaultLimit,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return service.fetchAll?.();
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (typeof service.findFirst === "function") {
|
|
142
|
+
contextService.findFirst = async (quantity: number, filters: IDraxFieldFilter[] = []) =>
|
|
143
|
+
service.findFirst?.(quantity, helper.applyReadFilters(filters));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (typeof service.findLast === "function") {
|
|
147
|
+
contextService.findLast = async (quantity: number, filters: IDraxFieldFilter[] = []) =>
|
|
148
|
+
service.findLast?.(quantity, helper.applyReadFilters(filters));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (typeof service.search === "function") {
|
|
152
|
+
contextService.search = async (value: string, limit: number = helper.defaultLimit, filters: IDraxFieldFilter[] = []) =>
|
|
153
|
+
service.search?.(value, limit, helper.applyReadFilters(filters));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (typeof service.find === "function") {
|
|
157
|
+
contextService.find = async (findOptions: any) =>
|
|
158
|
+
service.find?.({
|
|
159
|
+
...findOptions,
|
|
160
|
+
filters: helper.applyReadFilters(findOptions?.filters ?? []),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (typeof service.paginate === "function") {
|
|
165
|
+
contextService.paginate = async (paginateOptions: any) =>
|
|
166
|
+
service.paginate?.({
|
|
167
|
+
...paginateOptions,
|
|
168
|
+
filters: helper.applyReadFilters(paginateOptions?.filters ?? []),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (typeof service.groupBy === "function") {
|
|
173
|
+
contextService.groupBy = async (groupByOptions: any) =>
|
|
174
|
+
service.groupBy?.({
|
|
175
|
+
...groupByOptions,
|
|
176
|
+
filters: helper.applyReadFilters(groupByOptions?.filters ?? []),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return contextService;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
class BuildContextToolServiceHelper {
|
|
185
|
+
readonly defaultLimit: number;
|
|
186
|
+
|
|
187
|
+
private readonly context: BuildContextToolContext;
|
|
188
|
+
private readonly permission?: Partial<IDraxPermission>;
|
|
189
|
+
private readonly tenantField: string;
|
|
190
|
+
private readonly userField: string;
|
|
191
|
+
private readonly tenantFilter: boolean;
|
|
192
|
+
private readonly tenantSetter: boolean;
|
|
193
|
+
private readonly tenantAssert: boolean;
|
|
194
|
+
private readonly userFilter: boolean;
|
|
195
|
+
private readonly userSetter: boolean;
|
|
196
|
+
private readonly userAssert: boolean;
|
|
197
|
+
|
|
198
|
+
constructor(options: BuildContextToolOptions) {
|
|
199
|
+
this.context = options.context ?? {};
|
|
200
|
+
this.permission = options.permission;
|
|
201
|
+
this.tenantField = options.tenantField ?? "tenant";
|
|
202
|
+
this.userField = options.userField ?? "user";
|
|
203
|
+
this.tenantFilter = options.tenantFilter ?? false;
|
|
204
|
+
this.tenantSetter = options.tenantSetter ?? false;
|
|
205
|
+
this.tenantAssert = options.tenantAssert ?? false;
|
|
206
|
+
this.userFilter = options.userFilter ?? false;
|
|
207
|
+
this.userSetter = options.userSetter ?? false;
|
|
208
|
+
this.userAssert = options.userAssert ?? false;
|
|
209
|
+
this.defaultLimit = options.defaultLimit ?? 1000;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
clonePayload<TPayload>(payload: TPayload): TPayload {
|
|
213
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
214
|
+
return payload;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {...payload};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
applySetters<TPayload>(payload: TPayload): TPayload {
|
|
221
|
+
if (!payload || typeof payload !== "object") {
|
|
222
|
+
return payload;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (this.tenantSetter && this.context.tenantId) {
|
|
226
|
+
setNestedValue(payload as object, this.tenantField, this.context.tenantId);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (this.userSetter && this.context.userId) {
|
|
230
|
+
setNestedValue(payload as object, this.userField, this.context.userId);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return payload;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
removeSetterFields<TPayload>(payload: TPayload): TPayload {
|
|
237
|
+
if (!payload || typeof payload !== "object") {
|
|
238
|
+
return payload;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (this.tenantSetter) {
|
|
242
|
+
this.deleteNestedValue(payload as Record<string, any>, this.tenantField);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (this.userSetter) {
|
|
246
|
+
this.deleteNestedValue(payload as Record<string, any>, this.userField);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return payload;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
applyReadFilters(filters: IDraxFieldFilter[] = []): IDraxFieldFilter[] {
|
|
253
|
+
const nextFilters = [...filters];
|
|
254
|
+
|
|
255
|
+
if (this.tenantFilter && this.context.tenantId) {
|
|
256
|
+
nextFilters.push({field: this.tenantField, operator: "eq", value: this.context.tenantId});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (
|
|
260
|
+
this.userFilter &&
|
|
261
|
+
this.context.userId &&
|
|
262
|
+
!this.hasSomePermission([this.permission?.All, this.permission?.ViewAll])
|
|
263
|
+
) {
|
|
264
|
+
nextFilters.push({field: this.userField, operator: "eq", value: this.context.userId});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return nextFilters;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
assertReadable(item: any): void {
|
|
271
|
+
if (this.hasSomePermission([this.permission?.All, this.permission?.ViewAll])) {
|
|
272
|
+
this.assertTenant(item);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
this.assertTenant(item);
|
|
277
|
+
this.assertUser(item);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
assertReadableItems(items: any): void {
|
|
281
|
+
if (!Array.isArray(items)) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
items.forEach(item => this.assertReadable(item));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
assertWritable(item: any, operation: "update" | "delete"): void {
|
|
289
|
+
const allPermission = this.permission?.All;
|
|
290
|
+
const operationAllPermission = operation === "update"
|
|
291
|
+
? this.permission?.UpdateAll
|
|
292
|
+
: this.permission?.DeleteAll;
|
|
293
|
+
|
|
294
|
+
if (!this.hasSomePermission([allPermission, operationAllPermission])) {
|
|
295
|
+
this.assertUser(item);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.assertTenant(item);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private assertTenant(item: any): void {
|
|
302
|
+
if (!this.tenantAssert || !this.context.tenantId) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const tenantId = this.resolveItemFieldId(item, this.tenantField);
|
|
307
|
+
if (tenantId) {
|
|
308
|
+
this.assertId(this.context.tenantId, tenantId);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private assertUser(item: any): void {
|
|
313
|
+
if (!this.userAssert || !this.context.userId) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const userId = this.resolveItemFieldId(item, this.userField);
|
|
318
|
+
if (userId) {
|
|
319
|
+
this.assertId(this.context.userId, userId);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private assertId(expectedId: string, actualId: string): void {
|
|
324
|
+
if (expectedId !== actualId) {
|
|
325
|
+
throw new UnauthorizedError();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private hasSomePermission(permissions: Array<string | undefined>): boolean {
|
|
330
|
+
const requiredPermissions = permissions.filter((permission): permission is string => !!permission);
|
|
331
|
+
|
|
332
|
+
if (requiredPermissions.length === 0) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (this.context.hasSomePermission?.(requiredPermissions)) {
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return requiredPermissions.some(permission => this.context.permissions?.includes(permission));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private resolveItemFieldId(item: any, field: string): string | null {
|
|
344
|
+
const value = this.getNestedValue(item, field);
|
|
345
|
+
return this.stringifyRelationId(value);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private getNestedValue(item: any, path: string): any {
|
|
349
|
+
return path.split(".").reduce((value, key) => value?.[key], item);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private stringifyRelationId(value: any): string | null {
|
|
353
|
+
if (value === null || value === undefined || value === "") {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const id = value?._id ?? value?.id ?? value;
|
|
358
|
+
|
|
359
|
+
if (typeof id === "string" || typeof id === "number" || typeof id === "boolean") {
|
|
360
|
+
return String(id);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (typeof id?.toString === "function") {
|
|
364
|
+
const stringId = id.toString();
|
|
365
|
+
return stringId && stringId !== "[object Object]" ? stringId : null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private deleteNestedValue(payload: Record<string, any>, path: string): void {
|
|
372
|
+
const keys = path.split(".");
|
|
373
|
+
const lastKey = keys.pop();
|
|
374
|
+
|
|
375
|
+
if (!lastKey) {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const parent = keys.reduce((value, key) => value?.[key], payload);
|
|
380
|
+
if (parent && typeof parent === "object") {
|
|
381
|
+
delete parent[lastKey];
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export default BuildContextTool;
|
|
387
|
+
export {BuildContextTool};
|
|
388
|
+
export type {BuildContextToolContext, BuildContextToolOptions};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {describe, expect, test} from "vitest";
|
|
2
|
+
import {z} from "zod";
|
|
3
|
+
import {BuildContextTool} from "../src/tools/BuildContextTool.js";
|
|
4
|
+
|
|
5
|
+
describe("BuildContextTool", () => {
|
|
6
|
+
const PersonBaseSchema = z.object({
|
|
7
|
+
fullname: z.string().min(1),
|
|
8
|
+
tenant: z.string().optional(),
|
|
9
|
+
user: z.string().optional(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("sets user and tenant context on create payloads", async () => {
|
|
13
|
+
const calls: any[] = [];
|
|
14
|
+
const service: any = {
|
|
15
|
+
async create(data: any) {
|
|
16
|
+
calls.push(data);
|
|
17
|
+
return data;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const builder = new BuildContextTool({
|
|
22
|
+
entityName: "person",
|
|
23
|
+
schema: PersonBaseSchema,
|
|
24
|
+
service,
|
|
25
|
+
methods: ["create"],
|
|
26
|
+
context: {
|
|
27
|
+
userId: "user-1",
|
|
28
|
+
tenantId: "tenant-1",
|
|
29
|
+
},
|
|
30
|
+
userSetter: true,
|
|
31
|
+
tenantSetter: true,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const [tool] = builder.getTools();
|
|
35
|
+
await expect(tool.execute({data: {fullname: "Ada Lovelace"}})).resolves.toEqual({
|
|
36
|
+
fullname: "Ada Lovelace",
|
|
37
|
+
tenant: "tenant-1",
|
|
38
|
+
user: "user-1",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(calls).toEqual([{
|
|
42
|
+
fullname: "Ada Lovelace",
|
|
43
|
+
tenant: "tenant-1",
|
|
44
|
+
user: "user-1",
|
|
45
|
+
}]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("adds user and tenant filters to read operations", async () => {
|
|
49
|
+
const calls: any[] = [];
|
|
50
|
+
const service: any = {
|
|
51
|
+
async search(value: string, limit: number, filters: any[]) {
|
|
52
|
+
calls.push(["search", value, limit, filters]);
|
|
53
|
+
return [];
|
|
54
|
+
},
|
|
55
|
+
async paginate(options: any) {
|
|
56
|
+
calls.push(["paginate", options]);
|
|
57
|
+
return {items: [], total: 0};
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const builder = new BuildContextTool({
|
|
62
|
+
entityName: "person",
|
|
63
|
+
schema: PersonBaseSchema,
|
|
64
|
+
service,
|
|
65
|
+
methods: ["search", "paginate"],
|
|
66
|
+
context: {
|
|
67
|
+
userId: "user-1",
|
|
68
|
+
tenantId: "tenant-1",
|
|
69
|
+
},
|
|
70
|
+
userFilter: true,
|
|
71
|
+
tenantFilter: true,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const [searchTool, paginateTool] = builder.getTools();
|
|
75
|
+
|
|
76
|
+
await searchTool.execute({
|
|
77
|
+
value: "Ada",
|
|
78
|
+
filters: [{field: "active", operator: "eq", value: true}],
|
|
79
|
+
});
|
|
80
|
+
await paginateTool.execute({page: 1, limit: 5});
|
|
81
|
+
|
|
82
|
+
expect(calls).toEqual([
|
|
83
|
+
["search", "Ada", 1000, [
|
|
84
|
+
{field: "active", operator: "eq", value: true},
|
|
85
|
+
{field: "tenant", operator: "eq", value: "tenant-1"},
|
|
86
|
+
{field: "user", operator: "eq", value: "user-1"},
|
|
87
|
+
]],
|
|
88
|
+
["paginate", {
|
|
89
|
+
page: 1,
|
|
90
|
+
limit: 5,
|
|
91
|
+
orderBy: undefined,
|
|
92
|
+
order: "asc",
|
|
93
|
+
search: "",
|
|
94
|
+
filters: [
|
|
95
|
+
{field: "tenant", operator: "eq", value: "tenant-1"},
|
|
96
|
+
{field: "user", operator: "eq", value: "user-1"},
|
|
97
|
+
],
|
|
98
|
+
}],
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("asserts tenant and user ownership on item operations", async () => {
|
|
103
|
+
const service: any = {
|
|
104
|
+
async findById(id: string) {
|
|
105
|
+
return {_id: id, fullname: "Grace Hopper", tenant: "tenant-2", user: "user-1"};
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const builder = new BuildContextTool({
|
|
110
|
+
entityName: "person",
|
|
111
|
+
schema: PersonBaseSchema,
|
|
112
|
+
service,
|
|
113
|
+
methods: ["findById"],
|
|
114
|
+
context: {
|
|
115
|
+
userId: "user-1",
|
|
116
|
+
tenantId: "tenant-1",
|
|
117
|
+
},
|
|
118
|
+
userAssert: true,
|
|
119
|
+
tenantAssert: true,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const [tool] = builder.getTools();
|
|
123
|
+
|
|
124
|
+
await expect(tool.execute({id: "123"})).rejects.toMatchObject({
|
|
125
|
+
name: "UnauthorizedError",
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("removes ownership fields on updates when setters are enabled", async () => {
|
|
130
|
+
const calls: any[] = [];
|
|
131
|
+
const service: any = {
|
|
132
|
+
async findById(id: string) {
|
|
133
|
+
return {_id: id, tenant: "tenant-1", user: "user-1"};
|
|
134
|
+
},
|
|
135
|
+
async updatePartial(id: string, data: any) {
|
|
136
|
+
calls.push([id, data]);
|
|
137
|
+
return {_id: id, ...data};
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const builder = new BuildContextTool({
|
|
142
|
+
entityName: "person",
|
|
143
|
+
schema: PersonBaseSchema,
|
|
144
|
+
service,
|
|
145
|
+
methods: ["updatePartial"],
|
|
146
|
+
context: {
|
|
147
|
+
userId: "user-1",
|
|
148
|
+
tenantId: "tenant-1",
|
|
149
|
+
},
|
|
150
|
+
userSetter: true,
|
|
151
|
+
tenantSetter: true,
|
|
152
|
+
userAssert: true,
|
|
153
|
+
tenantAssert: true,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const [tool] = builder.getTools();
|
|
157
|
+
await tool.execute({
|
|
158
|
+
id: "123",
|
|
159
|
+
data: {
|
|
160
|
+
fullname: "Ada",
|
|
161
|
+
tenant: "tenant-2",
|
|
162
|
+
user: "user-2",
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(calls).toEqual([["123", {fullname: "Ada"}]]);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("fails when a requested service method is not available", () => {
|
|
170
|
+
const builder = new BuildContextTool({
|
|
171
|
+
entityName: "person",
|
|
172
|
+
schema: PersonBaseSchema,
|
|
173
|
+
service: {} as any,
|
|
174
|
+
methods: ["delete"],
|
|
175
|
+
context: {
|
|
176
|
+
userId: "user-1",
|
|
177
|
+
tenantId: "tenant-1",
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(() => builder.getTools()).toThrow("Tool method not available on service: delete");
|
|
182
|
+
});
|
|
183
|
+
});
|