@oneuptime/common 7.0.4349 → 7.0.4372
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/Models/AnalyticsModels/ExceptionInstance.ts +2 -2
- package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.ts +2 -2
- package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.ts +2 -2
- package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.ts +2 -2
- package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +2 -2
- package/Models/DatabaseModels/Probe.ts +7 -1
- package/Models/DatabaseModels/ServiceCatalog.ts +2 -2
- package/Models/DatabaseModels/ServiceCopilotCodeRepository.ts +2 -2
- package/Models/DatabaseModels/StatusPage.ts +37 -0
- package/Models/DatabaseModels/StatusPageSubscriber.ts +60 -0
- package/Server/API/StatusPageAPI.ts +104 -10
- package/Server/Infrastructure/Postgres/SchemaMigrations/1749065784320-MigrationName.ts +23 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1749133333893-MigrationName.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/ScheduledMaintenanceService.ts +21 -0
- package/Server/Services/StatusPageSubscriberService.ts +116 -1
- package/Server/Utils/OpenAPI.ts +738 -11
- package/Server/Utils/Workspace/Slack/Slack.ts +14 -0
- package/Utils/Schema/AnalyticsModelSchema.ts +764 -0
- package/Utils/Schema/BaseSchema.ts +450 -0
- package/Utils/Schema/ModelSchema.ts +1099 -38
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +2 -2
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.js +2 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.js +2 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.js +2 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +2 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Probe.js +7 -1
- package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceCatalog.js +2 -2
- package/build/dist/Models/DatabaseModels/ServiceCatalog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceCopilotCodeRepository.js +2 -2
- package/build/dist/Models/DatabaseModels/ServiceCopilotCodeRepository.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +39 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +62 -0
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +73 -10
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749065784320-MigrationName.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749065784320-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749133333893-MigrationName.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1749133333893-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +19 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +98 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Utils/OpenAPI.js +578 -11
- package/build/dist/Server/Utils/OpenAPI.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +15 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Utils/Schema/AnalyticsModelSchema.js +636 -0
- package/build/dist/Utils/Schema/AnalyticsModelSchema.js.map +1 -0
- package/build/dist/Utils/Schema/BaseSchema.js +295 -0
- package/build/dist/Utils/Schema/BaseSchema.js.map +1 -0
- package/build/dist/Utils/Schema/ModelSchema.js +940 -27
- package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,13 +6,62 @@ import {
|
|
|
6
6
|
} from "../../Types/Database/TableColumn";
|
|
7
7
|
import Dictionary from "../../Types/Dictionary";
|
|
8
8
|
import DatabaseBaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
9
|
-
import SortOrder from "../../Types/BaseDatabase/SortOrder";
|
|
10
9
|
import logger from "../../Server/Utils/Logger";
|
|
11
10
|
import Color from "../../Types/Color";
|
|
11
|
+
import { z as ZodTypes } from "zod";
|
|
12
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
13
|
+
import Permission, { PermissionHelper } from "../../Types/Permission";
|
|
14
|
+
import { ColumnAccessControl } from "../../Types/BaseDatabase/AccessControl";
|
|
15
|
+
import { BaseSchema, SchemaExample, ShapeRecord } from "./BaseSchema";
|
|
12
16
|
|
|
13
17
|
export type ModelSchemaType = ZodSchema;
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
// Type for schema method functions
|
|
20
|
+
type SchemaMethodFunction = (data: {
|
|
21
|
+
modelType: new () => DatabaseBaseModel;
|
|
22
|
+
}) => ModelSchemaType;
|
|
23
|
+
|
|
24
|
+
export class ModelSchema extends BaseSchema {
|
|
25
|
+
/**
|
|
26
|
+
* Format permissions array into a human-readable string for OpenAPI documentation
|
|
27
|
+
*/
|
|
28
|
+
private static formatPermissionsForSchema(
|
|
29
|
+
permissions: Array<Permission> | undefined,
|
|
30
|
+
): string {
|
|
31
|
+
if (!permissions || permissions.length === 0) {
|
|
32
|
+
return "No access - you don't have permission for this operation";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return PermissionHelper.getPermissionTitles(permissions).join(", ");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get permissions description for a column to add to OpenAPI schema
|
|
40
|
+
*/
|
|
41
|
+
private static getColumnPermissionsDescription(
|
|
42
|
+
model: DatabaseBaseModel,
|
|
43
|
+
key: string,
|
|
44
|
+
): string {
|
|
45
|
+
const accessControl: ColumnAccessControl | undefined =
|
|
46
|
+
model.getColumnAccessControlForAllColumns()[key];
|
|
47
|
+
|
|
48
|
+
if (!accessControl) {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const createPermissions: string = this.formatPermissionsForSchema(
|
|
53
|
+
accessControl.create,
|
|
54
|
+
);
|
|
55
|
+
const readPermissions: string = this.formatPermissionsForSchema(
|
|
56
|
+
accessControl.read,
|
|
57
|
+
);
|
|
58
|
+
const updatePermissions: string = this.formatPermissionsForSchema(
|
|
59
|
+
accessControl.update,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return `Permissions - Create: [${createPermissions}], Read: [${readPermissions}], Update: [${updatePermissions}]`;
|
|
63
|
+
}
|
|
64
|
+
|
|
16
65
|
public static getModelSchema(data: {
|
|
17
66
|
modelType: new () => DatabaseBaseModel;
|
|
18
67
|
}): ModelSchemaType {
|
|
@@ -21,14 +70,32 @@ export class ModelSchema {
|
|
|
21
70
|
|
|
22
71
|
const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
|
|
23
72
|
|
|
24
|
-
const shape:
|
|
73
|
+
const shape: ShapeRecord = {};
|
|
74
|
+
|
|
75
|
+
// Get column access control for permission filtering
|
|
76
|
+
const columnAccessControl: Dictionary<ColumnAccessControl> =
|
|
77
|
+
model.getColumnAccessControlForAllColumns();
|
|
25
78
|
|
|
26
79
|
for (const key in columns) {
|
|
27
80
|
const column: TableColumnMetadata | undefined = columns[key];
|
|
28
81
|
if (!column) {
|
|
29
82
|
continue;
|
|
30
83
|
}
|
|
31
|
-
|
|
84
|
+
|
|
85
|
+
// Filter out columns with no permissions (root-only access)
|
|
86
|
+
const accessControl: ColumnAccessControl | undefined =
|
|
87
|
+
columnAccessControl[key];
|
|
88
|
+
if (accessControl) {
|
|
89
|
+
// Check if column has any permissions defined for read operation (general schema assumes read access)
|
|
90
|
+
const hasReadPermissions: boolean =
|
|
91
|
+
accessControl.read && accessControl.read.length > 0;
|
|
92
|
+
|
|
93
|
+
// If no read permissions are defined, exclude the column from general schema
|
|
94
|
+
if (!hasReadPermissions) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
let zodType: ZodTypes.ZodTypeAny;
|
|
32
99
|
|
|
33
100
|
if (column.type === TableColumnType.ObjectID) {
|
|
34
101
|
zodType = z.string().openapi({
|
|
@@ -268,6 +335,13 @@ export class ModelSchema {
|
|
|
268
335
|
zodType = zodType.describe(column.title);
|
|
269
336
|
}
|
|
270
337
|
|
|
338
|
+
// Add permissions description to the schema
|
|
339
|
+
const permissionsDescription: string =
|
|
340
|
+
this.getColumnPermissionsDescription(model, key);
|
|
341
|
+
if (permissionsDescription) {
|
|
342
|
+
zodType = zodType.describe(permissionsDescription);
|
|
343
|
+
}
|
|
344
|
+
|
|
271
345
|
shape[key] = zodType;
|
|
272
346
|
}
|
|
273
347
|
|
|
@@ -304,20 +378,240 @@ export class ModelSchema {
|
|
|
304
378
|
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
305
379
|
const model: DatabaseBaseModel = new modelType();
|
|
306
380
|
|
|
307
|
-
|
|
381
|
+
return this.generateQuerySchema({
|
|
382
|
+
model,
|
|
383
|
+
tableName: model.tableName || "model",
|
|
384
|
+
getColumns: (model: DatabaseBaseModel) => {
|
|
385
|
+
const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
|
|
386
|
+
return Object.keys(columns)
|
|
387
|
+
.map((key: string) => {
|
|
388
|
+
const column: TableColumnMetadata | undefined = columns[key];
|
|
389
|
+
return column ? { key, type: column.type } : null;
|
|
390
|
+
})
|
|
391
|
+
.filter((col: { key: string; type: any } | null) => {
|
|
392
|
+
return col !== null;
|
|
393
|
+
}) as Array<{ key: string; type: any }>;
|
|
394
|
+
},
|
|
395
|
+
getValidOperatorsForColumnType: (columnType: TableColumnType) => {
|
|
396
|
+
return this.getValidOperatorsForColumnType(columnType);
|
|
397
|
+
},
|
|
398
|
+
getOperatorSchema: (
|
|
399
|
+
operatorType: string,
|
|
400
|
+
columnType: TableColumnType,
|
|
401
|
+
) => {
|
|
402
|
+
return this.getOperatorSchema(operatorType, columnType);
|
|
403
|
+
},
|
|
404
|
+
getQuerySchemaExample: () => {
|
|
405
|
+
return this.getQuerySchemaExample(modelType);
|
|
406
|
+
},
|
|
407
|
+
getExampleValueForColumn: (columnType: TableColumnType) => {
|
|
408
|
+
return this.getExampleValueForColumn(columnType);
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
}
|
|
308
412
|
|
|
309
|
-
|
|
413
|
+
private static getValidOperatorsForColumnType(
|
|
414
|
+
columnType: TableColumnType,
|
|
415
|
+
): Array<string> {
|
|
416
|
+
const commonOperators: Array<string> = [
|
|
417
|
+
"EqualTo",
|
|
418
|
+
"NotEqual",
|
|
419
|
+
"IsNull",
|
|
420
|
+
"NotNull",
|
|
421
|
+
"EqualToOrNull",
|
|
422
|
+
];
|
|
310
423
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
424
|
+
switch (columnType) {
|
|
425
|
+
case TableColumnType.ObjectID:
|
|
426
|
+
case TableColumnType.Email:
|
|
427
|
+
case TableColumnType.Phone:
|
|
428
|
+
case TableColumnType.HashedString:
|
|
429
|
+
case TableColumnType.Slug:
|
|
430
|
+
return [...commonOperators, "Includes"];
|
|
431
|
+
|
|
432
|
+
case TableColumnType.ShortText:
|
|
433
|
+
case TableColumnType.LongText:
|
|
434
|
+
case TableColumnType.VeryLongText:
|
|
435
|
+
case TableColumnType.Name:
|
|
436
|
+
case TableColumnType.Description:
|
|
437
|
+
case TableColumnType.Domain:
|
|
438
|
+
case TableColumnType.Markdown:
|
|
439
|
+
return [...commonOperators, "Search", "Includes"];
|
|
316
440
|
|
|
317
|
-
|
|
441
|
+
case TableColumnType.Number:
|
|
442
|
+
case TableColumnType.PositiveNumber:
|
|
443
|
+
case TableColumnType.SmallNumber:
|
|
444
|
+
case TableColumnType.SmallPositiveNumber:
|
|
445
|
+
case TableColumnType.BigNumber:
|
|
446
|
+
case TableColumnType.BigPositiveNumber:
|
|
447
|
+
return [
|
|
448
|
+
...commonOperators,
|
|
449
|
+
"GreaterThan",
|
|
450
|
+
"LessThan",
|
|
451
|
+
"GreaterThanOrEqual",
|
|
452
|
+
"LessThanOrEqual",
|
|
453
|
+
"InBetween",
|
|
454
|
+
];
|
|
455
|
+
|
|
456
|
+
case TableColumnType.Date:
|
|
457
|
+
return [
|
|
458
|
+
...commonOperators,
|
|
459
|
+
"GreaterThan",
|
|
460
|
+
"LessThan",
|
|
461
|
+
"GreaterThanOrEqual",
|
|
462
|
+
"LessThanOrEqual",
|
|
463
|
+
"InBetween",
|
|
464
|
+
];
|
|
465
|
+
|
|
466
|
+
case TableColumnType.Boolean:
|
|
467
|
+
return commonOperators;
|
|
468
|
+
|
|
469
|
+
case TableColumnType.JSON:
|
|
470
|
+
case TableColumnType.Array:
|
|
471
|
+
return ["EqualTo", "NotEqual", "IsNull", "NotNull"];
|
|
472
|
+
|
|
473
|
+
case TableColumnType.Entity:
|
|
474
|
+
case TableColumnType.EntityArray:
|
|
475
|
+
return ["EqualTo", "NotEqual", "IsNull", "NotNull", "Includes"];
|
|
476
|
+
|
|
477
|
+
case TableColumnType.Color:
|
|
478
|
+
case TableColumnType.File:
|
|
479
|
+
case TableColumnType.Buffer:
|
|
480
|
+
return commonOperators;
|
|
481
|
+
|
|
482
|
+
default:
|
|
483
|
+
return commonOperators;
|
|
318
484
|
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
private static getOperatorSchema(
|
|
488
|
+
operatorType: string,
|
|
489
|
+
columnType: TableColumnType,
|
|
490
|
+
): ZodTypes.ZodTypeAny {
|
|
491
|
+
const baseValue: ZodTypes.ZodTypeAny =
|
|
492
|
+
this.getBaseValueSchemaForColumnType(columnType);
|
|
493
|
+
|
|
494
|
+
switch (operatorType) {
|
|
495
|
+
case "EqualTo":
|
|
496
|
+
case "NotEqual":
|
|
497
|
+
case "EqualToOrNull":
|
|
498
|
+
return z.object({
|
|
499
|
+
_type: z.literal(operatorType),
|
|
500
|
+
value: baseValue,
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
case "GreaterThan":
|
|
504
|
+
case "LessThan":
|
|
505
|
+
case "GreaterThanOrEqual":
|
|
506
|
+
case "LessThanOrEqual":
|
|
507
|
+
return z.object({
|
|
508
|
+
_type: z.literal(operatorType),
|
|
509
|
+
value: baseValue,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
case "Search":
|
|
513
|
+
return z.object({
|
|
514
|
+
_type: z.literal("Search"),
|
|
515
|
+
value: z.string(),
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
case "InBetween":
|
|
519
|
+
return z.object({
|
|
520
|
+
_type: z.literal("InBetween"),
|
|
521
|
+
startValue: baseValue,
|
|
522
|
+
endValue: baseValue,
|
|
523
|
+
});
|
|
319
524
|
|
|
320
|
-
|
|
525
|
+
case "IsNull":
|
|
526
|
+
case "NotNull":
|
|
527
|
+
return z.object({
|
|
528
|
+
_type: z.literal(operatorType),
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
case "Includes":
|
|
532
|
+
return z.object({
|
|
533
|
+
_type: z.literal("Includes"),
|
|
534
|
+
value: z.array(baseValue),
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
default:
|
|
538
|
+
return z.object({
|
|
539
|
+
_type: z.literal(operatorType),
|
|
540
|
+
value: baseValue,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
private static getBaseValueSchemaForColumnType(
|
|
546
|
+
columnType: TableColumnType,
|
|
547
|
+
): ZodTypes.ZodTypeAny {
|
|
548
|
+
switch (columnType) {
|
|
549
|
+
case TableColumnType.ObjectID:
|
|
550
|
+
return z.string();
|
|
551
|
+
|
|
552
|
+
case TableColumnType.Email:
|
|
553
|
+
return z.string().email();
|
|
554
|
+
|
|
555
|
+
case TableColumnType.Phone:
|
|
556
|
+
case TableColumnType.HashedString:
|
|
557
|
+
case TableColumnType.Slug:
|
|
558
|
+
case TableColumnType.ShortText:
|
|
559
|
+
case TableColumnType.LongText:
|
|
560
|
+
case TableColumnType.VeryLongText:
|
|
561
|
+
case TableColumnType.Name:
|
|
562
|
+
case TableColumnType.Description:
|
|
563
|
+
case TableColumnType.Domain:
|
|
564
|
+
case TableColumnType.Markdown:
|
|
565
|
+
case TableColumnType.HTML:
|
|
566
|
+
case TableColumnType.JavaScript:
|
|
567
|
+
case TableColumnType.CSS:
|
|
568
|
+
case TableColumnType.LongURL:
|
|
569
|
+
case TableColumnType.ShortURL:
|
|
570
|
+
case TableColumnType.OTP:
|
|
571
|
+
case TableColumnType.Password:
|
|
572
|
+
case TableColumnType.Version:
|
|
573
|
+
return z.string();
|
|
574
|
+
|
|
575
|
+
case TableColumnType.Number:
|
|
576
|
+
case TableColumnType.PositiveNumber:
|
|
577
|
+
case TableColumnType.SmallNumber:
|
|
578
|
+
case TableColumnType.SmallPositiveNumber:
|
|
579
|
+
case TableColumnType.BigNumber:
|
|
580
|
+
case TableColumnType.BigPositiveNumber:
|
|
581
|
+
return z.number();
|
|
582
|
+
|
|
583
|
+
case TableColumnType.Date:
|
|
584
|
+
return z.date();
|
|
585
|
+
|
|
586
|
+
case TableColumnType.Boolean:
|
|
587
|
+
return z.boolean();
|
|
588
|
+
|
|
589
|
+
case TableColumnType.JSON:
|
|
590
|
+
case TableColumnType.Array:
|
|
591
|
+
case TableColumnType.Permission:
|
|
592
|
+
case TableColumnType.CustomFieldType:
|
|
593
|
+
return z.any();
|
|
594
|
+
|
|
595
|
+
case TableColumnType.Color:
|
|
596
|
+
return Color.getSchema();
|
|
597
|
+
|
|
598
|
+
case TableColumnType.Entity:
|
|
599
|
+
return z.string(); // Entity IDs are typically strings
|
|
600
|
+
|
|
601
|
+
case TableColumnType.EntityArray:
|
|
602
|
+
return z.array(z.string()); // Array of entity IDs
|
|
603
|
+
|
|
604
|
+
case TableColumnType.File:
|
|
605
|
+
case TableColumnType.Buffer:
|
|
606
|
+
return z.any();
|
|
607
|
+
|
|
608
|
+
case TableColumnType.MonitorType:
|
|
609
|
+
case TableColumnType.WorkflowStatus:
|
|
610
|
+
return z.string();
|
|
611
|
+
|
|
612
|
+
default:
|
|
613
|
+
return z.string();
|
|
614
|
+
}
|
|
321
615
|
}
|
|
322
616
|
|
|
323
617
|
public static getSortModelSchema(data: {
|
|
@@ -326,42 +620,375 @@ export class ModelSchema {
|
|
|
326
620
|
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
327
621
|
const model: DatabaseBaseModel = new modelType();
|
|
328
622
|
|
|
623
|
+
return this.generateSortSchema({
|
|
624
|
+
model,
|
|
625
|
+
tableName: model.tableName || "model",
|
|
626
|
+
getSortableTypes: () => {
|
|
627
|
+
return this.getSortableTypes();
|
|
628
|
+
},
|
|
629
|
+
getColumnsForSorting: (model: DatabaseBaseModel) => {
|
|
630
|
+
const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
|
|
631
|
+
return Object.keys(columns)
|
|
632
|
+
.map((key: string) => {
|
|
633
|
+
const column: TableColumnMetadata | undefined = columns[key];
|
|
634
|
+
return column ? { key, type: column.type } : null;
|
|
635
|
+
})
|
|
636
|
+
.filter((col: { key: string; type: any } | null) => {
|
|
637
|
+
return col !== null;
|
|
638
|
+
}) as Array<{ key: string; type: any }>;
|
|
639
|
+
},
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
public static getSelectModelSchema(data: {
|
|
644
|
+
modelType: new () => DatabaseBaseModel;
|
|
645
|
+
isNested?: boolean;
|
|
646
|
+
}): ModelSchemaType {
|
|
647
|
+
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
648
|
+
const model: DatabaseBaseModel = new modelType();
|
|
649
|
+
|
|
650
|
+
return this.generateSelectSchema({
|
|
651
|
+
model,
|
|
652
|
+
tableName: model.tableName || "model",
|
|
653
|
+
getColumns: (model: DatabaseBaseModel) => {
|
|
654
|
+
const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
|
|
655
|
+
return Object.keys(columns)
|
|
656
|
+
.map((key: string) => {
|
|
657
|
+
const column: TableColumnMetadata | undefined = columns[key];
|
|
658
|
+
return column ? { key, type: column.type } : null;
|
|
659
|
+
})
|
|
660
|
+
.filter((col: { key: string; type: any } | null) => {
|
|
661
|
+
return col !== null;
|
|
662
|
+
}) as Array<{ key: string; type?: any }>;
|
|
663
|
+
},
|
|
664
|
+
getSelectSchemaExample: () => {
|
|
665
|
+
return this.getSelectSchemaExample(modelType);
|
|
666
|
+
},
|
|
667
|
+
allowNested: !data.isNested,
|
|
668
|
+
getNestedSchema: (key: string, model: DatabaseBaseModel) => {
|
|
669
|
+
const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
|
|
670
|
+
const column: TableColumnMetadata | undefined = columns[key];
|
|
671
|
+
if (
|
|
672
|
+
column &&
|
|
673
|
+
column.modelType &&
|
|
674
|
+
(column.type === TableColumnType.EntityArray ||
|
|
675
|
+
column.type === TableColumnType.Entity)
|
|
676
|
+
) {
|
|
677
|
+
return this.getSelectModelSchema({
|
|
678
|
+
modelType: column.modelType as new () => DatabaseBaseModel,
|
|
679
|
+
isNested: true,
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
return null;
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
public static getGroupByModelSchema(data: {
|
|
688
|
+
modelType: new () => DatabaseBaseModel;
|
|
689
|
+
}): ModelSchemaType {
|
|
690
|
+
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
691
|
+
const model: DatabaseBaseModel = new modelType();
|
|
692
|
+
|
|
693
|
+
return this.generateGroupBySchema({
|
|
694
|
+
model,
|
|
695
|
+
tableName: model.tableName || "model",
|
|
696
|
+
getColumns: (model: DatabaseBaseModel) => {
|
|
697
|
+
const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
|
|
698
|
+
return Object.keys(columns)
|
|
699
|
+
.map((key: string) => {
|
|
700
|
+
const column: TableColumnMetadata | undefined = columns[key];
|
|
701
|
+
return column ? { key, type: column.type } : null;
|
|
702
|
+
})
|
|
703
|
+
.filter((col: { key: string; type: any } | null) => {
|
|
704
|
+
return col !== null;
|
|
705
|
+
}) as Array<{ key: string; type: any }>;
|
|
706
|
+
},
|
|
707
|
+
getGroupableTypes: () => {
|
|
708
|
+
return [
|
|
709
|
+
TableColumnType.ShortText,
|
|
710
|
+
TableColumnType.LongText,
|
|
711
|
+
TableColumnType.Name,
|
|
712
|
+
TableColumnType.Email,
|
|
713
|
+
TableColumnType.Slug,
|
|
714
|
+
TableColumnType.ObjectID,
|
|
715
|
+
TableColumnType.Boolean,
|
|
716
|
+
TableColumnType.Date,
|
|
717
|
+
TableColumnType.Number,
|
|
718
|
+
TableColumnType.PositiveNumber,
|
|
719
|
+
TableColumnType.SmallNumber,
|
|
720
|
+
TableColumnType.SmallPositiveNumber,
|
|
721
|
+
TableColumnType.BigNumber,
|
|
722
|
+
TableColumnType.BigPositiveNumber,
|
|
723
|
+
];
|
|
724
|
+
},
|
|
725
|
+
getGroupBySchemaExample: () => {
|
|
726
|
+
return this.getGroupBySchemaExample(modelType);
|
|
727
|
+
},
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
private static getExampleValueForColumn(
|
|
732
|
+
columnType: TableColumnType,
|
|
733
|
+
isSecondValue: boolean = false,
|
|
734
|
+
): unknown {
|
|
735
|
+
switch (columnType) {
|
|
736
|
+
case TableColumnType.ObjectID:
|
|
737
|
+
return isSecondValue
|
|
738
|
+
? "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
|
|
739
|
+
: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa";
|
|
740
|
+
case TableColumnType.Email:
|
|
741
|
+
return isSecondValue ? "jane@example.com" : "john@example.com";
|
|
742
|
+
case TableColumnType.Phone:
|
|
743
|
+
return isSecondValue ? "+1-555-987-6543" : "+1-555-123-4567";
|
|
744
|
+
case TableColumnType.Number:
|
|
745
|
+
case TableColumnType.PositiveNumber:
|
|
746
|
+
return isSecondValue ? 100 : 50;
|
|
747
|
+
case TableColumnType.SmallNumber:
|
|
748
|
+
return isSecondValue ? 20 : 10;
|
|
749
|
+
case TableColumnType.SmallPositiveNumber:
|
|
750
|
+
return isSecondValue ? 25 : 15;
|
|
751
|
+
case TableColumnType.BigNumber:
|
|
752
|
+
return isSecondValue ? 2000000 : 1000000;
|
|
753
|
+
case TableColumnType.BigPositiveNumber:
|
|
754
|
+
return isSecondValue ? 2500000 : 1500000;
|
|
755
|
+
case TableColumnType.Date:
|
|
756
|
+
return isSecondValue
|
|
757
|
+
? "2023-12-31T23:59:59.000Z"
|
|
758
|
+
: "2023-01-01T00:00:00.000Z";
|
|
759
|
+
case TableColumnType.Boolean:
|
|
760
|
+
return !isSecondValue;
|
|
761
|
+
case TableColumnType.ShortText:
|
|
762
|
+
case TableColumnType.Name:
|
|
763
|
+
return isSecondValue ? "Jane Doe" : "John Doe";
|
|
764
|
+
case TableColumnType.Description:
|
|
765
|
+
return isSecondValue
|
|
766
|
+
? "Second example description"
|
|
767
|
+
: "Example description";
|
|
768
|
+
case TableColumnType.Domain:
|
|
769
|
+
return isSecondValue ? "example.org" : "example.com";
|
|
770
|
+
default:
|
|
771
|
+
return isSecondValue ? "example_value_2" : "example_value_1";
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
private static getQuerySchemaExample(
|
|
776
|
+
modelType: new () => DatabaseBaseModel,
|
|
777
|
+
): SchemaExample {
|
|
778
|
+
const model: DatabaseBaseModel = new modelType();
|
|
329
779
|
const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
|
|
780
|
+
const example: SchemaExample = {};
|
|
330
781
|
|
|
331
|
-
|
|
782
|
+
let exampleCount: number = 0;
|
|
783
|
+
const maxExamples: number = 3;
|
|
332
784
|
|
|
333
785
|
for (const key in columns) {
|
|
786
|
+
if (exampleCount >= maxExamples) {
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
|
|
334
790
|
const column: TableColumnMetadata | undefined = columns[key];
|
|
335
791
|
if (!column) {
|
|
336
792
|
continue;
|
|
337
793
|
}
|
|
338
794
|
|
|
339
|
-
const
|
|
795
|
+
const validOperators: Array<string> = this.getValidOperatorsForColumnType(
|
|
340
796
|
column.type,
|
|
341
797
|
);
|
|
798
|
+
if (validOperators.length === 0) {
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Add example based on column type and available operators
|
|
803
|
+
if (
|
|
804
|
+
column.type === TableColumnType.ShortText ||
|
|
805
|
+
column.type === TableColumnType.Name
|
|
806
|
+
) {
|
|
807
|
+
if (validOperators.includes("EqualTo")) {
|
|
808
|
+
example[key] = { _type: "EqualTo", value: "Example Text" };
|
|
809
|
+
exampleCount++;
|
|
810
|
+
} else if (validOperators.includes("Search")) {
|
|
811
|
+
example[key] = { _type: "Search", value: "example" };
|
|
812
|
+
exampleCount++;
|
|
813
|
+
}
|
|
814
|
+
} else if (
|
|
815
|
+
column.type === TableColumnType.Email &&
|
|
816
|
+
validOperators.includes("EqualTo")
|
|
817
|
+
) {
|
|
818
|
+
example[key] = { _type: "EqualTo", value: "user@example.com" };
|
|
819
|
+
exampleCount++;
|
|
820
|
+
} else if (
|
|
821
|
+
column.type === TableColumnType.Date &&
|
|
822
|
+
validOperators.includes("GreaterThan")
|
|
823
|
+
) {
|
|
824
|
+
example[key] = {
|
|
825
|
+
_type: "GreaterThan",
|
|
826
|
+
value: "2023-01-01T00:00:00.000Z",
|
|
827
|
+
};
|
|
828
|
+
exampleCount++;
|
|
829
|
+
} else if (
|
|
830
|
+
column.type === TableColumnType.Boolean &&
|
|
831
|
+
validOperators.includes("EqualTo")
|
|
832
|
+
) {
|
|
833
|
+
example[key] = { _type: "EqualTo", value: true };
|
|
834
|
+
exampleCount++;
|
|
835
|
+
} else if (
|
|
836
|
+
(column.type === TableColumnType.Number ||
|
|
837
|
+
column.type === TableColumnType.PositiveNumber) &&
|
|
838
|
+
validOperators.includes("GreaterThan")
|
|
839
|
+
) {
|
|
840
|
+
example[key] = { _type: "GreaterThan", value: 10 };
|
|
841
|
+
exampleCount++;
|
|
842
|
+
} else if (validOperators.includes("EqualTo")) {
|
|
843
|
+
example[key] = {
|
|
844
|
+
_type: "EqualTo",
|
|
845
|
+
value: this.getExampleValueForColumn(column.type),
|
|
846
|
+
};
|
|
847
|
+
exampleCount++;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// If no examples were added, add a generic one
|
|
852
|
+
if (exampleCount === 0) {
|
|
853
|
+
example["_id"] = {
|
|
854
|
+
_type: "EqualTo",
|
|
855
|
+
value: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
return example;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private static getSelectSchemaExample(
|
|
863
|
+
modelType: new () => DatabaseBaseModel,
|
|
864
|
+
): SchemaExample {
|
|
865
|
+
if (!modelType) {
|
|
866
|
+
throw new BadDataException(
|
|
867
|
+
"Model type is required to generate select schema example.",
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const model: DatabaseBaseModel = new modelType();
|
|
872
|
+
const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
|
|
873
|
+
const example: SchemaExample = {};
|
|
874
|
+
|
|
875
|
+
// Add common fields that most models have
|
|
876
|
+
const commonFields: Array<string> = ["_id", "createdAt", "updatedAt"];
|
|
877
|
+
for (const field of commonFields) {
|
|
878
|
+
if (columns[field]) {
|
|
879
|
+
example[field] = true;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Add first few non-common fields as examples
|
|
884
|
+
let fieldCount: number = 0;
|
|
885
|
+
const maxFields: number = 5;
|
|
886
|
+
for (const key in columns) {
|
|
887
|
+
if (fieldCount >= maxFields) {
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const column: TableColumnMetadata | undefined = columns[key];
|
|
892
|
+
if (!column || commonFields.includes(key)) {
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Prioritize fields that are likely to be commonly selected
|
|
897
|
+
if (
|
|
898
|
+
column.type === TableColumnType.ShortText ||
|
|
899
|
+
column.type === TableColumnType.Name ||
|
|
900
|
+
column.type === TableColumnType.Email ||
|
|
901
|
+
column.type === TableColumnType.Description
|
|
902
|
+
) {
|
|
903
|
+
example[key] = true;
|
|
904
|
+
fieldCount++;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
return example;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
private static getGroupBySchemaExample(
|
|
912
|
+
modelType: new () => DatabaseBaseModel,
|
|
913
|
+
): SchemaExample {
|
|
914
|
+
if (!modelType) {
|
|
915
|
+
throw new BadDataException(
|
|
916
|
+
"Model type is required to generate group by schema example.",
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const model: DatabaseBaseModel = new modelType();
|
|
921
|
+
const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
|
|
342
922
|
|
|
343
|
-
|
|
923
|
+
// Find the first suitable field for grouping
|
|
924
|
+
for (const key in columns) {
|
|
925
|
+
const column: TableColumnMetadata | undefined = columns[key];
|
|
926
|
+
if (!column) {
|
|
344
927
|
continue;
|
|
345
928
|
}
|
|
346
929
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
.
|
|
930
|
+
// Prioritize common groupable fields
|
|
931
|
+
if (
|
|
932
|
+
column.type === TableColumnType.ShortText ||
|
|
933
|
+
column.type === TableColumnType.Name ||
|
|
934
|
+
column.type === TableColumnType.Boolean ||
|
|
935
|
+
column.type === TableColumnType.Date
|
|
936
|
+
) {
|
|
937
|
+
return { [key]: true };
|
|
938
|
+
}
|
|
350
939
|
}
|
|
351
940
|
|
|
352
|
-
|
|
941
|
+
// Fallback to any available field
|
|
942
|
+
for (const key in columns) {
|
|
943
|
+
const column: TableColumnMetadata | undefined = columns[key];
|
|
944
|
+
if (!column) {
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const isGroupable: boolean = [
|
|
949
|
+
TableColumnType.ShortText,
|
|
950
|
+
TableColumnType.LongText,
|
|
951
|
+
TableColumnType.Name,
|
|
952
|
+
TableColumnType.Email,
|
|
953
|
+
TableColumnType.Slug,
|
|
954
|
+
TableColumnType.ObjectID,
|
|
955
|
+
TableColumnType.Boolean,
|
|
956
|
+
TableColumnType.Date,
|
|
957
|
+
TableColumnType.Number,
|
|
958
|
+
TableColumnType.PositiveNumber,
|
|
959
|
+
TableColumnType.SmallNumber,
|
|
960
|
+
TableColumnType.SmallPositiveNumber,
|
|
961
|
+
TableColumnType.BigNumber,
|
|
962
|
+
TableColumnType.BigPositiveNumber,
|
|
963
|
+
].includes(column.type);
|
|
964
|
+
|
|
965
|
+
if (isGroupable) {
|
|
966
|
+
return { [key]: true };
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Final fallback
|
|
971
|
+
return { status: true };
|
|
353
972
|
}
|
|
354
973
|
|
|
355
|
-
|
|
974
|
+
// Shared method to build model schemas with different field exclusions
|
|
975
|
+
private static buildModelSchema(data: {
|
|
356
976
|
modelType: new () => DatabaseBaseModel;
|
|
357
|
-
|
|
977
|
+
excludedFields?: string[];
|
|
978
|
+
includedFields?: string[];
|
|
979
|
+
schemaType: "create" | "read" | "update" | "delete";
|
|
980
|
+
description: string;
|
|
981
|
+
example: SchemaExample;
|
|
982
|
+
makeOptional?: boolean;
|
|
358
983
|
}): ModelSchemaType {
|
|
359
984
|
const modelType: new () => DatabaseBaseModel = data.modelType;
|
|
360
985
|
const model: DatabaseBaseModel = new modelType();
|
|
361
|
-
|
|
362
986
|
const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
|
|
987
|
+
const shape: ShapeRecord = {};
|
|
363
988
|
|
|
364
|
-
|
|
989
|
+
// Get column access control for permission filtering
|
|
990
|
+
const columnAccessControl: Dictionary<ColumnAccessControl> =
|
|
991
|
+
model.getColumnAccessControlForAllColumns();
|
|
365
992
|
|
|
366
993
|
for (const key in columns) {
|
|
367
994
|
const column: TableColumnMetadata | undefined = columns[key];
|
|
@@ -369,24 +996,458 @@ export class ModelSchema {
|
|
|
369
996
|
continue;
|
|
370
997
|
}
|
|
371
998
|
|
|
372
|
-
//
|
|
373
|
-
if (
|
|
374
|
-
!data.isNested &&
|
|
375
|
-
column.modelType &&
|
|
376
|
-
(column.type === TableColumnType.EntityArray ||
|
|
377
|
-
column.type === TableColumnType.Entity)
|
|
378
|
-
) {
|
|
379
|
-
// can only do one level of nesting
|
|
380
|
-
shape[key] = this.getSelectModelSchema({
|
|
381
|
-
modelType: column.modelType as new () => DatabaseBaseModel,
|
|
382
|
-
isNested: true,
|
|
383
|
-
});
|
|
999
|
+
// Skip excluded fields
|
|
1000
|
+
if (data.excludedFields && data.excludedFields.includes(key)) {
|
|
384
1001
|
continue;
|
|
385
1002
|
}
|
|
386
1003
|
|
|
387
|
-
|
|
1004
|
+
// Only include specified fields if includedFields is provided
|
|
1005
|
+
if (data.includedFields && !data.includedFields.includes(key)) {
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Filter out columns with no permissions (root-only access)
|
|
1010
|
+
const accessControl: ColumnAccessControl | undefined =
|
|
1011
|
+
columnAccessControl[key];
|
|
1012
|
+
if (accessControl) {
|
|
1013
|
+
let hasPermissions: boolean = false;
|
|
1014
|
+
|
|
1015
|
+
// Check if column has any permissions defined for the current operation
|
|
1016
|
+
if (
|
|
1017
|
+
data.schemaType === "create" &&
|
|
1018
|
+
accessControl.create &&
|
|
1019
|
+
accessControl.create.length > 0
|
|
1020
|
+
) {
|
|
1021
|
+
hasPermissions = true;
|
|
1022
|
+
} else if (
|
|
1023
|
+
data.schemaType === "read" &&
|
|
1024
|
+
accessControl.read &&
|
|
1025
|
+
accessControl.read.length > 0
|
|
1026
|
+
) {
|
|
1027
|
+
hasPermissions = true;
|
|
1028
|
+
} else if (
|
|
1029
|
+
data.schemaType === "update" &&
|
|
1030
|
+
accessControl.update &&
|
|
1031
|
+
accessControl.update.length > 0
|
|
1032
|
+
) {
|
|
1033
|
+
hasPermissions = true;
|
|
1034
|
+
} else if (data.schemaType === "delete") {
|
|
1035
|
+
// For delete operations, we don't filter by column permissions
|
|
1036
|
+
hasPermissions = true;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// If no permissions are defined for this operation, exclude the column
|
|
1040
|
+
if (!hasPermissions) {
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
let zodType: ZodTypes.ZodTypeAny = this.getZodTypeForColumn(
|
|
1046
|
+
column,
|
|
1047
|
+
key,
|
|
1048
|
+
data.schemaType,
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
// Make fields optional if specified
|
|
1052
|
+
if (data.makeOptional) {
|
|
1053
|
+
zodType = zodType.optional();
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// Add title and description to the schema
|
|
1057
|
+
if (column.title) {
|
|
1058
|
+
zodType = zodType.describe(column.title);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
shape[key] = zodType;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const schema: ModelSchemaType = z.object(shape).openapi({
|
|
1065
|
+
type: "object",
|
|
1066
|
+
description: `${data.description} schema for ${model.tableName || "model"} model. ${data.description}`,
|
|
1067
|
+
example: data.example,
|
|
1068
|
+
additionalProperties: false,
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
logger.debug(
|
|
1072
|
+
`${data.schemaType} model schema for ${model.tableName} created with shape: ${JSON.stringify(
|
|
1073
|
+
shape,
|
|
1074
|
+
null,
|
|
1075
|
+
2,
|
|
1076
|
+
)}`,
|
|
1077
|
+
);
|
|
1078
|
+
|
|
1079
|
+
return schema;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// Helper method to get Zod type for a column
|
|
1083
|
+
private static getZodTypeForColumn(
|
|
1084
|
+
column: TableColumnMetadata,
|
|
1085
|
+
key: string,
|
|
1086
|
+
schemaType: "create" | "read" | "update" | "delete",
|
|
1087
|
+
): ZodTypes.ZodTypeAny {
|
|
1088
|
+
let zodType: ZodTypes.ZodTypeAny;
|
|
1089
|
+
|
|
1090
|
+
if (column.type === TableColumnType.ObjectID) {
|
|
1091
|
+
zodType = z.string().openapi({
|
|
1092
|
+
type: "string",
|
|
1093
|
+
example: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
1094
|
+
});
|
|
1095
|
+
} else if (column.type === TableColumnType.Color) {
|
|
1096
|
+
zodType = Color.getSchema();
|
|
1097
|
+
} else if (column.type === TableColumnType.Date) {
|
|
1098
|
+
zodType = z.date().openapi({
|
|
1099
|
+
type: "string",
|
|
1100
|
+
format: "date-time",
|
|
1101
|
+
example: "2023-01-15T12:30:00.000Z",
|
|
1102
|
+
});
|
|
1103
|
+
} else if (column.type === TableColumnType.VeryLongText) {
|
|
1104
|
+
zodType = z.string().openapi({
|
|
1105
|
+
type: "string",
|
|
1106
|
+
example:
|
|
1107
|
+
"This is an example of very long text content that might be stored in this field. It can contain a lot of information, such as detailed descriptions, comments, or any other lengthy text data that needs to be stored in the database.",
|
|
1108
|
+
});
|
|
1109
|
+
} else if (
|
|
1110
|
+
column.type === TableColumnType.Number ||
|
|
1111
|
+
column.type === TableColumnType.PositiveNumber
|
|
1112
|
+
) {
|
|
1113
|
+
zodType = z.number().openapi({ type: "number", example: 42 });
|
|
1114
|
+
} else if (column.type === TableColumnType.Email) {
|
|
1115
|
+
zodType = z.string().email().openapi({
|
|
1116
|
+
type: "string",
|
|
1117
|
+
format: "email",
|
|
1118
|
+
example: "user@example.com",
|
|
1119
|
+
});
|
|
1120
|
+
} else if (column.type === TableColumnType.HashedString) {
|
|
1121
|
+
zodType = z
|
|
1122
|
+
.string()
|
|
1123
|
+
.openapi({ type: "string", example: "hashed_string_value" });
|
|
1124
|
+
} else if (column.type === TableColumnType.Slug) {
|
|
1125
|
+
zodType = z
|
|
1126
|
+
.string()
|
|
1127
|
+
.openapi({ type: "string", example: "example-slug-value" });
|
|
1128
|
+
} else if (column.type === TableColumnType.ShortText) {
|
|
1129
|
+
zodType = z
|
|
1130
|
+
.string()
|
|
1131
|
+
.openapi({ type: "string", example: "Example short text" });
|
|
1132
|
+
} else if (column.type === TableColumnType.LongText) {
|
|
1133
|
+
zodType = z.string().openapi({
|
|
1134
|
+
type: "string",
|
|
1135
|
+
example:
|
|
1136
|
+
"This is an example of longer text content that might be stored in this field.",
|
|
1137
|
+
});
|
|
1138
|
+
} else if (column.type === TableColumnType.Phone) {
|
|
1139
|
+
zodType = z
|
|
1140
|
+
.string()
|
|
1141
|
+
.openapi({ type: "string", example: "+1-555-123-4567" });
|
|
1142
|
+
} else if (column.type === TableColumnType.Version) {
|
|
1143
|
+
zodType = z.string().openapi({
|
|
1144
|
+
type: "string",
|
|
1145
|
+
example: "1.0.0",
|
|
1146
|
+
});
|
|
1147
|
+
} else if (column.type === TableColumnType.Password) {
|
|
1148
|
+
zodType = z.string().openapi({
|
|
1149
|
+
type: "string",
|
|
1150
|
+
format: "password",
|
|
1151
|
+
example: "••••••••",
|
|
1152
|
+
});
|
|
1153
|
+
} else if (column.type === TableColumnType.Name) {
|
|
1154
|
+
zodType = z.string().openapi({
|
|
1155
|
+
type: "string",
|
|
1156
|
+
example: "John Doe",
|
|
1157
|
+
});
|
|
1158
|
+
} else if (column.type === TableColumnType.Description) {
|
|
1159
|
+
zodType = z.string().openapi({
|
|
1160
|
+
type: "string",
|
|
1161
|
+
example: "This is a description of the item",
|
|
1162
|
+
});
|
|
1163
|
+
} else if (column.type === TableColumnType.File) {
|
|
1164
|
+
zodType = z.any().openapi({
|
|
1165
|
+
type: "string",
|
|
1166
|
+
format: "binary",
|
|
1167
|
+
});
|
|
1168
|
+
} else if (column.type === TableColumnType.Buffer) {
|
|
1169
|
+
zodType = z.any().openapi({
|
|
1170
|
+
type: "string",
|
|
1171
|
+
format: "binary",
|
|
1172
|
+
});
|
|
1173
|
+
} else if (column.type === TableColumnType.ShortURL) {
|
|
1174
|
+
zodType = z.string().url().openapi({
|
|
1175
|
+
type: "string",
|
|
1176
|
+
example: "https://short.url/abc123",
|
|
1177
|
+
});
|
|
1178
|
+
} else if (column.type === TableColumnType.Markdown) {
|
|
1179
|
+
zodType = z.string().openapi({
|
|
1180
|
+
type: "string",
|
|
1181
|
+
example: "# Heading\n\nThis is **markdown** content",
|
|
1182
|
+
});
|
|
1183
|
+
} else if (column.type === TableColumnType.Domain) {
|
|
1184
|
+
zodType = z.string().openapi({
|
|
1185
|
+
type: "string",
|
|
1186
|
+
example: "example.com",
|
|
1187
|
+
});
|
|
1188
|
+
} else if (column.type === TableColumnType.LongURL) {
|
|
1189
|
+
zodType = z.string().url().openapi({
|
|
1190
|
+
type: "string",
|
|
1191
|
+
example: "https://www.example.com/path/to/resource?param=value",
|
|
1192
|
+
});
|
|
1193
|
+
} else if (column.type === TableColumnType.OTP) {
|
|
1194
|
+
zodType = z.string().openapi({
|
|
1195
|
+
type: "string",
|
|
1196
|
+
example: "123456",
|
|
1197
|
+
});
|
|
1198
|
+
} else if (column.type === TableColumnType.HTML) {
|
|
1199
|
+
zodType = z.string().openapi({
|
|
1200
|
+
type: "string",
|
|
1201
|
+
example: "<div><h1>Title</h1><p>Content</p></div>",
|
|
1202
|
+
});
|
|
1203
|
+
} else if (column.type === TableColumnType.JavaScript) {
|
|
1204
|
+
zodType = z.string().openapi({
|
|
1205
|
+
type: "string",
|
|
1206
|
+
example: "function example() { return true; }",
|
|
1207
|
+
});
|
|
1208
|
+
} else if (column.type === TableColumnType.CSS) {
|
|
1209
|
+
zodType = z.string().openapi({
|
|
1210
|
+
type: "string",
|
|
1211
|
+
example: "body { color: #333; margin: 0; }",
|
|
1212
|
+
});
|
|
1213
|
+
} else if (column.type === TableColumnType.Array) {
|
|
1214
|
+
zodType = z.array(z.any()).openapi({
|
|
1215
|
+
type: "array",
|
|
1216
|
+
items: {
|
|
1217
|
+
type: "string",
|
|
1218
|
+
},
|
|
1219
|
+
example: ["item1", "item2", "item3"],
|
|
1220
|
+
});
|
|
1221
|
+
} else if (column.type === TableColumnType.SmallPositiveNumber) {
|
|
1222
|
+
zodType = z.number().int().nonnegative().openapi({
|
|
1223
|
+
type: "integer",
|
|
1224
|
+
example: 5,
|
|
1225
|
+
});
|
|
1226
|
+
} else if (column.type === TableColumnType.BigPositiveNumber) {
|
|
1227
|
+
zodType = z.number().nonnegative().openapi({
|
|
1228
|
+
type: "number",
|
|
1229
|
+
example: 1000000,
|
|
1230
|
+
});
|
|
1231
|
+
} else if (column.type === TableColumnType.SmallNumber) {
|
|
1232
|
+
zodType = z.number().int().openapi({
|
|
1233
|
+
type: "integer",
|
|
1234
|
+
example: 10,
|
|
1235
|
+
});
|
|
1236
|
+
} else if (column.type === TableColumnType.BigNumber) {
|
|
1237
|
+
zodType = z.number().openapi({
|
|
1238
|
+
type: "number",
|
|
1239
|
+
example: 1000000,
|
|
1240
|
+
});
|
|
1241
|
+
} else if (column.type === TableColumnType.Permission) {
|
|
1242
|
+
zodType = z.any().openapi({
|
|
1243
|
+
type: "object",
|
|
1244
|
+
example: { read: true, write: false, delete: false },
|
|
1245
|
+
});
|
|
1246
|
+
} else if (column.type === TableColumnType.CustomFieldType) {
|
|
1247
|
+
zodType = z.any().openapi({
|
|
1248
|
+
type: "object",
|
|
1249
|
+
example: { type: "text", required: true },
|
|
1250
|
+
});
|
|
1251
|
+
} else if (column.type === TableColumnType.MonitorType) {
|
|
1252
|
+
zodType = z.string().openapi({
|
|
1253
|
+
type: "string",
|
|
1254
|
+
example: "HTTP",
|
|
1255
|
+
});
|
|
1256
|
+
} else if (column.type === TableColumnType.WorkflowStatus) {
|
|
1257
|
+
zodType = z.string().openapi({
|
|
1258
|
+
type: "string",
|
|
1259
|
+
example: "In Progress",
|
|
1260
|
+
});
|
|
1261
|
+
} else if (column.type === TableColumnType.Boolean) {
|
|
1262
|
+
zodType = z.boolean().openapi({ type: "boolean", example: true });
|
|
1263
|
+
} else if (column.type === TableColumnType.JSON) {
|
|
1264
|
+
zodType = z.any().openapi({
|
|
1265
|
+
type: "object",
|
|
1266
|
+
example: { key: "value", nested: { data: 123 } },
|
|
1267
|
+
});
|
|
1268
|
+
} else if (column.type === TableColumnType.EntityArray) {
|
|
1269
|
+
const entityArrayType: (new () => DatabaseBaseModel) | undefined =
|
|
1270
|
+
column.modelType;
|
|
1271
|
+
if (!entityArrayType) {
|
|
1272
|
+
logger.debug(`Entity type is not defined for column ${key}`);
|
|
1273
|
+
return z.any().openapi({ type: "null", example: null });
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// Use the appropriate schema method based on the operation type
|
|
1277
|
+
let schemaMethod: SchemaMethodFunction;
|
|
1278
|
+
switch (schemaType) {
|
|
1279
|
+
case "create":
|
|
1280
|
+
schemaMethod = ModelSchema.getCreateModelSchema;
|
|
1281
|
+
break;
|
|
1282
|
+
case "read":
|
|
1283
|
+
schemaMethod = ModelSchema.getReadModelSchema;
|
|
1284
|
+
break;
|
|
1285
|
+
case "update":
|
|
1286
|
+
schemaMethod = ModelSchema.getUpdateModelSchema;
|
|
1287
|
+
break;
|
|
1288
|
+
case "delete":
|
|
1289
|
+
schemaMethod = ModelSchema.getDeleteModelSchema;
|
|
1290
|
+
break;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
zodType = z
|
|
1294
|
+
.array(
|
|
1295
|
+
z.lazy(() => {
|
|
1296
|
+
return schemaMethod({
|
|
1297
|
+
modelType: entityArrayType as new () => DatabaseBaseModel,
|
|
1298
|
+
});
|
|
1299
|
+
}),
|
|
1300
|
+
)
|
|
1301
|
+
.openapi({
|
|
1302
|
+
type: "array",
|
|
1303
|
+
items: {
|
|
1304
|
+
type: "object",
|
|
1305
|
+
properties: {
|
|
1306
|
+
id: { type: "string" },
|
|
1307
|
+
},
|
|
1308
|
+
},
|
|
1309
|
+
example: [{ id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }],
|
|
1310
|
+
});
|
|
1311
|
+
} else if (column.type === TableColumnType.Entity) {
|
|
1312
|
+
const entityType: (new () => DatabaseBaseModel) | undefined =
|
|
1313
|
+
column.modelType;
|
|
1314
|
+
|
|
1315
|
+
if (!entityType) {
|
|
1316
|
+
logger.debug(`Entity type is not defined for column ${key}`);
|
|
1317
|
+
return z.any().openapi({ type: "null", example: null });
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// Use the appropriate schema method based on the operation type
|
|
1321
|
+
let schema: ModelSchemaType;
|
|
1322
|
+
switch (schemaType) {
|
|
1323
|
+
case "create":
|
|
1324
|
+
schema = ModelSchema.getCreateModelSchema({ modelType: entityType });
|
|
1325
|
+
break;
|
|
1326
|
+
case "read":
|
|
1327
|
+
schema = ModelSchema.getReadModelSchema({ modelType: entityType });
|
|
1328
|
+
break;
|
|
1329
|
+
case "update":
|
|
1330
|
+
schema = ModelSchema.getUpdateModelSchema({ modelType: entityType });
|
|
1331
|
+
break;
|
|
1332
|
+
case "delete":
|
|
1333
|
+
schema = ModelSchema.getDeleteModelSchema({ modelType: entityType });
|
|
1334
|
+
break;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
zodType = z
|
|
1338
|
+
.lazy(() => {
|
|
1339
|
+
return schema;
|
|
1340
|
+
})
|
|
1341
|
+
.openapi({
|
|
1342
|
+
type: "object",
|
|
1343
|
+
example: { id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" },
|
|
1344
|
+
});
|
|
1345
|
+
} else {
|
|
1346
|
+
zodType = z.any().openapi({ type: "null", example: null });
|
|
388
1347
|
}
|
|
389
1348
|
|
|
390
|
-
return
|
|
1349
|
+
return zodType;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
public static getCreateModelSchema(data: {
|
|
1353
|
+
modelType: new () => DatabaseBaseModel;
|
|
1354
|
+
}): ModelSchemaType {
|
|
1355
|
+
// Auto-generated fields to exclude from create schema
|
|
1356
|
+
const excludedFields: Array<string> = [
|
|
1357
|
+
"_id",
|
|
1358
|
+
"createdAt",
|
|
1359
|
+
"updatedAt",
|
|
1360
|
+
"deletedAt",
|
|
1361
|
+
"version",
|
|
1362
|
+
];
|
|
1363
|
+
|
|
1364
|
+
return this.buildModelSchema({
|
|
1365
|
+
modelType: data.modelType,
|
|
1366
|
+
excludedFields,
|
|
1367
|
+
schemaType: "create",
|
|
1368
|
+
description: "Create",
|
|
1369
|
+
example: this.getCreateSchemaExample(),
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
public static getReadModelSchema(data: {
|
|
1374
|
+
modelType: new () => DatabaseBaseModel;
|
|
1375
|
+
}): ModelSchemaType {
|
|
1376
|
+
// For read operations, include all fields
|
|
1377
|
+
return this.buildModelSchema({
|
|
1378
|
+
modelType: data.modelType,
|
|
1379
|
+
schemaType: "read",
|
|
1380
|
+
description: "Read",
|
|
1381
|
+
example: this.getReadSchemaExample(),
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
public static getUpdateModelSchema(data: {
|
|
1386
|
+
modelType: new () => DatabaseBaseModel;
|
|
1387
|
+
}): ModelSchemaType {
|
|
1388
|
+
// Auto-generated fields to exclude from update schema (but allow _id for identification)
|
|
1389
|
+
const excludedFields: Array<string> = [
|
|
1390
|
+
"createdAt",
|
|
1391
|
+
"updatedAt",
|
|
1392
|
+
"deletedAt",
|
|
1393
|
+
"version",
|
|
1394
|
+
];
|
|
1395
|
+
|
|
1396
|
+
return this.buildModelSchema({
|
|
1397
|
+
modelType: data.modelType,
|
|
1398
|
+
excludedFields,
|
|
1399
|
+
schemaType: "update",
|
|
1400
|
+
description: "Update",
|
|
1401
|
+
example: this.getUpdateSchemaExample(),
|
|
1402
|
+
makeOptional: true, // All fields are optional for updates
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
public static getDeleteModelSchema(data: {
|
|
1407
|
+
modelType: new () => DatabaseBaseModel;
|
|
1408
|
+
}): ModelSchemaType {
|
|
1409
|
+
// For delete, we typically only need the ID
|
|
1410
|
+
const includedFields: Array<string> = ["_id"];
|
|
1411
|
+
|
|
1412
|
+
return this.buildModelSchema({
|
|
1413
|
+
modelType: data.modelType,
|
|
1414
|
+
includedFields,
|
|
1415
|
+
schemaType: "delete",
|
|
1416
|
+
description: "Delete",
|
|
1417
|
+
example: this.getDeleteSchemaExample(),
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
private static getCreateSchemaExample(): SchemaExample {
|
|
1422
|
+
return {
|
|
1423
|
+
name: "John Doe",
|
|
1424
|
+
email: "john@example.com",
|
|
1425
|
+
description: "Example user description",
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
private static getReadSchemaExample(): SchemaExample {
|
|
1430
|
+
return {
|
|
1431
|
+
_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
1432
|
+
name: "John Doe",
|
|
1433
|
+
email: "john@example.com",
|
|
1434
|
+
description: "Example user description",
|
|
1435
|
+
createdAt: "2023-01-15T12:30:00.000Z",
|
|
1436
|
+
updatedAt: "2023-01-15T12:30:00.000Z",
|
|
1437
|
+
version: 1,
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
private static getUpdateSchemaExample(): SchemaExample {
|
|
1442
|
+
return {
|
|
1443
|
+
name: "Jane Doe",
|
|
1444
|
+
email: "jane@example.com",
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
private static getDeleteSchemaExample(): SchemaExample {
|
|
1449
|
+
return {
|
|
1450
|
+
_id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
1451
|
+
};
|
|
391
1452
|
}
|
|
392
1453
|
}
|