@oneuptime/common 8.0.5574 → 8.0.5576
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/DatabaseModels/Index.ts +4 -0
- package/Models/DatabaseModels/StatusPagePrivateUserSession.ts +413 -0
- package/Models/DatabaseModels/UserSession.ts +318 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1762890441920-MigrationName.ts +91 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/StatusPagePrivateUserSessionService.ts +368 -0
- package/Server/Services/UserSessionService.ts +350 -0
- package/Server/Utils/Cookie.ts +122 -5
- package/Server/Utils/Express.ts +100 -0
- package/Server/Utils/JsonWebToken.ts +8 -1
- package/Types/CookieName.ts +1 -0
- package/Types/JsonWebTokenData.ts +1 -0
- package/Types/Text.ts +15 -0
- package/UI/Utils/API/API.ts +39 -1
- package/Utils/API.ts +74 -5
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUserSession.js +456 -0
- package/build/dist/Models/DatabaseModels/StatusPagePrivateUserSession.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserSession.js +356 -0
- package/build/dist/Models/DatabaseModels/UserSession.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1762890441920-MigrationName.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1762890441920-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +214 -0
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -0
- package/build/dist/Server/Services/UserSessionService.js +202 -0
- package/build/dist/Server/Services/UserSessionService.js.map +1 -0
- package/build/dist/Server/Utils/Cookie.js +74 -7
- package/build/dist/Server/Utils/Cookie.js.map +1 -1
- package/build/dist/Server/Utils/Express.js +62 -0
- package/build/dist/Server/Utils/Express.js.map +1 -1
- package/build/dist/Server/Utils/JsonWebToken.js +8 -2
- package/build/dist/Server/Utils/JsonWebToken.js.map +1 -1
- package/build/dist/Types/CookieName.js +1 -0
- package/build/dist/Types/CookieName.js.map +1 -1
- package/build/dist/Types/Text.js +9 -0
- package/build/dist/Types/Text.js.map +1 -1
- package/build/dist/UI/Utils/API/API.js +27 -0
- package/build/dist/UI/Utils/API/API.js.map +1 -1
- package/build/dist/Utils/API.js +36 -6
- package/build/dist/Utils/API.js.map +1 -1
- package/package.json +146 -146
|
@@ -118,6 +118,7 @@ import StatusPageHistoryChartBarColorRule from "./StatusPageHistoryChartBarColor
|
|
|
118
118
|
import StatusPageOwnerTeam from "./StatusPageOwnerTeam";
|
|
119
119
|
import StatusPageOwnerUser from "./StatusPageOwnerUser";
|
|
120
120
|
import StatusPagePrivateUser from "./StatusPagePrivateUser";
|
|
121
|
+
import StatusPagePrivateUserSession from "./StatusPagePrivateUserSession";
|
|
121
122
|
import StatusPageResource from "./StatusPageResource";
|
|
122
123
|
import StatusPageSCIM from "./StatusPageSCIM";
|
|
123
124
|
import StatusPageSSO from "./StatusPageSso";
|
|
@@ -130,6 +131,7 @@ import TeamComplianceSetting from "./TeamComplianceSetting";
|
|
|
130
131
|
import TelemetryService from "./TelemetryService";
|
|
131
132
|
import UsageBilling from "./TelemetryUsageBilling";
|
|
132
133
|
import User from "./User";
|
|
134
|
+
import UserSession from "./UserSession";
|
|
133
135
|
import UserCall from "./UserCall";
|
|
134
136
|
// Notification Methods
|
|
135
137
|
import UserEmail from "./UserEmail";
|
|
@@ -266,6 +268,7 @@ const AllModelTypes: Array<{
|
|
|
266
268
|
StatusPageFooterLink,
|
|
267
269
|
StatusPageHeaderLink,
|
|
268
270
|
StatusPagePrivateUser,
|
|
271
|
+
StatusPagePrivateUserSession,
|
|
269
272
|
StatusPageHistoryChartBarColorRule,
|
|
270
273
|
|
|
271
274
|
ScheduledMaintenanceState,
|
|
@@ -375,6 +378,7 @@ const AllModelTypes: Array<{
|
|
|
375
378
|
ProbeOwnerTeam,
|
|
376
379
|
ProbeOwnerUser,
|
|
377
380
|
|
|
381
|
+
UserSession,
|
|
378
382
|
UserTotpAuth,
|
|
379
383
|
UserWebAuthn,
|
|
380
384
|
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel";
|
|
2
|
+
import Project from "./Project";
|
|
3
|
+
import StatusPage from "./StatusPage";
|
|
4
|
+
import StatusPagePrivateUser from "./StatusPagePrivateUser";
|
|
5
|
+
import Route from "../../Types/API/Route";
|
|
6
|
+
import { PlanType } from "../../Types/Billing/SubscriptionPlan";
|
|
7
|
+
import AllowAccessIfSubscriptionIsUnpaid from "../../Types/Database/AccessControl/AllowAccessIfSubscriptionIsUnpaid";
|
|
8
|
+
import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl";
|
|
9
|
+
import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl";
|
|
10
|
+
import TableBillingAccessControl from "../../Types/Database/AccessControl/TableBillingAccessControl";
|
|
11
|
+
import CanAccessIfCanReadOn from "../../Types/Database/CanAccessIfCanReadOn";
|
|
12
|
+
import ColumnLength from "../../Types/Database/ColumnLength";
|
|
13
|
+
import ColumnType from "../../Types/Database/ColumnType";
|
|
14
|
+
import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint";
|
|
15
|
+
import TableColumn from "../../Types/Database/TableColumn";
|
|
16
|
+
import TableColumnType from "../../Types/Database/TableColumnType";
|
|
17
|
+
import TableMetadata from "../../Types/Database/TableMetadata";
|
|
18
|
+
import TenantColumn from "../../Types/Database/TenantColumn";
|
|
19
|
+
import HashedString from "../../Types/HashedString";
|
|
20
|
+
import IconProp from "../../Types/Icon/IconProp";
|
|
21
|
+
import { JSONObject } from "../../Types/JSON";
|
|
22
|
+
import ObjectID from "../../Types/ObjectID";
|
|
23
|
+
import { Column, Entity, Index, JoinColumn, ManyToOne } from "typeorm";
|
|
24
|
+
|
|
25
|
+
@AllowAccessIfSubscriptionIsUnpaid()
|
|
26
|
+
@TableBillingAccessControl({
|
|
27
|
+
create: PlanType.Growth,
|
|
28
|
+
read: PlanType.Growth,
|
|
29
|
+
update: PlanType.Growth,
|
|
30
|
+
delete: PlanType.Growth,
|
|
31
|
+
})
|
|
32
|
+
@CanAccessIfCanReadOn("statusPage")
|
|
33
|
+
@TenantColumn("projectId")
|
|
34
|
+
@TableAccessControl({
|
|
35
|
+
create: [],
|
|
36
|
+
read: [],
|
|
37
|
+
delete: [],
|
|
38
|
+
update: [],
|
|
39
|
+
})
|
|
40
|
+
@CrudApiEndpoint(new Route("/status-page-private-user-session"))
|
|
41
|
+
@Entity({
|
|
42
|
+
name: "StatusPagePrivateUserSession",
|
|
43
|
+
})
|
|
44
|
+
@TableMetadata({
|
|
45
|
+
tableName: "StatusPagePrivateUserSession",
|
|
46
|
+
singularName: "Status Page Private User Session",
|
|
47
|
+
pluralName: "Status Page Private User Sessions",
|
|
48
|
+
icon: IconProp.Lock,
|
|
49
|
+
tableDescription:
|
|
50
|
+
"Stores status page private user sessions, refresh tokens, and device metadata for secure access control.",
|
|
51
|
+
})
|
|
52
|
+
export default class StatusPagePrivateUserSession extends BaseModel {
|
|
53
|
+
@ColumnAccessControl({
|
|
54
|
+
create: [],
|
|
55
|
+
read: [],
|
|
56
|
+
update: [],
|
|
57
|
+
})
|
|
58
|
+
@TableColumn({
|
|
59
|
+
manyToOneRelationColumn: "projectId",
|
|
60
|
+
type: TableColumnType.Entity,
|
|
61
|
+
modelType: Project,
|
|
62
|
+
title: "Project",
|
|
63
|
+
description: "Project that owns this private status page session.",
|
|
64
|
+
})
|
|
65
|
+
@ManyToOne(
|
|
66
|
+
() => {
|
|
67
|
+
return Project;
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
eager: false,
|
|
71
|
+
nullable: true,
|
|
72
|
+
onDelete: "CASCADE",
|
|
73
|
+
orphanedRowAction: "nullify",
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
@JoinColumn({ name: "projectId" })
|
|
77
|
+
public project?: Project = undefined;
|
|
78
|
+
|
|
79
|
+
@ColumnAccessControl({
|
|
80
|
+
create: [],
|
|
81
|
+
read: [],
|
|
82
|
+
update: [],
|
|
83
|
+
})
|
|
84
|
+
@Index()
|
|
85
|
+
@TableColumn({
|
|
86
|
+
type: TableColumnType.ObjectID,
|
|
87
|
+
title: "Project ID",
|
|
88
|
+
description: "Project identifier for this session.",
|
|
89
|
+
required: true,
|
|
90
|
+
canReadOnRelationQuery: true,
|
|
91
|
+
})
|
|
92
|
+
@Column({
|
|
93
|
+
type: ColumnType.ObjectID,
|
|
94
|
+
nullable: false,
|
|
95
|
+
transformer: ObjectID.getDatabaseTransformer(),
|
|
96
|
+
})
|
|
97
|
+
public projectId?: ObjectID = undefined;
|
|
98
|
+
|
|
99
|
+
@ColumnAccessControl({
|
|
100
|
+
create: [],
|
|
101
|
+
read: [],
|
|
102
|
+
update: [],
|
|
103
|
+
})
|
|
104
|
+
@TableColumn({
|
|
105
|
+
manyToOneRelationColumn: "statusPageId",
|
|
106
|
+
type: TableColumnType.Entity,
|
|
107
|
+
modelType: StatusPage,
|
|
108
|
+
title: "Status Page",
|
|
109
|
+
description: "Status page associated with this session.",
|
|
110
|
+
})
|
|
111
|
+
@ManyToOne(
|
|
112
|
+
() => {
|
|
113
|
+
return StatusPage;
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
eager: false,
|
|
117
|
+
nullable: true,
|
|
118
|
+
onDelete: "CASCADE",
|
|
119
|
+
orphanedRowAction: "nullify",
|
|
120
|
+
},
|
|
121
|
+
)
|
|
122
|
+
@JoinColumn({ name: "statusPageId" })
|
|
123
|
+
public statusPage?: StatusPage = undefined;
|
|
124
|
+
|
|
125
|
+
@ColumnAccessControl({
|
|
126
|
+
create: [],
|
|
127
|
+
read: [],
|
|
128
|
+
update: [],
|
|
129
|
+
})
|
|
130
|
+
@Index()
|
|
131
|
+
@TableColumn({
|
|
132
|
+
type: TableColumnType.ObjectID,
|
|
133
|
+
title: "Status Page ID",
|
|
134
|
+
description: "Identifier for the status page.",
|
|
135
|
+
required: true,
|
|
136
|
+
})
|
|
137
|
+
@Column({
|
|
138
|
+
type: ColumnType.ObjectID,
|
|
139
|
+
nullable: false,
|
|
140
|
+
transformer: ObjectID.getDatabaseTransformer(),
|
|
141
|
+
})
|
|
142
|
+
public statusPageId?: ObjectID = undefined;
|
|
143
|
+
|
|
144
|
+
@ColumnAccessControl({
|
|
145
|
+
create: [],
|
|
146
|
+
read: [],
|
|
147
|
+
update: [],
|
|
148
|
+
})
|
|
149
|
+
@TableColumn({
|
|
150
|
+
manyToOneRelationColumn: "statusPagePrivateUserId",
|
|
151
|
+
type: TableColumnType.Entity,
|
|
152
|
+
modelType: StatusPagePrivateUser,
|
|
153
|
+
title: "Status Page Private User",
|
|
154
|
+
description: "Private user record associated with this session.",
|
|
155
|
+
})
|
|
156
|
+
@ManyToOne(
|
|
157
|
+
() => {
|
|
158
|
+
return StatusPagePrivateUser;
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
eager: false,
|
|
162
|
+
nullable: false,
|
|
163
|
+
onDelete: "CASCADE",
|
|
164
|
+
orphanedRowAction: "delete",
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
@JoinColumn({ name: "statusPagePrivateUserId" })
|
|
168
|
+
public statusPagePrivateUser?: StatusPagePrivateUser = undefined;
|
|
169
|
+
|
|
170
|
+
@ColumnAccessControl({
|
|
171
|
+
create: [],
|
|
172
|
+
read: [],
|
|
173
|
+
update: [],
|
|
174
|
+
})
|
|
175
|
+
@Index()
|
|
176
|
+
@TableColumn({
|
|
177
|
+
type: TableColumnType.ObjectID,
|
|
178
|
+
title: "Status Page Private User ID",
|
|
179
|
+
description: "Identifier for the status page private user.",
|
|
180
|
+
required: true,
|
|
181
|
+
canReadOnRelationQuery: true,
|
|
182
|
+
})
|
|
183
|
+
@Column({
|
|
184
|
+
type: ColumnType.ObjectID,
|
|
185
|
+
nullable: false,
|
|
186
|
+
transformer: ObjectID.getDatabaseTransformer(),
|
|
187
|
+
})
|
|
188
|
+
public statusPagePrivateUserId?: ObjectID = undefined;
|
|
189
|
+
|
|
190
|
+
@ColumnAccessControl({
|
|
191
|
+
create: [],
|
|
192
|
+
read: [],
|
|
193
|
+
update: [],
|
|
194
|
+
})
|
|
195
|
+
@Index({ unique: true })
|
|
196
|
+
@TableColumn({
|
|
197
|
+
type: TableColumnType.HashedString,
|
|
198
|
+
title: "Refresh Token",
|
|
199
|
+
description: "Hashed refresh token for the private user session.",
|
|
200
|
+
required: true,
|
|
201
|
+
hideColumnInDocumentation: true,
|
|
202
|
+
})
|
|
203
|
+
@Column({
|
|
204
|
+
type: ColumnType.HashedString,
|
|
205
|
+
length: ColumnLength.HashedString,
|
|
206
|
+
nullable: false,
|
|
207
|
+
unique: true,
|
|
208
|
+
transformer: HashedString.getDatabaseTransformer(),
|
|
209
|
+
})
|
|
210
|
+
public refreshToken?: HashedString = undefined;
|
|
211
|
+
|
|
212
|
+
@ColumnAccessControl({
|
|
213
|
+
create: [],
|
|
214
|
+
read: [],
|
|
215
|
+
update: [],
|
|
216
|
+
})
|
|
217
|
+
@TableColumn({
|
|
218
|
+
type: TableColumnType.Date,
|
|
219
|
+
title: "Refresh Token Expires At",
|
|
220
|
+
description: "Expiration timestamp for the refresh token.",
|
|
221
|
+
required: true,
|
|
222
|
+
})
|
|
223
|
+
@Column({
|
|
224
|
+
type: ColumnType.Date,
|
|
225
|
+
nullable: false,
|
|
226
|
+
})
|
|
227
|
+
public refreshTokenExpiresAt?: Date = undefined;
|
|
228
|
+
|
|
229
|
+
@ColumnAccessControl({
|
|
230
|
+
create: [],
|
|
231
|
+
read: [],
|
|
232
|
+
update: [],
|
|
233
|
+
})
|
|
234
|
+
@TableColumn({
|
|
235
|
+
type: TableColumnType.Date,
|
|
236
|
+
title: "Last Active At",
|
|
237
|
+
description: "Last time this session was active.",
|
|
238
|
+
})
|
|
239
|
+
@Column({
|
|
240
|
+
type: ColumnType.Date,
|
|
241
|
+
nullable: true,
|
|
242
|
+
})
|
|
243
|
+
public lastActiveAt?: Date = undefined;
|
|
244
|
+
|
|
245
|
+
@ColumnAccessControl({
|
|
246
|
+
create: [],
|
|
247
|
+
read: [],
|
|
248
|
+
update: [],
|
|
249
|
+
})
|
|
250
|
+
@TableColumn({
|
|
251
|
+
type: TableColumnType.ShortText,
|
|
252
|
+
title: "Device Name",
|
|
253
|
+
description: "Friendly name for the device used to access the status page.",
|
|
254
|
+
})
|
|
255
|
+
@Column({
|
|
256
|
+
type: ColumnType.ShortText,
|
|
257
|
+
length: ColumnLength.ShortText,
|
|
258
|
+
nullable: true,
|
|
259
|
+
})
|
|
260
|
+
public deviceName?: string = undefined;
|
|
261
|
+
|
|
262
|
+
@ColumnAccessControl({
|
|
263
|
+
create: [],
|
|
264
|
+
read: [],
|
|
265
|
+
update: [],
|
|
266
|
+
})
|
|
267
|
+
@TableColumn({
|
|
268
|
+
type: TableColumnType.ShortText,
|
|
269
|
+
title: "Device Type",
|
|
270
|
+
description: "Type of device (desktop, mobile, etc).",
|
|
271
|
+
})
|
|
272
|
+
@Column({
|
|
273
|
+
type: ColumnType.ShortText,
|
|
274
|
+
length: ColumnLength.ShortText,
|
|
275
|
+
nullable: true,
|
|
276
|
+
})
|
|
277
|
+
public deviceType?: string = undefined;
|
|
278
|
+
|
|
279
|
+
@ColumnAccessControl({
|
|
280
|
+
create: [],
|
|
281
|
+
read: [],
|
|
282
|
+
update: [],
|
|
283
|
+
})
|
|
284
|
+
@TableColumn({
|
|
285
|
+
type: TableColumnType.ShortText,
|
|
286
|
+
title: "Device OS",
|
|
287
|
+
description: "Operating system reported for this session.",
|
|
288
|
+
})
|
|
289
|
+
@Column({
|
|
290
|
+
type: ColumnType.ShortText,
|
|
291
|
+
length: ColumnLength.ShortText,
|
|
292
|
+
nullable: true,
|
|
293
|
+
})
|
|
294
|
+
public deviceOS?: string = undefined;
|
|
295
|
+
|
|
296
|
+
@ColumnAccessControl({
|
|
297
|
+
create: [],
|
|
298
|
+
read: [],
|
|
299
|
+
update: [],
|
|
300
|
+
})
|
|
301
|
+
@TableColumn({
|
|
302
|
+
type: TableColumnType.ShortText,
|
|
303
|
+
title: "Browser",
|
|
304
|
+
description: "Browser or client application used for the session.",
|
|
305
|
+
})
|
|
306
|
+
@Column({
|
|
307
|
+
type: ColumnType.ShortText,
|
|
308
|
+
length: ColumnLength.ShortText,
|
|
309
|
+
nullable: true,
|
|
310
|
+
})
|
|
311
|
+
public deviceBrowser?: string = undefined;
|
|
312
|
+
|
|
313
|
+
@ColumnAccessControl({
|
|
314
|
+
create: [],
|
|
315
|
+
read: [],
|
|
316
|
+
update: [],
|
|
317
|
+
})
|
|
318
|
+
@TableColumn({
|
|
319
|
+
type: TableColumnType.ShortText,
|
|
320
|
+
title: "IP Address",
|
|
321
|
+
description: "IP address recorded for this session.",
|
|
322
|
+
})
|
|
323
|
+
@Column({
|
|
324
|
+
type: ColumnType.ShortText,
|
|
325
|
+
length: ColumnLength.ShortText,
|
|
326
|
+
nullable: true,
|
|
327
|
+
})
|
|
328
|
+
public ipAddress?: string = undefined;
|
|
329
|
+
|
|
330
|
+
@ColumnAccessControl({
|
|
331
|
+
create: [],
|
|
332
|
+
read: [],
|
|
333
|
+
update: [],
|
|
334
|
+
})
|
|
335
|
+
@TableColumn({
|
|
336
|
+
type: TableColumnType.VeryLongText,
|
|
337
|
+
title: "User Agent",
|
|
338
|
+
description: "User agent string supplied by the client.",
|
|
339
|
+
})
|
|
340
|
+
@Column({
|
|
341
|
+
type: ColumnType.VeryLongText,
|
|
342
|
+
nullable: true,
|
|
343
|
+
})
|
|
344
|
+
public userAgent?: string = undefined;
|
|
345
|
+
|
|
346
|
+
@ColumnAccessControl({
|
|
347
|
+
create: [],
|
|
348
|
+
read: [],
|
|
349
|
+
update: [],
|
|
350
|
+
})
|
|
351
|
+
@TableColumn({
|
|
352
|
+
type: TableColumnType.Boolean,
|
|
353
|
+
title: "Is Revoked",
|
|
354
|
+
description: "Indicates if the session has been revoked.",
|
|
355
|
+
isDefaultValueColumn: true,
|
|
356
|
+
defaultValue: false,
|
|
357
|
+
})
|
|
358
|
+
@Column({
|
|
359
|
+
type: ColumnType.Boolean,
|
|
360
|
+
nullable: false,
|
|
361
|
+
default: false,
|
|
362
|
+
})
|
|
363
|
+
public isRevoked?: boolean = undefined;
|
|
364
|
+
|
|
365
|
+
@ColumnAccessControl({
|
|
366
|
+
create: [],
|
|
367
|
+
read: [],
|
|
368
|
+
update: [],
|
|
369
|
+
})
|
|
370
|
+
@TableColumn({
|
|
371
|
+
type: TableColumnType.Date,
|
|
372
|
+
title: "Revoked At",
|
|
373
|
+
description: "Timestamp when the session was revoked, if applicable.",
|
|
374
|
+
})
|
|
375
|
+
@Column({
|
|
376
|
+
type: ColumnType.Date,
|
|
377
|
+
nullable: true,
|
|
378
|
+
})
|
|
379
|
+
public revokedAt?: Date = undefined;
|
|
380
|
+
|
|
381
|
+
@ColumnAccessControl({
|
|
382
|
+
create: [],
|
|
383
|
+
read: [],
|
|
384
|
+
update: [],
|
|
385
|
+
})
|
|
386
|
+
@TableColumn({
|
|
387
|
+
type: TableColumnType.ShortText,
|
|
388
|
+
title: "Revoked Reason",
|
|
389
|
+
description: "Reason provided for revoking this session.",
|
|
390
|
+
})
|
|
391
|
+
@Column({
|
|
392
|
+
type: ColumnType.ShortText,
|
|
393
|
+
length: ColumnLength.ShortText,
|
|
394
|
+
nullable: true,
|
|
395
|
+
})
|
|
396
|
+
public revokedReason?: string = undefined;
|
|
397
|
+
|
|
398
|
+
@ColumnAccessControl({
|
|
399
|
+
create: [],
|
|
400
|
+
read: [],
|
|
401
|
+
update: [],
|
|
402
|
+
})
|
|
403
|
+
@TableColumn({
|
|
404
|
+
type: TableColumnType.JSON,
|
|
405
|
+
title: "Additional Info",
|
|
406
|
+
description: "Flexible JSON payload for storing structured metadata.",
|
|
407
|
+
})
|
|
408
|
+
@Column({
|
|
409
|
+
type: ColumnType.JSON,
|
|
410
|
+
nullable: true,
|
|
411
|
+
})
|
|
412
|
+
public additionalInfo?: JSONObject = undefined;
|
|
413
|
+
}
|