@stoker-platform/cli 0.5.12

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 (94) hide show
  1. package/LICENSE.md +102 -0
  2. package/init-files/.##gitignore## +102 -0
  3. package/init-files/.devcontainer/devcontainer.json +14 -0
  4. package/init-files/.env/.env +70 -0
  5. package/init-files/.eslintrc.cjs +35 -0
  6. package/init-files/.firebaserc +6 -0
  7. package/init-files/.prettierignore +86 -0
  8. package/init-files/.prettierrc +5 -0
  9. package/init-files/bin/build.js +221 -0
  10. package/init-files/bin/shim.js +159 -0
  11. package/init-files/extensions/firestore-send-email.env +7 -0
  12. package/init-files/external.package.json +4 -0
  13. package/init-files/firebase-rules/database.rules.json +9 -0
  14. package/init-files/firebase-rules/firestore.custom.rules +19 -0
  15. package/init-files/firebase-rules/firestore.indexes.json +0 -0
  16. package/init-files/firebase-rules/firestore.rules +0 -0
  17. package/init-files/firebase-rules/storage.rules +0 -0
  18. package/init-files/firebase.hosting.json +122 -0
  19. package/init-files/firebase.json +52 -0
  20. package/init-files/functions/.##gitignore## +14 -0
  21. package/init-files/functions/.eslintrc.cjs +28 -0
  22. package/init-files/functions/package.json +46 -0
  23. package/init-files/functions/prompts/chat.prompt +17 -0
  24. package/init-files/functions/src/index.ts +457 -0
  25. package/init-files/functions/tsconfig.dev.json +3 -0
  26. package/init-files/functions/tsconfig.json +17 -0
  27. package/init-files/icons/logo-large.png +0 -0
  28. package/init-files/icons/logo-small.png +0 -0
  29. package/init-files/ops.js +25 -0
  30. package/init-files/package.json +53 -0
  31. package/init-files/project-data.json +5 -0
  32. package/init-files/remoteconfig.template.json +1 -0
  33. package/init-files/src/collections/Inbox.ts +444 -0
  34. package/init-files/src/collections/Outbox.ts +270 -0
  35. package/init-files/src/collections/Settings.ts +44 -0
  36. package/init-files/src/collections/Users.ts +138 -0
  37. package/init-files/src/main.ts +245 -0
  38. package/init-files/src/utils.ts +3 -0
  39. package/init-files/src/vite-env.d.ts +1 -0
  40. package/init-files/test/test.ts +5 -0
  41. package/init-files/tsconfig.json +23 -0
  42. package/init-files/vitest.config.ts +9 -0
  43. package/lib/package.json +45 -0
  44. package/lib/src/data/exportToBigQuery.js +41 -0
  45. package/lib/src/data/seedData.js +347 -0
  46. package/lib/src/deploy/applySchema.js +43 -0
  47. package/lib/src/deploy/cloud-functions/getFunctionsData.js +18 -0
  48. package/lib/src/deploy/deployProject.js +116 -0
  49. package/lib/src/deploy/firestore-export/exportFirestoreData.js +29 -0
  50. package/lib/src/deploy/firestore-ttl/deployTTLs.js +127 -0
  51. package/lib/src/deploy/live-update/liveUpdate.js +22 -0
  52. package/lib/src/deploy/maintenance/activateMaintenanceMode.js +9 -0
  53. package/lib/src/deploy/maintenance/disableMaintenanceMode.js +9 -0
  54. package/lib/src/deploy/maintenance/setDeploymentStatus.js +22 -0
  55. package/lib/src/deploy/rules-indexes/generateFirestoreIndexes.js +23 -0
  56. package/lib/src/deploy/rules-indexes/generateFirestoreRules.js +35 -0
  57. package/lib/src/deploy/rules-indexes/generateStorageRules.js +23 -0
  58. package/lib/src/deploy/schema/generateSchema.js +184 -0
  59. package/lib/src/deploy/schema/persistSchema.js +14 -0
  60. package/lib/src/lint/lintSchema.js +1491 -0
  61. package/lib/src/lint/securityReport.js +223 -0
  62. package/lib/src/main.js +460 -0
  63. package/lib/src/migration/firestore/migrateFirestore.js +8 -0
  64. package/lib/src/migration/firestore/operations/deleteField.js +58 -0
  65. package/lib/src/migration/migrateAll.js +30 -0
  66. package/lib/src/ops/auditDenormalized.js +124 -0
  67. package/lib/src/ops/auditPermissions.js +92 -0
  68. package/lib/src/ops/auditRelations.js +186 -0
  69. package/lib/src/ops/explainPreloadQueries.js +65 -0
  70. package/lib/src/ops/getUser.js +10 -0
  71. package/lib/src/ops/getUserPermissions.js +19 -0
  72. package/lib/src/ops/getUserRecord.js +20 -0
  73. package/lib/src/ops/listProjects.js +8 -0
  74. package/lib/src/ops/setUserCollection.js +14 -0
  75. package/lib/src/ops/setUserDocument.js +11 -0
  76. package/lib/src/ops/setUserRole.js +14 -0
  77. package/lib/src/project/addProject.js +935 -0
  78. package/lib/src/project/addRecord.js +9 -0
  79. package/lib/src/project/addRecordPrompt.js +205 -0
  80. package/lib/src/project/addTenant.js +59 -0
  81. package/lib/src/project/buildWebApp.js +10 -0
  82. package/lib/src/project/customDomain.js +157 -0
  83. package/lib/src/project/deleteProject.js +51 -0
  84. package/lib/src/project/deleteRecord.js +11 -0
  85. package/lib/src/project/deleteTenant.js +49 -0
  86. package/lib/src/project/getOne.js +25 -0
  87. package/lib/src/project/getSome.js +28 -0
  88. package/lib/src/project/initProject.js +16 -0
  89. package/lib/src/project/prepareEmulatorData.js +125 -0
  90. package/lib/src/project/setProject.js +13 -0
  91. package/lib/src/project/startEmulators.js +30 -0
  92. package/lib/src/project/updateRecord.js +9 -0
  93. package/lib/tsconfig.tsbuildinfo +1 -0
  94. package/package.json +45 -0
@@ -0,0 +1,444 @@
1
+ import type { CollectionSchema, GenerateSchema, StokerRecord, WebUtilities } from "@stoker-platform/types"
2
+ import {
3
+ Forward,
4
+ Inbox as InboxIcon,
5
+ MessageCircle,
6
+ ReplyAllIcon,
7
+ ReplyIcon,
8
+ SquarePen,
9
+ Timer,
10
+ TrendingUp,
11
+ User,
12
+ Users,
13
+ } from "lucide-react"
14
+ import { blueField, greenField, redField } from "../utils.js"
15
+ import { parseDate } from "@stoker-platform/utils"
16
+
17
+ const toasted: string[] = []
18
+
19
+ const Inbox: GenerateSchema = (sdk, utils, context): CollectionSchema => {
20
+ const { toast } = (context || {}) as {
21
+ toast: ({
22
+ title,
23
+ description,
24
+ variant,
25
+ duration,
26
+ }: {
27
+ title: string
28
+ description: string
29
+ variant?: "default" | "destructive" | null | undefined
30
+ duration?: number
31
+ }) => void
32
+ }
33
+ return {
34
+ labels: {
35
+ collection: "Inbox",
36
+ record: "Inbox_Message",
37
+ },
38
+ access: {
39
+ operations: {
40
+ read: ["Admin", "User"],
41
+ create: ["Admin", "User"],
42
+ update: ["Admin", "User"],
43
+ },
44
+ attributeRestrictions: [
45
+ {
46
+ type: "Record_User",
47
+ collectionField: "Recipient",
48
+ roles: [{ role: "Admin" }, { role: "User" }],
49
+ operations: ["Read", "Update"],
50
+ },
51
+ ],
52
+ },
53
+ indexExemption: true,
54
+ preloadCache: {
55
+ roles: ["Admin", "User"],
56
+ range: {
57
+ fields: ["Saved_At"],
58
+ labels: ["Received"],
59
+ start: -30,
60
+ end: 14,
61
+ selector: ["week", "month", "range"],
62
+ },
63
+ },
64
+ recordTitleField: "Subject",
65
+ fullTextSearch: ["Subject", "Message"],
66
+ softDelete: {
67
+ archivedField: "Archived",
68
+ timestampField: "Archived_At",
69
+ retentionPeriod: 7,
70
+ },
71
+ custom: {
72
+ async postRead(_context, _refs, record?: StokerRecord) {
73
+ if (sdk === "web") {
74
+ if (record && record.Status === "Unread" && !record.Notified && !toasted.includes(record.id)) {
75
+ toasted.push(record.id)
76
+ const { updateRecord } = await import("@stoker-platform/web-client")
77
+ toast({ title: "New Message", description: record.Subject, duration: 10000000 })
78
+ updateRecord(["Inbox"], record.id, {
79
+ Notified: true,
80
+ })
81
+ }
82
+ }
83
+ },
84
+ },
85
+ admin: {
86
+ navbarPosition: 1,
87
+ titles: {
88
+ collection: "Inbox",
89
+ record: "Message",
90
+ },
91
+ icon: InboxIcon as React.FC,
92
+ itemsPerPage: 20,
93
+ defaultRangeSelector: "range",
94
+ defaultSort: {
95
+ field: "Received",
96
+ direction: "desc",
97
+ },
98
+ statusField: {
99
+ field: "Status",
100
+ active: ["Unread", "Read"],
101
+ archived: ["Archived"],
102
+ },
103
+ cards: {
104
+ headerField: "Saved_At",
105
+ sections: [
106
+ {
107
+ title: "People",
108
+ fields: ["Sender", "Recipients"],
109
+ maxSectionLines: 3,
110
+ },
111
+ ],
112
+ },
113
+ metrics: [
114
+ {
115
+ type: "count",
116
+ title: "Total Messages",
117
+ },
118
+ {
119
+ type: "area",
120
+ dateField: "Saved_At",
121
+ defaultRange: "30d",
122
+ title: "Messages Over Time",
123
+ },
124
+ ],
125
+ filters: [
126
+ {
127
+ type: "select",
128
+ field: "Status",
129
+ },
130
+ {
131
+ type: "relation",
132
+ field: "Sender",
133
+ },
134
+ {
135
+ type: "relation",
136
+ field: "Recipients",
137
+ },
138
+ ],
139
+ rowHighlight: [
140
+ {
141
+ condition(record) {
142
+ return record.Status === "Unread"
143
+ },
144
+ className: "bg-blue-500/30 dark:bg-blue-500/50 hover:bg-blue-500/50 dark:hover:bg-blue-500",
145
+ },
146
+ ],
147
+ addRecordButtonOverride(record?: StokerRecord) {
148
+ const { getSchema } = utils as WebUtilities
149
+ const schema = getSchema()
150
+ const { createRecordForm } = context
151
+ createRecordForm(schema.collections.Outbox, ["Outbox"], record)
152
+ },
153
+ async onFormOpen(operation, record) {
154
+ if (operation === "update" && record?.Status === "Unread") {
155
+ const { updateRecord } = await import("@stoker-platform/web-client")
156
+ await updateRecord(["Inbox"], record.id, {
157
+ Status: "Read",
158
+ })
159
+ }
160
+ },
161
+ formButtons: [
162
+ {
163
+ title: "Reply",
164
+ icon: ReplyIcon as React.FC,
165
+ async action(_operation, _formValues, originalRecord) {
166
+ if (!originalRecord) return
167
+ const { getSchema } = utils as WebUtilities
168
+ const schema = getSchema()
169
+ const { getCurrentUser } = await import("@stoker-platform/web-client")
170
+ const user = getCurrentUser()
171
+ const { claims } = user.token
172
+ const originalMessage = originalRecord.Message as unknown as
173
+ | { ops?: Array<Record<string, unknown>> }
174
+ | string
175
+ | undefined
176
+ const originalOps =
177
+ typeof originalMessage === "object" && originalMessage && Array.isArray(originalMessage.ops)
178
+ ? originalMessage.ops
179
+ : [
180
+ {
181
+ insert: `${typeof originalMessage === "string" ? originalMessage : ""}`,
182
+ },
183
+ ]
184
+ const replyMessage = {
185
+ ops: [
186
+ {
187
+ insert: "\n\n\n--------------------------------\n\n\n",
188
+ },
189
+ ...originalOps,
190
+ ],
191
+ }
192
+ const replyRecord = {
193
+ Subject: `Re: ${originalRecord.Subject}`,
194
+ Message: replyMessage,
195
+ Sender: {
196
+ [claims.doc as string]: {
197
+ Collection_Path: ["Users"],
198
+ Name: user.displayName,
199
+ },
200
+ },
201
+ Recipients: originalRecord.Sender,
202
+ }
203
+ const { createRecordForm } = context
204
+ createRecordForm(schema.collections.Outbox, ["Outbox"], replyRecord)
205
+ },
206
+ condition(operation) {
207
+ return operation === "update"
208
+ },
209
+ },
210
+ {
211
+ title: "Reply All",
212
+ icon: ReplyAllIcon as React.FC,
213
+ async action(_operation, _formValues, originalRecord) {
214
+ if (!originalRecord) return
215
+ const { getSchema } = utils as WebUtilities
216
+ const schema = getSchema()
217
+ const { getCurrentUser } = await import("@stoker-platform/web-client")
218
+ const user = getCurrentUser()
219
+ const { claims } = user.token
220
+ const originalMessage = originalRecord.Message as unknown as
221
+ | { ops?: Array<Record<string, unknown>> }
222
+ | string
223
+ | undefined
224
+ const originalOps =
225
+ typeof originalMessage === "object" && originalMessage && Array.isArray(originalMessage.ops)
226
+ ? originalMessage.ops
227
+ : [
228
+ {
229
+ insert: `${typeof originalMessage === "string" ? originalMessage : ""}`,
230
+ },
231
+ ]
232
+ const replyMessage = {
233
+ ops: [
234
+ {
235
+ insert: "\n\n\n--------------------------------\n\n\n",
236
+ },
237
+ ...originalOps,
238
+ ],
239
+ }
240
+ const recipients = Object.entries(originalRecord.Recipients).filter(([key, value]) => {
241
+ return key !== Object.keys(originalRecord.Recipient)[0] ? value : undefined
242
+ })
243
+ const replyRecord = {
244
+ Subject: `Re: ${originalRecord.Subject}`,
245
+ Message: replyMessage,
246
+ Sender: {
247
+ [claims.doc as string]: {
248
+ Collection_Path: ["Users"],
249
+ Name: user.displayName,
250
+ },
251
+ },
252
+ Recipients: { ...Object.fromEntries(recipients), ...originalRecord.Sender },
253
+ }
254
+ const { createRecordForm } = context
255
+ createRecordForm(schema.collections.Outbox, ["Outbox"], replyRecord)
256
+ },
257
+ condition(operation) {
258
+ return operation === "update"
259
+ },
260
+ },
261
+ ],
262
+ },
263
+ fields: [
264
+ {
265
+ name: "Received",
266
+ type: "Computed",
267
+ async formula(record?: StokerRecord) {
268
+ if (!record) return ""
269
+ if (sdk === "web") {
270
+ const { displayDate } = await import("@stoker-platform/web-client")
271
+ return displayDate(record.Saved_At)
272
+ } else {
273
+ const { displayDate } = await import("@stoker-platform/node-client")
274
+ return displayDate(record.Saved_At)
275
+ }
276
+ },
277
+ admin: {
278
+ condition: {
279
+ form(operation) {
280
+ return operation === "update"
281
+ },
282
+ },
283
+ icon: {
284
+ component: Timer as React.FC,
285
+ className: blueField,
286
+ },
287
+ hidden: "md",
288
+ sort(record?: StokerRecord) {
289
+ return parseDate(record?.Received)
290
+ },
291
+ },
292
+ },
293
+ {
294
+ name: "Sender",
295
+ type: "OneToMany",
296
+ required: true,
297
+ collection: "Users",
298
+ includeFields: ["Name"],
299
+ titleField: "Name",
300
+ admin: {
301
+ icon: {
302
+ component: Forward as React.FC,
303
+ className: redField,
304
+ },
305
+ },
306
+ restrictUpdate: true,
307
+ },
308
+ {
309
+ name: "Recipient",
310
+ type: "OneToMany",
311
+ collection: "Users",
312
+ required: true,
313
+ includeFields: ["Name"],
314
+ titleField: "Name",
315
+ admin: {
316
+ condition: {
317
+ list: false,
318
+ form: false,
319
+ },
320
+ icon: {
321
+ component: User as React.FC,
322
+ className: redField,
323
+ },
324
+ },
325
+ restrictUpdate: true,
326
+ },
327
+ {
328
+ name: "Recipients",
329
+ type: "ManyToMany",
330
+ collection: "Users",
331
+ required: true,
332
+ includeFields: ["Name"],
333
+ titleField: "Name",
334
+ admin: {
335
+ icon: {
336
+ component: Users as React.FC,
337
+ className: redField,
338
+ },
339
+ hidden: "md",
340
+ },
341
+ restrictUpdate: true,
342
+ },
343
+ {
344
+ name: "Subject",
345
+ type: "String",
346
+ required: true,
347
+ admin: {
348
+ icon: {
349
+ component: MessageCircle as React.FC,
350
+ className: greenField,
351
+ },
352
+ },
353
+ restrictUpdate: true,
354
+ },
355
+ {
356
+ name: "Status",
357
+ type: "String",
358
+ values: ["Unread", "Read", "Archived"],
359
+ required: true,
360
+ admin: {
361
+ live: true,
362
+ badge(record?: StokerRecord) {
363
+ if (!record) return true
364
+ switch (record.Status) {
365
+ case "Unread":
366
+ return "destructive"
367
+ case "Read":
368
+ return "secondary"
369
+ case "Archived":
370
+ return "primary"
371
+ default:
372
+ return true
373
+ }
374
+ },
375
+ icon: {
376
+ component: TrendingUp as React.FC,
377
+ className: greenField,
378
+ },
379
+ hidden: "lg",
380
+ },
381
+ },
382
+ {
383
+ name: "Message",
384
+ type: "Map",
385
+ required: true,
386
+ admin: {
387
+ richText: true,
388
+ condition: {
389
+ list: false,
390
+ },
391
+ icon: {
392
+ component: SquarePen as React.FC,
393
+ className: greenField,
394
+ },
395
+ },
396
+ restrictUpdate: true,
397
+ },
398
+
399
+ {
400
+ name: "Outbox_Message",
401
+ type: "String",
402
+ admin: {
403
+ condition: {
404
+ list: false,
405
+ form: false,
406
+ },
407
+ },
408
+ restrictUpdate: true,
409
+ },
410
+ {
411
+ name: "Notified",
412
+ type: "Boolean",
413
+ admin: {
414
+ condition: {
415
+ list: false,
416
+ form: false,
417
+ },
418
+ },
419
+ },
420
+ {
421
+ name: "Archived",
422
+ type: "Boolean",
423
+ admin: {
424
+ condition: {
425
+ list: false,
426
+ form: false,
427
+ },
428
+ },
429
+ },
430
+ {
431
+ name: "Archived_At",
432
+ type: "Timestamp",
433
+ admin: {
434
+ condition: {
435
+ list: false,
436
+ form: false,
437
+ },
438
+ },
439
+ },
440
+ ],
441
+ }
442
+ }
443
+
444
+ export default Inbox