@oneuptime/common 7.0.4358 → 7.0.4395

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.
Files changed (83) hide show
  1. package/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.ts +0 -7
  2. package/Models/AnalyticsModels/AnalyticsBaseModel/CommonModel.ts +0 -13
  3. package/Models/AnalyticsModels/ExceptionInstance.ts +2 -2
  4. package/Models/DatabaseModels/Monitor.ts +1 -1
  5. package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.ts +2 -2
  6. package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.ts +2 -2
  7. package/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.ts +2 -2
  8. package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +2 -2
  9. package/Models/DatabaseModels/Probe.ts +7 -1
  10. package/Models/DatabaseModels/ServiceCatalog.ts +2 -2
  11. package/Models/DatabaseModels/ServiceCopilotCodeRepository.ts +2 -2
  12. package/Server/Middleware/ProjectAuthorization.ts +11 -3
  13. package/Server/Services/OpenTelemetryIngestService.ts +13 -9
  14. package/Server/Utils/AnalyticsDatabase/Statement.ts +0 -1
  15. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +28 -81
  16. package/Server/Utils/OpenAPI.ts +605 -16
  17. package/Server/Utils/StartServer.ts +2 -2
  18. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +0 -49
  19. package/Types/AnalyticsDatabase/TableColumn.ts +0 -26
  20. package/Types/AnalyticsDatabase/TableColumnType.ts +2 -1
  21. package/Types/Database/TableColumnType.ts +3 -0
  22. package/Types/GenericFunction.ts +1 -1
  23. package/Types/GenericObject.ts +1 -1
  24. package/Types/Object.ts +1 -1
  25. package/UI/esbuild-config.js +214 -0
  26. package/Utils/Schema/AnalyticsModelSchema.ts +741 -0
  27. package/Utils/Schema/BaseSchema.ts +450 -0
  28. package/Utils/Schema/ModelSchema.ts +227 -460
  29. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js +0 -6
  30. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js.map +1 -1
  31. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/CommonModel.js +0 -9
  32. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/CommonModel.js.map +1 -1
  33. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +2 -2
  34. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
  35. package/build/dist/Models/DatabaseModels/Monitor.js +1 -1
  36. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  37. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.js +2 -2
  38. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule.js.map +1 -1
  39. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.js +2 -2
  40. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleTeam.js.map +1 -1
  41. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.js +2 -2
  42. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyEscalationRuleUser.js.map +1 -1
  43. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +2 -2
  44. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
  45. package/build/dist/Models/DatabaseModels/Probe.js +7 -1
  46. package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
  47. package/build/dist/Models/DatabaseModels/ServiceCatalog.js +2 -2
  48. package/build/dist/Models/DatabaseModels/ServiceCatalog.js.map +1 -1
  49. package/build/dist/Models/DatabaseModels/ServiceCopilotCodeRepository.js +2 -2
  50. package/build/dist/Models/DatabaseModels/ServiceCopilotCodeRepository.js.map +1 -1
  51. package/build/dist/Server/Middleware/ProjectAuthorization.js +7 -3
  52. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  53. package/build/dist/Server/Services/OpenTelemetryIngestService.js +8 -4
  54. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  55. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +0 -1
  56. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  57. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +15 -55
  58. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  59. package/build/dist/Server/Utils/OpenAPI.js +467 -12
  60. package/build/dist/Server/Utils/OpenAPI.js.map +1 -1
  61. package/build/dist/Server/Utils/StartServer.js +2 -2
  62. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  63. package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js +0 -43
  64. package/build/dist/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.js.map +1 -1
  65. package/build/dist/Types/AnalyticsDatabase/TableColumn.js +0 -19
  66. package/build/dist/Types/AnalyticsDatabase/TableColumn.js.map +1 -1
  67. package/build/dist/Types/AnalyticsDatabase/TableColumnType.js +2 -1
  68. package/build/dist/Types/AnalyticsDatabase/TableColumnType.js.map +1 -1
  69. package/build/dist/Types/Database/TableColumnType.js +3 -0
  70. package/build/dist/Types/Database/TableColumnType.js.map +1 -1
  71. package/build/dist/Utils/Schema/AnalyticsModelSchema.js +619 -0
  72. package/build/dist/Utils/Schema/AnalyticsModelSchema.js.map +1 -0
  73. package/build/dist/Utils/Schema/BaseSchema.js +295 -0
  74. package/build/dist/Utils/Schema/BaseSchema.js.map +1 -0
  75. package/build/dist/Utils/Schema/ModelSchema.js +207 -390
  76. package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
  77. package/package.json +3 -1
  78. package/Models/AnalyticsModels/AnalyticsBaseModel/NestedModel.ts +0 -8
  79. package/Models/AnalyticsModels/NestedModels/KeyValueNestedModel.ts +0 -59
  80. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/NestedModel.js +0 -7
  81. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/NestedModel.js.map +0 -1
  82. package/build/dist/Models/AnalyticsModels/NestedModels/KeyValueNestedModel.js +0 -51
  83. package/build/dist/Models/AnalyticsModels/NestedModels/KeyValueNestedModel.js.map +0 -1
@@ -6,42 +6,31 @@ 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";
12
11
  import { z as ZodTypes } from "zod";
13
12
  import BadDataException from "../../Types/Exception/BadDataException";
14
13
  import Permission, { PermissionHelper } from "../../Types/Permission";
15
14
  import { ColumnAccessControl } from "../../Types/BaseDatabase/AccessControl";
15
+ import { BaseSchema, SchemaExample, ShapeRecord } from "./BaseSchema";
16
+ import ObjectID from "../../Types/ObjectID";
17
+ import Email from "../../Types/Email";
18
+ import Phone from "../../Types/Phone";
19
+ import Domain from "../../Types/Domain";
20
+ import Version from "../../Types/Version";
21
+ import Name from "../../Types/Name";
22
+ import IP from "../../Types/IP/IP";
23
+ import Port from "../../Types/Port";
24
+ import MonitorSteps from "../../Types/Monitor/MonitorSteps";
16
25
 
17
26
  export type ModelSchemaType = ZodSchema;
18
27
 
19
- // Type for schema examples
20
- type SchemaExample = Record<string, unknown>;
21
-
22
- // Type for operator examples in OpenAPI format
23
- type OperatorExample = {
24
- properties: Record<
25
- string,
26
- {
27
- type: string;
28
- enum?: string[];
29
- items?: { type: string };
30
- }
31
- >;
32
- required: string[];
33
- example: Record<string, unknown>;
34
- };
35
-
36
- // Type for shape objects using Zod's type inference
37
- type ShapeRecord = Record<string, ZodTypes.ZodTypeAny>;
38
-
39
28
  // Type for schema method functions
40
29
  type SchemaMethodFunction = (data: {
41
30
  modelType: new () => DatabaseBaseModel;
42
31
  }) => ModelSchemaType;
43
32
 
44
- export class ModelSchema {
33
+ export class ModelSchema extends BaseSchema {
45
34
  /**
46
35
  * Format permissions array into a human-readable string for OpenAPI documentation
47
36
  */
@@ -49,7 +38,7 @@ export class ModelSchema {
49
38
  permissions: Array<Permission> | undefined,
50
39
  ): string {
51
40
  if (!permissions || permissions.length === 0) {
52
- return "No permissions required";
41
+ return "No access - you don't have permission for this operation";
53
42
  }
54
43
 
55
44
  return PermissionHelper.getPermissionTitles(permissions).join(", ");
@@ -92,20 +81,37 @@ export class ModelSchema {
92
81
 
93
82
  const shape: ShapeRecord = {};
94
83
 
84
+ // Get column access control for permission filtering
85
+ const columnAccessControl: Dictionary<ColumnAccessControl> =
86
+ model.getColumnAccessControlForAllColumns();
87
+
95
88
  for (const key in columns) {
96
89
  const column: TableColumnMetadata | undefined = columns[key];
97
90
  if (!column) {
98
91
  continue;
99
92
  }
93
+
94
+ // Filter out columns with no permissions (root-only access)
95
+ const accessControl: ColumnAccessControl | undefined =
96
+ columnAccessControl[key];
97
+ if (accessControl) {
98
+ // Check if column has any permissions defined for read operation (general schema assumes read access)
99
+ const hasReadPermissions: boolean =
100
+ accessControl.read && accessControl.read.length > 0;
101
+
102
+ // If no read permissions are defined, exclude the column from general schema
103
+ if (!hasReadPermissions) {
104
+ continue;
105
+ }
106
+ }
100
107
  let zodType: ZodTypes.ZodTypeAny;
101
108
 
102
109
  if (column.type === TableColumnType.ObjectID) {
103
- zodType = z.string().openapi({
104
- type: "string",
105
- example: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
106
- });
110
+ zodType = ObjectID.getSchema();
107
111
  } else if (column.type === TableColumnType.Color) {
108
112
  zodType = Color.getSchema();
113
+ } else if (column.type === TableColumnType.MonitorSteps) {
114
+ zodType = MonitorSteps.getSchema();
109
115
  } else if (column.type === TableColumnType.Date) {
110
116
  zodType = z.date().openapi({
111
117
  type: "string",
@@ -124,11 +130,7 @@ export class ModelSchema {
124
130
  ) {
125
131
  zodType = z.number().openapi({ type: "number", example: 42 });
126
132
  } else if (column.type === TableColumnType.Email) {
127
- zodType = z.string().email().openapi({
128
- type: "string",
129
- format: "email",
130
- example: "user@example.com",
131
- });
133
+ zodType = Email.getSchema();
132
134
  } else if (column.type === TableColumnType.HashedString) {
133
135
  zodType = z
134
136
  .string()
@@ -148,14 +150,9 @@ export class ModelSchema {
148
150
  "This is an example of longer text content that might be stored in this field.",
149
151
  });
150
152
  } else if (column.type === TableColumnType.Phone) {
151
- zodType = z
152
- .string()
153
- .openapi({ type: "string", example: "+1-555-123-4567" });
153
+ zodType = Phone.getSchema();
154
154
  } else if (column.type === TableColumnType.Version) {
155
- zodType = z.string().openapi({
156
- type: "string",
157
- example: "1.0.0",
158
- });
155
+ zodType = Version.getSchema();
159
156
  } else if (column.type === TableColumnType.Password) {
160
157
  zodType = z.string().openapi({
161
158
  type: "string",
@@ -163,10 +160,7 @@ export class ModelSchema {
163
160
  example: "••••••••",
164
161
  });
165
162
  } else if (column.type === TableColumnType.Name) {
166
- zodType = z.string().openapi({
167
- type: "string",
168
- example: "John Doe",
169
- });
163
+ zodType = Name.getSchema();
170
164
  } else if (column.type === TableColumnType.Description) {
171
165
  zodType = z.string().openapi({
172
166
  type: "string",
@@ -193,10 +187,11 @@ export class ModelSchema {
193
187
  example: "# Heading\n\nThis is **markdown** content",
194
188
  });
195
189
  } else if (column.type === TableColumnType.Domain) {
196
- zodType = z.string().openapi({
197
- type: "string",
198
- example: "example.com",
199
- });
190
+ zodType = Domain.getSchema();
191
+ } else if (column.type === TableColumnType.Port) {
192
+ zodType = Port.getSchema();
193
+ } else if (column.type === TableColumnType.IP) {
194
+ zodType = IP.getSchema();
200
195
  } else if (column.type === TableColumnType.LongURL) {
201
196
  zodType = z.string().url().openapi({
202
197
  type: "string",
@@ -380,72 +375,36 @@ export class ModelSchema {
380
375
  const modelType: new () => DatabaseBaseModel = data.modelType;
381
376
  const model: DatabaseBaseModel = new modelType();
382
377
 
383
- const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
384
-
385
- const shape: ShapeRecord = {};
386
-
387
- for (const key in columns) {
388
- const column: TableColumnMetadata | undefined = columns[key];
389
- if (!column) {
390
- continue;
391
- }
392
-
393
- // Get valid operators for this column type
394
- const validOperators: Array<string> = this.getValidOperatorsForColumnType(
395
- column.type,
396
- );
397
-
398
- if (validOperators.length === 0) {
399
- continue;
400
- }
401
-
402
- // Create a union type of all valid operators for this column
403
- const operatorSchemas: Array<ZodTypes.ZodTypeAny> = validOperators.map(
404
- (operatorType: string) => {
405
- return this.getOperatorSchema(operatorType, column.type);
406
- },
407
- );
408
-
409
- let columnSchema: ZodTypes.ZodTypeAny;
410
- if (operatorSchemas.length === 1 && operatorSchemas[0]) {
411
- columnSchema = operatorSchemas[0].optional();
412
- } else if (operatorSchemas.length > 1) {
413
- columnSchema = z
414
- .union(
415
- operatorSchemas as [
416
- ZodTypes.ZodTypeAny,
417
- ZodTypes.ZodTypeAny,
418
- ...ZodTypes.ZodTypeAny[],
419
- ],
420
- )
421
- .optional();
422
- } else {
423
- // Fallback for empty operators array
424
- columnSchema = z.any().optional();
425
- }
426
-
427
- // Add OpenAPI documentation for query operators
428
- const operatorExamples: Array<OperatorExample> =
429
- this.getQueryOperatorExamples(column.type, validOperators);
430
- columnSchema = columnSchema.openapi({
431
- type: "object",
432
- description: `Query operators for ${key} field of type ${column.type}. Supported operators: ${validOperators.join(", ")}`,
433
- example:
434
- operatorExamples.length > 0 && operatorExamples[0]
435
- ? operatorExamples[0].example
436
- : {},
437
- });
438
-
439
- shape[key] = columnSchema;
440
- }
441
-
442
- const schema: ModelSchemaType = z.object(shape).openapi({
443
- type: "object",
444
- description: `Query schema for ${model.tableName || "model"} model. Each field can use various operators based on its data type.`,
445
- example: this.getQuerySchemaExample(modelType),
378
+ return this.generateQuerySchema({
379
+ model,
380
+ tableName: model.tableName || "model",
381
+ getColumns: (model: DatabaseBaseModel) => {
382
+ const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
383
+ return Object.keys(columns)
384
+ .map((key: string) => {
385
+ const column: TableColumnMetadata | undefined = columns[key];
386
+ return column ? { key, type: column.type } : null;
387
+ })
388
+ .filter((col: { key: string; type: any } | null) => {
389
+ return col !== null;
390
+ }) as Array<{ key: string; type: any }>;
391
+ },
392
+ getValidOperatorsForColumnType: (columnType: TableColumnType) => {
393
+ return this.getValidOperatorsForColumnType(columnType);
394
+ },
395
+ getOperatorSchema: (
396
+ operatorType: string,
397
+ columnType: TableColumnType,
398
+ ) => {
399
+ return this.getOperatorSchema(operatorType, columnType);
400
+ },
401
+ getQuerySchemaExample: () => {
402
+ return this.getQuerySchemaExample(modelType);
403
+ },
404
+ getExampleValueForColumn: (columnType: TableColumnType) => {
405
+ return this.getExampleValueForColumn(columnType);
406
+ },
446
407
  });
447
-
448
- return schema;
449
408
  }
450
409
 
451
410
  private static getValidOperatorsForColumnType(
@@ -585,20 +544,34 @@ export class ModelSchema {
585
544
  ): ZodTypes.ZodTypeAny {
586
545
  switch (columnType) {
587
546
  case TableColumnType.ObjectID:
588
- return z.string();
547
+ return ObjectID.getSchema();
589
548
 
590
549
  case TableColumnType.Email:
591
- return z.string().email();
550
+ return Email.getSchema();
592
551
 
593
552
  case TableColumnType.Phone:
553
+ return Phone.getSchema();
554
+
555
+ case TableColumnType.Name:
556
+ return Name.getSchema();
557
+
558
+ case TableColumnType.Domain:
559
+ return Domain.getSchema();
560
+
561
+ case TableColumnType.Version:
562
+ return Version.getSchema();
563
+
564
+ case TableColumnType.IP:
565
+ return IP.getSchema();
566
+ case TableColumnType.Port:
567
+ return Port.getSchema();
568
+
594
569
  case TableColumnType.HashedString:
595
570
  case TableColumnType.Slug:
596
571
  case TableColumnType.ShortText:
597
572
  case TableColumnType.LongText:
598
573
  case TableColumnType.VeryLongText:
599
- case TableColumnType.Name:
600
574
  case TableColumnType.Description:
601
- case TableColumnType.Domain:
602
575
  case TableColumnType.Markdown:
603
576
  case TableColumnType.HTML:
604
577
  case TableColumnType.JavaScript:
@@ -607,7 +580,6 @@ export class ModelSchema {
607
580
  case TableColumnType.ShortURL:
608
581
  case TableColumnType.OTP:
609
582
  case TableColumnType.Password:
610
- case TableColumnType.Version:
611
583
  return z.string();
612
584
 
613
585
  case TableColumnType.Number:
@@ -658,40 +630,23 @@ export class ModelSchema {
658
630
  const modelType: new () => DatabaseBaseModel = data.modelType;
659
631
  const model: DatabaseBaseModel = new modelType();
660
632
 
661
- const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
662
-
663
- const shape: ShapeRecord = {};
664
-
665
- for (const key in columns) {
666
- const column: TableColumnMetadata | undefined = columns[key];
667
- if (!column) {
668
- continue;
669
- }
670
-
671
- const isSortable: boolean = ModelSchema.getSortableTypes().includes(
672
- column.type,
673
- );
674
-
675
- if (!isSortable) {
676
- continue;
677
- }
678
-
679
- shape[key] = z
680
- .enum([SortOrder.Ascending, SortOrder.Descending])
681
- .optional()
682
- .openapi({
683
- type: "string",
684
- enum: [SortOrder.Ascending, SortOrder.Descending],
685
- description: `Sort order for ${key} field`,
686
- example: SortOrder.Ascending,
687
- });
688
- }
689
-
690
- return z.object(shape).openapi({
691
- type: "object",
692
- description: `Sort schema for ${model.tableName || "model"} model. Only sortable fields are included.`,
693
- example: this.getSortSchemaExample(),
694
- additionalProperties: false,
633
+ return this.generateSortSchema({
634
+ model,
635
+ tableName: model.tableName || "model",
636
+ getSortableTypes: () => {
637
+ return this.getSortableTypes();
638
+ },
639
+ getColumnsForSorting: (model: DatabaseBaseModel) => {
640
+ const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
641
+ return Object.keys(columns)
642
+ .map((key: string) => {
643
+ const column: TableColumnMetadata | undefined = columns[key];
644
+ return column ? { key, type: column.type } : null;
645
+ })
646
+ .filter((col: { key: string; type: any } | null) => {
647
+ return col !== null;
648
+ }) as Array<{ key: string; type: any }>;
649
+ },
695
650
  });
696
651
  }
697
652
 
@@ -702,50 +657,40 @@ export class ModelSchema {
702
657
  const modelType: new () => DatabaseBaseModel = data.modelType;
703
658
  const model: DatabaseBaseModel = new modelType();
704
659
 
705
- const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
706
-
707
- const shape: ShapeRecord = {};
708
-
709
- for (const key in columns) {
710
- const column: TableColumnMetadata | undefined = columns[key];
711
- if (!column) {
712
- continue;
713
- }
714
-
715
- // if its entity array or entity then you can select nested properties
716
- if (
717
- !data.isNested &&
718
- column.modelType &&
719
- (column.type === TableColumnType.EntityArray ||
720
- column.type === TableColumnType.Entity)
721
- ) {
722
- // can only do one level of nesting
723
- shape[key] = this.getSelectModelSchema({
724
- modelType: column.modelType as new () => DatabaseBaseModel,
725
- isNested: true,
726
- }).openapi({
727
- type: "object",
728
- description: `Select fields for nested ${key} entity`,
729
- example: { id: true, name: true },
730
- });
731
- continue;
732
- }
733
-
734
- shape[key] = z
735
- .boolean()
736
- .optional()
737
- .openapi({
738
- type: "boolean",
739
- description: `Select ${key} field in the response`,
740
- example: true,
741
- });
742
- }
743
-
744
- return z.object(shape).openapi({
745
- type: "object",
746
- description: `Select schema for ${model.tableName || "model"} model. Set fields to true to include them in the response.`,
747
- example: this.getSelectSchemaExample(modelType),
748
- additionalProperties: false,
660
+ return this.generateSelectSchema({
661
+ model,
662
+ tableName: model.tableName || "model",
663
+ getColumns: (model: DatabaseBaseModel) => {
664
+ const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
665
+ return Object.keys(columns)
666
+ .map((key: string) => {
667
+ const column: TableColumnMetadata | undefined = columns[key];
668
+ return column ? { key, type: column.type } : null;
669
+ })
670
+ .filter((col: { key: string; type: any } | null) => {
671
+ return col !== null;
672
+ }) as Array<{ key: string; type?: any }>;
673
+ },
674
+ getSelectSchemaExample: () => {
675
+ return this.getSelectSchemaExample(modelType);
676
+ },
677
+ allowNested: !data.isNested,
678
+ getNestedSchema: (key: string, model: DatabaseBaseModel) => {
679
+ const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
680
+ const column: TableColumnMetadata | undefined = columns[key];
681
+ if (
682
+ column &&
683
+ column.modelType &&
684
+ (column.type === TableColumnType.EntityArray ||
685
+ column.type === TableColumnType.Entity)
686
+ ) {
687
+ return this.getSelectModelSchema({
688
+ modelType: column.modelType as new () => DatabaseBaseModel,
689
+ isNested: true,
690
+ });
691
+ }
692
+ return null;
693
+ },
749
694
  });
750
695
  }
751
696
 
@@ -755,244 +700,44 @@ export class ModelSchema {
755
700
  const modelType: new () => DatabaseBaseModel = data.modelType;
756
701
  const model: DatabaseBaseModel = new modelType();
757
702
 
758
- const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
759
-
760
- const shape: ShapeRecord = {};
761
-
762
- for (const key in columns) {
763
- const column: TableColumnMetadata | undefined = columns[key];
764
- if (!column) {
765
- continue;
766
- }
767
-
768
- // Only allow grouping by certain field types that make sense for aggregation
769
- const isGroupable: boolean = [
770
- TableColumnType.ShortText,
771
- TableColumnType.LongText,
772
- TableColumnType.Name,
773
- TableColumnType.Email,
774
- TableColumnType.Slug,
775
- TableColumnType.ObjectID,
776
- TableColumnType.Boolean,
777
- TableColumnType.Date,
778
- TableColumnType.Number,
779
- TableColumnType.PositiveNumber,
780
- TableColumnType.SmallNumber,
781
- TableColumnType.SmallPositiveNumber,
782
- TableColumnType.BigNumber,
783
- TableColumnType.BigPositiveNumber,
784
- ].includes(column.type);
785
-
786
- if (!isGroupable) {
787
- continue;
788
- }
789
-
790
- shape[key] = z
791
- .literal(true)
792
- .optional()
793
- .openapi({
794
- type: "boolean",
795
- description: `Group by ${key} field. Only one field can be selected for grouping.`,
796
- example: true,
797
- });
798
- }
799
-
800
- return z.object(shape).openapi({
801
- type: "object",
802
- description: `Group by schema for ${model.tableName || "model"} model. Only one field can be set to true for grouping.`,
803
- example: this.getGroupBySchemaExample(modelType),
804
- additionalProperties: false,
703
+ return this.generateGroupBySchema({
704
+ model,
705
+ tableName: model.tableName || "model",
706
+ getColumns: (model: DatabaseBaseModel) => {
707
+ const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
708
+ return Object.keys(columns)
709
+ .map((key: string) => {
710
+ const column: TableColumnMetadata | undefined = columns[key];
711
+ return column ? { key, type: column.type } : null;
712
+ })
713
+ .filter((col: { key: string; type: any } | null) => {
714
+ return col !== null;
715
+ }) as Array<{ key: string; type: any }>;
716
+ },
717
+ getGroupableTypes: () => {
718
+ return [
719
+ TableColumnType.ShortText,
720
+ TableColumnType.LongText,
721
+ TableColumnType.Name,
722
+ TableColumnType.Email,
723
+ TableColumnType.Slug,
724
+ TableColumnType.ObjectID,
725
+ TableColumnType.Boolean,
726
+ TableColumnType.Date,
727
+ TableColumnType.Number,
728
+ TableColumnType.PositiveNumber,
729
+ TableColumnType.SmallNumber,
730
+ TableColumnType.SmallPositiveNumber,
731
+ TableColumnType.BigNumber,
732
+ TableColumnType.BigPositiveNumber,
733
+ ];
734
+ },
735
+ getGroupBySchemaExample: () => {
736
+ return this.getGroupBySchemaExample(modelType);
737
+ },
805
738
  });
806
739
  }
807
740
 
808
- private static getQueryOperatorExamples(
809
- columnType: TableColumnType,
810
- validOperators: Array<string>,
811
- ): Array<OperatorExample> {
812
- const examples: Array<OperatorExample> = [];
813
-
814
- for (const operator of validOperators) {
815
- switch (operator) {
816
- case "EqualTo":
817
- examples.push({
818
- properties: {
819
- _type: { type: "string", enum: ["EqualTo"] },
820
- value: { type: this.getOpenAPITypeForColumn(columnType) },
821
- },
822
- required: ["_type", "value"],
823
- example: {
824
- _type: "EqualTo",
825
- value: this.getExampleValueForColumn(columnType),
826
- },
827
- });
828
- break;
829
- case "NotEqual":
830
- examples.push({
831
- properties: {
832
- _type: { type: "string", enum: ["NotEqual"] },
833
- value: { type: this.getOpenAPITypeForColumn(columnType) },
834
- },
835
- required: ["_type", "value"],
836
- example: {
837
- _type: "NotEqual",
838
- value: this.getExampleValueForColumn(columnType),
839
- },
840
- });
841
- break;
842
- case "Search":
843
- examples.push({
844
- properties: {
845
- _type: { type: "string", enum: ["Search"] },
846
- value: { type: "string" },
847
- },
848
- required: ["_type", "value"],
849
- example: { _type: "Search", value: "search term" },
850
- });
851
- break;
852
- case "GreaterThan":
853
- examples.push({
854
- properties: {
855
- _type: { type: "string", enum: ["GreaterThan"] },
856
- value: { type: this.getOpenAPITypeForColumn(columnType) },
857
- },
858
- required: ["_type", "value"],
859
- example: {
860
- _type: "GreaterThan",
861
- value: this.getExampleValueForColumn(columnType),
862
- },
863
- });
864
- break;
865
- case "LessThan":
866
- examples.push({
867
- properties: {
868
- _type: { type: "string", enum: ["LessThan"] },
869
- value: { type: this.getOpenAPITypeForColumn(columnType) },
870
- },
871
- required: ["_type", "value"],
872
- example: {
873
- _type: "LessThan",
874
- value: this.getExampleValueForColumn(columnType),
875
- },
876
- });
877
- break;
878
- case "GreaterThanOrEqual":
879
- examples.push({
880
- properties: {
881
- _type: { type: "string", enum: ["GreaterThanOrEqual"] },
882
- value: { type: this.getOpenAPITypeForColumn(columnType) },
883
- },
884
- required: ["_type", "value"],
885
- example: {
886
- _type: "GreaterThanOrEqual",
887
- value: this.getExampleValueForColumn(columnType),
888
- },
889
- });
890
- break;
891
- case "LessThanOrEqual":
892
- examples.push({
893
- properties: {
894
- _type: { type: "string", enum: ["LessThanOrEqual"] },
895
- value: { type: this.getOpenAPITypeForColumn(columnType) },
896
- },
897
- required: ["_type", "value"],
898
- example: {
899
- _type: "LessThanOrEqual",
900
- value: this.getExampleValueForColumn(columnType),
901
- },
902
- });
903
- break;
904
- case "EqualToOrNull":
905
- examples.push({
906
- properties: {
907
- _type: { type: "string", enum: ["EqualToOrNull"] },
908
- value: { type: this.getOpenAPITypeForColumn(columnType) },
909
- },
910
- required: ["_type", "value"],
911
- example: {
912
- _type: "EqualToOrNull",
913
- value: this.getExampleValueForColumn(columnType),
914
- },
915
- });
916
- break;
917
- case "InBetween":
918
- examples.push({
919
- properties: {
920
- _type: { type: "string", enum: ["InBetween"] },
921
- startValue: { type: this.getOpenAPITypeForColumn(columnType) },
922
- endValue: { type: this.getOpenAPITypeForColumn(columnType) },
923
- },
924
- required: ["_type", "startValue", "endValue"],
925
- example: {
926
- _type: "InBetween",
927
- startValue: this.getExampleValueForColumn(columnType),
928
- endValue: this.getExampleValueForColumn(columnType, true),
929
- },
930
- });
931
- break;
932
- case "IsNull":
933
- examples.push({
934
- properties: {
935
- _type: { type: "string", enum: ["IsNull"] },
936
- },
937
- required: ["_type"],
938
- example: { _type: "IsNull" },
939
- });
940
- break;
941
- case "NotNull":
942
- examples.push({
943
- properties: {
944
- _type: { type: "string", enum: ["NotNull"] },
945
- },
946
- required: ["_type"],
947
- example: { _type: "NotNull" },
948
- });
949
- break;
950
- case "Includes":
951
- examples.push({
952
- properties: {
953
- _type: { type: "string", enum: ["Includes"] },
954
- value: {
955
- type: "array",
956
- items: { type: this.getOpenAPITypeForColumn(columnType) },
957
- },
958
- },
959
- required: ["_type", "value"],
960
- example: {
961
- _type: "Includes",
962
- value: [
963
- this.getExampleValueForColumn(columnType),
964
- this.getExampleValueForColumn(columnType, true),
965
- ],
966
- },
967
- });
968
- break;
969
- }
970
- }
971
-
972
- return examples;
973
- }
974
-
975
- private static getOpenAPITypeForColumn(columnType: TableColumnType): string {
976
- switch (columnType) {
977
- case TableColumnType.Number:
978
- case TableColumnType.PositiveNumber:
979
- case TableColumnType.SmallNumber:
980
- case TableColumnType.SmallPositiveNumber:
981
- case TableColumnType.BigNumber:
982
- case TableColumnType.BigPositiveNumber:
983
- return "number";
984
- case TableColumnType.Boolean:
985
- return "boolean";
986
- case TableColumnType.Date:
987
- return "string";
988
- case TableColumnType.JSON:
989
- case TableColumnType.Array:
990
- return "object";
991
- default:
992
- return "string";
993
- }
994
- }
995
-
996
741
  private static getExampleValueForColumn(
997
742
  columnType: TableColumnType,
998
743
  isSecondValue: boolean = false,
@@ -1124,12 +869,6 @@ export class ModelSchema {
1124
869
  return example;
1125
870
  }
1126
871
 
1127
- private static getSortSchemaExample(): SchemaExample {
1128
- return {
1129
- createdAt: "Descending",
1130
- };
1131
- }
1132
-
1133
872
  private static getSelectSchemaExample(
1134
873
  modelType: new () => DatabaseBaseModel,
1135
874
  ): SchemaExample {
@@ -1257,6 +996,10 @@ export class ModelSchema {
1257
996
  const columns: Dictionary<TableColumnMetadata> = getTableColumns(model);
1258
997
  const shape: ShapeRecord = {};
1259
998
 
999
+ // Get column access control for permission filtering
1000
+ const columnAccessControl: Dictionary<ColumnAccessControl> =
1001
+ model.getColumnAccessControlForAllColumns();
1002
+
1260
1003
  for (const key in columns) {
1261
1004
  const column: TableColumnMetadata | undefined = columns[key];
1262
1005
  if (!column) {
@@ -1273,6 +1016,42 @@ export class ModelSchema {
1273
1016
  continue;
1274
1017
  }
1275
1018
 
1019
+ // Filter out columns with no permissions (root-only access)
1020
+ const accessControl: ColumnAccessControl | undefined =
1021
+ columnAccessControl[key];
1022
+ if (accessControl) {
1023
+ let hasPermissions: boolean = false;
1024
+
1025
+ // Check if column has any permissions defined for the current operation
1026
+ if (
1027
+ data.schemaType === "create" &&
1028
+ accessControl.create &&
1029
+ accessControl.create.length > 0
1030
+ ) {
1031
+ hasPermissions = true;
1032
+ } else if (
1033
+ data.schemaType === "read" &&
1034
+ accessControl.read &&
1035
+ accessControl.read.length > 0
1036
+ ) {
1037
+ hasPermissions = true;
1038
+ } else if (
1039
+ data.schemaType === "update" &&
1040
+ accessControl.update &&
1041
+ accessControl.update.length > 0
1042
+ ) {
1043
+ hasPermissions = true;
1044
+ } else if (data.schemaType === "delete") {
1045
+ // For delete operations, we don't filter by column permissions
1046
+ hasPermissions = true;
1047
+ }
1048
+
1049
+ // If no permissions are defined for this operation, exclude the column
1050
+ if (!hasPermissions) {
1051
+ continue;
1052
+ }
1053
+ }
1054
+
1276
1055
  let zodType: ZodTypes.ZodTypeAny = this.getZodTypeForColumn(
1277
1056
  column,
1278
1057
  key,
@@ -1319,10 +1098,13 @@ export class ModelSchema {
1319
1098
  let zodType: ZodTypes.ZodTypeAny;
1320
1099
 
1321
1100
  if (column.type === TableColumnType.ObjectID) {
1322
- zodType = z.string().openapi({
1323
- type: "string",
1324
- example: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
1325
- });
1101
+ zodType = ObjectID.getSchema();
1102
+ } else if (column.type === TableColumnType.Port) {
1103
+ zodType = Port.getSchema();
1104
+ } else if (column.type === TableColumnType.MonitorSteps) {
1105
+ zodType = MonitorSteps.getSchema();
1106
+ } else if (column.type === TableColumnType.IP) {
1107
+ zodType = IP.getSchema();
1326
1108
  } else if (column.type === TableColumnType.Color) {
1327
1109
  zodType = Color.getSchema();
1328
1110
  } else if (column.type === TableColumnType.Date) {
@@ -1343,11 +1125,7 @@ export class ModelSchema {
1343
1125
  ) {
1344
1126
  zodType = z.number().openapi({ type: "number", example: 42 });
1345
1127
  } else if (column.type === TableColumnType.Email) {
1346
- zodType = z.string().email().openapi({
1347
- type: "string",
1348
- format: "email",
1349
- example: "user@example.com",
1350
- });
1128
+ zodType = Email.getSchema();
1351
1129
  } else if (column.type === TableColumnType.HashedString) {
1352
1130
  zodType = z
1353
1131
  .string()
@@ -1367,14 +1145,9 @@ export class ModelSchema {
1367
1145
  "This is an example of longer text content that might be stored in this field.",
1368
1146
  });
1369
1147
  } else if (column.type === TableColumnType.Phone) {
1370
- zodType = z
1371
- .string()
1372
- .openapi({ type: "string", example: "+1-555-123-4567" });
1148
+ zodType = Phone.getSchema();
1373
1149
  } else if (column.type === TableColumnType.Version) {
1374
- zodType = z.string().openapi({
1375
- type: "string",
1376
- example: "1.0.0",
1377
- });
1150
+ zodType = Version.getSchema();
1378
1151
  } else if (column.type === TableColumnType.Password) {
1379
1152
  zodType = z.string().openapi({
1380
1153
  type: "string",
@@ -1382,10 +1155,7 @@ export class ModelSchema {
1382
1155
  example: "••••••••",
1383
1156
  });
1384
1157
  } else if (column.type === TableColumnType.Name) {
1385
- zodType = z.string().openapi({
1386
- type: "string",
1387
- example: "John Doe",
1388
- });
1158
+ zodType = Name.getSchema();
1389
1159
  } else if (column.type === TableColumnType.Description) {
1390
1160
  zodType = z.string().openapi({
1391
1161
  type: "string",
@@ -1412,10 +1182,7 @@ export class ModelSchema {
1412
1182
  example: "# Heading\n\nThis is **markdown** content",
1413
1183
  });
1414
1184
  } else if (column.type === TableColumnType.Domain) {
1415
- zodType = z.string().openapi({
1416
- type: "string",
1417
- example: "example.com",
1418
- });
1185
+ zodType = Domain.getSchema();
1419
1186
  } else if (column.type === TableColumnType.LongURL) {
1420
1187
  zodType = z.string().url().openapi({
1421
1188
  type: "string",