@jmruthers/pace-core 0.5.184 → 0.5.186
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/CHANGELOG.md +38 -0
- package/README.md +60 -1
- package/core-usage-manifest.json +312 -0
- package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
- package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
- package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-DIzEzwKl.d.ts} +23 -10
- package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
- package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
- package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
- package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
- package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
- package/dist/{chunk-W22JP75J.js → chunk-DAGICKHT.js} +9 -7
- package/dist/chunk-DAGICKHT.js.map +1 -0
- package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
- package/dist/chunk-FXFJRTKI.js.map +1 -0
- package/dist/{chunk-CSOFYHAG.js → chunk-GRIQLQ52.js} +374 -60
- package/dist/chunk-GRIQLQ52.js.map +1 -0
- package/dist/{chunk-NQPMQGS2.js → chunk-HDCUMOOI.js} +497 -399
- package/dist/chunk-HDCUMOOI.js.map +1 -0
- package/dist/chunk-HESYZWZW.js +388 -0
- package/dist/chunk-HESYZWZW.js.map +1 -0
- package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
- package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
- package/dist/{chunk-PWAHJW4G.js → chunk-OALXJH4Y.js} +86 -33
- package/dist/chunk-OALXJH4Y.js.map +1 -0
- package/dist/{chunk-MI7HBHN3.js → chunk-TC7D3CR3.js} +89 -9
- package/dist/chunk-TC7D3CR3.js.map +1 -0
- package/dist/chunk-THRPYOFK.js +215 -0
- package/dist/chunk-THRPYOFK.js.map +1 -0
- package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
- package/dist/chunk-U6WNSFX5.js.map +1 -0
- package/dist/{chunk-UHNYIBXL.js → chunk-UQWSHFVX.js} +1 -1
- package/dist/chunk-UQWSHFVX.js.map +1 -0
- package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
- package/dist/components.d.ts +182 -6
- package/dist/components.js +157 -11
- package/dist/components.js.map +1 -1
- package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
- package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
- package/dist/{file-reference-D06mEEWW.d.ts → file-reference-PRTSLxKx.d.ts} +10 -1
- package/dist/hooks.d.ts +52 -15
- package/dist/hooks.js +12 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +82 -18
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +206 -15
- package/dist/rbac/index.js +28 -6
- package/dist/timezone-_pgH8qrY.d.ts +530 -0
- package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +114 -3
- package/dist/utils.d.ts +110 -152
- package/dist/utils.js +128 -138
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +60 -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 +178 -0
- 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 +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +54 -0
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +18 -2
- 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 +30 -0
- 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 +85 -0
- package/docs/api/interfaces/DatabaseIssue.md +41 -0
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +6 -6
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- 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 +48 -8
- package/docs/api/interfaces/FileUploadProps.md +46 -13
- 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 +9 -9
- 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 +62 -0
- 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 +36 -23
- 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/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
- 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 +52 -0
- 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 +4 -4
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- 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 +7 -7
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +5 -5
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
- 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 +41 -0
- 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 +62 -0
- package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- 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 +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- 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 +746 -50
- package/docs/api-reference/components.md +26 -12
- package/docs/api-reference/hooks.md +111 -0
- package/docs/api-reference/rpc-functions.md +1 -1
- package/docs/api-reference/utilities.md +184 -0
- package/docs/getting-started/installation-guide.md +75 -16
- package/docs/getting-started/quick-start.md +61 -11
- package/docs/implementation-guides/authentication.md +88 -12
- package/docs/implementation-guides/file-reference-system.md +26 -3
- package/docs/implementation-guides/file-upload-storage.md +30 -1
- package/docs/rbac/README.md +1 -0
- package/docs/rbac/compliance/compliance-guide.md +544 -0
- package/docs/rbac/getting-started.md +158 -33
- package/docs/standards/pace-core-compliance.md +432 -0
- package/eslint-config-pace-core.cjs +93 -0
- package/package.json +15 -3
- package/scripts/analyze-bundle.js +232 -0
- package/scripts/build-css.js +56 -0
- package/scripts/build-docs-incremental.js +1015 -0
- package/scripts/check-pace-core-compliance.cjs +2353 -0
- package/scripts/check-pace-core-compliance.js +512 -0
- package/scripts/generate-docs.js +157 -0
- package/scripts/setup-build-cache.js +73 -0
- package/scripts/utils/command-runner.js +131 -0
- package/scripts/utils/env.js +33 -0
- package/scripts/utils/index.js +10 -0
- package/scripts/utils/logger.js +88 -0
- package/scripts/utils/path-helpers.js +37 -0
- package/scripts/validate-formats.js +133 -0
- package/scripts/validate-master.js +155 -0
- package/scripts/validate-pre-publish.js +140 -0
- package/scripts/validate-theme.js +142 -0
- package/src/components/Calendar/Calendar.tsx +8 -1
- package/src/components/Card/Card.tsx +47 -8
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
- package/src/components/DatePickerWithTimezone/README.md +135 -0
- package/src/components/DatePickerWithTimezone/index.ts +10 -0
- package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
- package/src/components/DateTimeField/DateTimeField.tsx +232 -0
- package/src/components/DateTimeField/README.md +148 -0
- package/src/components/DateTimeField/index.ts +10 -0
- package/src/components/FileUpload/FileUpload.test.tsx +2 -0
- package/src/components/FileUpload/FileUpload.tsx +10 -1
- package/src/components/Header/Header.test.tsx +47 -18
- package/src/components/Header/Header.tsx +22 -7
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
- package/src/components/PaceAppLayout/README.md +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +146 -5
- package/src/components/index.ts +8 -0
- package/src/eslint-rules/pace-core-compliance.cjs +406 -0
- package/src/eslint-rules/pace-core-compliance.js +640 -0
- package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useFileReference.test.ts +2 -0
- package/src/hooks/useFormDialog.ts +147 -0
- package/src/hooks/usePreventTabReload.ts +106 -0
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/index.ts +27 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
- package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
- package/src/rbac/compliance/database-validator.ts +165 -0
- package/src/rbac/compliance/index.ts +38 -0
- package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
- package/src/rbac/compliance/runtime-compliance.ts +77 -0
- package/src/rbac/compliance/setup-validator.ts +131 -0
- package/src/rbac/components/PagePermissionGuard.tsx +8 -64
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
- package/src/rbac/docs/event-based-apps.md +285 -0
- package/src/rbac/errors.ts +11 -0
- package/src/rbac/hooks/useRoleManagement.ts +292 -12
- package/src/rbac/index.ts +30 -0
- package/src/services/OrganisationService.ts +4 -0
- package/src/styles/core.css +5 -5
- package/src/types/database.generated.ts +63 -9
- package/src/types/file-reference.ts +9 -0
- package/src/utils/__tests__/timezone.test.ts +345 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +60 -4
- package/src/utils/file-reference/index.ts +13 -2
- package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
- package/src/utils/formatting/formatting.ts +179 -0
- package/src/utils/index.ts +27 -1
- package/src/utils/location/index.ts +16 -0
- package/src/utils/location/location.test.ts +286 -0
- package/src/utils/location/location.ts +175 -0
- package/src/utils/security/secureDataAccess.ts +1 -1
- package/src/utils/storage/helpers.ts +68 -0
- package/src/utils/timezone/index.ts +17 -0
- package/src/utils/timezone/timezone.test.ts +349 -0
- package/src/utils/timezone/timezone.ts +281 -0
- package/dist/chunk-CSOFYHAG.js.map +0 -1
- package/dist/chunk-FUEYYMX5.js.map +0 -1
- package/dist/chunk-HKIT6O7W.js +0 -198
- package/dist/chunk-HKIT6O7W.js.map +0 -1
- package/dist/chunk-KUEN3HFB.js +0 -94
- package/dist/chunk-KUEN3HFB.js.map +0 -1
- package/dist/chunk-M7W4CP3M.js.map +0 -1
- package/dist/chunk-MI7HBHN3.js.map +0 -1
- package/dist/chunk-NQPMQGS2.js.map +0 -1
- package/dist/chunk-PWAHJW4G.js.map +0 -1
- package/dist/chunk-UHNYIBXL.js.map +0 -1
- package/dist/chunk-W22JP75J.js.map +0 -1
- package/dist/formatting-5wETwiGF.d.ts +0 -162
- /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
- /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
- /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
package/src/styles/core.css
CHANGED
|
@@ -240,14 +240,14 @@
|
|
|
240
240
|
/* Custom utility styles go here */
|
|
241
241
|
|
|
242
242
|
|
|
243
|
-
/* Hide spinner arrows on number inputs
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
/* Hide spinner arrows on all number inputs (modern UX convention) */
|
|
244
|
+
input[type="number"]::-webkit-inner-spin-button,
|
|
245
|
+
input[type="number"]::-webkit-outer-spin-button {
|
|
246
246
|
-webkit-appearance: none;
|
|
247
247
|
margin: 0;
|
|
248
248
|
}
|
|
249
|
-
|
|
250
|
-
|
|
249
|
+
|
|
250
|
+
input[type="number"] {
|
|
251
251
|
-moz-appearance: textfield;
|
|
252
252
|
}
|
|
253
253
|
}
|
|
@@ -3320,13 +3320,13 @@ export type Database = {
|
|
|
3320
3320
|
},
|
|
3321
3321
|
]
|
|
3322
3322
|
}
|
|
3323
|
-
|
|
3323
|
+
pace_identification: {
|
|
3324
3324
|
Row: {
|
|
3325
3325
|
created_at: string | null
|
|
3326
3326
|
document_number: string | null
|
|
3327
|
-
document_type: string
|
|
3328
3327
|
expiry_date: string | null
|
|
3329
3328
|
id: string
|
|
3329
|
+
identification_type_id: number | null
|
|
3330
3330
|
issue_city: string | null
|
|
3331
3331
|
issue_country: string | null
|
|
3332
3332
|
issue_date: string | null
|
|
@@ -3339,9 +3339,9 @@ export type Database = {
|
|
|
3339
3339
|
Insert: {
|
|
3340
3340
|
created_at?: string | null
|
|
3341
3341
|
document_number?: string | null
|
|
3342
|
-
document_type: string
|
|
3343
3342
|
expiry_date?: string | null
|
|
3344
3343
|
id?: string
|
|
3344
|
+
identification_type_id?: number | null
|
|
3345
3345
|
issue_city?: string | null
|
|
3346
3346
|
issue_country?: string | null
|
|
3347
3347
|
issue_date?: string | null
|
|
@@ -3354,9 +3354,9 @@ export type Database = {
|
|
|
3354
3354
|
Update: {
|
|
3355
3355
|
created_at?: string | null
|
|
3356
3356
|
document_number?: string | null
|
|
3357
|
-
document_type?: string
|
|
3358
3357
|
expiry_date?: string | null
|
|
3359
3358
|
id?: string
|
|
3359
|
+
identification_type_id?: number | null
|
|
3360
3360
|
issue_city?: string | null
|
|
3361
3361
|
issue_country?: string | null
|
|
3362
3362
|
issue_date?: string | null
|
|
@@ -3368,14 +3368,21 @@ export type Database = {
|
|
|
3368
3368
|
}
|
|
3369
3369
|
Relationships: [
|
|
3370
3370
|
{
|
|
3371
|
-
foreignKeyName: "
|
|
3371
|
+
foreignKeyName: "fk_pace_identification_organisation_id"
|
|
3372
3372
|
columns: ["organisation_id"]
|
|
3373
3373
|
isOneToOne: false
|
|
3374
3374
|
referencedRelation: "organisations"
|
|
3375
3375
|
referencedColumns: ["id"]
|
|
3376
3376
|
},
|
|
3377
3377
|
{
|
|
3378
|
-
foreignKeyName: "
|
|
3378
|
+
foreignKeyName: "fk_pace_identification_type_id"
|
|
3379
|
+
columns: ["identification_type_id"]
|
|
3380
|
+
isOneToOne: false
|
|
3381
|
+
referencedRelation: "pace_identification_type"
|
|
3382
|
+
referencedColumns: ["id"]
|
|
3383
|
+
},
|
|
3384
|
+
{
|
|
3385
|
+
foreignKeyName: "pace_identification_member_id_fkey"
|
|
3379
3386
|
columns: ["member_id"]
|
|
3380
3387
|
isOneToOne: false
|
|
3381
3388
|
referencedRelation: "pace_member"
|
|
@@ -3383,6 +3390,53 @@ export type Database = {
|
|
|
3383
3390
|
},
|
|
3384
3391
|
]
|
|
3385
3392
|
}
|
|
3393
|
+
pace_identification_type: {
|
|
3394
|
+
Row: {
|
|
3395
|
+
created_at: string | null
|
|
3396
|
+
created_by: string | null
|
|
3397
|
+
description: string | null
|
|
3398
|
+
id: number
|
|
3399
|
+
is_active: boolean | null
|
|
3400
|
+
name: string
|
|
3401
|
+
organisation_id: string
|
|
3402
|
+
sort_order: number | null
|
|
3403
|
+
updated_at: string | null
|
|
3404
|
+
updated_by: string | null
|
|
3405
|
+
}
|
|
3406
|
+
Insert: {
|
|
3407
|
+
created_at?: string | null
|
|
3408
|
+
created_by?: string | null
|
|
3409
|
+
description?: string | null
|
|
3410
|
+
id?: never
|
|
3411
|
+
is_active?: boolean | null
|
|
3412
|
+
name: string
|
|
3413
|
+
organisation_id: string
|
|
3414
|
+
sort_order?: number | null
|
|
3415
|
+
updated_at?: string | null
|
|
3416
|
+
updated_by?: string | null
|
|
3417
|
+
}
|
|
3418
|
+
Update: {
|
|
3419
|
+
created_at?: string | null
|
|
3420
|
+
created_by?: string | null
|
|
3421
|
+
description?: string | null
|
|
3422
|
+
id?: never
|
|
3423
|
+
is_active?: boolean | null
|
|
3424
|
+
name?: string
|
|
3425
|
+
organisation_id?: string
|
|
3426
|
+
sort_order?: number | null
|
|
3427
|
+
updated_at?: string | null
|
|
3428
|
+
updated_by?: string | null
|
|
3429
|
+
}
|
|
3430
|
+
Relationships: [
|
|
3431
|
+
{
|
|
3432
|
+
foreignKeyName: "pace_identification_type_organisation_id_fkey"
|
|
3433
|
+
columns: ["organisation_id"]
|
|
3434
|
+
isOneToOne: false
|
|
3435
|
+
referencedRelation: "organisations"
|
|
3436
|
+
referencedColumns: ["id"]
|
|
3437
|
+
},
|
|
3438
|
+
]
|
|
3439
|
+
}
|
|
3386
3440
|
pace_member: {
|
|
3387
3441
|
Row: {
|
|
3388
3442
|
address_id: string | null
|
|
@@ -3858,7 +3912,7 @@ export type Database = {
|
|
|
3858
3912
|
},
|
|
3859
3913
|
]
|
|
3860
3914
|
}
|
|
3861
|
-
|
|
3915
|
+
pace_qualification: {
|
|
3862
3916
|
Row: {
|
|
3863
3917
|
created_at: string | null
|
|
3864
3918
|
credential_id: string | null
|
|
@@ -3900,14 +3954,14 @@ export type Database = {
|
|
|
3900
3954
|
}
|
|
3901
3955
|
Relationships: [
|
|
3902
3956
|
{
|
|
3903
|
-
foreignKeyName: "
|
|
3957
|
+
foreignKeyName: "fk_pace_qualification_organisation_id"
|
|
3904
3958
|
columns: ["organisation_id"]
|
|
3905
3959
|
isOneToOne: false
|
|
3906
3960
|
referencedRelation: "organisations"
|
|
3907
3961
|
referencedColumns: ["id"]
|
|
3908
3962
|
},
|
|
3909
3963
|
{
|
|
3910
|
-
foreignKeyName: "
|
|
3964
|
+
foreignKeyName: "pace_qualification_member_id_fkey"
|
|
3911
3965
|
columns: ["member_id"]
|
|
3912
3966
|
isOneToOne: false
|
|
3913
3967
|
referencedRelation: "pace_member"
|
|
@@ -51,12 +51,21 @@ export enum FileCategory {
|
|
|
51
51
|
TRAC_TRANSPORT = 'trac_transport'
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Options for uploading a file with a file reference
|
|
56
|
+
* @property pageContext - The page context where the file upload occurs (e.g., 'configuration', 'forms', 'applications')
|
|
57
|
+
* Used for context-aware permission checks. Required to check appropriate page-level permissions.
|
|
58
|
+
* @property event_id - Optional event ID for event-scoped permission checks. Required for event-based apps.
|
|
59
|
+
*/
|
|
54
60
|
export interface FileUploadOptions {
|
|
55
61
|
table_name: string;
|
|
56
62
|
record_id: string;
|
|
57
63
|
organisation_id: string;
|
|
58
64
|
app_id: AppId;
|
|
59
65
|
category: FileCategory;
|
|
66
|
+
folder: string; // Folder name in storage bucket (e.g., 'profile_photos', 'documents')
|
|
67
|
+
pageContext: string;
|
|
68
|
+
event_id?: string;
|
|
60
69
|
is_public?: boolean;
|
|
61
70
|
custom_metadata?: Record<string, unknown>;
|
|
62
71
|
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Timezone Utilities Unit Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/__tests__/Timezone
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive tests for timezone utility functions covering all critical functionality,
|
|
8
|
+
* edge cases, and error handling.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
12
|
+
import {
|
|
13
|
+
formatInTimeZone,
|
|
14
|
+
getTimezoneAbbreviation,
|
|
15
|
+
formatTimeInTimeZone,
|
|
16
|
+
getUserTimeZone,
|
|
17
|
+
toZonedTime,
|
|
18
|
+
fromZonedTime,
|
|
19
|
+
roundToNearestMinutes,
|
|
20
|
+
getTimeZoneDifference
|
|
21
|
+
} from '../timezone';
|
|
22
|
+
|
|
23
|
+
describe('Timezone Utilities', () => {
|
|
24
|
+
describe('formatInTimeZone', () => {
|
|
25
|
+
it('should format date in specific timezone', () => {
|
|
26
|
+
const utcDate = new Date('2024-01-15T10:00:00Z');
|
|
27
|
+
const result = formatInTimeZone(utcDate, 'America/New_York', 'MMM dd, yyyy HH:mm');
|
|
28
|
+
|
|
29
|
+
// EST is UTC-5, so 10:00 UTC = 05:00 EST
|
|
30
|
+
expect(result).toContain('Jan 15, 2024');
|
|
31
|
+
expect(result).toContain('05:00');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should handle ISO string input', () => {
|
|
35
|
+
const result = formatInTimeZone('2024-01-15T10:00:00Z', 'America/New_York', 'MMM dd, yyyy');
|
|
36
|
+
expect(result).toContain('Jan 15, 2024');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should handle timestamp input', () => {
|
|
40
|
+
const timestamp = new Date('2024-01-15T10:00:00Z').getTime();
|
|
41
|
+
const result = formatInTimeZone(timestamp, 'America/New_York', 'MMM dd, yyyy');
|
|
42
|
+
expect(result).toContain('Jan 15, 2024');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should handle different format strings', () => {
|
|
46
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
47
|
+
const result = formatInTimeZone(date, 'America/New_York', 'yyyy-MM-dd');
|
|
48
|
+
expect(result).toBe('2024-01-15');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should return "Invalid date" for invalid date input', () => {
|
|
52
|
+
const result = formatInTimeZone(new Date('invalid'), 'America/New_York', 'MMM dd, yyyy');
|
|
53
|
+
expect(result).toBe('Invalid date');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return "Invalid date" for invalid timezone', () => {
|
|
57
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
58
|
+
const result = formatInTimeZone(date, '', 'MMM dd, yyyy');
|
|
59
|
+
expect(result).toBe('Invalid date');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle DST transitions', () => {
|
|
63
|
+
// Summer date (DST)
|
|
64
|
+
const summerDate = new Date('2024-07-15T10:00:00Z');
|
|
65
|
+
const summerResult = formatInTimeZone(summerDate, 'America/New_York', 'HH:mm');
|
|
66
|
+
|
|
67
|
+
// Winter date (no DST)
|
|
68
|
+
const winterDate = new Date('2024-01-15T10:00:00Z');
|
|
69
|
+
const winterResult = formatInTimeZone(winterDate, 'America/New_York', 'HH:mm');
|
|
70
|
+
|
|
71
|
+
// Summer should be UTC-4 (EDT), winter should be UTC-5 (EST)
|
|
72
|
+
expect(summerResult).not.toBe(winterResult);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('getTimezoneAbbreviation', () => {
|
|
77
|
+
it('should get timezone abbreviation', () => {
|
|
78
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
79
|
+
const result = getTimezoneAbbreviation(date, 'America/New_York');
|
|
80
|
+
|
|
81
|
+
// Should return EST or EDT depending on DST
|
|
82
|
+
expect(result).toMatch(/EST|EDT|America\/New_York/);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should handle different timezones', () => {
|
|
86
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
87
|
+
const pst = getTimezoneAbbreviation(date, 'America/Los_Angeles');
|
|
88
|
+
const est = getTimezoneAbbreviation(date, 'America/New_York');
|
|
89
|
+
|
|
90
|
+
expect(pst).toBeTruthy();
|
|
91
|
+
expect(est).toBeTruthy();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should return timezone name on error', () => {
|
|
95
|
+
const date = new Date('invalid');
|
|
96
|
+
const result = getTimezoneAbbreviation(date, 'America/New_York');
|
|
97
|
+
expect(result).toBeTruthy();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should handle invalid timezone gracefully', () => {
|
|
101
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
102
|
+
const result = getTimezoneAbbreviation(date, '');
|
|
103
|
+
// Empty string returns 'UTC' as fallback per implementation
|
|
104
|
+
expect(result).toBe('UTC');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('formatTimeInTimeZone', () => {
|
|
109
|
+
it('should format time only in timezone', () => {
|
|
110
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
111
|
+
const result = formatTimeInTimeZone(date, 'America/New_York');
|
|
112
|
+
|
|
113
|
+
// Should be in HH:mm format
|
|
114
|
+
expect(result).toMatch(/^\d{2}:\d{2}$/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle string date input', () => {
|
|
118
|
+
const result = formatTimeInTimeZone('2024-01-15T10:00:00Z', 'America/New_York');
|
|
119
|
+
expect(result).toMatch(/^\d{2}:\d{2}$/);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('getUserTimeZone', () => {
|
|
124
|
+
it('should return user timezone', () => {
|
|
125
|
+
const result = getUserTimeZone();
|
|
126
|
+
expect(typeof result).toBe('string');
|
|
127
|
+
expect(result.length).toBeGreaterThan(0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should return UTC as fallback', () => {
|
|
131
|
+
// Mock Intl to be undefined
|
|
132
|
+
const originalIntl = global.Intl;
|
|
133
|
+
// @ts-expect-error - Testing error case
|
|
134
|
+
global.Intl = undefined;
|
|
135
|
+
|
|
136
|
+
const result = getUserTimeZone();
|
|
137
|
+
expect(result).toBe('UTC');
|
|
138
|
+
|
|
139
|
+
global.Intl = originalIntl;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should handle Intl.DateTimeFormat errors gracefully', () => {
|
|
143
|
+
const originalIntl = global.Intl;
|
|
144
|
+
// @ts-expect-error - Testing error case
|
|
145
|
+
global.Intl = {
|
|
146
|
+
DateTimeFormat: () => {
|
|
147
|
+
throw new Error('Test error');
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const result = getUserTimeZone();
|
|
152
|
+
expect(result).toBe('UTC');
|
|
153
|
+
|
|
154
|
+
global.Intl = originalIntl;
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('toZonedTime', () => {
|
|
159
|
+
it('should convert UTC to timezone local time', () => {
|
|
160
|
+
const utcDate = new Date('2024-01-15T10:00:00Z');
|
|
161
|
+
const result = toZonedTime(utcDate, 'America/New_York');
|
|
162
|
+
|
|
163
|
+
expect(result).toBeInstanceOf(Date);
|
|
164
|
+
expect(isNaN(result.getTime())).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should handle invalid date input', () => {
|
|
168
|
+
const result = toZonedTime(new Date('invalid'), 'America/New_York');
|
|
169
|
+
expect(isNaN(result.getTime())).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should handle invalid timezone', () => {
|
|
173
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
174
|
+
const result = toZonedTime(date, '');
|
|
175
|
+
// Empty timezone returns original date per implementation
|
|
176
|
+
expect(result).toBe(date);
|
|
177
|
+
expect(result.getTime()).toBe(date.getTime());
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should handle DST correctly', () => {
|
|
181
|
+
const summerDate = new Date('2024-07-15T10:00:00Z');
|
|
182
|
+
const winterDate = new Date('2024-01-15T10:00:00Z');
|
|
183
|
+
|
|
184
|
+
const summerResult = toZonedTime(summerDate, 'America/New_York');
|
|
185
|
+
const winterResult = toZonedTime(winterDate, 'America/New_York');
|
|
186
|
+
|
|
187
|
+
// Results should be different due to DST
|
|
188
|
+
expect(summerResult.getHours()).not.toBe(winterResult.getHours());
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('fromZonedTime', () => {
|
|
193
|
+
it('should convert local time to UTC', () => {
|
|
194
|
+
const localDate = new Date(2024, 0, 15, 10, 0); // Jan 15, 2024 10:00 AM (local)
|
|
195
|
+
const result = fromZonedTime(localDate, 'America/New_York');
|
|
196
|
+
|
|
197
|
+
expect(result).toBeInstanceOf(Date);
|
|
198
|
+
expect(isNaN(result.getTime())).toBe(false);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should handle invalid date input', () => {
|
|
202
|
+
const result = fromZonedTime(new Date('invalid'), 'America/New_York');
|
|
203
|
+
expect(isNaN(result.getTime())).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should handle invalid timezone', () => {
|
|
207
|
+
const date = new Date(2024, 0, 15, 10, 0);
|
|
208
|
+
const result = fromZonedTime(date, '');
|
|
209
|
+
// Empty timezone returns original date per implementation
|
|
210
|
+
expect(result).toBe(date);
|
|
211
|
+
expect(result.getTime()).toBe(date.getTime());
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should correctly convert to UTC', () => {
|
|
215
|
+
// 10:00 AM EST (UTC-5) should be 15:00 UTC
|
|
216
|
+
// Using a specific date in January (no DST) for predictable results
|
|
217
|
+
const localDate = new Date(2024, 0, 15, 10, 0, 0, 0);
|
|
218
|
+
const utcResult = fromZonedTime(localDate, 'America/New_York');
|
|
219
|
+
|
|
220
|
+
// Check that it's approximately 5 hours ahead (EST is UTC-5)
|
|
221
|
+
// The actual difference depends on how fromZonedTime interprets the local date
|
|
222
|
+
// We just verify it's a valid date and the conversion happened
|
|
223
|
+
expect(isNaN(utcResult.getTime())).toBe(false);
|
|
224
|
+
expect(utcResult).toBeInstanceOf(Date);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('roundToNearestMinutes', () => {
|
|
229
|
+
it('should round to nearest 5 minutes by default', () => {
|
|
230
|
+
const date = new Date('2024-01-15T10:23:00Z');
|
|
231
|
+
const result = roundToNearestMinutes(date);
|
|
232
|
+
|
|
233
|
+
expect(result).toBeInstanceOf(Date);
|
|
234
|
+
const minutes = result.getMinutes();
|
|
235
|
+
expect([0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]).toContain(minutes);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should round to specified minutes step', () => {
|
|
239
|
+
const date = new Date('2024-01-15T10:23:00Z');
|
|
240
|
+
const result = roundToNearestMinutes(date, 15);
|
|
241
|
+
|
|
242
|
+
const minutes = result.getMinutes();
|
|
243
|
+
expect([0, 15, 30, 45]).toContain(minutes);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should handle invalid date input', () => {
|
|
247
|
+
const result = roundToNearestMinutes(new Date('invalid'));
|
|
248
|
+
expect(isNaN(result.getTime())).toBe(true);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should return original date for invalid minutesStep', () => {
|
|
252
|
+
const date = new Date('2024-01-15T10:23:00Z');
|
|
253
|
+
const result = roundToNearestMinutes(date, -5);
|
|
254
|
+
expect(result.getTime()).toBe(date.getTime());
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should return original date for non-integer minutesStep', () => {
|
|
258
|
+
const date = new Date('2024-01-15T10:23:00Z');
|
|
259
|
+
const result = roundToNearestMinutes(date, 5.5);
|
|
260
|
+
expect(result.getTime()).toBe(date.getTime());
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should round up correctly', () => {
|
|
264
|
+
const date = new Date('2024-01-15T10:23:00Z');
|
|
265
|
+
const result = roundToNearestMinutes(date, 5);
|
|
266
|
+
// Should round to 25 (closer to 25 than 20)
|
|
267
|
+
expect(result.getMinutes()).toBe(25);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe('getTimeZoneDifference', () => {
|
|
272
|
+
it('should calculate timezone difference in hours', () => {
|
|
273
|
+
const result = getTimeZoneDifference('America/New_York', 'America/Los_Angeles');
|
|
274
|
+
// EST is 3 hours ahead of PST
|
|
275
|
+
expect(result).toBe(-3);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should handle UTC timezone', () => {
|
|
279
|
+
const result = getTimeZoneDifference('UTC', 'America/New_York');
|
|
280
|
+
// UTC to EST is -5 hours (or -4 during DST)
|
|
281
|
+
expect([-5, -4]).toContain(result);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should return 0 for same timezone', () => {
|
|
285
|
+
const result = getTimeZoneDifference('America/New_York', 'America/New_York');
|
|
286
|
+
expect(result).toBe(0);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should handle invalid timezones gracefully', () => {
|
|
290
|
+
const result = getTimeZoneDifference('', 'America/New_York');
|
|
291
|
+
expect(result).toBe(0);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should handle reversed timezones', () => {
|
|
295
|
+
const forward = getTimeZoneDifference('America/New_York', 'America/Los_Angeles');
|
|
296
|
+
const backward = getTimeZoneDifference('America/Los_Angeles', 'America/New_York');
|
|
297
|
+
|
|
298
|
+
expect(forward).toBe(-backward);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('should account for DST in calculations', () => {
|
|
302
|
+
// Test with a summer date (DST active)
|
|
303
|
+
const summerDiff = getTimeZoneDifference('UTC', 'America/New_York');
|
|
304
|
+
// Should be -4 (EDT) or -5 (EST) depending on current date
|
|
305
|
+
expect([-4, -5]).toContain(summerDiff);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('Edge cases and error handling', () => {
|
|
310
|
+
it('should handle null and undefined gracefully', () => {
|
|
311
|
+
// @ts-expect-error - Testing error case
|
|
312
|
+
expect(formatInTimeZone(null, 'America/New_York', 'MMM dd, yyyy')).toBe('Invalid date');
|
|
313
|
+
// @ts-expect-error - Testing error case
|
|
314
|
+
expect(formatInTimeZone(undefined, 'America/New_York', 'MMM dd, yyyy')).toBe('Invalid date');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should handle various timezone formats', () => {
|
|
318
|
+
const date = new Date('2024-01-15T10:00:00Z');
|
|
319
|
+
const timezones = [
|
|
320
|
+
'America/New_York',
|
|
321
|
+
'Europe/London',
|
|
322
|
+
'Asia/Tokyo',
|
|
323
|
+
'Australia/Sydney',
|
|
324
|
+
'UTC'
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
timezones.forEach(tz => {
|
|
328
|
+
const result = formatInTimeZone(date, tz, 'MMM dd, yyyy');
|
|
329
|
+
expect(result).not.toBe('Invalid date');
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should handle extreme dates', () => {
|
|
334
|
+
const farFuture = new Date('2099-12-31T23:59:59Z');
|
|
335
|
+
// Use a date that won't have timezone boundary issues
|
|
336
|
+
const farPast = new Date('1900-01-01T12:00:00Z');
|
|
337
|
+
|
|
338
|
+
expect(formatInTimeZone(farFuture, 'America/New_York', 'yyyy')).toContain('2099');
|
|
339
|
+
// The year might be 1899 or 1900 depending on timezone conversion, so just check it's valid
|
|
340
|
+
const pastResult = formatInTimeZone(farPast, 'America/New_York', 'yyyy');
|
|
341
|
+
expect(pastResult).not.toBe('Invalid date');
|
|
342
|
+
expect(pastResult.length).toBeGreaterThan(0);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
});
|
|
@@ -50,6 +50,8 @@ const mockFileUploadOptions = {
|
|
|
50
50
|
organisation_id: 'test-org-123',
|
|
51
51
|
app_id: 'test-app-123',
|
|
52
52
|
category: FileCategory.GENERAL_DOCUMENTS,
|
|
53
|
+
folder: 'documents',
|
|
54
|
+
pageContext: 'configuration',
|
|
53
55
|
is_public: false
|
|
54
56
|
};
|
|
55
57
|
|
|
@@ -104,7 +106,7 @@ describe('[service] FileReferenceServiceImpl', () => {
|
|
|
104
106
|
testFile,
|
|
105
107
|
expect.objectContaining({
|
|
106
108
|
orgId: mockFileUploadOptions.organisation_id,
|
|
107
|
-
customPath: mockFileUploadOptions.
|
|
109
|
+
customPath: mockFileUploadOptions.folder,
|
|
108
110
|
isPublic: mockFileUploadOptions.is_public
|
|
109
111
|
})
|
|
110
112
|
);
|
|
@@ -116,6 +118,7 @@ describe('[service] FileReferenceServiceImpl', () => {
|
|
|
116
118
|
p_record_id: mockFileUploadOptions.record_id,
|
|
117
119
|
p_organisation_id: mockFileUploadOptions.organisation_id,
|
|
118
120
|
p_app_id: mockFileUploadOptions.app_id,
|
|
121
|
+
p_page_context: mockFileUploadOptions.pageContext,
|
|
119
122
|
p_file_metadata: expect.objectContaining({
|
|
120
123
|
fileName: testFile.name,
|
|
121
124
|
fileType: testFile.type,
|
|
@@ -755,22 +758,75 @@ describe('[utility] createFileReferenceService', () => {
|
|
|
755
758
|
describe('[utility] uploadFileWithReference', () => {
|
|
756
759
|
it('uploads file and creates reference successfully', async () => {
|
|
757
760
|
const testFile = createTestFile();
|
|
761
|
+
|
|
762
|
+
// Mock successful RPC call that returns file reference ID
|
|
763
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
764
|
+
data: 'file-ref-123',
|
|
765
|
+
error: null
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
// Mock successful file reference fetch - using the same pattern as other tests
|
|
769
|
+
(mockSupabase.from() as any).select().eq().eq().eq().single.mockResolvedValue({
|
|
770
|
+
data: mockFileReference,
|
|
771
|
+
error: null
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
// Mock storage signed URL generation
|
|
775
|
+
mockSupabase.storage = {
|
|
776
|
+
from: vi.fn().mockReturnValue({
|
|
777
|
+
createSignedUrl: vi.fn().mockResolvedValue({
|
|
778
|
+
data: { signedUrl: 'https://example.com/signed-url' },
|
|
779
|
+
error: null
|
|
780
|
+
})
|
|
781
|
+
})
|
|
782
|
+
} as any;
|
|
783
|
+
|
|
758
784
|
const result = await uploadFileWithReference(mockSupabase, mockFileUploadOptions, testFile);
|
|
759
785
|
expect(result).toHaveProperty('file_reference');
|
|
760
786
|
expect('file_url' in result).toBe(true);
|
|
761
787
|
});
|
|
762
788
|
|
|
763
789
|
it('handles upload failures gracefully', async () => {
|
|
764
|
-
//
|
|
790
|
+
// Mock upload failure
|
|
791
|
+
mockUploadFile.mockResolvedValue({
|
|
792
|
+
success: false,
|
|
793
|
+
error: 'Upload failed'
|
|
794
|
+
});
|
|
795
|
+
|
|
765
796
|
const testFile = createTestFile();
|
|
766
|
-
|
|
767
|
-
|
|
797
|
+
await expect(uploadFileWithReference(mockSupabase, mockFileUploadOptions, testFile))
|
|
798
|
+
.rejects.toThrow('Upload failed');
|
|
768
799
|
});
|
|
769
800
|
|
|
770
801
|
it('handles URL generation failures gracefully', async () => {
|
|
771
802
|
const testFile = createTestFile();
|
|
803
|
+
|
|
804
|
+
// Mock successful RPC call
|
|
805
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
806
|
+
data: 'file-ref-123',
|
|
807
|
+
error: null
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
// Mock successful file reference fetch
|
|
811
|
+
(mockSupabase.from() as any).select().eq().eq().eq().single.mockResolvedValue({
|
|
812
|
+
data: mockFileReference,
|
|
813
|
+
error: null
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
// Mock storage to fail on signed URL generation
|
|
817
|
+
mockSupabase.storage = {
|
|
818
|
+
from: vi.fn().mockReturnValue({
|
|
819
|
+
createSignedUrl: vi.fn().mockResolvedValue({
|
|
820
|
+
data: null,
|
|
821
|
+
error: { message: 'URL generation failed' }
|
|
822
|
+
})
|
|
823
|
+
})
|
|
824
|
+
} as any;
|
|
825
|
+
|
|
772
826
|
const result = await uploadFileWithReference(mockSupabase, mockFileUploadOptions, testFile);
|
|
773
827
|
expect(result).toHaveProperty('file_reference');
|
|
828
|
+
// URL should be empty string when generation fails
|
|
829
|
+
expect(result.file_url).toBe('');
|
|
774
830
|
});
|
|
775
831
|
|
|
776
832
|
it('validates required parameters', async () => {
|
|
@@ -50,13 +50,13 @@ export class FileReferenceServiceImpl implements FileReferenceService {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// Step 1: Upload file to storage bucket first
|
|
53
|
-
// This generates a unique path: {orgId}/{
|
|
53
|
+
// This generates a unique path: {orgId}/{folder}/{timestamp-uuid-filename}
|
|
54
54
|
// Bucket is automatically selected based on is_public flag
|
|
55
55
|
const uploadResult = await uploadFile(this.supabase, file, {
|
|
56
56
|
appName: 'file-reference',
|
|
57
57
|
orgId: options.organisation_id,
|
|
58
58
|
isPublic: options.is_public || false,
|
|
59
|
-
customPath: options.
|
|
59
|
+
customPath: options.folder // Use folder prop as the custom path segment
|
|
60
60
|
});
|
|
61
61
|
if (!uploadResult.success) {
|
|
62
62
|
throw new Error(`Failed to upload file: ${uploadResult.error}`);
|
|
@@ -88,6 +88,8 @@ export class FileReferenceServiceImpl implements FileReferenceService {
|
|
|
88
88
|
p_file_path: filePath, // Storage path from step 1
|
|
89
89
|
p_organisation_id: options.organisation_id,
|
|
90
90
|
p_app_id: options.app_id,
|
|
91
|
+
p_page_context: options.pageContext,
|
|
92
|
+
p_event_id: options.event_id || null, // Pass event_id for event-based apps
|
|
91
93
|
p_file_metadata: {
|
|
92
94
|
fileName: file.name,
|
|
93
95
|
fileType: file.type,
|
|
@@ -105,6 +107,13 @@ export class FileReferenceServiceImpl implements FileReferenceService {
|
|
|
105
107
|
throw new Error(`Failed to create file reference: ${error.message}`);
|
|
106
108
|
}
|
|
107
109
|
|
|
110
|
+
// Check if RPC returned null (permission denied or other failure)
|
|
111
|
+
if (!data || data === null) {
|
|
112
|
+
// Clean up the uploaded file since DB insert failed
|
|
113
|
+
await deleteFile(this.supabase, filePath, options.is_public || false);
|
|
114
|
+
throw new Error(`File upload denied: insufficient permissions. You need 'create:page.${options.pageContext}' or 'update:page.${options.pageContext}' permission for the '${options.pageContext}' page. Make sure the page exists in rbac_app_pages table.`);
|
|
115
|
+
}
|
|
116
|
+
|
|
108
117
|
// Get the created file reference
|
|
109
118
|
const { data: fileRef, error: fetchError } = await this.supabase
|
|
110
119
|
.from('file_references')
|
|
@@ -113,6 +122,8 @@ export class FileReferenceServiceImpl implements FileReferenceService {
|
|
|
113
122
|
.single();
|
|
114
123
|
|
|
115
124
|
if (fetchError || !fileRef) {
|
|
125
|
+
// Clean up uploaded file if we can't fetch the reference
|
|
126
|
+
await deleteFile(this.supabase, filePath, options.is_public || false);
|
|
116
127
|
throw new Error(`Failed to fetch created file reference: ${fetchError?.message}`);
|
|
117
128
|
}
|
|
118
129
|
|