@rebasepro/client-firebase 0.0.1-canary.4d4fb3e → 0.0.1-canary.ca2cb6e
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/dist/components/RebaseFirebaseAppProps.d.ts +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useAppCheck.d.ts +1 -1
- package/dist/hooks/useBuildUserManagement.d.ts +46 -0
- package/dist/index.es.js +451 -148
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +452 -152
- package/dist/index.umd.js.map +1 -1
- package/package.json +61 -59
- package/src/components/FirebaseLoginView.tsx +37 -47
- package/src/components/RebaseFirebaseApp.tsx +42 -26
- package/src/components/RebaseFirebaseAppProps.tsx +1 -1
- package/src/components/social_icons.tsx +8 -8
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAppCheck.ts +1 -1
- package/src/hooks/useBuildUserManagement.tsx +374 -0
- package/src/hooks/useFirebaseAuthController.ts +1 -1
- package/src/hooks/useFirebaseRealTimeDBDelegate.ts +8 -8
- package/src/hooks/useFirebaseStorageSource.ts +15 -16
- package/src/hooks/useFirestoreDriver.ts +19 -13
- package/src/utils/algolia.ts +1 -1
- package/src/utils/collections_firestore.ts +4 -5
- package/src/utils/local_text_search_controller.ts +4 -4
- package/src/utils/pinecone.ts +1 -1
- package/src/utils/rebase_search_controller.ts +6 -5
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import React, { useCallback, useEffect } from "react";
|
|
2
|
+
import equal from "react-fast-compare"
|
|
3
|
+
|
|
4
|
+
import { removeUndefined } from "@rebasepro/utils";
|
|
5
|
+
import {
|
|
6
|
+
AuthController,
|
|
7
|
+
Authenticator,
|
|
8
|
+
DataDriver,
|
|
9
|
+
Entity,
|
|
10
|
+
Role,
|
|
11
|
+
User,
|
|
12
|
+
UserManagementDelegate
|
|
13
|
+
} from "@rebasepro/types";
|
|
14
|
+
|
|
15
|
+
type UserWithRoleIds<USER extends User = any> = Omit<USER, "roles"> & { roles: string[] };
|
|
16
|
+
|
|
17
|
+
export interface UserManagementDelegateParams<CONTROLLER extends AuthController<any> = AuthController<any>> {
|
|
18
|
+
|
|
19
|
+
authController: CONTROLLER;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The delegate in charge of persisting the data.
|
|
23
|
+
*/
|
|
24
|
+
dataSourceDelegate?: DataDriver;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Path where the plugin users configuration is stored.
|
|
28
|
+
* Default: __FIRECMS/config/users
|
|
29
|
+
* You can specify a different path if you want to store the user management configuration in a different place.
|
|
30
|
+
* Please keep in mind that the FireCMS users are not necessarily the same as the Firebase users (but they can be).
|
|
31
|
+
* The path should be relative to the root of the database, and should always have an odd number of segments.
|
|
32
|
+
*/
|
|
33
|
+
usersPath?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Path where the plugin roles configuration is stored.
|
|
37
|
+
* Default: __FIRECMS/config/roles
|
|
38
|
+
*/
|
|
39
|
+
rolesPath?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The roles that are available in the user management system.
|
|
43
|
+
* If you provide this, the user management system will not fetch the roles from the database.
|
|
44
|
+
*/
|
|
45
|
+
roles?: Role[];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* If there are no roles in the database, provide a button to create the default roles.
|
|
49
|
+
*/
|
|
50
|
+
allowDefaultRolesCreation?: boolean;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Include the collection config permissions in the user management system.
|
|
54
|
+
*/
|
|
55
|
+
includeCollectionConfigPermissions?: boolean;
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* This hook is used to build a user management object that can be used to
|
|
61
|
+
* manage users and roles in a Firestore backend.
|
|
62
|
+
* @param authController
|
|
63
|
+
* @param dataSourceDelegate
|
|
64
|
+
* @param usersPath
|
|
65
|
+
* @param rolesPath
|
|
66
|
+
* @param roles
|
|
67
|
+
* @param allowDefaultRolesCreation
|
|
68
|
+
* @param includeCollectionConfigPermissions
|
|
69
|
+
*/
|
|
70
|
+
export function useBuildUserManagement<CONTROLLER extends AuthController<any> = AuthController<any>,
|
|
71
|
+
USER extends User = CONTROLLER extends AuthController<infer U> ? U : any>
|
|
72
|
+
({
|
|
73
|
+
authController,
|
|
74
|
+
dataSourceDelegate,
|
|
75
|
+
roles: rolesProp,
|
|
76
|
+
usersPath = "__FIRECMS/config/users",
|
|
77
|
+
rolesPath = "__FIRECMS/config/roles",
|
|
78
|
+
allowDefaultRolesCreation,
|
|
79
|
+
includeCollectionConfigPermissions
|
|
80
|
+
}: UserManagementDelegateParams<CONTROLLER>): UserManagementDelegate<USER> & CONTROLLER {
|
|
81
|
+
|
|
82
|
+
if (!authController) {
|
|
83
|
+
throw Error("useBuildUserManagement: You need to provide an authController since version 3.0.0-beta.11. Check https://firecms.co/docs/pro/migrating_from_v3_beta");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const rolesDefinedInCode = (rolesProp ?? [])?.length > 0;
|
|
87
|
+
const [rolesLoading, setRolesLoading] = React.useState<boolean>(!rolesDefinedInCode);
|
|
88
|
+
const [usersLoading, setUsersLoading] = React.useState<boolean>(true);
|
|
89
|
+
const [roles, setRoles] = React.useState<Role[]>(rolesProp ?? []);
|
|
90
|
+
const [usersWithRoleIds, setUsersWithRoleIds] = React.useState<UserWithRoleIds<USER>[]>([]);
|
|
91
|
+
|
|
92
|
+
const users = usersWithRoleIds.map(u => ({
|
|
93
|
+
...u,
|
|
94
|
+
}) as unknown as USER);
|
|
95
|
+
|
|
96
|
+
const [rolesError, setRolesError] = React.useState<Error | undefined>();
|
|
97
|
+
const [usersError, setUsersError] = React.useState<Error | undefined>();
|
|
98
|
+
|
|
99
|
+
const _usersLoading = usersLoading;
|
|
100
|
+
const _rolesLoading = rolesLoading;
|
|
101
|
+
|
|
102
|
+
const loading = _rolesLoading || _usersLoading;
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (rolesDefinedInCode) return;
|
|
106
|
+
if (!dataSourceDelegate || !rolesPath) return;
|
|
107
|
+
if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) return;
|
|
108
|
+
if (authController?.initialLoading) return;
|
|
109
|
+
|
|
110
|
+
setRolesLoading(true);
|
|
111
|
+
return dataSourceDelegate.listenCollection?.({
|
|
112
|
+
path: rolesPath,
|
|
113
|
+
onUpdate(entities: Entity<any>[]): void {
|
|
114
|
+
setRolesError(undefined);
|
|
115
|
+
console.debug("Updating roles", entities);
|
|
116
|
+
try {
|
|
117
|
+
const newRoles = entityToRoles(entities);
|
|
118
|
+
if (!equal(newRoles, roles)) {
|
|
119
|
+
setRoles(newRoles);
|
|
120
|
+
}
|
|
121
|
+
} catch (e) {
|
|
122
|
+
setRoles([]);
|
|
123
|
+
console.error("Error loading roles", e);
|
|
124
|
+
setRolesError(e as Error);
|
|
125
|
+
}
|
|
126
|
+
setRolesLoading(false);
|
|
127
|
+
},
|
|
128
|
+
onError(e: any): void {
|
|
129
|
+
setRoles([]);
|
|
130
|
+
console.error("Error loading roles", e);
|
|
131
|
+
setRolesError(e);
|
|
132
|
+
setRolesLoading(false);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
}, [rolesDefinedInCode, dataSourceDelegate?.initialised, authController?.initialLoading, authController?.user?.uid, rolesPath]);
|
|
137
|
+
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (!dataSourceDelegate || !usersPath) return;
|
|
140
|
+
if (dataSourceDelegate.initialised !== undefined && !dataSourceDelegate.initialised) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (authController?.initialLoading) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
setUsersLoading(true);
|
|
148
|
+
return dataSourceDelegate.listenCollection?.({
|
|
149
|
+
path: usersPath,
|
|
150
|
+
onUpdate(entities: Entity<any>[]): void {
|
|
151
|
+
console.debug("Updating users", entities);
|
|
152
|
+
setUsersError(undefined);
|
|
153
|
+
try {
|
|
154
|
+
const newUsers = entitiesToUsers(entities);
|
|
155
|
+
// if (!equal(newUsers, usersWithRoleIds))
|
|
156
|
+
setUsersWithRoleIds(newUsers);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
setUsersWithRoleIds([]);
|
|
159
|
+
console.error("Error loading users", e);
|
|
160
|
+
setUsersError(e as Error);
|
|
161
|
+
}
|
|
162
|
+
setUsersLoading(false);
|
|
163
|
+
},
|
|
164
|
+
onError(e: any): void {
|
|
165
|
+
console.error("Error loading users", e);
|
|
166
|
+
setUsersWithRoleIds([]);
|
|
167
|
+
setUsersError(e);
|
|
168
|
+
setUsersLoading(false);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
}, [dataSourceDelegate?.initialised, authController?.initialLoading, authController?.user?.uid, usersPath]);
|
|
173
|
+
|
|
174
|
+
const saveUser = useCallback(async (user: USER): Promise<USER> => {
|
|
175
|
+
if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
|
|
176
|
+
if (!usersPath) throw Error("useBuildUserManagement Firestore not initialised");
|
|
177
|
+
|
|
178
|
+
console.debug("Persisting user", user);
|
|
179
|
+
|
|
180
|
+
const roleIds = user.roles;
|
|
181
|
+
const email = user.email?.toLowerCase().trim();
|
|
182
|
+
if (!email) throw Error("Email is required");
|
|
183
|
+
|
|
184
|
+
const userExists = users.find(u => u.email?.toLowerCase() === email);
|
|
185
|
+
const data = {
|
|
186
|
+
...user,
|
|
187
|
+
roles: roleIds ?? []
|
|
188
|
+
};
|
|
189
|
+
if (!userExists) {
|
|
190
|
+
// @ts-ignore
|
|
191
|
+
data.created_on = new Date();
|
|
192
|
+
}
|
|
193
|
+
// delete the previous user entry if it exists and the uid has changed
|
|
194
|
+
if (userExists && userExists.uid !== user.uid) {
|
|
195
|
+
const entity: Entity<any> = {
|
|
196
|
+
values: {},
|
|
197
|
+
path: usersPath,
|
|
198
|
+
id: userExists.uid
|
|
199
|
+
}
|
|
200
|
+
await dataSourceDelegate.deleteEntity({ entity })
|
|
201
|
+
.then(() => {
|
|
202
|
+
console.debug("Deleted previous user", userExists);
|
|
203
|
+
})
|
|
204
|
+
.catch(e => {
|
|
205
|
+
console.error("Error deleting user", e);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return dataSourceDelegate.saveEntity({
|
|
211
|
+
status: "existing",
|
|
212
|
+
path: usersPath,
|
|
213
|
+
entityId: email,
|
|
214
|
+
values: removeUndefined(data) as Record<string, unknown>
|
|
215
|
+
}).then(() => user);
|
|
216
|
+
}, [usersPath, dataSourceDelegate?.initialised]);
|
|
217
|
+
|
|
218
|
+
const saveRole = useCallback((role: Role): Promise<void> => {
|
|
219
|
+
if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
|
|
220
|
+
if (!rolesPath) throw Error("useBuildUserManagement Firestore not initialised");
|
|
221
|
+
console.debug("Persisting role", role);
|
|
222
|
+
const {
|
|
223
|
+
id,
|
|
224
|
+
...roleData
|
|
225
|
+
} = role;
|
|
226
|
+
return dataSourceDelegate.saveEntity({
|
|
227
|
+
status: "existing",
|
|
228
|
+
path: rolesPath,
|
|
229
|
+
entityId: id,
|
|
230
|
+
values: removeUndefined(roleData) as Record<string, unknown>
|
|
231
|
+
}).then(() => {
|
|
232
|
+
return;
|
|
233
|
+
});
|
|
234
|
+
}, [rolesPath, dataSourceDelegate?.initialised]);
|
|
235
|
+
|
|
236
|
+
const deleteUser = useCallback(async (user: User): Promise<void> => {
|
|
237
|
+
if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
|
|
238
|
+
if (!usersPath) throw Error("useBuildUserManagement Firestore not initialised");
|
|
239
|
+
console.debug("Deleting", user);
|
|
240
|
+
const { uid } = user;
|
|
241
|
+
const entity: Entity<any> = {
|
|
242
|
+
path: usersPath,
|
|
243
|
+
id: uid,
|
|
244
|
+
values: {}
|
|
245
|
+
};
|
|
246
|
+
await dataSourceDelegate.deleteEntity({ entity })
|
|
247
|
+
}, [usersPath, dataSourceDelegate?.initialised]);
|
|
248
|
+
|
|
249
|
+
const deleteRole = useCallback(async (role: Role): Promise<void> => {
|
|
250
|
+
if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
|
|
251
|
+
if (!rolesPath) throw Error("useBuildUserManagement Firestore not initialised");
|
|
252
|
+
console.debug("Deleting", role);
|
|
253
|
+
const { id } = role;
|
|
254
|
+
const entity: Entity<any> = {
|
|
255
|
+
path: rolesPath,
|
|
256
|
+
id: id,
|
|
257
|
+
values: {}
|
|
258
|
+
};
|
|
259
|
+
await dataSourceDelegate.deleteEntity({ entity })
|
|
260
|
+
}, [rolesPath, dataSourceDelegate?.initialised]);
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
const defineRolesFor: ((user: User) => Role[] | undefined) = useCallback((user) => {
|
|
265
|
+
if (!usersWithRoleIds) throw Error("Users not loaded");
|
|
266
|
+
const mgmtUser = usersWithRoleIds.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
|
|
267
|
+
if (!mgmtUser || !mgmtUser.roles) return undefined;
|
|
268
|
+
return roles.filter(r => mgmtUser.roles.includes(r.id));
|
|
269
|
+
}, [roles, usersWithRoleIds]);
|
|
270
|
+
|
|
271
|
+
const authenticator: Authenticator<USER> = useCallback(({ user }) => {
|
|
272
|
+
|
|
273
|
+
if (loading) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
if (user === null) {
|
|
277
|
+
console.warn("User is null, returning");
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (users.length === 0) {
|
|
282
|
+
console.warn("No users created yet");
|
|
283
|
+
return true; // If there are no users created yet, we allow access to every user
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
|
|
287
|
+
if (mgmtUser) {
|
|
288
|
+
// check if the uid or photoURL needs to be updated in the user management system
|
|
289
|
+
const needsUidUpdate = mgmtUser.uid !== user.uid;
|
|
290
|
+
const needsPhotoUpdate = user.photoURL && mgmtUser.photoURL !== user.photoURL;
|
|
291
|
+
|
|
292
|
+
if (needsUidUpdate || needsPhotoUpdate) {
|
|
293
|
+
const updateReason = needsUidUpdate ? "uid" : "photoURL";
|
|
294
|
+
console.debug(`User ${updateReason} has changed, updating user in user management system`);
|
|
295
|
+
saveUser({
|
|
296
|
+
...mgmtUser,
|
|
297
|
+
uid: user.uid,
|
|
298
|
+
...(needsPhotoUpdate ? { photoURL: user.photoURL } : {})
|
|
299
|
+
}).then(() => {
|
|
300
|
+
console.debug("User updated in user management system", mgmtUser);
|
|
301
|
+
}).catch(e => {
|
|
302
|
+
console.error("Error updating user in user management system", e);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
console.debug("User found in user management system", mgmtUser);
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
throw Error("Could not find a user with the provided email in the user management system.");
|
|
310
|
+
}, [loading, users]);
|
|
311
|
+
|
|
312
|
+
const userRoles = authController.user ? defineRolesFor(authController.user) : undefined;
|
|
313
|
+
const isAdmin = (userRoles ?? []).some(r => r.id === "admin");
|
|
314
|
+
|
|
315
|
+
const userRoleIds = userRoles?.map(r => r.id);
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
console.debug("Setting user roles", {
|
|
318
|
+
userRoles,
|
|
319
|
+
roles
|
|
320
|
+
});
|
|
321
|
+
authController.setUserRoles?.(userRoles ?? []);
|
|
322
|
+
}, [userRoleIds]);
|
|
323
|
+
|
|
324
|
+
const getUser = useCallback((uid: string): USER | null => {
|
|
325
|
+
if (!users) return null;
|
|
326
|
+
const user = users.find(u => u.uid === uid);
|
|
327
|
+
return user ?? null;
|
|
328
|
+
}, [users]);
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
loading,
|
|
332
|
+
roles,
|
|
333
|
+
users,
|
|
334
|
+
saveUser,
|
|
335
|
+
saveRole,
|
|
336
|
+
rolesError,
|
|
337
|
+
deleteUser,
|
|
338
|
+
deleteRole,
|
|
339
|
+
usersError,
|
|
340
|
+
isAdmin,
|
|
341
|
+
allowDefaultRolesCreation: allowDefaultRolesCreation === undefined ? true : allowDefaultRolesCreation,
|
|
342
|
+
includeCollectionConfigPermissions: Boolean(includeCollectionConfigPermissions),
|
|
343
|
+
defineRolesFor,
|
|
344
|
+
authenticator,
|
|
345
|
+
...authController,
|
|
346
|
+
initialLoading: authController.initialLoading || loading,
|
|
347
|
+
userRoles: userRoles,
|
|
348
|
+
getUser,
|
|
349
|
+
user: authController.user ? {
|
|
350
|
+
...authController.user,
|
|
351
|
+
roles: userRoles
|
|
352
|
+
} : null
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const entitiesToUsers = (docs: Entity<Omit<UserWithRoleIds, "id">>[]): (UserWithRoleIds)[] => {
|
|
357
|
+
return docs.map((doc) => {
|
|
358
|
+
const data = doc.values as any;
|
|
359
|
+
const newVar = {
|
|
360
|
+
uid: doc.id,
|
|
361
|
+
...data,
|
|
362
|
+
created_on: data?.created_on,
|
|
363
|
+
updated_on: data?.updated_on
|
|
364
|
+
};
|
|
365
|
+
return newVar as (UserWithRoleIds);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const entityToRoles = (entities: Entity<Omit<Role, "id">>[]): Role[] => {
|
|
370
|
+
return entities.map((doc) => ({
|
|
371
|
+
id: doc.id,
|
|
372
|
+
...doc.values
|
|
373
|
+
} as Role));
|
|
374
|
+
}
|
|
@@ -299,7 +299,7 @@ export const useFirebaseAuthController = <USER extends FirebaseUserWrapper = any
|
|
|
299
299
|
const firebaseUserWrapper: FirebaseUserWrapper | null = loggedUser
|
|
300
300
|
? {
|
|
301
301
|
...loggedUser,
|
|
302
|
-
roles: userRoles?.map(r => r.id),
|
|
302
|
+
roles: userRoles?.map(r => r.id), // User.roles is string[], keep Role[] internally only
|
|
303
303
|
firebaseUser: loggedUser
|
|
304
304
|
}
|
|
305
305
|
: null;
|
|
@@ -48,7 +48,7 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
48
48
|
return Object.entries(snapshot.val()).map(([id, values]) => ({
|
|
49
49
|
id,
|
|
50
50
|
path: path,
|
|
51
|
-
values: delegateToCMSModel(values) as M
|
|
51
|
+
values: delegateToCMSModel(values) as M
|
|
52
52
|
}));
|
|
53
53
|
}
|
|
54
54
|
return [];
|
|
@@ -56,7 +56,7 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
56
56
|
|
|
57
57
|
const listenCollection = useCallback(<M extends Record<string, any>>({
|
|
58
58
|
path,
|
|
59
|
-
onUpdate
|
|
59
|
+
onUpdate
|
|
60
60
|
// Realtime Database does not directly support onError in onValue
|
|
61
61
|
}: ListenCollectionProps<M>): () => void => {
|
|
62
62
|
if (!firebaseApp) {
|
|
@@ -70,7 +70,7 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
70
70
|
const result: Entity<M>[] = Object.entries(snapshot.val()).map(([id, values]) => ({
|
|
71
71
|
id,
|
|
72
72
|
path: path,
|
|
73
|
-
values: delegateToCMSModel(values) as M
|
|
73
|
+
values: delegateToCMSModel(values) as M
|
|
74
74
|
}));
|
|
75
75
|
onUpdate(result);
|
|
76
76
|
} else {
|
|
@@ -83,7 +83,7 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
83
83
|
|
|
84
84
|
const fetchEntity = useCallback(async <M extends Record<string, any>>({
|
|
85
85
|
path,
|
|
86
|
-
entityId
|
|
86
|
+
entityId
|
|
87
87
|
}: FetchEntityProps<M>): Promise<Entity<M> | undefined> => {
|
|
88
88
|
if (!firebaseApp) {
|
|
89
89
|
throw new Error("Firebase app not provided");
|
|
@@ -131,7 +131,7 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
131
131
|
const saveEntity = useCallback(async <M extends Record<string, any>>({
|
|
132
132
|
path,
|
|
133
133
|
entityId,
|
|
134
|
-
values
|
|
134
|
+
values
|
|
135
135
|
}: SaveEntityProps<M>): Promise<Entity<M>> => {
|
|
136
136
|
if (!firebaseApp) {
|
|
137
137
|
throw new Error("Firebase app not provided");
|
|
@@ -156,7 +156,7 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
156
156
|
}, [firebaseApp]);
|
|
157
157
|
|
|
158
158
|
const deleteEntity = useCallback(async <M extends Record<string, any>>({
|
|
159
|
-
entity
|
|
159
|
+
entity
|
|
160
160
|
}: DeleteEntityProps<M>): Promise<void> => {
|
|
161
161
|
if (!firebaseApp) {
|
|
162
162
|
throw new Error("Firebase app not provided");
|
|
@@ -213,7 +213,6 @@ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: Firebas
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
|
|
216
|
-
|
|
217
216
|
/**
|
|
218
217
|
* Transform data from RTDB format back to CMS format
|
|
219
218
|
* This is used internally when fetching/listening to data
|
|
@@ -263,7 +262,8 @@ function cmsToRTDBModel(data: any, database: Database): any {
|
|
|
263
262
|
else
|
|
264
263
|
return {};
|
|
265
264
|
})
|
|
266
|
-
.reduce((a, b) => ({ ...a,
|
|
265
|
+
.reduce((a, b) => ({ ...a,
|
|
266
|
+
...b }), {});
|
|
267
267
|
}
|
|
268
268
|
return data;
|
|
269
269
|
}
|
|
@@ -29,10 +29,9 @@ export function useFirebaseStorageSource({
|
|
|
29
29
|
const projectId = firebaseApp?.options?.projectId;
|
|
30
30
|
const urlsCache: Record<string, DownloadConfig> = {};
|
|
31
31
|
return {
|
|
32
|
-
|
|
32
|
+
putObject({
|
|
33
33
|
file,
|
|
34
|
-
|
|
35
|
-
path,
|
|
34
|
+
key,
|
|
36
35
|
metadata,
|
|
37
36
|
bucket
|
|
38
37
|
}: UploadFileProps)
|
|
@@ -42,9 +41,8 @@ export function useFirebaseStorageSource({
|
|
|
42
41
|
const storageBucketUrl = bucket ?? bucketUrl;
|
|
43
42
|
const storage = getStorage(firebaseApp, storageBucketUrl);
|
|
44
43
|
if (!storage) throw Error("useFirebaseStorageSource Firebase not initialised");
|
|
45
|
-
const usedFilename = fileName ?? file.name;
|
|
46
44
|
|
|
47
|
-
const storageRef = ref(storage,
|
|
45
|
+
const storageRef = ref(storage, key);
|
|
48
46
|
const uploadTask = uploadBytesResumable(storageRef, file, metadata);
|
|
49
47
|
|
|
50
48
|
return new Promise((resolve, reject) => {
|
|
@@ -111,9 +109,9 @@ export function useFirebaseStorageSource({
|
|
|
111
109
|
const fullPath = uploadTask.snapshot.ref.fullPath;
|
|
112
110
|
const bucketName = uploadTask.snapshot.ref.bucket;
|
|
113
111
|
resolve({
|
|
114
|
-
|
|
112
|
+
key: fullPath,
|
|
115
113
|
bucket: bucketName,
|
|
116
|
-
storageUrl: `
|
|
114
|
+
storageUrl: `s3://${bucketName}/${fullPath}`
|
|
117
115
|
});
|
|
118
116
|
}
|
|
119
117
|
);
|
|
@@ -123,7 +121,7 @@ export function useFirebaseStorageSource({
|
|
|
123
121
|
}
|
|
124
122
|
},
|
|
125
123
|
|
|
126
|
-
async
|
|
124
|
+
async getObject(path: string, bucket?: string): Promise<File | null> {
|
|
127
125
|
try {
|
|
128
126
|
if (!firebaseApp) throw Error("useFirebaseStorageSource Firebase not initialised");
|
|
129
127
|
const storageBucketUrl = bucket ?? bucketUrl;
|
|
@@ -140,15 +138,16 @@ export function useFirebaseStorageSource({
|
|
|
140
138
|
}
|
|
141
139
|
},
|
|
142
140
|
|
|
143
|
-
async
|
|
141
|
+
async getSignedUrl(storagePathOrUrl: string, bucket?: string): Promise<DownloadConfig> {
|
|
144
142
|
if (!firebaseApp) throw Error("useFirebaseStorageSource Firebase not initialised");
|
|
145
143
|
|
|
146
|
-
// Support fully-qualified gs:// URLs
|
|
144
|
+
// Support fully-qualified s3:// and gs:// URLs for backward compatibility
|
|
147
145
|
let resolvedPathOrUrl = storagePathOrUrl;
|
|
148
146
|
let resolvedBucket = bucket;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
147
|
+
const match = storagePathOrUrl.match(/^(s3|gs):\/\//);
|
|
148
|
+
if (match) {
|
|
149
|
+
const protocolLength = match[0].length;
|
|
150
|
+
const withoutProtocol = storagePathOrUrl.substring(protocolLength);
|
|
152
151
|
const firstSlash = withoutProtocol.indexOf("/");
|
|
153
152
|
if (firstSlash > 0) {
|
|
154
153
|
resolvedBucket = withoutProtocol.substring(0, firstSlash);
|
|
@@ -180,7 +179,7 @@ export function useFirebaseStorageSource({
|
|
|
180
179
|
}
|
|
181
180
|
},
|
|
182
181
|
|
|
183
|
-
async
|
|
182
|
+
async listObjects(prefix: string, options?: {
|
|
184
183
|
bucket?: string,
|
|
185
184
|
maxResults?: number,
|
|
186
185
|
pageToken?: string
|
|
@@ -189,14 +188,14 @@ export function useFirebaseStorageSource({
|
|
|
189
188
|
const storageBucketUrl = options?.bucket ?? bucketUrl;
|
|
190
189
|
const storage = getStorage(firebaseApp, storageBucketUrl);
|
|
191
190
|
if (!storage) throw Error("useFirebaseStorageSource Firebase not initialised");
|
|
192
|
-
const folderRef = ref(storage,
|
|
191
|
+
const folderRef = ref(storage, prefix);
|
|
193
192
|
return await list(folderRef, {
|
|
194
193
|
maxResults: options?.maxResults,
|
|
195
194
|
pageToken: options?.pageToken
|
|
196
195
|
});
|
|
197
196
|
},
|
|
198
197
|
|
|
199
|
-
async
|
|
198
|
+
async deleteObject(path: string, bucket?: string): Promise<void> {
|
|
200
199
|
if (!firebaseApp) throw Error("useFirebaseStorageSource Firebase not initialised");
|
|
201
200
|
const storageBucketUrl = bucket ?? bucketUrl;
|
|
202
201
|
const storage = getStorage(firebaseApp, storageBucketUrl);
|
|
@@ -160,7 +160,7 @@ export function useFirestoreDriver({
|
|
|
160
160
|
entityId,
|
|
161
161
|
collection,
|
|
162
162
|
onUpdate,
|
|
163
|
-
onError
|
|
163
|
+
onError
|
|
164
164
|
}: ListenEntityProps<M>): () => void => {
|
|
165
165
|
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
166
166
|
|
|
@@ -183,7 +183,7 @@ export function useFirestoreDriver({
|
|
|
183
183
|
path,
|
|
184
184
|
databaseId,
|
|
185
185
|
searchString,
|
|
186
|
-
onUpdate
|
|
186
|
+
onUpdate
|
|
187
187
|
}: {
|
|
188
188
|
path: string,
|
|
189
189
|
databaseId?: string,
|
|
@@ -297,7 +297,7 @@ export function useFirestoreDriver({
|
|
|
297
297
|
searchString,
|
|
298
298
|
orderBy,
|
|
299
299
|
order,
|
|
300
|
-
collection
|
|
300
|
+
collection
|
|
301
301
|
}: FetchCollectionProps<M>
|
|
302
302
|
): Promise<Entity<M>[]> => {
|
|
303
303
|
|
|
@@ -346,7 +346,7 @@ export function useFirestoreDriver({
|
|
|
346
346
|
order,
|
|
347
347
|
onUpdate,
|
|
348
348
|
onError,
|
|
349
|
-
collection
|
|
349
|
+
collection
|
|
350
350
|
}: ListenCollectionProps<M>
|
|
351
351
|
): () => void => {
|
|
352
352
|
|
|
@@ -372,7 +372,7 @@ export function useFirestoreDriver({
|
|
|
372
372
|
path,
|
|
373
373
|
searchString,
|
|
374
374
|
onUpdate,
|
|
375
|
-
databaseId
|
|
375
|
+
databaseId
|
|
376
376
|
});
|
|
377
377
|
}
|
|
378
378
|
|
|
@@ -404,7 +404,7 @@ export function useFirestoreDriver({
|
|
|
404
404
|
fetchEntity: useCallback(<M extends Record<string, any>>({
|
|
405
405
|
path,
|
|
406
406
|
entityId,
|
|
407
|
-
collection
|
|
407
|
+
collection
|
|
408
408
|
}: FetchEntityProps<M>
|
|
409
409
|
): Promise<Entity<M> | undefined> => {
|
|
410
410
|
const resolvedPath = path;
|
|
@@ -440,7 +440,7 @@ export function useFirestoreDriver({
|
|
|
440
440
|
entityId,
|
|
441
441
|
values: valuesProp,
|
|
442
442
|
collection,
|
|
443
|
-
status
|
|
443
|
+
status
|
|
444
444
|
}: SaveEntityProps<M>): Promise<Entity<M>> => {
|
|
445
445
|
|
|
446
446
|
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
@@ -482,7 +482,7 @@ export function useFirestoreDriver({
|
|
|
482
482
|
return {
|
|
483
483
|
id: documentReference.id,
|
|
484
484
|
path,
|
|
485
|
-
values: firestoreToCMSModel(values)
|
|
485
|
+
values: firestoreToCMSModel(values)
|
|
486
486
|
} as Entity<M>;
|
|
487
487
|
})
|
|
488
488
|
.catch((error) => {
|
|
@@ -548,7 +548,7 @@ export function useFirestoreDriver({
|
|
|
548
548
|
filter,
|
|
549
549
|
order,
|
|
550
550
|
orderBy,
|
|
551
|
-
collection
|
|
551
|
+
collection
|
|
552
552
|
}: FetchCollectionProps<any>): Promise<number> => {
|
|
553
553
|
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
554
554
|
const databaseId = collection?.databaseId;
|
|
@@ -562,7 +562,7 @@ export function useFirestoreDriver({
|
|
|
562
562
|
path,
|
|
563
563
|
collection,
|
|
564
564
|
filterValues,
|
|
565
|
-
sortBy
|
|
565
|
+
sortBy
|
|
566
566
|
}: {
|
|
567
567
|
path: string,
|
|
568
568
|
collection: EntityCollection<any>,
|
|
@@ -656,7 +656,8 @@ export function firestoreToCMSModel(data: any): any {
|
|
|
656
656
|
return data; // already translated
|
|
657
657
|
}
|
|
658
658
|
if (data instanceof VectorValue || (typeof data === "object" && data !== null && typeof data.toArray === "function" && data.constructor?.name === "VectorValue")) {
|
|
659
|
-
return { __type__: "__vector__",
|
|
659
|
+
return { __type__: "__vector__",
|
|
660
|
+
value: data.toArray() };
|
|
660
661
|
}
|
|
661
662
|
|
|
662
663
|
if (data instanceof FirestoreGeoPoint) {
|
|
@@ -665,7 +666,9 @@ export function firestoreToCMSModel(data: any): any {
|
|
|
665
666
|
if (data instanceof DocumentReference) {
|
|
666
667
|
// @ts-ignore
|
|
667
668
|
const databaseId = data?.firestore?._databaseId?.database;
|
|
668
|
-
return new EntityReference({ id: data.id,
|
|
669
|
+
return new EntityReference({ id: data.id,
|
|
670
|
+
path: getCMSPathFromFirestorePath(data.path),
|
|
671
|
+
databaseId });
|
|
669
672
|
}
|
|
670
673
|
if (Array.isArray(data)) {
|
|
671
674
|
return data.map(firestoreToCMSModel).filter(v => v !== undefined);
|
|
@@ -703,6 +706,8 @@ export function cmsToFirestoreModel(data: any, firestore: Firestore, inArray = f
|
|
|
703
706
|
} else if (data.isEntityReference && data.isEntityReference()) {
|
|
704
707
|
const targetFirestore = data.databaseId ? getFirestore(firestore.app, data.databaseId) : firestore;
|
|
705
708
|
return doc(targetFirestore, data.path, data.id);
|
|
709
|
+
} else if (data && typeof data === "object" && data.__type === "relation" && data.path && data.id) {
|
|
710
|
+
return doc(firestore, data.path, String(data.id));
|
|
706
711
|
} else if (data instanceof GeoPoint) {
|
|
707
712
|
return new FirestoreGeoPoint(data.latitude, data.longitude);
|
|
708
713
|
} else if (data instanceof Date) {
|
|
@@ -718,7 +723,8 @@ export function cmsToFirestoreModel(data: any, firestore: Firestore, inArray = f
|
|
|
718
723
|
else
|
|
719
724
|
return {};
|
|
720
725
|
})
|
|
721
|
-
.reduce((a, b) => ({ ...a,
|
|
726
|
+
.reduce((a, b) => ({ ...a,
|
|
727
|
+
...b }), {});
|
|
722
728
|
}
|
|
723
729
|
return data;
|
|
724
730
|
}
|
package/src/utils/algolia.ts
CHANGED
|
@@ -14,7 +14,7 @@ export function performAlgoliaTextSearch(client: any, indexName: string, query:
|
|
|
14
14
|
|
|
15
15
|
return client.searchSingleIndex({
|
|
16
16
|
indexName,
|
|
17
|
-
searchParams: { query }
|
|
17
|
+
searchParams: { query }
|
|
18
18
|
}).then(({ hits }: any) => {
|
|
19
19
|
return hits.map((hit: any) => hit.objectID as string);
|
|
20
20
|
})
|