@tellescope/schema 0.0.4 → 0.0.8

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/src/schema.ts CHANGED
@@ -1,7 +1,29 @@
1
- import "@tellescope/types"
2
- // import "@tellescope/types-server"
1
+ import {
2
+ ServerModelForName,
3
+ DatabaseModel,
4
+ DatabaseRecord,
5
+ Enduser,
6
+ ObjectId,
7
+ ModelName,
8
+ } from "@tellescope/types-server"
9
+ import {
10
+ ErrorInfo,
11
+ Indexable,
12
+ Operation,
13
+ JSONType,
14
+ CRUD,
15
+ HTTPMethod,
16
+ } from "@tellescope/types-utilities"
17
+ import {
18
+ EnduserSession,
19
+ ConfiguredSession,
20
+ JourneyState,
21
+ UserSession,
22
+ } from "@tellescope/types-models"
3
23
 
4
24
  import {
25
+ EscapeBuilder,
26
+
5
27
  booleanValidator,
6
28
  dateValidator,
7
29
  emailValidator,
@@ -32,6 +54,9 @@ import {
32
54
  messageTemplateTypeValidator,
33
55
  stringValidator250,
34
56
  stringValidator5000,
57
+ listOfDisplayNameInfo,
58
+ fileTypeValidator,
59
+ fileSizeValidator,
35
60
  } from "@tellescope/validation"
36
61
 
37
62
  import {
@@ -39,123 +64,126 @@ import {
39
64
  DEFAULT_OPERATIONS,
40
65
  PLACEHOLDER_ID,
41
66
  } from "@tellescope/constants"
67
+ export type RelationshipConstraint<T> = {
68
+ explanation: string; // human readable, for documentation purposes
69
+ evaluate: (v: T, dependencies: Indexable<Partial<DatabaseModel>>) => string | void;
70
+ }
71
+
72
+ export type DependencyAccessConstraint <T> = { type: 'dependency', foreignModel: ModelName, foreignField: string, accessField: keyof T }
73
+
74
+ export type AccessConstraint <T> = { type: 'creatorOnly' }
75
+ | { type: 'filter', field: string }
76
+ | DependencyAccessConstraint<T>
77
+
78
+ export type UniqueArrayConstraint <T> = { array: keyof T, itemKey?: string }
79
+
80
+ export type Constraint <T> = {
81
+ unique: (keyof T & string | UniqueArrayConstraint<T>)[];
82
+ globalUnique?: (keyof T)[];
83
+ relationship: RelationshipConstraint<Partial<T>>[];
84
+ access?: AccessConstraint<T>[];
85
+ }
86
+
87
+ export type Initializer <T, R> = (a: T, s: ConfiguredSession | EnduserSession) => R
88
+
89
+ export type EndpointOptions = {
90
+ // parameters used for endpoint that aren't stored in the model
91
+ parameters?: { [index: string]: EscapeBuilder<any> },
92
+ }
93
+
94
+ export type DependencyDeletionAction = 'delete' | 'unset' | 'setNull' | 'nop'
95
+ export type DependecyRelationship = 'foreignKey' | 'value'
96
+
97
+ export type Dependency <T=DatabaseRecord> = {
98
+ dependsOn: ModelName[], // list of => OR, multiple dependency records => AND
99
+ dependencyField: string,
100
+ relationship: DependecyRelationship,
101
+ onDependencyDelete: DependencyDeletionAction,
102
+ getDependentValues?: (t: T) => JSONType[], // for accessing the values of a Dependency
103
+ filterByDependency?: (foreignValue: JSONType, foreignModel?: DatabaseModel) => { // for filtering against a Dependency
104
+ field: string,
105
+ value: JSONType | 'any',
106
+ },
107
+ }
108
+
109
+ export type ModelFieldInfo <T, R> = {
110
+ validator: EscapeBuilder<R>,
111
+ readonly?: boolean,
112
+ required?: boolean,
113
+ updatesDisabled?: boolean,
114
+ examples?: JSONType[],
115
+ initializer?: Initializer<Partial<T>, R>, // should include the required fields of T, not just partial
116
+ dependencies?: Dependency<Partial<T>>[],
117
+ }
118
+
119
+ export type ModelFields<T> = {
120
+ [K in keyof T]: ModelFieldInfo<T, T[K]>
121
+ }
122
+ export type extractFields<Type> = Type extends ModelFields<infer X> ? X : never
123
+
124
+ type ArgumentInfo = {
125
+ description?: string;
126
+ }
127
+
128
+ type ActionInfo = {
129
+ name?: string,
130
+ description?: string,
131
+ notes?: string[],
132
+ warnings?: string[],
133
+ }
134
+
135
+ type CustomAction <P=any, R=any> = {
136
+ op: Operation | 'custom',
137
+ access: CRUD,
138
+ // parameters: InputValidation<P>,
139
+ parameters: ModelFields<P>,
140
+ returns: R extends Array<any> ? ModelFieldInfo<any, R> : ModelFields<R>,
141
+ path?: string,
142
+ method?: HTTPMethod,
143
+ enduserOnly?: boolean,
144
+ } & ActionInfo
145
+
146
+ export type EnduserAction = {
147
+ field?: string,
148
+ } & ActionInfo
149
+
150
+ type CustomActionsForModel = {
151
+ [K in ModelName]: { [index: string]: CustomAction }
152
+ }
153
+
154
+ type ReadFilter <T> = { [K in keyof T]?: { required: boolean } }
155
+
42
156
 
43
- // type RelationshipConstraint<T> = {
44
- // explanation: string; // human readable, for documentation purposes
45
- // evaluate: (v: T, dependencies: Indexable<Partial<DatabaseModel>>) => string | void;
46
- // }
47
-
48
- // type DependencyAccessConstraint <T> = { type: 'dependency', foreignModel: ModelName, foreignField: string, accessField: keyof T }
49
-
50
- // type AccessConstraint <T> = { type: 'creatorOnly' }
51
- // | { type: 'filter', field: string }
52
- // | DependencyAccessConstraint<T>
53
-
54
- // type UniqueArrayConstraint <T> = { array: keyof T, itemKey?: string }
55
-
56
- // type Constraint <T> = {
57
- // unique: (keyof T & string | UniqueArrayConstraint<T>)[];
58
- // globalUnique?: (keyof T)[];
59
- // relationship: RelationshipConstraint<Partial<T>>[];
60
- // access?: AccessConstraint<T>[];
61
- // }
62
-
63
- // type Initializer <T, R> = (a: T, s: ConfiguredSession) => R
64
-
65
- // type EndpointOptions = {
66
- // // parameters used for endpoint that aren't stored in the model
67
- // parameters?: { [index: string]: EscapeBuilder<any> },
68
- // }
69
-
70
- // type DependencyDeletionAction = 'delete' | 'unset' | 'setNull' | 'nop'
71
- // type DependecyRelationship = 'foreignKey' | 'value'
72
-
73
- // type Dependency <T=DatabaseRecord> = {
74
- // dependsOn: ModelName[], // list of => OR, multiple dependency records => AND
75
- // dependencyField: string,
76
- // relationship: DependecyRelationship,
77
- // onDependencyDelete: DependencyDeletionAction,
78
- // getDependentValues?: (t: T) => JSONType[], // for accessing the values of a Dependency
79
- // filterByDependency?: (foreignValue: JSONType, foreignModel?: DatabaseModel) => { // for filtering against a Dependency
80
- // field: string,
81
- // value: JSONType | 'any',
82
- // },
83
- // }
84
-
85
- // type ModelFieldInfo <T, R> = {
86
- // validator: EscapeBuilder<R>,
87
- // readonly?: boolean,
88
- // required?: boolean,
89
- // updatesDisabled?: boolean,
90
- // examples?: JSONType[],
91
- // initializer?: Initializer<Partial<T>, R>, // should include the required fields of T, not just partial
92
- // dependencies?: Dependency<Partial<T>>[],
93
- // }
94
-
95
- // export type ModelFields<T> = {
96
- // [K in keyof T]: ModelFieldInfo<T, T[K]>
97
- // }
98
- // type extractFields<Type> = Type extends ModelFields<infer X> ? X : never
99
-
100
- // type ArgumentInfo = {
101
- // description?: string;
102
- // }
103
-
104
- // type ActionInfo = {
105
- // name?: string,
106
- // description?: string,
107
- // notes?: string[],
108
- // warnings?: string[],
109
- // }
110
-
111
- // type CustomAction <P=any, R=any> = {
112
- // op: Operation | 'custom',
113
- // access: CRUD,
114
- // // parameters: InputValidation<P>,
115
- // parameters: ModelFields<P>,
116
- // returns: ModelFields<R>,
117
- // path?: string,
118
- // method?: HTTPMethod,
119
- // } & ActionInfo
120
-
121
- // type CustomActionsForModel = {
122
- // [K in ModelName]: { [index: string]: CustomAction }
123
- // }
124
-
125
- // type ReadFilter <T> = { [K in keyof T]?: { required: boolean } }
126
-
127
-
128
- // // m is the original model (or undefined, if create)
129
- // // allows for easier event handling based on specific updates (by comparing args to pre-update model)
130
- // type SideEffectHandler <T, O=any> = (args: Partial<T>[], m: (Partial<T> | undefined)[] | undefined, n: (Partial<T> & { _id: ObjectId })[], s: ConfiguredSession, o: O) => Promise<ErrorInfo[]>;
131
-
132
- // type SideEffect = {
133
- // name: string;
134
- // description: string;
135
- // }
136
-
137
- // export type Model<T, N extends ModelName> = {
138
- // info: {
139
- // name?: string,
140
- // description?: string,
141
- // sideEffects?: { [K in Operation]?: SideEffect[] }
142
- // },
143
- // fields: ModelFields<T>,
144
- // constraints: Constraint<T>,
145
- // defaultActions: { [K in Operation]?: ActionInfo },
146
- // customActions: CustomActionsForModel[N],
147
- // readFilter?: ReadFilter<T>,
148
- // options?: {
149
- // create?: EndpointOptions,
150
- // }
151
- // }
152
- // type extractModelType<Type> = Type extends Model<infer T, infer N> ? T : never
153
-
154
- // type DatabaseModelForName = ToServerModels<ModelForName>
155
-
156
- // type Schema = {
157
- // [N in keyof DatabaseModelForName]: Model<DatabaseModelForName[N], ModelName>
158
- // }
157
+ // m is the original model (or undefined, if create)
158
+ // allows for easier event handling based on specific updates (by comparing args to pre-update model)
159
+ export type SideEffectHandler <T, O=any> = (args: Partial<T>[], m: (Partial<T> | undefined)[] | undefined, n: (Partial<T> & { _id: ObjectId })[], s: ConfiguredSession | EnduserSession, o: O) => Promise<ErrorInfo[]>;
160
+
161
+ type SideEffect = {
162
+ name: string;
163
+ description: string;
164
+ }
165
+
166
+ export type Model<T, N extends ModelName> = {
167
+ info: {
168
+ name?: string,
169
+ description?: string,
170
+ sideEffects?: { [K in Operation]?: SideEffect[] }
171
+ },
172
+ fields: ModelFields<T>,
173
+ constraints: Constraint<T>,
174
+ defaultActions: { [k in Operation]?: ActionInfo },
175
+ enduserActions?: { [index: string]: EnduserAction },
176
+ customActions: CustomActionsForModel[N],
177
+ readFilter?: ReadFilter<T>,
178
+ options?: {
179
+ create?: EndpointOptions,
180
+ }
181
+ }
182
+ export type extractModelType<Type> = Type extends Model<infer T, infer N> ? T : never
183
+
184
+ export type Schema = {
185
+ [N in keyof ServerModelForName]: Model<ServerModelForName[N], ModelName>
186
+ }
159
187
 
160
188
  const sideEffects = {
161
189
  trackJourneyEngagement: {
@@ -169,6 +197,10 @@ const sideEffects = {
169
197
  sendSMSMessages: {
170
198
  name: "sendSMSMessages",
171
199
  description: "Sends emails if logOnly is not true"
200
+ },
201
+ updateChatroomCache: {
202
+ name: "updateChatroomCache",
203
+ description: "Updates the chatroom with a preview of the recent message and sender"
172
204
  }
173
205
  }
174
206
  export type SideEffectNames = keyof typeof sideEffects
@@ -198,7 +230,11 @@ export type BuiltInFields_T = typeof BuiltInFields
198
230
  export type CustomActions = {
199
231
  api_keys: {
200
232
  create: CustomAction<{}, { id: string, key: string}>,
201
- }
233
+ },
234
+ files: {
235
+ prepare_file_upload: CustomAction<{ name: string, size: number, type: string }, { presignedUpload: object, file: File }>,
236
+ file_download_URL: CustomAction<{ secureName: string }, { downloadURL: string }>,
237
+ },
202
238
  journeys: {
203
239
  update_state: CustomAction<{ updates: JourneyState, id: string, name: string }, {}>,
204
240
  },
@@ -208,6 +244,12 @@ export type CustomActions = {
208
244
  { id: string, authToken: string },
209
245
  { isAuthenticated: true, enduser: Enduser } | { isAuthenticated: false, enduser: null }
210
246
  >,
247
+ refresh_session: CustomAction<{}, { enduser: Enduser, authToken: string }>,
248
+ logout: CustomAction<{ }, { }>,
249
+ },
250
+ users: {
251
+ display_names: CustomAction<{ }, { fname: string, lname: string, id: string }[]>,
252
+ refresh_session: CustomAction<{}, { user: UserSession, authToken: string }>,
211
253
  }
212
254
  }
213
255
 
@@ -235,50 +277,26 @@ export const schema: SchemaV1 = {
235
277
  update: [sideEffects.trackJourneyEngagement],
236
278
  }
237
279
  },
238
- customActions: {
239
- set_password: {
240
- op: "custom", access: 'update', method: "post",
241
- name: 'Set enduser password',
242
- path: '/set-enduser-password',
243
- description: "Sets (or resets) an enduser's password. Minimum length 8 characters.",
244
- parameters: {
245
- id: { validator: mongoIdStringValidator, required: true },
246
- password: { validator: stringValidator100, required: true },
247
- },
248
- returns: { } //authToken: { validator: stringValidator5000 } },
249
- },
250
- is_authenticated: {
251
- op: "custom", access: 'read', method: "get",
252
- name: 'Check enduser authentication',
253
- path: '/enduser-is-authenticated',
254
- description: "Checks the validity of an enduser's authToken",
255
- parameters: {
256
- id: { validator: mongoIdStringValidator, required: true },
257
- authToken: { validator: stringValidator5000, required: true },
258
- },
259
- returns: {
260
- isAuthenticated: { validator: booleanValidator, required: true },
261
- enduser: { validator: 'enduser' },
262
- } as any // add enduser eventually, when validator defined
263
- },
264
- },
265
- publicActions: {
266
- login: {
267
- op: "custom", access: 'read', method: "post",
268
- name: 'Login enduser',
269
- path: '/login-enduser',
270
- description: "Generates an authentication token for access to enduser-facing endpoints",
271
- parameters: {
272
- id: { validator: mongoIdStringValidator },
273
- password: { validator: stringValidator100, required: true }, // required until optional challenge token available
274
- phone: { validator: phoneValidator },
275
- email: { validator: emailValidator },
276
- },
277
- returns: { authToken: { validator: stringValidator5000 } },
278
- },
280
+ constraints: {
281
+ unique: ['email', 'phone', 'externalId'],
282
+ relationship: [
283
+ {
284
+ explanation: 'One of email or phone is required',
285
+ evaluate: ({ email, phone }) => {
286
+ if (!(email || phone))
287
+ return 'One of email or phone is required'
288
+ }
289
+ }
290
+ ],
279
291
  },
292
+ defaultActions: DEFAULT_OPERATIONS,
293
+ enduserActions: { logout: {}, refresh_session: {} },
280
294
  fields: {
281
295
  ...BuiltInFields,
296
+ externalId: {
297
+ validator: stringValidator250,
298
+ examples: ['addfed3e-ddea-415b-b52b-df820c944dbb'],
299
+ },
282
300
  email: {
283
301
  validator: emailValidator,
284
302
  examples: ['test@tellescope.com'],
@@ -341,23 +359,84 @@ export const schema: SchemaV1 = {
341
359
  lastCommunication: {
342
360
  validator: dateValidator,
343
361
  },
362
+ avatar: {
363
+ validator: stringValidator100,
364
+ dependencies: [
365
+ {
366
+ dependsOn: ['files'],
367
+ dependencyField: 'secureName',
368
+ relationship: 'foreignKey',
369
+ onDependencyDelete: 'unset',
370
+ },
371
+ ]
372
+ },
344
373
  // recentMessagePreview: {
345
374
  // validator: stringValidator,
346
375
  // },
347
376
  },
348
- constraints: {
349
- unique: ['email', 'phone'],
350
- relationship: [
351
- {
352
- explanation: 'One of email or phone is required',
353
- evaluate: ({ email, phone }) => {
354
- if (!(email || phone))
355
- return 'One of email or phone is required'
356
- }
357
- }
358
- ],
377
+ customActions: {
378
+ set_password: {
379
+ op: "custom", access: 'update', method: "post",
380
+ name: 'Set enduser password',
381
+ path: '/set-enduser-password',
382
+ description: "Sets (or resets) an enduser's password. Minimum length 8 characters.",
383
+ parameters: {
384
+ id: { validator: mongoIdStringValidator, required: true },
385
+ password: { validator: stringValidator100, required: true },
386
+ },
387
+ returns: { } //authToken: { validator: stringValidator5000 } },
388
+ },
389
+ is_authenticated: {
390
+ op: "custom", access: 'read', method: "get",
391
+ name: 'Check enduser authentication',
392
+ path: '/enduser-is-authenticated',
393
+ description: "Checks the validity of an enduser's authToken",
394
+ parameters: {
395
+ id: { validator: mongoIdStringValidator, required: true },
396
+ authToken: { validator: stringValidator5000, required: true },
397
+ },
398
+ returns: {
399
+ isAuthenticated: { validator: booleanValidator, required: true },
400
+ enduser: { validator: 'enduser' },
401
+ } as any // todo: add enduser eventually, when validator defined
402
+ },
403
+ refresh_session: {
404
+ op: "custom", access: 'update', method: "post",
405
+ name: 'Refresh enduser authentication',
406
+ path: '/refresh-enduser-session',
407
+ description: "When called by an authenticated enduser, generates a new session",
408
+ parameters: { },
409
+ enduserOnly: true,
410
+ returns: {
411
+ authToken: { validator: stringValidator, required: true },
412
+ enduser: { validator: 'enduser' },
413
+ } as any // todo: add enduser eventually, when validator defined
414
+ },
415
+ logout: {
416
+ op: "custom", access: 'update', method: "post",
417
+ name: 'Logout enduser',
418
+ path: '/logout-enduser',
419
+ description: "Logs out an enduser",
420
+ parameters: {},
421
+ returns: {},
422
+ },
423
+ },
424
+ publicActions: {
425
+ login: {
426
+ op: "custom", access: 'read', method: "post",
427
+ name: 'Login enduser',
428
+ path: '/login-enduser',
429
+ description: "Generates an authentication token for access to enduser-facing endpoints",
430
+ enduserOnly: true, // implemented as authenticate in enduser sdk only
431
+ parameters: {
432
+ id: { validator: mongoIdStringValidator },
433
+ password: { validator: stringValidator100, required: true }, // required until optional challenge token available
434
+ phone: { validator: phoneValidator },
435
+ email: { validator: emailValidator },
436
+ },
437
+ returns: { authToken: { validator: stringValidator5000 } },
438
+ },
359
439
  },
360
- defaultActions: DEFAULT_OPERATIONS,
361
440
  },
362
441
  api_keys: {
363
442
  info: {},
@@ -575,7 +654,7 @@ export const schema: SchemaV1 = {
575
654
  validator: mongoIdStringValidator,
576
655
  examples: [PLACEHOLDER_ID],
577
656
  readonly: true,
578
- initializer: (a, s) => (s as UserSession).userId,
657
+ initializer: (a, s) => (s as UserSession).id,
579
658
  },
580
659
  subject: {
581
660
  validator: stringValidator,
@@ -695,7 +774,7 @@ export const schema: SchemaV1 = {
695
774
  businessUserId: {
696
775
  validator: mongoIdStringValidator,
697
776
  readonly: true, // default to only self-sending, for now
698
- initializer: (a, s) => (s as UserSession).userId,
777
+ initializer: (a, s) => (s as UserSession).id,
699
778
  dependencies: [{
700
779
  dependsOn: ['users'],
701
780
  dependencyField: '_id',
@@ -740,6 +819,9 @@ export const schema: SchemaV1 = {
740
819
  },
741
820
  fields: {
742
821
  ...BuiltInFields,
822
+ title: {
823
+ validator: stringValidator100,
824
+ },
743
825
  type: {
744
826
  validator: chatRoomTypeValidator,
745
827
  initializer: () => 'internal'
@@ -759,6 +841,16 @@ export const schema: SchemaV1 = {
759
841
  enduserIds: {
760
842
  validator: listOfMongoIdStringValidator,
761
843
  // add pull dependency for enduser deletion?
844
+ },
845
+ recentMessage: {
846
+ validator: stringValidator,
847
+ initializer: () => '',
848
+ readonly: true,
849
+ },
850
+ recentSender: {
851
+ validator: mongoIdStringValidator,
852
+ initializer: () => '',
853
+ readonly: true,
762
854
  }
763
855
  },
764
856
  defaultActions: DEFAULT_OPERATIONS,
@@ -766,12 +858,22 @@ export const schema: SchemaV1 = {
766
858
  customActions: {},
767
859
  },
768
860
  chats: {
769
- info: {},
861
+ info: {
862
+ sideEffects: {
863
+ create: [sideEffects.updateChatroomCache]
864
+ }
865
+ },
770
866
  constraints: {
771
867
  unique: [],
772
868
  relationship: [],
773
869
  access: [{ type: 'dependency', foreignModel: 'chat_rooms', foreignField: '_id', accessField: 'roomId' }]
774
870
  },
871
+ defaultActions: { create: {}, read: {}, readMany: {}, delete: {} }, // avoid createMany for now
872
+ readFilter: {
873
+ roomId: { required: true },
874
+ },
875
+ enduserActions: { create: {}, read: {}, readMany: {} },
876
+ customActions: {},
775
877
  fields: {
776
878
  ...BuiltInFields,
777
879
  roomId: {
@@ -789,7 +891,7 @@ export const schema: SchemaV1 = {
789
891
  senderId: {
790
892
  validator: mongoIdStringValidator,
791
893
  readonly: true, // create a separate endpoint for storing enduser chats
792
- initializer: (a, s) => (s as UserSession).userId ?? (s as EnduserSession).enduserId,
894
+ initializer: (a, s) => (s as UserSession).id ?? (s as EnduserSession).id,
793
895
  examples: [PLACEHOLDER_ID],
794
896
  dependencies: [{ // can be userId or enduserId
795
897
  dependsOn: ['users', 'endusers'],
@@ -817,12 +919,6 @@ export const schema: SchemaV1 = {
817
919
  validator: idStringToDateValidator,
818
920
  },
819
921
  },
820
- defaultActions: DEFAULT_OPERATIONS,
821
- readFilter: {
822
- roomId: { required: true },
823
- },
824
- enduserActions: { create: {}, read: {}, readMany: {} },
825
- customActions: {},
826
922
  },
827
923
  users: {
828
924
  info: {},
@@ -832,7 +928,31 @@ export const schema: SchemaV1 = {
832
928
  relationship: [],
833
929
  },
834
930
  defaultActions: { read: {}, readMany: {} },
835
- customActions: {},
931
+ customActions: {
932
+ display_names: {
933
+ op: "custom", access: 'read', method: "get",
934
+ name: 'User Display Names',
935
+ path: '/user-display-names',
936
+ description: "Gets display names for users, accessible by endusers",
937
+ parameters: {},
938
+ returns: {
939
+ validator: listOfDisplayNameInfo
940
+ },
941
+ },
942
+ refresh_session: {
943
+ op: "custom", access: 'update', method: "post",
944
+ name: 'Refresh enduser authentication',
945
+ path: '/refresh-session',
946
+ description: "When called by an authenticated user, generates a new session",
947
+ parameters: { },
948
+ returns: {
949
+ authToken: { validator: stringValidator, required: true },
950
+ enduser: { validator: 'user' },
951
+ } as any // add enduser eventually, when validator defined
952
+ },
953
+
954
+ },
955
+ enduserActions: { display_names: {} },
836
956
  fields: {
837
957
  ...BuiltInFields,
838
958
  email: {
@@ -865,6 +985,17 @@ export const schema: SchemaV1 = {
865
985
  roles: {
866
986
  validator: listOfStringsValidator,
867
987
  },
988
+ avatar: {
989
+ validator: stringValidator100,
990
+ dependencies: [
991
+ {
992
+ dependsOn: ['files'],
993
+ dependencyField: 'secureName',
994
+ relationship: 'foreignKey',
995
+ onDependencyDelete: 'unset',
996
+ },
997
+ ]
998
+ },
868
999
  }
869
1000
  },
870
1001
  templates: {
@@ -897,5 +1028,72 @@ export const schema: SchemaV1 = {
897
1028
  initializer: () => 'enduser'
898
1029
  },
899
1030
  }
1031
+ },
1032
+ files: {
1033
+ info: {},
1034
+ constraints: { unique: [], relationship: [] },
1035
+ defaultActions: { read: {}, readMany: {}, update: {} },
1036
+ fields: {
1037
+ ...BuiltInFields,
1038
+ name: {
1039
+ validator: stringValidator250,
1040
+ required: true,
1041
+ },
1042
+ size: {
1043
+ validator: fileSizeValidator,
1044
+ required: true,
1045
+ },
1046
+ type: {
1047
+ validator: fileTypeValidator,
1048
+ required: true
1049
+ },
1050
+ secureName: {
1051
+ validator: stringValidator250,
1052
+ readonly: true,
1053
+ },
1054
+ },
1055
+ enduserActions: { prepare_file_upload: {} },
1056
+ customActions: {
1057
+ prepare_file_upload: {
1058
+ op: "custom", access: 'create', method: "post",
1059
+ name: 'Prepare File Upload',
1060
+ path: '/prepare-file-upload',
1061
+ description: "Generates an upload link for a file, storing metadata as a File record.",
1062
+ parameters: {
1063
+ name: {
1064
+ validator: stringValidator250,
1065
+ required: true,
1066
+ },
1067
+ size: {
1068
+ validator: fileSizeValidator,
1069
+ required: true,
1070
+ },
1071
+ type: {
1072
+ validator: fileTypeValidator,
1073
+ required: true
1074
+ },
1075
+ },
1076
+ returns: {
1077
+ presignedUpload: {
1078
+ validator: objectAnyFieldsValidator,
1079
+ },
1080
+ file: {
1081
+ validator: 'file' as any, // todo: add file validator
1082
+ },
1083
+ },
1084
+ },
1085
+ file_download_URL: {
1086
+ op: "custom", access: 'read', method: "get",
1087
+ name: 'Generate File Download',
1088
+ path: '/file-download-link',
1089
+ description: "Generates a temporary download link for a file.",
1090
+ parameters: {
1091
+ secureName: { validator: stringValidator250 },
1092
+ },
1093
+ returns: {
1094
+ downloadURL: { validator: stringValidator250 },
1095
+ },
1096
+ },
1097
+ },
900
1098
  }
901
1099
  }