@jmruthers/pace-core 0.5.186 → 0.5.187
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/{DataTable-Z9NLVJh0.d.ts → DataTable-IVYljGJ6.d.ts} +1 -1
- package/dist/{DataTable-IX2NBUTP.js → DataTable-K3RJRSOX.js} +7 -7
- package/dist/{PublicPageProvider-DIzEzwKl.d.ts → PublicPageProvider-DrLDztHt.d.ts} +211 -106
- package/dist/{UnifiedAuthProvider-A4BCQRJY.js → UnifiedAuthProvider-B76OWOAT.js} +2 -2
- package/dist/{api-BMFCXVQX.js → api-YP7XD5L6.js} +3 -3
- package/dist/{audit-WRS3KJKI.js → audit-B5P6FFIR.js} +2 -2
- package/dist/{chunk-445GEP27.js → chunk-3IC5WCMO.js} +33 -8
- package/dist/chunk-3IC5WCMO.js.map +1 -0
- package/dist/{chunk-OALXJH4Y.js → chunk-3NFNJOO7.js} +8 -8
- package/dist/chunk-3NFNJOO7.js.map +1 -0
- package/dist/{chunk-FSFQFJCU.js → chunk-63FOKYGO.js} +174 -6
- package/dist/chunk-63FOKYGO.js.map +1 -0
- package/dist/{chunk-TC7D3CR3.js → chunk-C4OYJOV4.js} +556 -101
- package/dist/chunk-C4OYJOV4.js.map +1 -0
- package/dist/{chunk-HGPQUCBC.js → chunk-FMTK4XNN.js} +3 -3
- package/dist/{chunk-U6WNSFX5.js → chunk-HEHYGYOX.js} +279 -44
- package/dist/chunk-HEHYGYOX.js.map +1 -0
- package/dist/{chunk-XAUHJD3L.js → chunk-K2JGDXGU.js} +2 -2
- package/dist/{chunk-HDCUMOOI.js → chunk-LBBUPSSC.js} +792 -559
- package/dist/chunk-LBBUPSSC.js.map +1 -0
- package/dist/{chunk-UQWSHFVX.js → chunk-SAUPYVLF.js} +1 -1
- package/dist/{chunk-UQWSHFVX.js.map → chunk-SAUPYVLF.js.map} +1 -1
- package/dist/{chunk-GRIQLQ52.js → chunk-T6ZJVI3A.js} +27 -23
- package/dist/chunk-T6ZJVI3A.js.map +1 -0
- package/dist/{chunk-DAGICKHT.js → chunk-ULX5FYEM.js} +3 -3
- package/dist/{chunk-FXFJRTKI.js → chunk-WK2Y6TGA.js} +3 -3
- package/dist/chunk-WK2Y6TGA.js.map +1 -0
- package/dist/chunk-YHCN776L.js +447 -0
- package/dist/chunk-YHCN776L.js.map +1 -0
- package/dist/components.d.ts +4 -4
- package/dist/components.js +12 -10
- package/dist/components.js.map +1 -1
- package/dist/{file-reference-PRTSLxKx.d.ts → file-reference-D037xOFK.d.ts} +0 -1
- package/dist/hooks.d.ts +221 -6
- package/dist/hooks.js +146 -49
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +24 -9
- package/dist/index.js +62 -28
- package/dist/index.js.map +1 -1
- package/dist/providers.js +1 -1
- package/dist/rbac/index.d.ts +124 -7
- package/dist/rbac/index.js +27 -7
- package/dist/{types-DUyCRSTj.d.ts → types-Bwgl--Xo.d.ts} +162 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-D71QLlg4.d.ts → usePublicRouteParams-CTDELQ7H.d.ts} +2 -2
- package/dist/utils.d.ts +213 -3
- package/dist/utils.js +22 -2
- package/dist/utils.js.map +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +21 -17
- package/docs/api/classes/RBACCache.md +31 -23
- package/docs/api/classes/RBACEngine.md +5 -5
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +241 -0
- package/docs/api/interfaces/AddressFieldRef.md +94 -0
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +75 -0
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +15 -15
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +120 -0
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +26 -3
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +5 -5
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +138 -0
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +318 -59
- package/docs/best-practices/performance.md +11 -0
- package/docs/implementation-guides/file-upload-storage.md +29 -0
- package/docs/rbac/README.md +2 -1
- package/docs/rbac/api-reference.md +11 -0
- package/docs/rbac/performance.md +320 -0
- package/docs/standards/01-architecture-standard.md +5 -0
- package/docs/standards/05-security-standard.md +12 -0
- package/package.json +1 -1
- package/src/components/AddressField/AddressField.test.tsx +411 -0
- package/src/components/AddressField/AddressField.tsx +323 -0
- package/src/components/AddressField/README.md +336 -0
- package/src/components/AddressField/index.ts +10 -0
- package/src/components/AddressField/types.ts +65 -0
- package/src/components/FileDisplay/FileDisplay.test.tsx +454 -0
- package/src/components/FileDisplay/FileDisplay.tsx +28 -1
- package/src/components/index.ts +2 -0
- package/src/hooks/__tests__/useFileDisplay.unit.test.ts +30 -5
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +11 -10
- package/src/hooks/__tests__/usePublicFileDisplay.test.ts +31 -6
- package/src/hooks/index.ts +6 -0
- package/src/hooks/public/usePublicFileDisplay.ts +8 -10
- package/src/hooks/useAddressAutocomplete.test.ts +318 -0
- package/src/hooks/useAddressAutocomplete.ts +268 -0
- package/src/hooks/useFileDisplay.ts +3 -15
- package/src/hooks/useFileReference.test.ts +20 -3
- package/src/hooks/useFileReference.ts +3 -24
- package/src/hooks/useFileUrlCache.ts +246 -0
- package/src/hooks/useInactivityTracker.ts +31 -20
- package/src/hooks/useOrganisationSecurity.test.ts +10 -7
- package/src/hooks/useOrganisationSecurity.ts +3 -3
- package/src/hooks/useQueryCache.ts +315 -0
- package/src/index.ts +2 -0
- package/src/providers/services/EventServiceProvider.tsx +4 -1
- package/src/rbac/api.test.ts +21 -6
- package/src/rbac/api.ts +32 -11
- package/src/rbac/audit-batched.ts +223 -0
- package/src/rbac/audit-enhanced.ts +2 -2
- package/src/rbac/audit.test.ts +6 -5
- package/src/rbac/audit.ts +34 -6
- package/src/rbac/cache-invalidation.ts +63 -12
- package/src/rbac/cache.test.ts +2 -2
- package/src/rbac/cache.ts +61 -14
- package/src/rbac/components/PagePermissionGuard.tsx +19 -10
- package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +248 -0
- package/src/rbac/config.ts +9 -0
- package/src/rbac/engine.ts +2 -21
- package/src/rbac/hooks/usePermissions.ts +21 -5
- package/src/rbac/index.ts +19 -0
- package/src/rbac/performance.ts +210 -0
- package/src/rbac/request-deduplication.ts +87 -0
- package/src/rbac/utils/deep-equal.ts +93 -0
- package/src/types/file-reference.ts +0 -1
- package/src/utils/file-reference/__tests__/file-reference.test.ts +31 -4
- package/src/utils/file-reference/index.ts +44 -15
- package/src/utils/google-places/googlePlacesUtils.test.ts +403 -0
- package/src/utils/google-places/googlePlacesUtils.ts +475 -0
- package/src/utils/google-places/index.ts +26 -0
- package/src/utils/google-places/loadGoogleMapsScript.ts +207 -0
- package/src/utils/google-places/types.ts +94 -0
- package/src/utils/index.ts +23 -0
- package/src/utils/request-deduplication.ts +165 -0
- package/src/utils/storage/helpers.ts +143 -4
- package/dist/chunk-445GEP27.js.map +0 -1
- package/dist/chunk-FMUCXFII.js +0 -76
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-FSFQFJCU.js.map +0 -1
- package/dist/chunk-FXFJRTKI.js.map +0 -1
- package/dist/chunk-GRIQLQ52.js.map +0 -1
- package/dist/chunk-HDCUMOOI.js.map +0 -1
- package/dist/chunk-OALXJH4Y.js.map +0 -1
- package/dist/chunk-TC7D3CR3.js.map +0 -1
- package/dist/chunk-U6WNSFX5.js.map +0 -1
- /package/dist/{DataTable-IX2NBUTP.js.map → DataTable-K3RJRSOX.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-A4BCQRJY.js.map → UnifiedAuthProvider-B76OWOAT.js.map} +0 -0
- /package/dist/{api-BMFCXVQX.js.map → api-YP7XD5L6.js.map} +0 -0
- /package/dist/{audit-WRS3KJKI.js.map → audit-B5P6FFIR.js.map} +0 -0
- /package/dist/{chunk-HGPQUCBC.js.map → chunk-FMTK4XNN.js.map} +0 -0
- /package/dist/{chunk-XAUHJD3L.js.map → chunk-K2JGDXGU.js.map} +0 -0
- /package/dist/{chunk-DAGICKHT.js.map → chunk-ULX5FYEM.js.map} +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Deduplication for RBAC Permission Checks
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module RBAC/RequestDeduplication
|
|
5
|
+
* @since 2.0.0
|
|
6
|
+
*
|
|
7
|
+
* This module provides request deduplication to prevent multiple identical
|
|
8
|
+
* permission checks from being made simultaneously. When multiple components
|
|
9
|
+
* request the same permission at the same time, they share the same promise.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { PermissionCheck } from './types';
|
|
13
|
+
import { RBACCache } from './cache';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Map of in-flight permission check requests
|
|
17
|
+
* Key: cache key string, Value: Promise<boolean>
|
|
18
|
+
*/
|
|
19
|
+
const inFlightRequests = new Map<string, Promise<boolean>>();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate a deduplication key from permission check input
|
|
23
|
+
*
|
|
24
|
+
* @param input - Permission check input
|
|
25
|
+
* @returns Deduplication key string
|
|
26
|
+
*/
|
|
27
|
+
function generateDeduplicationKey(input: PermissionCheck): string {
|
|
28
|
+
return RBACCache.generatePermissionKey({
|
|
29
|
+
userId: input.userId,
|
|
30
|
+
organisationId: input.scope.organisationId!,
|
|
31
|
+
eventId: input.scope.eventId,
|
|
32
|
+
appId: input.scope.appId,
|
|
33
|
+
permission: input.permission,
|
|
34
|
+
pageId: input.pageId,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get or create a deduplicated permission check request
|
|
40
|
+
*
|
|
41
|
+
* If a request for the same permission is already in-flight, returns the existing promise.
|
|
42
|
+
* Otherwise, creates a new request and tracks it.
|
|
43
|
+
*
|
|
44
|
+
* @param input - Permission check input
|
|
45
|
+
* @param checkFn - Function to perform the actual permission check
|
|
46
|
+
* @returns Promise resolving to permission result
|
|
47
|
+
*/
|
|
48
|
+
export async function getOrCreateRequest(
|
|
49
|
+
input: PermissionCheck,
|
|
50
|
+
checkFn: (input: PermissionCheck) => Promise<boolean>
|
|
51
|
+
): Promise<boolean> {
|
|
52
|
+
const key = generateDeduplicationKey(input);
|
|
53
|
+
|
|
54
|
+
// Check if request is already in-flight
|
|
55
|
+
const existingRequest = inFlightRequests.get(key);
|
|
56
|
+
if (existingRequest) {
|
|
57
|
+
return existingRequest;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Create new request
|
|
61
|
+
const requestPromise = checkFn(input).finally(() => {
|
|
62
|
+
// Clean up when request completes (success or failure)
|
|
63
|
+
inFlightRequests.delete(key);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Track the request
|
|
67
|
+
inFlightRequests.set(key, requestPromise);
|
|
68
|
+
|
|
69
|
+
return requestPromise;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Clear all in-flight requests (useful for testing or cleanup)
|
|
74
|
+
*/
|
|
75
|
+
export function clearInFlightRequests(): void {
|
|
76
|
+
inFlightRequests.clear();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get count of in-flight requests (useful for monitoring)
|
|
81
|
+
*
|
|
82
|
+
* @returns Number of in-flight requests
|
|
83
|
+
*/
|
|
84
|
+
export function getInFlightRequestCount(): number {
|
|
85
|
+
return inFlightRequests.size;
|
|
86
|
+
}
|
|
87
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep equality check utility for RBAC
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module RBAC/Utils/DeepEqual
|
|
5
|
+
* @since 2.0.0
|
|
6
|
+
*
|
|
7
|
+
* Provides deep equality checking for scope objects and other RBAC data structures.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Scope } from '../types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Deep equality check for two values
|
|
14
|
+
*
|
|
15
|
+
* @param a - First value
|
|
16
|
+
* @param b - Second value
|
|
17
|
+
* @returns True if values are deeply equal
|
|
18
|
+
*/
|
|
19
|
+
export function deepEqual(a: unknown, b: unknown): boolean {
|
|
20
|
+
if (a === b) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (a == null || b == null) {
|
|
25
|
+
return a === b;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof a !== typeof b) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof a !== 'object') {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (Array.isArray(a) !== Array.isArray(b)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
41
|
+
if (a.length !== b.length) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
for (let i = 0; i < a.length; i++) {
|
|
45
|
+
if (!deepEqual(a[i], b[i])) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const keysA = Object.keys(a as Record<string, unknown>);
|
|
53
|
+
const keysB = Object.keys(b as Record<string, unknown>);
|
|
54
|
+
|
|
55
|
+
if (keysA.length !== keysB.length) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const key of keysA) {
|
|
60
|
+
if (!keysB.includes(key)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (!deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Deep equality check for Scope objects
|
|
73
|
+
*
|
|
74
|
+
* @param a - First scope
|
|
75
|
+
* @param b - Second scope
|
|
76
|
+
* @returns True if scopes are deeply equal
|
|
77
|
+
*/
|
|
78
|
+
export function scopeEqual(a: Scope | null | undefined, b: Scope | null | undefined): boolean {
|
|
79
|
+
if (a === b) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (a == null || b == null) {
|
|
84
|
+
return a === b;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
a.organisationId === b.organisationId &&
|
|
89
|
+
a.eventId === b.eventId &&
|
|
90
|
+
a.appId === b.appId
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
@@ -449,9 +449,22 @@ describe('[service] FileReferenceServiceImpl', () => {
|
|
|
449
449
|
});
|
|
450
450
|
|
|
451
451
|
it('lists all file references for record', async () => {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
452
|
+
// Mock RPC to return full data structure (as per new implementation)
|
|
453
|
+
// RPC returns: id, file_path, file_metadata, is_public, created_at
|
|
454
|
+
// The code constructs FileReference objects from this RPC response
|
|
455
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
456
|
+
data: [{
|
|
457
|
+
id: 'file-ref-123',
|
|
458
|
+
file_path: mockFileReference.file_path,
|
|
459
|
+
file_metadata: {
|
|
460
|
+
...mockFileReference.file_metadata,
|
|
461
|
+
app_id: mockFileReference.app_id // Include app_id in metadata for proper construction
|
|
462
|
+
},
|
|
463
|
+
is_public: mockFileReference.is_public,
|
|
464
|
+
created_at: mockFileReference.created_at
|
|
465
|
+
}],
|
|
466
|
+
error: null
|
|
467
|
+
});
|
|
455
468
|
|
|
456
469
|
const result = await service.listFileReferences(
|
|
457
470
|
'test_table',
|
|
@@ -459,7 +472,21 @@ describe('[service] FileReferenceServiceImpl', () => {
|
|
|
459
472
|
'test-org-123'
|
|
460
473
|
);
|
|
461
474
|
|
|
462
|
-
|
|
475
|
+
// Verify result has correct structure (constructed from RPC response)
|
|
476
|
+
expect(result).toHaveLength(1);
|
|
477
|
+
expect(result[0].id).toBe('file-ref-123');
|
|
478
|
+
expect(result[0].table_name).toBe('test_table');
|
|
479
|
+
expect(result[0].record_id).toBe('test-record-123');
|
|
480
|
+
expect(result[0].organisation_id).toBe('test-org-123');
|
|
481
|
+
expect(result[0].file_path).toBe(mockFileReference.file_path);
|
|
482
|
+
// file_metadata: code extracts fileName and fileType from file_path, then spreads item.file_metadata
|
|
483
|
+
// Since item.file_metadata has fileName: 'test-document.pdf' and fileType: 'application/pdf',
|
|
484
|
+
// the spread overwrites the extracted values
|
|
485
|
+
// So we expect the metadata's values, not the extracted ones
|
|
486
|
+
expect(result[0].file_metadata.fileName).toBe('test-document.pdf');
|
|
487
|
+
expect(result[0].file_metadata.fileType).toBe('application/pdf');
|
|
488
|
+
expect(result[0].is_public).toBe(mockFileReference.is_public);
|
|
489
|
+
expect(result[0].app_id).toBe(mockFileReference.app_id);
|
|
463
490
|
});
|
|
464
491
|
});
|
|
465
492
|
|
|
@@ -26,7 +26,7 @@ export class FileReferenceServiceImpl implements FileReferenceService {
|
|
|
26
26
|
*
|
|
27
27
|
* Storage Flow:
|
|
28
28
|
* 1. Upload file to storage bucket first (files or public-files based on is_public flag)
|
|
29
|
-
* - Path format: {orgId}/{
|
|
29
|
+
* - Path format: {orgId}/{folder}/{timestamp-uuid-filename}
|
|
30
30
|
* - Bucket selection: 'files' (private) or 'public-files' (public)
|
|
31
31
|
* 2. Extract file metadata (dimensions, hash, etc.)
|
|
32
32
|
* 3. Set organisation context for RLS policies
|
|
@@ -48,6 +48,9 @@ export class FileReferenceServiceImpl implements FileReferenceService {
|
|
|
48
48
|
if (!options.record_id) {
|
|
49
49
|
throw new Error('record_id is required for file upload');
|
|
50
50
|
}
|
|
51
|
+
if (!options.folder) {
|
|
52
|
+
throw new Error('folder is required for file upload. The folder prop determines the storage path.');
|
|
53
|
+
}
|
|
51
54
|
|
|
52
55
|
// Step 1: Upload file to storage bucket first
|
|
53
56
|
// This generates a unique path: {orgId}/{folder}/{timestamp-uuid-filename}
|
|
@@ -117,7 +120,7 @@ export class FileReferenceServiceImpl implements FileReferenceService {
|
|
|
117
120
|
// Get the created file reference
|
|
118
121
|
const { data: fileRef, error: fetchError } = await this.supabase
|
|
119
122
|
.from('file_references')
|
|
120
|
-
.select('
|
|
123
|
+
.select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')
|
|
121
124
|
.eq('id', data)
|
|
122
125
|
.single();
|
|
123
126
|
|
|
@@ -146,7 +149,7 @@ export class FileReferenceServiceImpl implements FileReferenceService {
|
|
|
146
149
|
try {
|
|
147
150
|
const { data, error } = await this.supabase
|
|
148
151
|
.from('file_references')
|
|
149
|
-
.select('
|
|
152
|
+
.select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')
|
|
150
153
|
.eq('table_name', table_name)
|
|
151
154
|
.eq('record_id', record_id)
|
|
152
155
|
.eq('organisation_id', organisation_id)
|
|
@@ -294,24 +297,50 @@ export class FileReferenceServiceImpl implements FileReferenceService {
|
|
|
294
297
|
throw new Error(`Failed to list file references: ${error.message}`);
|
|
295
298
|
}
|
|
296
299
|
|
|
297
|
-
// RPC returns
|
|
300
|
+
// RPC returns: id, file_path, file_metadata, is_public, created_at
|
|
301
|
+
// We can construct FileReference objects directly from RPC response + function parameters
|
|
302
|
+
// This avoids a second query and reduces network requests
|
|
298
303
|
if (!data || data.length === 0) {
|
|
299
304
|
return [];
|
|
300
305
|
}
|
|
301
306
|
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (fetchError) {
|
|
311
|
-
throw new Error(`Failed to fetch file references: ${fetchError.message}`);
|
|
307
|
+
// Construct FileReference objects from RPC response
|
|
308
|
+
// This avoids RLS issues with direct queries - the RPC already validated permissions
|
|
309
|
+
interface RpcFileItem {
|
|
310
|
+
id: string;
|
|
311
|
+
file_path: string;
|
|
312
|
+
file_metadata: { app_id?: string; [key: string]: unknown };
|
|
313
|
+
is_public?: boolean;
|
|
314
|
+
created_at?: string;
|
|
312
315
|
}
|
|
316
|
+
const fileReferences: FileReference[] = data
|
|
317
|
+
.filter((item: RpcFileItem) => item.id && item.file_path && item.file_metadata)
|
|
318
|
+
.map((item: RpcFileItem) => {
|
|
319
|
+
// Extract file name and type from file_path
|
|
320
|
+
const fileName = item.file_path.split('/').pop() || 'unknown';
|
|
321
|
+
const fileType = fileName.split('.').pop() || 'unknown';
|
|
322
|
+
|
|
323
|
+
// Construct complete FileReference from RPC response + function parameters
|
|
324
|
+
const fileRef: FileReference = {
|
|
325
|
+
id: item.id,
|
|
326
|
+
table_name: table_name,
|
|
327
|
+
record_id: record_id,
|
|
328
|
+
file_path: item.file_path,
|
|
329
|
+
file_metadata: {
|
|
330
|
+
fileName,
|
|
331
|
+
fileType,
|
|
332
|
+
...(item.file_metadata || {}),
|
|
333
|
+
} as FileMetadata,
|
|
334
|
+
organisation_id: organisation_id,
|
|
335
|
+
app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(''), // May not be in metadata, use empty string
|
|
336
|
+
is_public: item.is_public ?? false,
|
|
337
|
+
created_at: item.created_at || new Date().toISOString(),
|
|
338
|
+
updated_at: item.created_at || new Date().toISOString() // RPC doesn't return updated_at, use created_at
|
|
339
|
+
};
|
|
340
|
+
return fileRef;
|
|
341
|
+
});
|
|
313
342
|
|
|
314
|
-
return
|
|
343
|
+
return fileReferences;
|
|
315
344
|
} catch (error) {
|
|
316
345
|
log.error('Error listing file references:', error);
|
|
317
346
|
throw error;
|