@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.
- package/LICENSE.md +102 -0
- package/init-files/.##gitignore## +102 -0
- package/init-files/.devcontainer/devcontainer.json +14 -0
- package/init-files/.env/.env +70 -0
- package/init-files/.eslintrc.cjs +35 -0
- package/init-files/.firebaserc +6 -0
- package/init-files/.prettierignore +86 -0
- package/init-files/.prettierrc +5 -0
- package/init-files/bin/build.js +221 -0
- package/init-files/bin/shim.js +159 -0
- package/init-files/extensions/firestore-send-email.env +7 -0
- package/init-files/external.package.json +4 -0
- package/init-files/firebase-rules/database.rules.json +9 -0
- package/init-files/firebase-rules/firestore.custom.rules +19 -0
- package/init-files/firebase-rules/firestore.indexes.json +0 -0
- package/init-files/firebase-rules/firestore.rules +0 -0
- package/init-files/firebase-rules/storage.rules +0 -0
- package/init-files/firebase.hosting.json +122 -0
- package/init-files/firebase.json +52 -0
- package/init-files/functions/.##gitignore## +14 -0
- package/init-files/functions/.eslintrc.cjs +28 -0
- package/init-files/functions/package.json +46 -0
- package/init-files/functions/prompts/chat.prompt +17 -0
- package/init-files/functions/src/index.ts +457 -0
- package/init-files/functions/tsconfig.dev.json +3 -0
- package/init-files/functions/tsconfig.json +17 -0
- package/init-files/icons/logo-large.png +0 -0
- package/init-files/icons/logo-small.png +0 -0
- package/init-files/ops.js +25 -0
- package/init-files/package.json +53 -0
- package/init-files/project-data.json +5 -0
- package/init-files/remoteconfig.template.json +1 -0
- package/init-files/src/collections/Inbox.ts +444 -0
- package/init-files/src/collections/Outbox.ts +270 -0
- package/init-files/src/collections/Settings.ts +44 -0
- package/init-files/src/collections/Users.ts +138 -0
- package/init-files/src/main.ts +245 -0
- package/init-files/src/utils.ts +3 -0
- package/init-files/src/vite-env.d.ts +1 -0
- package/init-files/test/test.ts +5 -0
- package/init-files/tsconfig.json +23 -0
- package/init-files/vitest.config.ts +9 -0
- package/lib/package.json +45 -0
- package/lib/src/data/exportToBigQuery.js +41 -0
- package/lib/src/data/seedData.js +347 -0
- package/lib/src/deploy/applySchema.js +43 -0
- package/lib/src/deploy/cloud-functions/getFunctionsData.js +18 -0
- package/lib/src/deploy/deployProject.js +116 -0
- package/lib/src/deploy/firestore-export/exportFirestoreData.js +29 -0
- package/lib/src/deploy/firestore-ttl/deployTTLs.js +127 -0
- package/lib/src/deploy/live-update/liveUpdate.js +22 -0
- package/lib/src/deploy/maintenance/activateMaintenanceMode.js +9 -0
- package/lib/src/deploy/maintenance/disableMaintenanceMode.js +9 -0
- package/lib/src/deploy/maintenance/setDeploymentStatus.js +22 -0
- package/lib/src/deploy/rules-indexes/generateFirestoreIndexes.js +23 -0
- package/lib/src/deploy/rules-indexes/generateFirestoreRules.js +35 -0
- package/lib/src/deploy/rules-indexes/generateStorageRules.js +23 -0
- package/lib/src/deploy/schema/generateSchema.js +184 -0
- package/lib/src/deploy/schema/persistSchema.js +14 -0
- package/lib/src/lint/lintSchema.js +1491 -0
- package/lib/src/lint/securityReport.js +223 -0
- package/lib/src/main.js +460 -0
- package/lib/src/migration/firestore/migrateFirestore.js +8 -0
- package/lib/src/migration/firestore/operations/deleteField.js +58 -0
- package/lib/src/migration/migrateAll.js +30 -0
- package/lib/src/ops/auditDenormalized.js +124 -0
- package/lib/src/ops/auditPermissions.js +92 -0
- package/lib/src/ops/auditRelations.js +186 -0
- package/lib/src/ops/explainPreloadQueries.js +65 -0
- package/lib/src/ops/getUser.js +10 -0
- package/lib/src/ops/getUserPermissions.js +19 -0
- package/lib/src/ops/getUserRecord.js +20 -0
- package/lib/src/ops/listProjects.js +8 -0
- package/lib/src/ops/setUserCollection.js +14 -0
- package/lib/src/ops/setUserDocument.js +11 -0
- package/lib/src/ops/setUserRole.js +14 -0
- package/lib/src/project/addProject.js +935 -0
- package/lib/src/project/addRecord.js +9 -0
- package/lib/src/project/addRecordPrompt.js +205 -0
- package/lib/src/project/addTenant.js +59 -0
- package/lib/src/project/buildWebApp.js +10 -0
- package/lib/src/project/customDomain.js +157 -0
- package/lib/src/project/deleteProject.js +51 -0
- package/lib/src/project/deleteRecord.js +11 -0
- package/lib/src/project/deleteTenant.js +49 -0
- package/lib/src/project/getOne.js +25 -0
- package/lib/src/project/getSome.js +28 -0
- package/lib/src/project/initProject.js +16 -0
- package/lib/src/project/prepareEmulatorData.js +125 -0
- package/lib/src/project/setProject.js +13 -0
- package/lib/src/project/startEmulators.js +30 -0
- package/lib/src/project/updateRecord.js +9 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import type { CollectionSchema, GenerateSchema, StokerRecord, StokerRelationObject } from "@stoker-platform/types"
|
|
2
|
+
import { MessageCircle, Send, SquarePen, Timer, TrendingUp, Users } from "lucide-react"
|
|
3
|
+
import { blueField, greenField, redField } from "../utils.js"
|
|
4
|
+
import { parseDate } from "@stoker-platform/utils"
|
|
5
|
+
|
|
6
|
+
const Outbox: GenerateSchema = (sdk: "web" | "node"): CollectionSchema => {
|
|
7
|
+
return {
|
|
8
|
+
labels: {
|
|
9
|
+
collection: "Outbox",
|
|
10
|
+
record: "Outbox_Message",
|
|
11
|
+
},
|
|
12
|
+
access: {
|
|
13
|
+
operations: {
|
|
14
|
+
read: ["Admin", "User"],
|
|
15
|
+
create: ["Admin", "User"],
|
|
16
|
+
update: ["Admin", "User"],
|
|
17
|
+
},
|
|
18
|
+
attributeRestrictions: [
|
|
19
|
+
{
|
|
20
|
+
type: "Record_Owner",
|
|
21
|
+
roles: [{ role: "Admin" }, { role: "User" }],
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
indexExemption: true,
|
|
26
|
+
preloadCache: {
|
|
27
|
+
roles: ["Admin", "User"],
|
|
28
|
+
range: {
|
|
29
|
+
fields: ["Saved_At"],
|
|
30
|
+
labels: ["Sent"],
|
|
31
|
+
start: -30,
|
|
32
|
+
end: 14,
|
|
33
|
+
selector: ["week", "month", "range"],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
recordTitleField: "Subject",
|
|
37
|
+
fullTextSearch: ["Subject", "Message"],
|
|
38
|
+
softDelete: {
|
|
39
|
+
archivedField: "Archived",
|
|
40
|
+
timestampField: "Archived_At",
|
|
41
|
+
retentionPeriod: 7,
|
|
42
|
+
},
|
|
43
|
+
custom: {
|
|
44
|
+
async preWrite(operation, data) {
|
|
45
|
+
if (operation === "create") {
|
|
46
|
+
data.Status = "Sending"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
async postWrite(operation, data) {
|
|
50
|
+
if (operation === "create") {
|
|
51
|
+
if (sdk === "web") {
|
|
52
|
+
const { addRecord, updateRecord, getCurrentUser } = await import("@stoker-platform/web-client")
|
|
53
|
+
const user = getCurrentUser()
|
|
54
|
+
if (!user) throw new Error("User not found")
|
|
55
|
+
const { claims } = user.token
|
|
56
|
+
try {
|
|
57
|
+
for (const [id, recipient] of Object.entries(data.Recipients as StokerRelationObject)) {
|
|
58
|
+
await addRecord(["Inbox"], {
|
|
59
|
+
Subject: data.Subject,
|
|
60
|
+
Message: data.Message,
|
|
61
|
+
Recipient: {
|
|
62
|
+
[id]: {
|
|
63
|
+
Collection_Path: ["Users"],
|
|
64
|
+
Name: recipient.Name,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
Recipients: data.Recipients,
|
|
68
|
+
Status: "Unread",
|
|
69
|
+
Sender: {
|
|
70
|
+
[claims.doc as string]: {
|
|
71
|
+
Collection_Path: ["Users"],
|
|
72
|
+
Name: user?.displayName,
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
Outbox_Message: data.id,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
await updateRecord(["Outbox"], data.id, {
|
|
80
|
+
Status: "Failed",
|
|
81
|
+
})
|
|
82
|
+
console.error(error)
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
const { addRecord, updateRecord, getUser } = await import("@stoker-platform/node-client")
|
|
86
|
+
if (data.Created_By === "System") return
|
|
87
|
+
const user = await getUser(data.Created_By)
|
|
88
|
+
try {
|
|
89
|
+
for (const [id, recipient] of Object.entries(data.Recipients as StokerRelationObject)) {
|
|
90
|
+
await addRecord(["Inbox"], {
|
|
91
|
+
Subject: data.Subject,
|
|
92
|
+
Message: data.Message,
|
|
93
|
+
Recipient: {
|
|
94
|
+
[id]: {
|
|
95
|
+
Collection_Path: ["Users"],
|
|
96
|
+
Name: recipient.Name,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
Recipients: data.Recipients,
|
|
100
|
+
Status: "Unread",
|
|
101
|
+
Sender: {
|
|
102
|
+
[user.customClaims?.doc as string]: {
|
|
103
|
+
Collection_Path: ["Users"],
|
|
104
|
+
Name: user.displayName,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
Outbox_Message: data.id,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
await updateRecord(["Outbox"], data.id, {
|
|
112
|
+
Status: "Failed",
|
|
113
|
+
})
|
|
114
|
+
throw error
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
admin: {
|
|
121
|
+
navbarPosition: 2,
|
|
122
|
+
titles: {
|
|
123
|
+
collection: "Outbox",
|
|
124
|
+
record: "Message",
|
|
125
|
+
},
|
|
126
|
+
icon: Send as React.FC,
|
|
127
|
+
itemsPerPage: 20,
|
|
128
|
+
defaultRangeSelector: "range",
|
|
129
|
+
defaultSort: {
|
|
130
|
+
field: "Sent",
|
|
131
|
+
direction: "desc",
|
|
132
|
+
},
|
|
133
|
+
filters: [
|
|
134
|
+
{
|
|
135
|
+
type: "relation",
|
|
136
|
+
field: "Recipients",
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
fields: [
|
|
141
|
+
{
|
|
142
|
+
name: "Sent",
|
|
143
|
+
type: "Computed",
|
|
144
|
+
async formula(record?: StokerRecord) {
|
|
145
|
+
if (!record) return ""
|
|
146
|
+
if (sdk === "web") {
|
|
147
|
+
const { displayDate } = await import("@stoker-platform/web-client")
|
|
148
|
+
return displayDate(record.Saved_At)
|
|
149
|
+
} else {
|
|
150
|
+
const { displayDate } = await import("@stoker-platform/node-client")
|
|
151
|
+
return displayDate(record.Saved_At)
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
admin: {
|
|
155
|
+
condition: {
|
|
156
|
+
form(operation) {
|
|
157
|
+
return operation === "update"
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
icon: {
|
|
161
|
+
component: Timer as React.FC,
|
|
162
|
+
className: blueField,
|
|
163
|
+
},
|
|
164
|
+
hidden: "md",
|
|
165
|
+
sort(record?: StokerRecord) {
|
|
166
|
+
return parseDate(record?.Sent)
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: "Recipients",
|
|
172
|
+
type: "ManyToMany",
|
|
173
|
+
collection: "Users",
|
|
174
|
+
required: true,
|
|
175
|
+
includeFields: ["Name"],
|
|
176
|
+
titleField: "Name",
|
|
177
|
+
dependencyFields: [{ field: "Name", roles: ["Admin", "User"] }],
|
|
178
|
+
admin: {
|
|
179
|
+
icon: {
|
|
180
|
+
component: Users as React.FC,
|
|
181
|
+
className: blueField,
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
restrictUpdate: true,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "Subject",
|
|
188
|
+
type: "String",
|
|
189
|
+
required: true,
|
|
190
|
+
admin: {
|
|
191
|
+
icon: {
|
|
192
|
+
component: MessageCircle as React.FC,
|
|
193
|
+
className: redField,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
restrictUpdate: true,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "Status",
|
|
200
|
+
type: "String",
|
|
201
|
+
values: ["Sending", "Success", "Failed"],
|
|
202
|
+
required: true,
|
|
203
|
+
admin: {
|
|
204
|
+
badge(record?: StokerRecord) {
|
|
205
|
+
if (!record) return true
|
|
206
|
+
switch (record.Status) {
|
|
207
|
+
case "Sending":
|
|
208
|
+
return "primary"
|
|
209
|
+
case "Success":
|
|
210
|
+
return "secondary"
|
|
211
|
+
case "Failed":
|
|
212
|
+
return "destructive"
|
|
213
|
+
default:
|
|
214
|
+
return true
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
readOnly: true,
|
|
218
|
+
condition: {
|
|
219
|
+
form(operation) {
|
|
220
|
+
return operation !== "create"
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
icon: {
|
|
224
|
+
component: TrendingUp as React.FC,
|
|
225
|
+
className: redField,
|
|
226
|
+
},
|
|
227
|
+
hidden: "lg",
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "Message",
|
|
232
|
+
type: "Map",
|
|
233
|
+
required: true,
|
|
234
|
+
admin: {
|
|
235
|
+
richText: true,
|
|
236
|
+
condition: {
|
|
237
|
+
list: false,
|
|
238
|
+
},
|
|
239
|
+
icon: {
|
|
240
|
+
component: SquarePen as React.FC,
|
|
241
|
+
className: greenField,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
restrictUpdate: true,
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: "Archived",
|
|
248
|
+
type: "Boolean",
|
|
249
|
+
admin: {
|
|
250
|
+
condition: {
|
|
251
|
+
list: false,
|
|
252
|
+
form: false,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "Archived_At",
|
|
258
|
+
type: "Timestamp",
|
|
259
|
+
admin: {
|
|
260
|
+
condition: {
|
|
261
|
+
list: false,
|
|
262
|
+
form: false,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export default Outbox
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CollectionSchema, GenerateSchema } from "@stoker-platform/types"
|
|
2
|
+
import { blueField } from "../utils.js"
|
|
3
|
+
import { Building2, SettingsIcon } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
const Settings: GenerateSchema = (): CollectionSchema => {
|
|
6
|
+
return {
|
|
7
|
+
labels: {
|
|
8
|
+
collection: "Settings",
|
|
9
|
+
record: "Settings",
|
|
10
|
+
},
|
|
11
|
+
singleton: true,
|
|
12
|
+
access: {
|
|
13
|
+
operations: {
|
|
14
|
+
assignable: ["Admin"],
|
|
15
|
+
read: ["Admin"],
|
|
16
|
+
create: ["Admin"],
|
|
17
|
+
update: ["Admin"],
|
|
18
|
+
delete: ["Admin"],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
indexExemption: true,
|
|
22
|
+
recordTitleField: "Organization_Name",
|
|
23
|
+
admin: {
|
|
24
|
+
navbarPosition: 4,
|
|
25
|
+
icon: SettingsIcon as React.FC,
|
|
26
|
+
},
|
|
27
|
+
fields: [
|
|
28
|
+
{
|
|
29
|
+
name: "Organization_Name",
|
|
30
|
+
type: "String",
|
|
31
|
+
required: true,
|
|
32
|
+
admin: {
|
|
33
|
+
label: "Organization Name",
|
|
34
|
+
icon: {
|
|
35
|
+
component: Building2 as React.FC,
|
|
36
|
+
className: blueField,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default Settings
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { GenerateSchema, CollectionSchema, StokerRecord } from "@stoker-platform/types"
|
|
2
|
+
import { Activity, ChevronsUp, Image, Mail, User, Users2 } from "lucide-react"
|
|
3
|
+
import { blueField, greenField, redField } from "../utils.js"
|
|
4
|
+
|
|
5
|
+
const Users: GenerateSchema = (): CollectionSchema => {
|
|
6
|
+
return {
|
|
7
|
+
labels: {
|
|
8
|
+
collection: "Users",
|
|
9
|
+
record: "User",
|
|
10
|
+
},
|
|
11
|
+
auth: true,
|
|
12
|
+
access: {
|
|
13
|
+
auth: ["Admin"],
|
|
14
|
+
operations: {
|
|
15
|
+
assignable: ["Admin"],
|
|
16
|
+
read: ["Admin"],
|
|
17
|
+
create: ["Admin"],
|
|
18
|
+
update: ["Admin"],
|
|
19
|
+
delete: ["Admin"],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
indexExemption: true,
|
|
23
|
+
enableWriteLog: true,
|
|
24
|
+
preloadCache: {
|
|
25
|
+
roles: ["Admin", "User"],
|
|
26
|
+
},
|
|
27
|
+
recordTitleField: "Name",
|
|
28
|
+
fullTextSearch: ["Name", "Email"],
|
|
29
|
+
admin: {
|
|
30
|
+
navbarPosition: 3,
|
|
31
|
+
icon: Users2 as React.FC,
|
|
32
|
+
statusField: {
|
|
33
|
+
field: "Enabled",
|
|
34
|
+
active: [true],
|
|
35
|
+
archived: [false],
|
|
36
|
+
},
|
|
37
|
+
itemsPerPage: 20,
|
|
38
|
+
filters: [
|
|
39
|
+
{
|
|
40
|
+
type: "select",
|
|
41
|
+
field: "Role",
|
|
42
|
+
style: "buttons",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
fields: [
|
|
47
|
+
{
|
|
48
|
+
name: "Name",
|
|
49
|
+
type: "String",
|
|
50
|
+
required: true,
|
|
51
|
+
admin: {
|
|
52
|
+
icon: {
|
|
53
|
+
component: User as React.FC,
|
|
54
|
+
className: blueField,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "Enabled",
|
|
60
|
+
type: "Boolean",
|
|
61
|
+
required: true,
|
|
62
|
+
admin: {
|
|
63
|
+
switch: true,
|
|
64
|
+
hidden: "lg",
|
|
65
|
+
icon: {
|
|
66
|
+
component: Activity as React.FC,
|
|
67
|
+
className: blueField,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "Role",
|
|
73
|
+
type: "String",
|
|
74
|
+
values: ["Admin", "User"],
|
|
75
|
+
required: true,
|
|
76
|
+
admin: {
|
|
77
|
+
badge(record?: StokerRecord) {
|
|
78
|
+
if (!record) return true
|
|
79
|
+
switch (record.Role) {
|
|
80
|
+
case "Admin":
|
|
81
|
+
return "destructive"
|
|
82
|
+
case "User":
|
|
83
|
+
return "primary"
|
|
84
|
+
default:
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
icon: {
|
|
89
|
+
component: ChevronsUp as React.FC,
|
|
90
|
+
className: blueField,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
sorting: true,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "Email",
|
|
97
|
+
type: "String",
|
|
98
|
+
email: true,
|
|
99
|
+
required: true,
|
|
100
|
+
unique: true,
|
|
101
|
+
admin: {
|
|
102
|
+
hidden: "sm",
|
|
103
|
+
icon: {
|
|
104
|
+
component: Mail as React.FC,
|
|
105
|
+
className: redField,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
sorting: true,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "Photo_URL",
|
|
112
|
+
type: "String",
|
|
113
|
+
pattern: "^https://firebasestorage\\.googleapis\\.com/.*$",
|
|
114
|
+
admin: {
|
|
115
|
+
label: "Photo",
|
|
116
|
+
image: true,
|
|
117
|
+
hidden: "xl",
|
|
118
|
+
icon: {
|
|
119
|
+
component: Image as React.FC,
|
|
120
|
+
className: greenField,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "User_ID",
|
|
126
|
+
type: "String",
|
|
127
|
+
admin: {
|
|
128
|
+
condition: {
|
|
129
|
+
list: false,
|
|
130
|
+
form: false,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export default Users
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import type { DialogContent, GenerateGlobalConfig, GlobalConfig, WebUtilities } from "@stoker-platform/types"
|
|
2
|
+
|
|
3
|
+
const globalConfig: GenerateGlobalConfig = (sdk, utils, context): GlobalConfig => {
|
|
4
|
+
const { setDialogContent, setConnectionStatus } = (context || {}) as {
|
|
5
|
+
setDialogContent: (dialogContent: DialogContent | null) => void
|
|
6
|
+
setConnectionStatus: (connectionStatus: "online" | "offline") => void
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
appName: "Stoker",
|
|
10
|
+
roles: ["Admin", "User"],
|
|
11
|
+
timezone: "Australia/Melbourne",
|
|
12
|
+
disabledCollections: [],
|
|
13
|
+
auth: {
|
|
14
|
+
enableMultiFactorAuth: [],
|
|
15
|
+
authPersistenceType: "LOCAL",
|
|
16
|
+
offlinePersistenceType: "NONE",
|
|
17
|
+
},
|
|
18
|
+
firebase: {
|
|
19
|
+
enableEmulators() {
|
|
20
|
+
const { getEnv } = utils as WebUtilities
|
|
21
|
+
const env = getEnv()
|
|
22
|
+
const mode = env.MODE
|
|
23
|
+
const enable = mode === "development"
|
|
24
|
+
return enable
|
|
25
|
+
},
|
|
26
|
+
GDPRSettings: false,
|
|
27
|
+
enableAnalytics() {
|
|
28
|
+
const { getEnv } = utils as WebUtilities
|
|
29
|
+
const env = getEnv()
|
|
30
|
+
const mode = env.MODE
|
|
31
|
+
const enable = mode === "production"
|
|
32
|
+
return enable
|
|
33
|
+
},
|
|
34
|
+
analyticsSettings: {},
|
|
35
|
+
analyticsConsentSettings: {},
|
|
36
|
+
logLevel: {
|
|
37
|
+
dev: "info",
|
|
38
|
+
},
|
|
39
|
+
permissionsIndexExemption: true,
|
|
40
|
+
writeLogIndexExemption: [],
|
|
41
|
+
serverTimestampOptions: "estimate",
|
|
42
|
+
},
|
|
43
|
+
preload: {
|
|
44
|
+
sync: [],
|
|
45
|
+
async: ["Inbox", "Outbox"],
|
|
46
|
+
},
|
|
47
|
+
enableUserIDLogging: false,
|
|
48
|
+
onConnectionStatusChange(status) {
|
|
49
|
+
if (status === "Offline") {
|
|
50
|
+
setConnectionStatus("offline")
|
|
51
|
+
console.error("Connection lost")
|
|
52
|
+
}
|
|
53
|
+
if (status === "Online") {
|
|
54
|
+
setConnectionStatus("online")
|
|
55
|
+
console.info("Connection restored")
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
onVersionUpdate(versionInfo) {
|
|
59
|
+
if (sdk === "web") {
|
|
60
|
+
const payload = versionInfo.payload as { delay: boolean; tabs: boolean }
|
|
61
|
+
if (payload.delay) {
|
|
62
|
+
setDialogContent({
|
|
63
|
+
title: "An update is available",
|
|
64
|
+
description: "The page will refresh in 30 seconds to update to the latest version.",
|
|
65
|
+
})
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
window.location.reload()
|
|
68
|
+
}, 30000)
|
|
69
|
+
}
|
|
70
|
+
if (payload.tabs) {
|
|
71
|
+
setDialogContent({
|
|
72
|
+
title: "An update is available",
|
|
73
|
+
description:
|
|
74
|
+
"Please close ALL of your open tabs to update to the latest version. A page refresh is not sufficient- please fully close all tabs.",
|
|
75
|
+
disableClose: true,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
onMaintenanceUpdate(status) {
|
|
81
|
+
if (sdk === "web") {
|
|
82
|
+
const { setMaintenance } = context as { setMaintenance: (maintenance: boolean) => void }
|
|
83
|
+
if (status === "on") {
|
|
84
|
+
setMaintenance(true)
|
|
85
|
+
} else {
|
|
86
|
+
setMaintenance(false)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
onFirestoreSlowConnection() {
|
|
91
|
+
setDialogContent({
|
|
92
|
+
title: "Slow connection detected",
|
|
93
|
+
description: "The app is experiencing a slow internet connection.",
|
|
94
|
+
})
|
|
95
|
+
},
|
|
96
|
+
async onFirestoreLoadFailure() {
|
|
97
|
+
if (sdk === "web") {
|
|
98
|
+
const { getAuth } = await import("firebase/auth")
|
|
99
|
+
const { sendAdminEmail } = await import("@stoker-platform/web-client")
|
|
100
|
+
const { currentUser } = getAuth()
|
|
101
|
+
if (!currentUser?.uid) throw new Error("Error sending Firestore load failure email")
|
|
102
|
+
await sendAdminEmail(
|
|
103
|
+
"Firestore Load Failure Detected",
|
|
104
|
+
`Firestore load failure detected for user ${currentUser.displayName} - ${currentUser.uid}`,
|
|
105
|
+
)
|
|
106
|
+
.then(() => {
|
|
107
|
+
window.location.reload()
|
|
108
|
+
})
|
|
109
|
+
.catch(() => {
|
|
110
|
+
throw new Error("Error sending Firestore load failure email")
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
onIndexedDBConnectionLost() {
|
|
115
|
+
setDialogContent({
|
|
116
|
+
title: "An error occurred",
|
|
117
|
+
description: "The app has experienced an issue and needs to refresh.",
|
|
118
|
+
disableClose: true,
|
|
119
|
+
buttons: [
|
|
120
|
+
{
|
|
121
|
+
label: "Refresh",
|
|
122
|
+
onClick: () => {
|
|
123
|
+
window.location.reload()
|
|
124
|
+
setDialogContent(null)
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
})
|
|
129
|
+
},
|
|
130
|
+
async onAppCheckTokenFailure(error) {
|
|
131
|
+
if (sdk === "web") {
|
|
132
|
+
if (
|
|
133
|
+
error.code === "appCheck/throttled" ||
|
|
134
|
+
error.code === "appCheck/initial-throttle" ||
|
|
135
|
+
error.code === "appCheck/internal-error" ||
|
|
136
|
+
error.code === "appCheck/recaptcha-error"
|
|
137
|
+
) {
|
|
138
|
+
setDialogContent({
|
|
139
|
+
title: "Page refresh required",
|
|
140
|
+
description: "The page needs to be refreshed for security reasons.",
|
|
141
|
+
disableClose: true,
|
|
142
|
+
buttons: [
|
|
143
|
+
{
|
|
144
|
+
label: "Refresh",
|
|
145
|
+
onClick: () => {
|
|
146
|
+
window.location.reload()
|
|
147
|
+
setDialogContent(null)
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
mail: {
|
|
156
|
+
emailVerification(verificationLink, appName) {
|
|
157
|
+
return {
|
|
158
|
+
subject: `${appName} - Please verify your email address`,
|
|
159
|
+
html: `Please verify your email address by clicking the link:
|
|
160
|
+
</br>
|
|
161
|
+
</br>
|
|
162
|
+
<a href="${verificationLink}">${verificationLink}</a>`,
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
async postWriteError(operation, _data, docId, context, error) {
|
|
167
|
+
if (sdk === "web") {
|
|
168
|
+
const { sendAdminEmail } = await import("@stoker-platform/web-client")
|
|
169
|
+
const { getAuth } = await import("firebase/auth")
|
|
170
|
+
const { currentUser } = getAuth()
|
|
171
|
+
if (!currentUser?.uid) throw new Error("Error sending Firestore write operation failure email")
|
|
172
|
+
await sendAdminEmail(
|
|
173
|
+
`Stoker Operation Failure`,
|
|
174
|
+
`Operation Type: ${operation}\n\nUser: ${currentUser.displayName}\n\nUser ID: ${currentUser.uid}\n\nCollection: ${context.collection}\n\nDocument ID: ${docId}\n\nError Details:\n\n${JSON.stringify(error)}`,
|
|
175
|
+
).catch(() => {
|
|
176
|
+
throw new Error("Error sending Firestore write operation failure email")
|
|
177
|
+
})
|
|
178
|
+
} else {
|
|
179
|
+
console.log(JSON.stringify(error))
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
async postLogout(errorDetails) {
|
|
183
|
+
if (errorDetails.error) {
|
|
184
|
+
for (const instance of errorDetails.instances) {
|
|
185
|
+
if (instance.code === "SIGN_OUT") {
|
|
186
|
+
setDialogContent({
|
|
187
|
+
title: "There was an error logging out",
|
|
188
|
+
description:
|
|
189
|
+
"You are still logged in. Please clear this browser's history to ensure that your sensitive data cannot be accessed by another user of this computer.",
|
|
190
|
+
})
|
|
191
|
+
} else if (instance.code === "CLEAR_CACHE") {
|
|
192
|
+
setDialogContent({
|
|
193
|
+
title: "There was an error clearing the cache",
|
|
194
|
+
description:
|
|
195
|
+
"Please clear this browser's history to ensure that your sensitive data cannot be accessed by another user of this computer.",
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
admin: {
|
|
202
|
+
meta: {
|
|
203
|
+
description: "A web app built using the Stoker platform.",
|
|
204
|
+
},
|
|
205
|
+
background: {
|
|
206
|
+
light: {
|
|
207
|
+
color: "#1f4583",
|
|
208
|
+
image: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='105' viewBox='0 0 80 105'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='death-star' fill='%233c66aa' fill-opacity='0.4'%3E%3Cpath d='M20 10a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V10zm15 35a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V45zM20 75a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V75zm30-65a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V10zm0 65a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V75zM35 10a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V10zM5 45a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V45zm0-35a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V10zm60 35a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V45zm0-35a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V10z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`,
|
|
209
|
+
},
|
|
210
|
+
dark: {
|
|
211
|
+
color: "#575757",
|
|
212
|
+
image: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='105' viewBox='0 0 80 105'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='death-star' fill='%23454545' fill-opacity='0.4'%3E%3Cpath d='M20 10a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V10zm15 35a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V45zM20 75a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V75zm30-65a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V10zm0 65a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V75zM35 10a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V10zM5 45a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V45zm0-35a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V10zm60 35a5 5 0 0 1 10 0v50a5 5 0 0 1-10 0V45zm0-35a5 5 0 0 1 10 0v20a5 5 0 0 1-10 0V10z' /%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`,
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
menu: {
|
|
216
|
+
groups: [
|
|
217
|
+
{
|
|
218
|
+
title: "Messages",
|
|
219
|
+
position: 1,
|
|
220
|
+
collections: ["Inbox", "Outbox"],
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
dashboard: [
|
|
225
|
+
{
|
|
226
|
+
kind: "reminder",
|
|
227
|
+
collection: "Inbox",
|
|
228
|
+
columns: ["Sender", "Subject"],
|
|
229
|
+
title: "Unread Messages",
|
|
230
|
+
constraints: [["Status", "==", "Unread"]],
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
kind: "chart",
|
|
234
|
+
collection: "Inbox",
|
|
235
|
+
type: "area",
|
|
236
|
+
dateField: "Saved_At",
|
|
237
|
+
defaultRange: "30d",
|
|
238
|
+
title: "Messages Over Time",
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export default globalConfig
|