From e890ccf2ed70adcd0ed459a77249102273431999 Mon Sep 17 00:00:00 2001 From: Dmitri Date: Thu, 19 Mar 2026 07:12:29 +0400 Subject: [PATCH] improved project structure (DB, BE, FE) --- .gitignore | 1 + backend/package.json | 5 + backend/src/config.js | 39 +- backend/src/db/api/access_logs.js | 580 +-- backend/src/db/api/asset_variants.js | 579 +-- backend/src/db/api/assets.js | 833 +--- backend/src/db/api/base.api.js | 313 ++ backend/src/db/api/page_elements.js | 774 +-- backend/src/db/api/page_links.js | 695 +-- backend/src/db/api/permissions.js | 362 +- backend/src/db/api/presigned_url_requests.js | 639 +-- backend/src/db/api/project_audio_tracks.js | 696 +-- backend/src/db/api/project_memberships.js | 558 +-- backend/src/db/api/projects.js | 810 +-- backend/src/db/api/publish_events.js | 775 +-- backend/src/db/api/pwa_caches.js | 576 +-- backend/src/db/api/roles.js | 489 +- backend/src/db/api/tour_pages.js | 782 +-- backend/src/db/api/transitions.js | 667 +-- backend/src/db/api/ui_elements.js | 233 - backend/src/db/api/users.js | 74 +- backend/src/db/db.config.js | 22 +- backend/src/db/migrations/1773663122940.js | 4341 ----------------- .../migrations/20260316000000-create-files.js | 124 - ...ate-users-custom-permissions-join-table.js | 95 - ...0000-make-page-elements-pageid-nullable.js | 123 - ...1000-drop-not-null-page-elements-pageid.js | 105 - .../20260317100000-create-ui-elements.js | 108 - ...add-title-description-to-publish-events.js | 85 - .../20260318102000-add-type-to-assets.js | 105 - backend/src/db/models/access_logs.js | 18 +- backend/src/db/models/asset_variants.js | 29 +- backend/src/db/models/assets.js | 12 +- backend/src/db/models/page_elements.js | 17 +- backend/src/db/models/page_links.js | 21 +- backend/src/db/models/permissions.js | 9 +- .../src/db/models/presigned_url_requests.js | 23 +- backend/src/db/models/project_audio_tracks.js | 13 +- backend/src/db/models/projects.js | 21 +- backend/src/db/models/publish_events.js | 26 +- backend/src/db/models/pwa_caches.js | 19 +- backend/src/db/models/roles.js | 8 +- backend/src/db/models/tour_pages.js | 16 +- backend/src/db/models/transitions.js | 11 +- backend/src/db/models/ui_elements.js | 50 - backend/src/db/models/users.js | 5 +- .../db/seeders/20200430130760-user-roles.js | 4 +- backend/src/db/sync.js | 15 + backend/src/factories/router.factory.js | 101 + backend/src/factories/service.factory.js | 97 + backend/src/index.js | 29 +- backend/src/middlewares/validate.js | 103 + backend/src/routes/access_logs.js | 475 +- backend/src/routes/asset_variants.js | 478 +- backend/src/routes/assets.js | 485 +- backend/src/routes/page_elements.js | 495 +- backend/src/routes/page_links.js | 478 +- backend/src/routes/permissions.js | 495 +- backend/src/routes/presigned_url_requests.js | 482 +- backend/src/routes/project_audio_tracks.js | 484 +- backend/src/routes/project_memberships.js | 478 +- backend/src/routes/publish_events.js | 488 +- backend/src/routes/pwa_caches.js | 478 +- backend/src/routes/roles.js | 474 +- backend/src/routes/tour_pages.js | 484 +- backend/src/routes/transitions.js | 484 +- backend/src/routes/ui_elements.js | 103 - backend/src/services/ui_elements.js | 114 - backend/src/utils/circuit-breaker.js | 79 + backend/src/utils/env-validation.js | 65 + backend/src/utils/errors.js | 48 + backend/src/utils/events.js | 53 + backend/src/utils/index.js | 7 + backend/src/utils/logger.js | 46 + backend/yarn.lock | 1033 ++-- frontend/package.json | 3 +- frontend/public/offline.html | 99 + frontend/public/sw.js | 210 + frontend/src/colors.ts | 59 +- .../Access_logs/CardAccess_logs.tsx | 198 +- .../Access_logs/ListAccess_logs.tsx | 217 +- .../Access_logs/TableAccess_logs.tsx | 503 +- .../Access_logs/configureAccess_logsCols.tsx | 324 +- frontend/src/components/AsideMenu.tsx | 24 +- frontend/src/components/AsideMenuItem.tsx | 78 +- frontend/src/components/AsideMenuLayer.tsx | 59 +- frontend/src/components/AsideMenuList.tsx | 38 +- .../Asset_variants/CardAsset_variants.tsx | 188 +- .../Asset_variants/ListAsset_variants.tsx | 205 +- .../Asset_variants/TableAsset_variants.tsx | 503 +- .../configureAsset_variantsCols.tsx | 284 +- frontend/src/components/Assets/CardAssets.tsx | 341 +- frontend/src/components/Assets/ListAssets.tsx | 306 +- .../src/components/Assets/TableAssets.tsx | 503 +- .../components/Assets/configureAssetsCols.tsx | 543 +-- frontend/src/components/BaseButton.tsx | 80 +- frontend/src/components/BaseButtons.tsx | 40 +- frontend/src/components/BaseDivider.tsx | 10 +- frontend/src/components/BaseIcon.tsx | 31 +- frontend/src/components/BigCalendar.tsx | 266 +- frontend/src/components/CardBox.tsx | 52 +- .../src/components/CardBoxComponentBody.tsx | 25 +- .../src/components/CardBoxComponentEmpty.tsx | 10 +- .../src/components/CardBoxComponentFooter.tsx | 10 +- .../src/components/CardBoxComponentTitle.tsx | 18 +- frontend/src/components/CardBoxModal.tsx | 75 +- .../src/components/ChartLineSample/config.ts | 22 +- .../src/components/ChartLineSample/index.tsx | 23 +- frontend/src/components/ClickOutside.tsx | 60 +- frontend/src/components/DevModeBadge.tsx | 268 +- frontend/src/components/ErrorBoundary.tsx | 104 +- frontend/src/components/FooterBar.tsx | 30 +- frontend/src/components/FormCheckRadio.tsx | 22 +- .../src/components/FormCheckRadioGroup.tsx | 24 +- frontend/src/components/FormField.tsx | 68 +- frontend/src/components/FormFilePicker.tsx | 91 +- frontend/src/components/FormImagePicker.tsx | 100 +- .../components/Generic/GenericFormField.tsx | 212 + .../src/components/Generic/GenericTable.tsx | 533 ++ frontend/src/components/Generic/index.ts | 6 + frontend/src/components/IconRounded.tsx | 30 +- frontend/src/components/ImageField.tsx | 54 +- frontend/src/components/IntroGuide.tsx | 15 +- .../components/KanbanBoard/KanbanBoard.tsx | 16 +- .../src/components/KanbanBoard/KanbanCard.tsx | 96 +- .../components/KanbanBoard/KanbanColumn.tsx | 356 +- frontend/src/components/LanguageSwitcher.tsx | 154 +- .../src/components/ListActionsPopover.tsx | 189 +- frontend/src/components/LoadingSpinner.tsx | 10 +- frontend/src/components/Logo/index.tsx | 10 +- frontend/src/components/NavBar.tsx | 45 +- frontend/src/components/NavBarItem.tsx | 89 +- frontend/src/components/NavBarItemPlain.tsx | 29 +- frontend/src/components/NavBarMenuList.tsx | 18 +- frontend/src/components/NotificationBar.tsx | 66 +- frontend/src/components/OverlayLayer.tsx | 26 +- frontend/src/components/PWALoadingOverlay.tsx | 122 + .../Page_elements/CardPage_elements.tsx | 282 +- .../Page_elements/ListPage_elements.tsx | 275 +- .../Page_elements/TablePage_elements.tsx | 679 +-- .../configurePage_elementsCols.tsx | 458 +- .../components/Page_links/CardPage_links.tsx | 206 +- .../components/Page_links/ListPage_links.tsx | 225 +- .../components/Page_links/TablePage_links.tsx | 503 +- .../Page_links/configurePage_linksCols.tsx | 335 +- frontend/src/components/Pagination.tsx | 2 +- .../src/components/PasswordSetOrReset.tsx | 182 +- .../Permissions/CardPermissions.tsx | 102 +- .../Permissions/ListPermissions.tsx | 145 +- .../Permissions/TablePermissions.tsx | 503 +- .../Permissions/configurePermissionsCols.tsx | 125 +- .../CardPresigned_url_requests.tsx | 239 +- .../ListPresigned_url_requests.tsx | 244 +- .../TablePresigned_url_requests.tsx | 501 +- .../configurePresigned_url_requestsCols.tsx | 384 +- .../CardProject_audio_tracks.tsx | 244 +- .../ListProject_audio_tracks.tsx | 253 +- .../TableProject_audio_tracks.tsx | 503 +- .../configureProject_audio_tracksCols.tsx | 398 +- .../CardProject_memberships.tsx | 188 +- .../ListProject_memberships.tsx | 213 +- .../TableProject_memberships.tsx | 503 +- .../configureProject_membershipsCols.tsx | 307 +- .../src/components/Projects/CardProjects.tsx | 294 +- .../src/components/Projects/ListProjects.tsx | 287 +- .../src/components/Projects/TableProjects.tsx | 503 +- .../Projects/configureProjectsCols.tsx | 459 +- .../Publish_events/CardPublish_events.tsx | 273 +- .../Publish_events/ListPublish_events.tsx | 296 +- .../Publish_events/TablePublish_events.tsx | 503 +- .../configurePublish_eventsCols.tsx | 502 +- .../components/Pwa_caches/CardPwa_caches.tsx | 204 +- .../components/Pwa_caches/ListPwa_caches.tsx | 219 +- .../components/Pwa_caches/TablePwa_caches.tsx | 503 +- .../Pwa_caches/configurePwa_cachesCols.tsx | 312 +- frontend/src/components/RichTextField.tsx | 69 +- frontend/src/components/Roles/CardRoles.tsx | 117 +- frontend/src/components/Roles/ListRoles.tsx | 160 +- frontend/src/components/Roles/TableRoles.tsx | 503 +- .../components/Roles/configureRolesCols.tsx | 164 +- frontend/src/components/Search.tsx | 6 +- frontend/src/components/SectionFullScreen.tsx | 33 +- frontend/src/components/SectionMain.tsx | 10 +- frontend/src/components/SectionTitle.tsx | 41 +- .../components/SectionTitleLineWithButton.tsx | 49 +- frontend/src/components/SelectField.tsx | 65 +- frontend/src/components/SelectFieldMany.tsx | 50 +- .../components/SmartWidget/SmartWidget.tsx | 98 +- .../components/BarChart/ChartJSBarChart.tsx | 2 +- .../components/SmartWidget/widgetHelpers.tsx | 14 +- frontend/src/components/SwitchField.tsx | 33 +- .../src/components/TableSampleClients.tsx | 100 +- frontend/src/components/TourFlowManager.tsx | 184 +- .../components/Tour_pages/CardTour_pages.tsx | 281 +- .../components/Tour_pages/ListTour_pages.tsx | 286 +- .../components/Tour_pages/TableTour_pages.tsx | 503 +- .../Tour_pages/configureTour_pagesCols.tsx | 446 +- .../Transitions/CardTransitions.tsx | 230 +- .../Transitions/ListTransitions.tsx | 241 +- .../Transitions/TableTransitions.tsx | 503 +- .../Transitions/configureTransitionsCols.tsx | 362 +- .../components/UiElements/ElementPreview.tsx | 39 +- .../src/components/UiElements/defaults.ts | 273 +- frontend/src/components/UiElements/types.ts | 64 +- .../src/components/Uploaders/FilesUploader.js | 55 +- .../components/Uploaders/ImagesUploader.js | 30 +- .../src/components/Uploaders/UploadService.js | 55 +- frontend/src/components/UserAvatar.tsx | 50 +- .../src/components/UserAvatarCurrentUser.tsx | 29 +- frontend/src/components/UserCard.tsx | 42 +- frontend/src/components/Users/CardUsers.tsx | 226 +- frontend/src/components/Users/ListUsers.tsx | 254 +- frontend/src/components/Users/TableUsers.tsx | 503 +- .../components/Users/configureUsersCols.tsx | 358 +- .../components/WidgetCreator/RoleSelect.tsx | 16 +- .../WidgetCreator/WidgetCreator.tsx | 239 +- frontend/src/config.ts | 25 +- frontend/src/factories/createFormPage.tsx | 314 ++ frontend/src/factories/createListPage.tsx | 193 + frontend/src/factories/index.ts | 6 + frontend/src/helpers/dataFormatter.js | 373 +- frontend/src/helpers/fileSaver.ts | 6 +- frontend/src/helpers/humanize.ts | 23 +- frontend/src/helpers/notifyStateHandler.ts | 3 +- frontend/src/helpers/pexels.ts | 17 +- frontend/src/helpers/userPermissions.ts | 9 +- frontend/src/helpers/zodAdapter.ts | 87 + frontend/src/hooks/index.ts | 8 + frontend/src/hooks/sampleData.ts | 16 +- frontend/src/hooks/useCSVHandling.ts | 134 + frontend/src/hooks/useDevCompilationStatus.ts | 56 +- frontend/src/hooks/useEntityTable.ts | 296 ++ frontend/src/hooks/useFilterItems.ts | 166 + frontend/src/hooks/useFormSync.ts | 108 + frontend/src/hooks/usePWAPreload.ts | 199 + frontend/src/i18n.ts | 30 +- frontend/src/interfaces/index.ts | 127 +- frontend/src/layouts/Authenticated.tsx | 174 +- frontend/src/layouts/Guest.tsx | 16 +- frontend/src/menuAside.ts | 17 +- frontend/src/menuNavBar.ts | 12 +- frontend/src/pages/_app.tsx | 299 +- .../src/pages/access_logs/[access_logsId].tsx | 740 +-- .../pages/access_logs/access_logs-edit.tsx | 761 +-- .../pages/access_logs/access_logs-list.tsx | 200 +- .../src/pages/access_logs/access_logs-new.tsx | 518 +- .../pages/access_logs/access_logs-table.tsx | 253 +- .../pages/access_logs/access_logs-view.tsx | 520 +- frontend/src/pages/api/hello.js | 2 +- frontend/src/pages/api/logError.ts | 4 +- .../asset_variants/[asset_variantsId].tsx | 628 +-- .../asset_variants/asset_variants-edit.tsx | 637 +-- .../asset_variants/asset_variants-list.tsx | 202 +- .../asset_variants/asset_variants-new.tsx | 475 +- .../asset_variants/asset_variants-table.tsx | 253 +- .../asset_variants/asset_variants-view.tsx | 420 +- frontend/src/pages/assets/[assetsId].tsx | 1225 +---- frontend/src/pages/assets/assets-edit.tsx | 1256 +---- frontend/src/pages/assets/assets-list.tsx | 597 ++- frontend/src/pages/assets/assets-new.tsx | 960 +--- frontend/src/pages/assets/assets-table.tsx | 264 +- frontend/src/pages/assets/assets-view.tsx | 890 +--- frontend/src/pages/constructor.tsx | 1421 ++++-- frontend/src/pages/dashboard.tsx | 1219 ++--- frontend/src/pages/error.tsx | 32 +- frontend/src/pages/forgot.tsx | 35 +- frontend/src/pages/forms.tsx | 136 +- frontend/src/pages/index.tsx | 153 +- frontend/src/pages/login.tsx | 432 +- .../pages/page_elements/[page_elementsId].tsx | 1066 +--- .../page_elements/page_elements-edit.tsx | 1083 +--- .../page_elements/page_elements-list.tsx | 578 ++- .../pages/page_elements/page_elements-new.tsx | 837 +--- .../page_elements/page_elements-table.tsx | 255 +- .../page_elements/page_elements-view.tsx | 652 +-- .../src/pages/page_links/[page_linksId].tsx | 769 +-- .../src/pages/page_links/page_links-edit.tsx | 785 +-- .../src/pages/page_links/page_links-list.tsx | 200 +- .../src/pages/page_links/page_links-new.tsx | 519 +- .../src/pages/page_links/page_links-table.tsx | 253 +- .../src/pages/page_links/page_links-view.tsx | 537 +- .../src/pages/permissions/[permissionsId].tsx | 215 +- .../pages/permissions/permissions-edit.tsx | 215 +- .../pages/permissions/permissions-list.tsx | 179 +- .../src/pages/permissions/permissions-new.tsx | 167 +- .../pages/permissions/permissions-table.tsx | 222 +- .../pages/permissions/permissions-view.tsx | 186 +- .../[presigned_url_requestsId].tsx | 902 +--- .../presigned_url_requests-edit.tsx | 930 +--- .../presigned_url_requests-list.tsx | 207 +- .../presigned_url_requests-new.tsx | 648 +-- .../presigned_url_requests-table.tsx | 264 +- .../presigned_url_requests-view.tsx | 596 +-- frontend/src/pages/privacy-policy.tsx | 2 +- frontend/src/pages/profile.tsx | 274 +- .../[project_audio_tracksId].tsx | 898 +--- .../project_audio_tracks-edit.tsx | 924 +--- .../project_audio_tracks-list.tsx | 197 +- .../project_audio_tracks-new.tsx | 694 +-- .../project_audio_tracks-table.tsx | 252 +- .../project_audio_tracks-view.tsx | 587 +-- .../[project_membershipsId].tsx | 705 +-- .../project_memberships-edit.tsx | 733 +-- .../project_memberships-list.tsx | 198 +- .../project_memberships-new.tsx | 477 +- .../project_memberships-table.tsx | 254 +- .../project_memberships-view.tsx | 518 +- frontend/src/pages/projects/[projectsId].tsx | 117 +- frontend/src/pages/projects/projects-edit.tsx | 1197 ++--- frontend/src/pages/projects/projects-list.tsx | 96 +- frontend/src/pages/projects/projects-new.tsx | 858 +--- .../src/pages/projects/projects-table.tsx | 245 +- frontend/src/pages/projects/projects-view.tsx | 2341 ++++----- .../publish_events/[publish_eventsId].tsx | 1072 +--- .../publish_events/publish_events-edit.tsx | 1107 +---- .../publish_events/publish_events-list.tsx | 69 +- .../publish_events/publish_events-new.tsx | 791 +-- .../publish_events/publish_events-table.tsx | 267 +- .../publish_events/publish_events-view.tsx | 720 +-- .../src/pages/pwa_caches/[pwa_cachesId].tsx | 690 +-- .../src/pages/pwa_caches/pwa_caches-edit.tsx | 715 +-- .../src/pages/pwa_caches/pwa_caches-list.tsx | 192 +- .../src/pages/pwa_caches/pwa_caches-new.tsx | 517 +- .../src/pages/pwa_caches/pwa_caches-table.tsx | 244 +- .../src/pages/pwa_caches/pwa_caches-view.tsx | 491 +- frontend/src/pages/register.tsx | 128 +- frontend/src/pages/roles/[rolesId].tsx | 321 +- frontend/src/pages/roles/roles-edit.tsx | 325 +- frontend/src/pages/roles/roles-list.tsx | 179 +- frontend/src/pages/roles/roles-new.tsx | 227 +- frontend/src/pages/roles/roles-table.tsx | 219 +- frontend/src/pages/roles/roles-view.tsx | 443 +- frontend/src/pages/runtime.tsx | 419 +- frontend/src/pages/search.tsx | 20 +- frontend/src/pages/tables.tsx | 38 +- frontend/src/pages/terms-of-use.tsx | 27 +- .../src/pages/tour_pages/[tour_pagesId].tsx | 1036 +--- .../src/pages/tour_pages/tour_pages-edit.tsx | 1059 +--- .../src/pages/tour_pages/tour_pages-list.tsx | 6 +- .../src/pages/tour_pages/tour_pages-new.tsx | 813 +-- .../src/pages/tour_pages/tour_pages-table.tsx | 247 +- .../src/pages/tour_pages/tour_pages-view.tsx | 1150 ++--- .../src/pages/transitions/[transitionsId].tsx | 824 +--- .../pages/transitions/transitions-edit.tsx | 838 +--- .../pages/transitions/transitions-list.tsx | 6 +- .../src/pages/transitions/transitions-new.tsx | 634 +-- .../pages/transitions/transitions-table.tsx | 249 +- .../pages/transitions/transitions-view.tsx | 672 +-- frontend/src/pages/ui-elements.tsx | 150 +- frontend/src/pages/ui-elements/[id].tsx | 293 +- frontend/src/pages/users/[usersId].tsx | 809 +-- frontend/src/pages/users/users-edit.tsx | 823 +--- frontend/src/pages/users/users-list.tsx | 189 +- frontend/src/pages/users/users-new.tsx | 590 +-- frontend/src/pages/users/users-table.tsx | 228 +- frontend/src/pages/users/users-view.tsx | 1266 ++--- frontend/src/pages/verify-email.tsx | 89 +- frontend/src/schemas/assetSchema.ts | 85 + frontend/src/schemas/index.ts | 9 + frontend/src/schemas/projectSchema.ts | 33 + frontend/src/schemas/roleSchema.ts | 17 + frontend/src/schemas/tourPageSchema.ts | 35 + frontend/src/schemas/userSchema.ts | 80 + .../stores/access_logs/access_logsSlice.ts | 248 +- .../asset_variants/asset_variantsSlice.ts | 248 +- frontend/src/stores/assets/assetsSlice.ts | 248 +- frontend/src/stores/authSlice.ts | 57 +- frontend/src/stores/createEntitySlice.ts | 342 ++ frontend/src/stores/hooks.ts | 8 +- frontend/src/stores/introSteps.ts | 8 +- frontend/src/stores/mainSlice.ts | 26 +- frontend/src/stores/openAiSlice.ts | 250 +- .../page_elements/page_elementsSlice.ts | 248 +- .../src/stores/page_links/page_linksSlice.ts | 248 +- .../stores/permissions/permissionsSlice.ts | 248 +- .../presigned_url_requestsSlice.ts | 248 +- .../project_audio_tracksSlice.ts | 248 +- .../project_membershipsSlice.ts | 248 +- frontend/src/stores/projects/projectsSlice.ts | 248 +- .../publish_events/publish_eventsSlice.ts | 248 +- .../src/stores/pwa_caches/pwa_cachesSlice.ts | 248 +- frontend/src/stores/roles/rolesSlice.ts | 310 +- frontend/src/stores/store.ts | 70 +- frontend/src/stores/styleSlice.ts | 64 +- .../src/stores/tour_pages/tour_pagesSlice.ts | 248 +- .../stores/transitions/transitionsSlice.ts | 248 +- frontend/src/stores/users/usersSlice.ts | 248 +- frontend/src/stores/usersSlice.ts | 140 +- frontend/src/styles.ts | 83 +- frontend/src/types/api.ts | 62 + frontend/src/types/entities.ts | 238 + frontend/src/types/filters.ts | 74 + frontend/src/types/forms.ts | 76 + frontend/src/types/index.ts | 10 + frontend/src/types/permissions.ts | 122 + frontend/src/types/redux.ts | 60 + frontend/tsconfig.tsbuildinfo | 2 +- frontend/yarn.lock | 5 + 398 files changed, 36483 insertions(+), 83342 deletions(-) create mode 100644 backend/src/db/api/base.api.js delete mode 100644 backend/src/db/api/ui_elements.js delete mode 100644 backend/src/db/migrations/1773663122940.js delete mode 100644 backend/src/db/migrations/20260316000000-create-files.js delete mode 100644 backend/src/db/migrations/20260316001000-create-users-custom-permissions-join-table.js delete mode 100644 backend/src/db/migrations/20260317090000-make-page-elements-pageid-nullable.js delete mode 100644 backend/src/db/migrations/20260317091000-drop-not-null-page-elements-pageid.js delete mode 100644 backend/src/db/migrations/20260317100000-create-ui-elements.js delete mode 100644 backend/src/db/migrations/20260317113000-add-title-description-to-publish-events.js delete mode 100644 backend/src/db/migrations/20260318102000-add-type-to-assets.js delete mode 100644 backend/src/db/models/ui_elements.js create mode 100644 backend/src/db/sync.js create mode 100644 backend/src/factories/router.factory.js create mode 100644 backend/src/factories/service.factory.js create mode 100644 backend/src/middlewares/validate.js delete mode 100644 backend/src/routes/ui_elements.js delete mode 100644 backend/src/services/ui_elements.js create mode 100644 backend/src/utils/circuit-breaker.js create mode 100644 backend/src/utils/env-validation.js create mode 100644 backend/src/utils/errors.js create mode 100644 backend/src/utils/events.js create mode 100644 backend/src/utils/index.js create mode 100644 backend/src/utils/logger.js create mode 100644 frontend/public/offline.html create mode 100644 frontend/public/sw.js create mode 100644 frontend/src/components/Generic/GenericFormField.tsx create mode 100644 frontend/src/components/Generic/GenericTable.tsx create mode 100644 frontend/src/components/Generic/index.ts create mode 100644 frontend/src/components/PWALoadingOverlay.tsx create mode 100644 frontend/src/factories/createFormPage.tsx create mode 100644 frontend/src/factories/createListPage.tsx create mode 100644 frontend/src/factories/index.ts create mode 100644 frontend/src/helpers/zodAdapter.ts create mode 100644 frontend/src/hooks/index.ts create mode 100644 frontend/src/hooks/useCSVHandling.ts create mode 100644 frontend/src/hooks/useEntityTable.ts create mode 100644 frontend/src/hooks/useFilterItems.ts create mode 100644 frontend/src/hooks/useFormSync.ts create mode 100644 frontend/src/hooks/usePWAPreload.ts create mode 100644 frontend/src/schemas/assetSchema.ts create mode 100644 frontend/src/schemas/index.ts create mode 100644 frontend/src/schemas/projectSchema.ts create mode 100644 frontend/src/schemas/roleSchema.ts create mode 100644 frontend/src/schemas/tourPageSchema.ts create mode 100644 frontend/src/schemas/userSchema.ts create mode 100644 frontend/src/stores/createEntitySlice.ts create mode 100644 frontend/src/types/api.ts create mode 100644 frontend/src/types/entities.ts create mode 100644 frontend/src/types/filters.ts create mode 100644 frontend/src/types/forms.ts create mode 100644 frontend/src/types/index.ts create mode 100644 frontend/src/types/permissions.ts create mode 100644 frontend/src/types/redux.ts diff --git a/.gitignore b/.gitignore index 727f2f5..21c7a5b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,6 @@ node_modules/ */node_modules/ **/node_modules/ */build/ +package-lock.json CLAUDE.md .claude/ \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 382839c..72c4241 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,9 +18,12 @@ "chokidar": "^4.0.3", "cors": "^2.8.6", "csv-parser": "^3.2.0", + "dotenv": "^16.4.0", "express": "4.18.2", + "express-validator": "^7.0.0", "formidable": "1.2.2", "helmet": "^8.0.0", + "joi": "^17.13.0", "json2csv": "^5.0.7", "jsonwebtoken": "^9.0.0", "lodash": "^4.17.23", @@ -33,6 +36,8 @@ "passport-jwt": "^4.0.1", "passport-microsoft": "^2.0.0", "pg": "^8.20.0", + "pino": "^9.0.0", + "pino-pretty": "^11.0.0", "pg-hstore": "2.3.4", "sequelize": "^6.37.0", "sequelize-json-schema": "^2.1.1", diff --git a/backend/src/config.js b/backend/src/config.js index eb65020..5bcff70 100644 --- a/backend/src/config.js +++ b/backend/src/config.js @@ -1,37 +1,10 @@ - - const os = require('os'); -const fs = require('fs'); const path = require('path'); -const envFilePath = path.resolve(__dirname, '../.env'); +require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); -if (fs.existsSync(envFilePath)) { - const envContent = fs.readFileSync(envFilePath, 'utf8'); - envContent.split('\n').forEach((line) => { - const trimmedLine = line.trim(); - - if (!trimmedLine || trimmedLine.startsWith('#')) { - return; - } - - const delimiterIndex = trimmedLine.indexOf('='); - - if (delimiterIndex === -1) { - return; - } - - const key = trimmedLine.slice(0, delimiterIndex).trim(); - const rawValue = trimmedLine.slice(delimiterIndex + 1).trim(); - - if (!key || Object.prototype.hasOwnProperty.call(process.env, key)) { - return; - } - - const unquotedValue = rawValue.replace(/^['"]|['"]$/g, ''); - process.env[key] = unquotedValue; - }); -} +const { validateEnv } = require('./utils/env-validation'); +validateEnv(); const config = { gcloud: { @@ -48,9 +21,9 @@ const config = { bcrypt: { saltRounds: 12 }, - admin_pass: "88dbeaf8", - user_pass: "c3baadeda5c6", - admin_email: "admin@flatlogic.com", + admin_pass: process.env.ADMIN_PASS || "88dbeaf8", + user_pass: process.env.USER_PASS || "c3baadeda5c6", + admin_email: process.env.ADMIN_EMAIL || "admin@flatlogic.com", providers: { LOCAL: 'local', GOOGLE: 'google', diff --git a/backend/src/db/api/access_logs.js b/backend/src/db/api/access_logs.js index c0a0d73..5b967f3 100644 --- a/backend/src/db/api/access_logs.js +++ b/backend/src/db/api/access_logs.js @@ -1,297 +1,76 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class Access_logsDBApi { - +class Access_logsDBApi extends GenericDBApi { + static get MODEL() { + return db.access_logs; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'access_logs'; + } - const access_logs = await db.access_logs.create( - { - id: data.id || undefined, - - environment: data.environment - || - null - , - - path: data.path - || - null - , - - ip_address: data.ip_address - || - null - , - - user_agent: data.user_agent - || - null - , - - accessed_at: data.accessed_at - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); + static get SEARCHABLE_FIELDS() { + return ['path', 'ip_address', 'user_agent']; + } - - await access_logs.setProject( data.project || null, { - transaction, - }); - - await access_logs.setUser( data.user || null, { - transaction, - }); - + static get RANGE_FIELDS() { + return ['accessed_at']; + } - + static get ENUM_FIELDS() { + return ['environment']; + } - + static get CSV_FIELDS() { + return ['id', 'environment', 'path', 'ip_address', 'user_agent', 'accessed_at', 'createdAt']; + } - return access_logs; - } - + static get AUTOCOMPLETE_FIELD() { + return 'path'; + } - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get ASSOCIATIONS() { + return [ + { field: 'project', setter: 'setProject', isArray: false }, + { field: 'user', setter: 'setUser', isArray: false }, + ]; + } - // Prepare data - wrapping individual data transformations in a map() method - const access_logsData = data.map((item, index) => ({ - id: item.id || undefined, - - environment: item.environment - || - null - , - - path: item.path - || - null - , - - ip_address: item.ip_address - || - null - , - - user_agent: item.user_agent - || - null - , - - accessed_at: item.accessed_at - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); + static get FIND_BY_INCLUDES() { + return [ + { association: 'project' }, + { association: 'user' }, + ]; + } - // Bulk create items - const access_logs = await db.access_logs.bulkCreate(access_logsData, { transaction }); + static getFieldMapping(data) { + return { + id: data.id || undefined, + environment: data.environment || null, + path: data.path || null, + ip_address: data.ip_address || null, + user_agent: data.user_agent || null, + accessed_at: data.accessed_at || null, + }; + } - // For each item created, replace relation files - + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - return access_logs; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const access_logs = await db.access_logs.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.environment !== undefined) updatePayload.environment = data.environment; - - - if (data.path !== undefined) updatePayload.path = data.path; - - - if (data.ip_address !== undefined) updatePayload.ip_address = data.ip_address; - - - if (data.user_agent !== undefined) updatePayload.user_agent = data.user_agent; - - - if (data.accessed_at !== undefined) updatePayload.accessed_at = data.accessed_at; - - - updatePayload.updatedById = currentUser.id; - - await access_logs.update(updatePayload, {transaction}); - - - - if (data.project !== undefined) { - await access_logs.setProject( - - data.project, - - { transaction } - ); - } - - if (data.user !== undefined) { - await access_logs.setUser( - - data.user, - - { transaction } - ); - } - - - - - - - - return access_logs; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const access_logs = await db.access_logs.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of access_logs) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of access_logs) { - await record.destroy({transaction}); - } - }); - - - return access_logs; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const access_logs = await db.access_logs.findByPk(id, options); - - await access_logs.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await access_logs.destroy({ - transaction - }); - - return access_logs; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const access_logs = await db.access_logs.findOne({ - where, - transaction, - }); - - if (!access_logs) { - return access_logs; - } - - const output = access_logs.get({plain: true}); - - - - - - - - - - - - - - - - - - - - output.project = await access_logs.getProject({ - transaction - }); - - - output.user = await access_logs.getUser({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - { model: db.projects, as: 'project', - where: filter.project ? { [Op.or]: [ { id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } }, @@ -302,13 +81,10 @@ module.exports = class Access_logsDBApi { }, ] } : {}, - }, - { model: db.users, as: 'user', - where: filter.user ? { [Op.or]: [ { id: { [Op.in]: filter.user.split('|').map(term => Utils.uuid(term)) } }, @@ -319,196 +95,78 @@ module.exports = class Access_logsDBApi { }, ] } : {}, - }, - - - ]; - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.path) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'access_logs', - 'path', - filter.path, - ), - }; - } - - if (filter.ip_address) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'access_logs', - 'ip_address', - filter.ip_address, - ), - }; - } - - if (filter.user_agent) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'access_logs', - 'user_agent', - filter.user_agent, - ), - }; - } - - - - - - - if (filter.accessed_atRange) { - const [start, end] = filter.accessed_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - accessed_at: { - ...where.accessed_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - accessed_at: { - ...where.accessed_at, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.environment) { - where = { - ...where, - environment: filter.environment, - }; - } - - - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.access_logs.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (filter.id) { + where.id = Utils.uuid(filter.id); } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'access_logs', - 'path', - query, - ), - ], - }; - } - - const records = await db.access_logs.findAll({ - attributes: [ 'id', 'path' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['path', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.path, - })); + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } } - -}; + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = Access_logsDBApi; diff --git a/backend/src/db/api/asset_variants.js b/backend/src/db/api/asset_variants.js index 8f3db7a..a902937 100644 --- a/backend/src/db/api/asset_variants.js +++ b/backend/src/db/api/asset_variants.js @@ -1,279 +1,72 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class Asset_variantsDBApi { - +class Asset_variantsDBApi extends GenericDBApi { + static get MODEL() { + return db.asset_variants; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'asset_variants'; + } - const asset_variants = await db.asset_variants.create( - { - id: data.id || undefined, - - variant_type: data.variant_type - || - null - , - - cdn_url: data.cdn_url - || - null - , - - width_px: data.width_px - || - null - , - - height_px: data.height_px - || - null - , - - size_mb: data.size_mb - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); + static get SEARCHABLE_FIELDS() { + return ['cdn_url']; + } - - await asset_variants.setAsset( data.asset || null, { - transaction, - }); - + static get RANGE_FIELDS() { + return ['width_px', 'height_px', 'size_mb']; + } - + static get ENUM_FIELDS() { + return ['variant_type']; + } - + static get CSV_FIELDS() { + return ['id', 'variant_type', 'cdn_url', 'width_px', 'height_px', 'size_mb', 'createdAt']; + } - return asset_variants; - } - + static get AUTOCOMPLETE_FIELD() { + return 'variant_type'; + } - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get ASSOCIATIONS() { + return [ + { field: 'asset', setter: 'setAsset', isArray: false }, + ]; + } - // Prepare data - wrapping individual data transformations in a map() method - const asset_variantsData = data.map((item, index) => ({ - id: item.id || undefined, - - variant_type: item.variant_type - || - null - , - - cdn_url: item.cdn_url - || - null - , - - width_px: item.width_px - || - null - , - - height_px: item.height_px - || - null - , - - size_mb: item.size_mb - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); + static get FIND_BY_INCLUDES() { + return [{ association: 'asset' }]; + } - // Bulk create items - const asset_variants = await db.asset_variants.bulkCreate(asset_variantsData, { transaction }); + static getFieldMapping(data) { + return { + id: data.id || undefined, + variant_type: data.variant_type || null, + cdn_url: data.cdn_url || null, + width_px: data.width_px || null, + height_px: data.height_px || null, + size_mb: data.size_mb || null, + }; + } - // For each item created, replace relation files - + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - return asset_variants; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const asset_variants = await db.asset_variants.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.variant_type !== undefined) updatePayload.variant_type = data.variant_type; - - - if (data.cdn_url !== undefined) updatePayload.cdn_url = data.cdn_url; - - - if (data.width_px !== undefined) updatePayload.width_px = data.width_px; - - - if (data.height_px !== undefined) updatePayload.height_px = data.height_px; - - - if (data.size_mb !== undefined) updatePayload.size_mb = data.size_mb; - - - updatePayload.updatedById = currentUser.id; - - await asset_variants.update(updatePayload, {transaction}); - - - - if (data.asset !== undefined) { - await asset_variants.setAsset( - - data.asset, - - { transaction } - ); - } - - - - - - - - return asset_variants; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const asset_variants = await db.asset_variants.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of asset_variants) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of asset_variants) { - await record.destroy({transaction}); - } - }); - - - return asset_variants; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const asset_variants = await db.asset_variants.findByPk(id, options); - - await asset_variants.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await asset_variants.destroy({ - transaction - }); - - return asset_variants; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const asset_variants = await db.asset_variants.findOne({ - where, - transaction, - }); - - if (!asset_variants) { - return asset_variants; - } - - const output = asset_variants.get({plain: true}); - - - - - - - - - - - - - - - - - - - - output.asset = await asset_variants.getAsset({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - { model: db.assets, as: 'asset', - where: filter.asset ? { [Op.or]: [ { id: { [Op.in]: filter.asset.split('|').map(term => Utils.uuid(term)) } }, @@ -284,220 +77,78 @@ module.exports = class Asset_variantsDBApi { }, ] } : {}, - }, - - - ]; - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.cdn_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'asset_variants', - 'cdn_url', - filter.cdn_url, - ), - }; - } - - - - - - - if (filter.width_pxRange) { - const [start, end] = filter.width_pxRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - width_px: { - ...where.width_px, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - width_px: { - ...where.width_px, - [Op.lte]: end, - }, - }; - } - } - - if (filter.height_pxRange) { - const [start, end] = filter.height_pxRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - height_px: { - ...where.height_px, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - height_px: { - ...where.height_px, - [Op.lte]: end, - }, - }; - } - } - - if (filter.size_mbRange) { - const [start, end] = filter.size_mbRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - size_mb: { - ...where.size_mb, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - size_mb: { - ...where.size_mb, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.variant_type) { - where = { - ...where, - variant_type: filter.variant_type, - }; - } - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.asset_variants.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (filter.id) { + where.id = Utils.uuid(filter.id); } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'asset_variants', - 'variant_type', - query, - ), - ], - }; - } - - const records = await db.asset_variants.findAll({ - attributes: [ 'id', 'variant_type' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['variant_type', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.variant_type, - })); + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } } - -}; + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = Asset_variantsDBApi; diff --git a/backend/src/db/api/assets.js b/backend/src/db/api/assets.js index 14efe2f..117da81 100644 --- a/backend/src/db/api/assets.js +++ b/backend/src/db/api/assets.js @@ -1,404 +1,90 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class AssetsDBApi { - +class AssetsDBApi extends GenericDBApi { + static get MODEL() { + return db.assets; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'assets'; + } - const assets = await db.assets.create( - { - id: data.id || undefined, - - name: data.name - || - null - , - - asset_type: data.asset_type - || - null - , + static get SEARCHABLE_FIELDS() { + return ['name', 'cdn_url', 'storage_key', 'mime_type', 'checksum']; + } - type: data.type - || - 'general' - , - - cdn_url: data.cdn_url - || - null - , - - storage_key: data.storage_key - || - null - , - - mime_type: data.mime_type - || - null - , - - size_mb: data.size_mb - || - null - , - - width_px: data.width_px - || - null - , - - height_px: data.height_px - || - null - , - - duration_sec: data.duration_sec - || - null - , - - checksum: data.checksum - || - null - , - - is_public: data.is_public - || - false - - , - - is_deleted: data.is_deleted - || - false - - , - - deleted_at_time: data.deleted_at_time - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); + static get RANGE_FIELDS() { + return ['size_mb', 'width_px', 'height_px', 'duration_sec', 'deleted_at_time']; + } - - await assets.setProject( data.project || null, { - transaction, - }); - + static get ENUM_FIELDS() { + return ['asset_type', 'type', 'is_public', 'is_deleted']; + } - + static get CSV_FIELDS() { + return ['id', 'name', 'asset_type', 'type', 'cdn_url', 'storage_key', 'mime_type', 'size_mb', 'createdAt']; + } - + static get AUTOCOMPLETE_FIELD() { + return 'name'; + } - return assets; - } - + static get ASSOCIATIONS() { + return [ + { field: 'project', setter: 'setProject', isArray: false }, + ]; + } - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get FIND_BY_INCLUDES() { + return [ + { association: 'asset_variants_asset' }, + { association: 'project' }, + ]; + } - // Prepare data - wrapping individual data transformations in a map() method - const assetsData = data.map((item, index) => ({ - id: item.id || undefined, - - name: item.name - || - null - , - - asset_type: item.asset_type - || - null - , + static get RELATION_FILTERS() { + return [ + { filterKey: 'project', model: db.projects, as: 'project', searchField: 'name' }, + ]; + } - type: item.type - || - 'general' - , - - cdn_url: item.cdn_url - || - null - , - - storage_key: item.storage_key - || - null - , - - mime_type: item.mime_type - || - null - , - - size_mb: item.size_mb - || - null - , - - width_px: item.width_px - || - null - , - - height_px: item.height_px - || - null - , - - duration_sec: item.duration_sec - || - null - , - - checksum: item.checksum - || - null - , - - is_public: item.is_public - || - false - - , - - is_deleted: item.is_deleted - || - false - - , - - deleted_at_time: item.deleted_at_time - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); + static getFieldMapping(data) { + return { + id: data.id || undefined, + name: data.name || null, + asset_type: data.asset_type || null, + type: data.type || 'general', + cdn_url: data.cdn_url || null, + storage_key: data.storage_key || null, + mime_type: data.mime_type || null, + size_mb: data.size_mb || null, + width_px: data.width_px || null, + height_px: data.height_px || null, + duration_sec: data.duration_sec || null, + checksum: data.checksum || null, + is_public: data.is_public || false, + is_deleted: data.is_deleted || false, + deleted_at_time: data.deleted_at_time || null, + }; + } - // Bulk create items - const assets = await db.assets.bulkCreate(assetsData, { transaction }); + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - // For each item created, replace relation files - - - return assets; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const assets = await db.assets.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.asset_type !== undefined) updatePayload.asset_type = data.asset_type; - - - if (data.type !== undefined) updatePayload.type = data.type; - - - if (data.cdn_url !== undefined) updatePayload.cdn_url = data.cdn_url; - - - if (data.storage_key !== undefined) updatePayload.storage_key = data.storage_key; - - - if (data.mime_type !== undefined) updatePayload.mime_type = data.mime_type; - - - if (data.size_mb !== undefined) updatePayload.size_mb = data.size_mb; - - - if (data.width_px !== undefined) updatePayload.width_px = data.width_px; - - - if (data.height_px !== undefined) updatePayload.height_px = data.height_px; - - - if (data.duration_sec !== undefined) updatePayload.duration_sec = data.duration_sec; - - - if (data.checksum !== undefined) updatePayload.checksum = data.checksum; - - - if (data.is_public !== undefined) updatePayload.is_public = data.is_public; - - - if (data.is_deleted !== undefined) updatePayload.is_deleted = data.is_deleted; - - - if (data.deleted_at_time !== undefined) updatePayload.deleted_at_time = data.deleted_at_time; - - - updatePayload.updatedById = currentUser.id; - - await assets.update(updatePayload, {transaction}); - - - - if (data.project !== undefined) { - await assets.setProject( - - data.project, - - { transaction } - ); - } - - - - - - - - return assets; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const assets = await db.assets.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of assets) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of assets) { - await record.destroy({transaction}); - } - }); - - - return assets; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const assets = await db.assets.findByPk(id, options); - - await assets.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await assets.destroy({ - transaction - }); - - return assets; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const assets = await db.assets.findOne({ - where, - transaction, - }); - - if (!assets) { - return assets; - } - - const output = assets.get({plain: true}); - - - - - - - - - output.asset_variants_asset = await assets.getAsset_variants_asset({ - transaction - }); - - - - - - - - - - - - - output.project = await assets.getProject({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - { model: db.projects, as: 'project', - where: filter.project ? { [Op.or]: [ { id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } }, @@ -409,333 +95,78 @@ module.exports = class AssetsDBApi { }, ] } : {}, - }, - - - ]; - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'assets', - 'name', - filter.name, - ), - }; - } - - if (filter.cdn_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'assets', - 'cdn_url', - filter.cdn_url, - ), - }; - } - - if (filter.storage_key) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'assets', - 'storage_key', - filter.storage_key, - ), - }; - } - - if (filter.mime_type) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'assets', - 'mime_type', - filter.mime_type, - ), - }; - } - - if (filter.checksum) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'assets', - 'checksum', - filter.checksum, - ), - }; - } - - - - - - - if (filter.size_mbRange) { - const [start, end] = filter.size_mbRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - size_mb: { - ...where.size_mb, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - size_mb: { - ...where.size_mb, - [Op.lte]: end, - }, - }; - } - } - - if (filter.width_pxRange) { - const [start, end] = filter.width_pxRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - width_px: { - ...where.width_px, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - width_px: { - ...where.width_px, - [Op.lte]: end, - }, - }; - } - } - - if (filter.height_pxRange) { - const [start, end] = filter.height_pxRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - height_px: { - ...where.height_px, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - height_px: { - ...where.height_px, - [Op.lte]: end, - }, - }; - } - } - - if (filter.duration_secRange) { - const [start, end] = filter.duration_secRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - duration_sec: { - ...where.duration_sec, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - duration_sec: { - ...where.duration_sec, - [Op.lte]: end, - }, - }; - } - } - - if (filter.deleted_at_timeRange) { - const [start, end] = filter.deleted_at_timeRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - deleted_at_time: { - ...where.deleted_at_time, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - deleted_at_time: { - ...where.deleted_at_time, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.asset_type) { - where = { - ...where, - asset_type: filter.asset_type, - }; - } - - if (filter.type) { - where = { - ...where, - type: filter.type, - }; - } - - if (filter.is_public) { - where = { - ...where, - is_public: filter.is_public, - }; - } - - if (filter.is_deleted) { - where = { - ...where, - is_deleted: filter.is_deleted, - }; - } - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.assets.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (filter.id) { + where.id = Utils.uuid(filter.id); } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'assets', - 'name', - query, - ), - ], - }; - } - - const records = await db.assets.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } } - -}; + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = AssetsDBApi; diff --git a/backend/src/db/api/base.api.js b/backend/src/db/api/base.api.js new file mode 100644 index 0000000..303dd83 --- /dev/null +++ b/backend/src/db/api/base.api.js @@ -0,0 +1,313 @@ +const db = require('../models'); +const Utils = require('../utils'); +const { parse } = require('json2csv'); + +const Sequelize = db.Sequelize; +const Op = Sequelize.Op; + +class GenericDBApi { + static get MODEL() { + throw new Error('MODEL must be defined in subclass'); + } + + static get TABLE_NAME() { + return this.MODEL.getTableName(); + } + + static get SEARCHABLE_FIELDS() { + return []; + } + + static get RANGE_FIELDS() { + return []; + } + + static get ENUM_FIELDS() { + return []; + } + + static get RELATION_FILTERS() { + return []; + } + + static get CSV_FIELDS() { + return ['id', 'createdAt']; + } + + static get AUTOCOMPLETE_FIELD() { + return 'name'; + } + + static get ASSOCIATIONS() { + return []; + } + + static get FIND_BY_INCLUDES() { + return []; + } + + static get FIND_ALL_INCLUDES() { + return []; + } + + static getFieldMapping(data) { + return data; + } + + static async create(data, options = {}) { + const currentUser = options.currentUser || { id: null }; + const transaction = options.transaction; + + const mappedData = this.getFieldMapping(data); + + const record = await this.MODEL.create( + { + ...mappedData, + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { transaction } + ); + + for (const assoc of this.ASSOCIATIONS) { + if (data[assoc.field] !== undefined) { + await record[assoc.setter](data[assoc.field] || (assoc.isArray ? [] : null), { transaction }); + } + } + + return record; + } + + static async bulkImport(data, options = {}) { + const currentUser = options.currentUser || { id: null }; + const transaction = options.transaction; + + const recordsData = data.map((item, index) => ({ + ...this.getFieldMapping(item), + importHash: item.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + createdAt: new Date(Date.now() + index * 1000), + })); + + return this.MODEL.bulkCreate(recordsData, { transaction }); + } + + static async update(id, data, options = {}) { + const currentUser = options.currentUser || { id: null }; + const transaction = options.transaction; + + const record = await this.MODEL.findByPk(id, { transaction }); + + if (!record) { + throw { status: 404, message: `${this.TABLE_NAME} not found` }; + } + + const updatePayload = { updatedById: currentUser.id }; + const mappedData = this.getFieldMapping(data); + + for (const [key, value] of Object.entries(mappedData)) { + if (value !== undefined) { + updatePayload[key] = value; + } + } + + await record.update(updatePayload, { transaction }); + + for (const assoc of this.ASSOCIATIONS) { + if (data[assoc.field] !== undefined) { + await record[assoc.setter](data[assoc.field], { transaction }); + } + } + + return record; + } + + static async deleteByIds(ids, options = {}) { + const currentUser = options.currentUser || { id: null }; + const transaction = options.transaction; + + const records = await this.MODEL.findAll({ + where: { id: { [Op.in]: ids } }, + transaction, + }); + + await db.sequelize.transaction(async (tx) => { + for (const record of records) { + await record.update({ deletedBy: currentUser.id }, { transaction: tx }); + } + for (const record of records) { + await record.destroy({ transaction: tx }); + } + }); + + return records; + } + + static async remove(id, options = {}) { + const currentUser = options.currentUser || { id: null }; + const transaction = options.transaction; + + const record = await this.MODEL.findByPk(id, { transaction }); + + if (!record) { + throw { status: 404, message: `${this.TABLE_NAME} not found` }; + } + + await record.update({ deletedBy: currentUser.id }, { transaction }); + await record.destroy({ transaction }); + + return record; + } + + static async findBy(where, options = {}) { + const transaction = options.transaction; + + const record = await this.MODEL.findOne({ + where, + transaction, + include: this.FIND_BY_INCLUDES, + }); + + if (!record) { + return null; + } + + return record.get({ plain: true }); + } + + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; + + let where = {}; + let include = [...this.FIND_ALL_INCLUDES]; + + if (filter.id) { + where.id = Utils.uuid(filter.id); + } + + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } + } + + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + for (const rel of this.RELATION_FILTERS) { + if (filter[rel.filterKey]) { + const searchTerms = filter[rel.filterKey].split('|'); + const relInclude = { + model: rel.model, + as: rel.as, + required: searchTerms.length > 0, + where: searchTerms.length > 0 ? { + [Op.or]: [ + { id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } }, + rel.searchField ? { + [rel.searchField]: { + [Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` })) + } + } : {} + ] + } : undefined + }; + include = [relInclude, ...include]; + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } + + static async findAllAutocomplete(query, limit, offset) { + let where = {}; + + if (query) { + where = { + [Op.or]: [ + { id: Utils.uuid(query) }, + Utils.ilike(this.TABLE_NAME, this.AUTOCOMPLETE_FIELD, query), + ], + }; + } + + const records = await this.MODEL.findAll({ + attributes: ['id', this.AUTOCOMPLETE_FIELD], + where, + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + order: [[this.AUTOCOMPLETE_FIELD, 'ASC']], + }); + + return records.map((record) => ({ + id: record.id, + label: record[this.AUTOCOMPLETE_FIELD], + })); + } + + static toCSV(rows) { + const opts = { fields: this.CSV_FIELDS }; + return parse(rows, opts); + } +} + +module.exports = GenericDBApi; diff --git a/backend/src/db/api/page_elements.js b/backend/src/db/api/page_elements.js index 7f36b77..0247171 100644 --- a/backend/src/db/api/page_elements.js +++ b/backend/src/db/api/page_elements.js @@ -1,4 +1,4 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); const { @@ -6,306 +6,102 @@ const { getRuntimeProjectSlug, } = require('./runtime-context'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class Page_elementsDBApi { - +class Page_elementsDBApi extends GenericDBApi { + static get MODEL() { + return db.page_elements; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'page_elements'; + } - const page_elements = await db.page_elements.create( - { - id: data.id || undefined, - - element_type: data.element_type ?? null, - - name: data.name ?? null, - - sort_order: data.sort_order ?? 0, - - is_visible: data.is_visible ?? false, - - x_percent: data.x_percent ?? null, - - y_percent: data.y_percent ?? null, - - width_percent: data.width_percent ?? null, - - height_percent: data.height_percent ?? null, - - rotation_deg: data.rotation_deg ?? null, - - style_json: data.style_json ?? null, - - content_json: data.content_json ?? null, - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); + static get SEARCHABLE_FIELDS() { + return ['name', 'style_json', 'content_json']; + } - - await page_elements.setPage( data.page || null, { - transaction, - }); - + static get RANGE_FIELDS() { + return ['sort_order', 'x_percent', 'y_percent', 'width_percent', 'height_percent', 'rotation_deg']; + } - + static get ENUM_FIELDS() { + return ['element_type', 'is_visible']; + } - + static get CSV_FIELDS() { + return ['id', 'element_type', 'name', 'sort_order', 'is_visible', 'x_percent', 'y_percent', 'createdAt']; + } - return page_elements; - } - + static get AUTOCOMPLETE_FIELD() { + return 'name'; + } - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get ASSOCIATIONS() { + return [ + { field: 'page', setter: 'setPage', isArray: false }, + ]; + } - // Prepare data - wrapping individual data transformations in a map() method - const page_elementsData = data.map((item, index) => ({ - id: item.id || undefined, - - element_type: item.element_type ?? null, - - name: item.name ?? null, - - sort_order: item.sort_order ?? 0, - - is_visible: item.is_visible ?? false, - - x_percent: item.x_percent ?? null, - - y_percent: item.y_percent ?? null, - - width_percent: item.width_percent ?? null, - - height_percent: item.height_percent ?? null, - - rotation_deg: item.rotation_deg ?? null, - - style_json: item.style_json ?? null, - - content_json: item.content_json ?? null, - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); + static getFieldMapping(data) { + return { + id: data.id || undefined, + element_type: data.element_type ?? null, + name: data.name ?? null, + sort_order: data.sort_order ?? 0, + is_visible: data.is_visible ?? false, + x_percent: data.x_percent ?? null, + y_percent: data.y_percent ?? null, + width_percent: data.width_percent ?? null, + height_percent: data.height_percent ?? null, + rotation_deg: data.rotation_deg ?? null, + style_json: data.style_json ?? null, + content_json: data.content_json ?? null, + }; + } - // Bulk create items - const page_elements = await db.page_elements.bulkCreate(page_elementsData, { transaction }); + static async findBy(where, options = {}) { + const transaction = options.transaction; + const runtimeEnvironment = getRuntimeEnvironment(options); + const runtimeProjectSlug = getRuntimeProjectSlug(options); + const pageInclude = { + model: db.tour_pages, + as: 'page', + required: Boolean(runtimeEnvironment || runtimeProjectSlug), + where: runtimeEnvironment ? { environment: runtimeEnvironment } : {}, + include: runtimeProjectSlug + ? [{ + model: db.projects, + as: 'project', + required: true, + where: { slug: runtimeProjectSlug }, + }] + : [], + }; - // For each item created, replace relation files - + const record = await this.MODEL.findOne({ + where, + transaction, + include: [pageInclude], + }); - return page_elements; - } + if (!record) return null; + return record.get({ plain: true }); + } - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - const page_elements = await db.page_elements.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.element_type !== undefined) updatePayload.element_type = data.element_type; - - - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.sort_order !== undefined) updatePayload.sort_order = data.sort_order; - - - if (data.is_visible !== undefined) updatePayload.is_visible = data.is_visible; - - - if (data.x_percent !== undefined) updatePayload.x_percent = data.x_percent; - - - if (data.y_percent !== undefined) updatePayload.y_percent = data.y_percent; - - - if (data.width_percent !== undefined) updatePayload.width_percent = data.width_percent; - - - if (data.height_percent !== undefined) updatePayload.height_percent = data.height_percent; - - - if (data.rotation_deg !== undefined) updatePayload.rotation_deg = data.rotation_deg; - - - if (data.style_json !== undefined) updatePayload.style_json = data.style_json; - - - if (data.content_json !== undefined) updatePayload.content_json = data.content_json; - - - updatePayload.updatedById = currentUser.id; - - await page_elements.update(updatePayload, {transaction}); - - - - if (data.page !== undefined) { - await page_elements.setPage( - - data.page, - - { transaction } - ); - } - - - - - - - - return page_elements; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const page_elements = await db.page_elements.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of page_elements) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of page_elements) { - await record.destroy({transaction}); - } - }); - - - return page_elements; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const page_elements = await db.page_elements.findByPk(id, options); - - await page_elements.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await page_elements.destroy({ - transaction - }); - - return page_elements; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - const runtimeEnvironment = getRuntimeEnvironment(options); - const runtimeProjectSlug = getRuntimeProjectSlug(options); - const pageInclude = { - model: db.tour_pages, - as: 'page', - required: Boolean(runtimeEnvironment || runtimeProjectSlug), - where: runtimeEnvironment ? { environment: runtimeEnvironment } : {}, - include: runtimeProjectSlug - ? [{ - model: db.projects, - as: 'project', - required: true, - where: { slug: runtimeProjectSlug }, - }] - : [], - }; - - const page_elements = await db.page_elements.findOne( - { where, include: [pageInclude], transaction }, - ); - - if (!page_elements) { - return page_elements; - } - - const output = page_elements.get({plain: true}); - - - - - - - - - - - - - - - - - - - - output.page = await page_elements.getPage({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - filter = filter || {}; - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - { model: db.tour_pages, as: 'page', - where: filter.page ? { [Op.or]: [ { id: { [Op.in]: filter.page.split('|').map(term => Utils.uuid(term)) } }, @@ -316,341 +112,99 @@ module.exports = class Page_elementsDBApi { }, ] } : {}, - }, - - - ]; - const runtimeEnvironment = getRuntimeEnvironment(options); - const runtimeProjectSlug = getRuntimeProjectSlug(options); - if (runtimeEnvironment) { - include[0].where = { - ...(include[0].where || {}), - environment: runtimeEnvironment, - }; - include[0].required = true; - } + const runtimeEnvironment = getRuntimeEnvironment(options); + const runtimeProjectSlug = getRuntimeProjectSlug(options); - if (runtimeProjectSlug) { - include[0].include = [{ - model: db.projects, - as: 'project', - required: true, - where: { slug: runtimeProjectSlug }, - }]; - include[0].required = true; - } - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'page_elements', - 'name', - filter.name, - ), - }; - } - - if (filter.style_json) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'page_elements', - 'style_json', - filter.style_json, - ), - }; - } - - if (filter.content_json) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'page_elements', - 'content_json', - filter.content_json, - ), - }; - } - - - - - - - if (filter.sort_orderRange) { - const [start, end] = filter.sort_orderRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - sort_order: { - ...where.sort_order, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - sort_order: { - ...where.sort_order, - [Op.lte]: end, - }, - }; - } - } - - if (filter.x_percentRange) { - const [start, end] = filter.x_percentRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - x_percent: { - ...where.x_percent, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - x_percent: { - ...where.x_percent, - [Op.lte]: end, - }, - }; - } - } - - if (filter.y_percentRange) { - const [start, end] = filter.y_percentRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - y_percent: { - ...where.y_percent, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - y_percent: { - ...where.y_percent, - [Op.lte]: end, - }, - }; - } - } - - if (filter.width_percentRange) { - const [start, end] = filter.width_percentRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - width_percent: { - ...where.width_percent, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - width_percent: { - ...where.width_percent, - [Op.lte]: end, - }, - }; - } - } - - if (filter.height_percentRange) { - const [start, end] = filter.height_percentRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - height_percent: { - ...where.height_percent, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - height_percent: { - ...where.height_percent, - [Op.lte]: end, - }, - }; - } - } - - if (filter.rotation_degRange) { - const [start, end] = filter.rotation_degRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - rotation_deg: { - ...where.rotation_deg, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - rotation_deg: { - ...where.rotation_deg, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.element_type) { - where = { - ...where, - element_type: filter.element_type, - }; - } - - if (filter.is_visible) { - where = { - ...where, - is_visible: filter.is_visible, - }; - } - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.page_elements.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (runtimeEnvironment) { + include[0].where = { + ...(include[0].where || {}), + environment: runtimeEnvironment, + }; + include[0].required = true; } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'page_elements', - 'name', - query, - ), - ], - }; - } - - const records = await db.page_elements.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); + if (runtimeProjectSlug) { + include[0].include = [{ + model: db.projects, + as: 'project', + required: true, + where: { slug: runtimeProjectSlug }, + }]; + include[0].required = true; } - -}; + if (filter.id) { + where.id = Utils.uuid(filter.id); + } + + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } + } + + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = Page_elementsDBApi; diff --git a/backend/src/db/api/page_links.js b/backend/src/db/api/page_links.js index c124855..ec7e66a 100644 --- a/backend/src/db/api/page_links.js +++ b/backend/src/db/api/page_links.js @@ -1,4 +1,4 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); const { @@ -6,334 +6,115 @@ const { getRuntimeProjectSlug, } = require('./runtime-context'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class Page_linksDBApi { - +class Page_linksDBApi extends GenericDBApi { + static get MODEL() { + return db.page_links; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'page_links'; + } - const page_links = await db.page_links.create( - { - id: data.id || undefined, - - direction: data.direction - || - null - , - - external_url: data.external_url - || - null - , - - is_active: data.is_active - || - false - - , - - trigger_selector: data.trigger_selector - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, + static get SEARCHABLE_FIELDS() { + return ['external_url', 'trigger_selector']; + } + + static get RANGE_FIELDS() { + return []; + } + + static get ENUM_FIELDS() { + return ['direction', 'is_active']; + } + + static get CSV_FIELDS() { + return ['id', 'direction', 'external_url', 'is_active', 'trigger_selector', 'createdAt']; + } + + static get AUTOCOMPLETE_FIELD() { + return 'direction'; + } + + static get ASSOCIATIONS() { + return [ + { field: 'from_page', setter: 'setFrom_page', isArray: false }, + { field: 'to_page', setter: 'setTo_page', isArray: false }, + { field: 'transition', setter: 'setTransition', isArray: false }, + ]; + } + + static getFieldMapping(data) { + return { + id: data.id || undefined, + direction: data.direction || null, + external_url: data.external_url || null, + is_active: data.is_active || false, + trigger_selector: data.trigger_selector || null, + }; + } + + static async findBy(where, options = {}) { + const transaction = options.transaction; + const runtimeEnvironment = getRuntimeEnvironment(options); + const runtimeProjectSlug = getRuntimeProjectSlug(options); + const buildProjectInclude = () => ( + runtimeProjectSlug + ? [{ + model: db.projects, + as: 'project', + required: true, + where: { slug: runtimeProjectSlug }, + }] + : [] ); - - await page_links.setFrom_page( data.from_page || null, { - transaction, - }); - - await page_links.setTo_page( data.to_page || null, { - transaction, - }); - - await page_links.setTransition( data.transition || null, { - transaction, - }); - + const record = await this.MODEL.findOne({ + where, + transaction, + include: [ + { + model: db.tour_pages, + as: 'from_page', + required: Boolean(runtimeEnvironment || runtimeProjectSlug), + where: runtimeEnvironment ? { environment: runtimeEnvironment } : {}, + include: buildProjectInclude(), + }, + { + model: db.tour_pages, + as: 'to_page', + required: false, + where: runtimeEnvironment ? { environment: runtimeEnvironment } : {}, + include: buildProjectInclude(), + }, + { + model: db.transitions, + as: 'transition', + required: false, + where: runtimeEnvironment ? { environment: runtimeEnvironment } : {}, + include: buildProjectInclude(), + }, + ], + }); - + if (!record) return null; + return record.get({ plain: true }); + } - + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - return page_links; - } - - - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - // Prepare data - wrapping individual data transformations in a map() method - const page_linksData = data.map((item, index) => ({ - id: item.id || undefined, - - direction: item.direction - || - null - , - - external_url: item.external_url - || - null - , - - is_active: item.is_active - || - false - - , - - trigger_selector: item.trigger_selector - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const page_links = await db.page_links.bulkCreate(page_linksData, { transaction }); - - // For each item created, replace relation files - - - return page_links; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const page_links = await db.page_links.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.direction !== undefined) updatePayload.direction = data.direction; - - - if (data.external_url !== undefined) updatePayload.external_url = data.external_url; - - - if (data.is_active !== undefined) updatePayload.is_active = data.is_active; - - - if (data.trigger_selector !== undefined) updatePayload.trigger_selector = data.trigger_selector; - - - updatePayload.updatedById = currentUser.id; - - await page_links.update(updatePayload, {transaction}); - - - - if (data.from_page !== undefined) { - await page_links.setFrom_page( - - data.from_page, - - { transaction } - ); - } - - if (data.to_page !== undefined) { - await page_links.setTo_page( - - data.to_page, - - { transaction } - ); - } - - if (data.transition !== undefined) { - await page_links.setTransition( - - data.transition, - - { transaction } - ); - } - - - - - - - - return page_links; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const page_links = await db.page_links.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of page_links) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of page_links) { - await record.destroy({transaction}); - } - }); - - - return page_links; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const page_links = await db.page_links.findByPk(id, options); - - await page_links.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await page_links.destroy({ - transaction - }); - - return page_links; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - const runtimeEnvironment = getRuntimeEnvironment(options); - const runtimeProjectSlug = getRuntimeProjectSlug(options); - const buildProjectInclude = () => ( - runtimeProjectSlug - ? [{ - model: db.projects, - as: 'project', - required: true, - where: { slug: runtimeProjectSlug }, - }] - : [] - ); - - const page_links = await db.page_links.findOne( - { - where, - include: [ - { - model: db.tour_pages, - as: 'from_page', - required: Boolean(runtimeEnvironment || runtimeProjectSlug), - where: runtimeEnvironment ? { environment: runtimeEnvironment } : {}, - include: buildProjectInclude(), - }, - { - model: db.transitions, - as: 'transition', - required: false, - where: runtimeEnvironment ? { environment: runtimeEnvironment } : {}, - include: buildProjectInclude(), - }, - ], - transaction, - }, - ); - - if (!page_links) { - return page_links; - } - - const output = page_links.get({plain: true}); - - - - - - - - - - - - - - - - - - - - output.from_page = await page_links.getFrom_page({ - transaction - }); - - - output.to_page = await page_links.getTo_page({ - transaction - }); - - - output.transition = await page_links.getTransition({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - filter = filter || {}; - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - { model: db.tour_pages, as: 'from_page', - where: filter.from_page ? { [Op.or]: [ { id: { [Op.in]: filter.from_page.split('|').map(term => Utils.uuid(term)) } }, @@ -344,13 +125,10 @@ module.exports = class Page_linksDBApi { }, ] } : {}, - }, - { model: db.tour_pages, as: 'to_page', - where: filter.to_page ? { [Op.or]: [ { id: { [Op.in]: filter.to_page.split('|').map(term => Utils.uuid(term)) } }, @@ -361,13 +139,10 @@ module.exports = class Page_linksDBApi { }, ] } : {}, - }, - { model: db.transitions, as: 'transition', - where: filter.transition ? { [Op.or]: [ { id: { [Op.in]: filter.transition.split('|').map(term => Utils.uuid(term)) } }, @@ -378,212 +153,90 @@ module.exports = class Page_linksDBApi { }, ] } : {}, - }, - - - ]; - const runtimeEnvironment = getRuntimeEnvironment(options); - const runtimeProjectSlug = getRuntimeProjectSlug(options); - if (runtimeEnvironment) { - include[0].where = { - ...(include[0].where || {}), - environment: runtimeEnvironment, - }; - include[0].required = true; - include[1].where = { - ...(include[1].where || {}), - environment: runtimeEnvironment, - }; - include[2].where = { - ...(include[2].where || {}), - environment: runtimeEnvironment, - }; - include[2].required = false; - } + const runtimeEnvironment = getRuntimeEnvironment(options); + const runtimeProjectSlug = getRuntimeProjectSlug(options); - if (runtimeProjectSlug) { - include[0].include = [{ - model: db.projects, - as: 'project', - required: true, - where: { slug: runtimeProjectSlug }, - }]; - include[0].required = true; - include[1].include = [{ - model: db.projects, - as: 'project', - required: true, - where: { slug: runtimeProjectSlug }, - }]; - include[2].include = [{ - model: db.projects, - as: 'project', - required: true, - where: { slug: runtimeProjectSlug }, - }]; - include[2].required = false; - } - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.external_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'page_links', - 'external_url', - filter.external_url, - ), - }; - } - - if (filter.trigger_selector) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'page_links', - 'trigger_selector', - filter.trigger_selector, - ), - }; - } - - - - - - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.direction) { - where = { - ...where, - direction: filter.direction, - }; - } - - if (filter.is_active) { - where = { - ...where, - is_active: filter.is_active, - }; - } - - - - - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.page_links.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (runtimeEnvironment) { + include[0].where = { ...(include[0].where || {}), environment: runtimeEnvironment }; + include[0].required = true; + include[1].where = { ...(include[1].where || {}), environment: runtimeEnvironment }; + include[2].where = { ...(include[2].where || {}), environment: runtimeEnvironment }; + include[2].required = false; } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'page_links', - 'direction', - query, - ), - ], - }; - } - - const records = await db.page_links.findAll({ - attributes: [ 'id', 'direction' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['direction', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.direction, - })); + if (runtimeProjectSlug) { + const projectInclude = [{ + model: db.projects, + as: 'project', + required: true, + where: { slug: runtimeProjectSlug }, + }]; + include[0].include = projectInclude; + include[0].required = true; + include[1].include = projectInclude; + include[2].include = projectInclude; + include[2].required = false; } - -}; + if (filter.id) { + where.id = Utils.uuid(filter.id); + } + + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = Page_linksDBApi; diff --git a/backend/src/db/api/permissions.js b/backend/src/db/api/permissions.js index b7f7e13..b002a8d 100644 --- a/backend/src/db/api/permissions.js +++ b/backend/src/db/api/permissions.js @@ -1,335 +1,53 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); -const Utils = require('../utils'); +class PermissionsDBApi extends GenericDBApi { + static get MODEL() { + return db.permissions; + } + static get TABLE_NAME() { + return 'permissions'; + } -const Sequelize = db.Sequelize; -const Op = Sequelize.Op; + static get SEARCHABLE_FIELDS() { + return ['name']; + } -module.exports = class PermissionsDBApi { - + static get RANGE_FIELDS() { + return []; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get ENUM_FIELDS() { + return []; + } - const permissions = await db.permissions.create( - { - id: data.id || undefined, - - name: data.name - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); + static get CSV_FIELDS() { + return ['id', 'name', 'createdAt']; + } - + static get AUTOCOMPLETE_FIELD() { + return 'name'; + } - + static get ASSOCIATIONS() { + return []; + } - + static get FIND_BY_INCLUDES() { + return []; + } - return permissions; - } - + static get FIND_ALL_INCLUDES() { + return []; + } - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static getFieldMapping(data) { + return { + id: data.id || undefined, + name: data.name || null, + }; + } +} - // Prepare data - wrapping individual data transformations in a map() method - const permissionsData = data.map((item, index) => ({ - id: item.id || undefined, - - name: item.name - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const permissions = await db.permissions.bulkCreate(permissionsData, { transaction }); - - // For each item created, replace relation files - - - return permissions; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const permissions = await db.permissions.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.name !== undefined) updatePayload.name = data.name; - - - updatePayload.updatedById = currentUser.id; - - await permissions.update(updatePayload, {transaction}); - - - - - - - - - - return permissions; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const permissions = await db.permissions.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of permissions) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of permissions) { - await record.destroy({transaction}); - } - }); - - - return permissions; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const permissions = await db.permissions.findByPk(id, options); - - await permissions.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await permissions.destroy({ - transaction - }); - - return permissions; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const permissions = await db.permissions.findOne({ - where, - transaction, - }); - - if (!permissions) { - return permissions; - } - - const output = permissions.get({plain: true}); - - - - - - - - - - - - - - - - - - - - - return output; - } - - static async findAll( - filter, - options - ) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; - - let include = [ - - - - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'permissions', - 'name', - filter.name, - ), - }; - } - - - - - - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.permissions.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } - } - - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'permissions', - 'name', - query, - ), - ], - }; - } - - const records = await db.permissions.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); - } - - -}; +module.exports = PermissionsDBApi; diff --git a/backend/src/db/api/presigned_url_requests.js b/backend/src/db/api/presigned_url_requests.js index dc4de3e..b478188 100644 --- a/backend/src/db/api/presigned_url_requests.js +++ b/backend/src/db/api/presigned_url_requests.js @@ -1,323 +1,78 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class Presigned_url_requestsDBApi { - +class Presigned_url_requestsDBApi extends GenericDBApi { + static get MODEL() { + return db.presigned_url_requests; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'presigned_url_requests'; + } - const presigned_url_requests = await db.presigned_url_requests.create( - { - id: data.id || undefined, - - purpose: data.purpose - || - null - , - - asset_type: data.asset_type - || - null - , - - requested_key: data.requested_key - || - null - , - - mime_type: data.mime_type - || - null - , - - requested_size_mb: data.requested_size_mb - || - null - , - - expires_at: data.expires_at - || - null - , - - status: data.status - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); + static get SEARCHABLE_FIELDS() { + return ['requested_key', 'mime_type', 'status']; + } - - await presigned_url_requests.setProject( data.project || null, { - transaction, - }); - - await presigned_url_requests.setUser( data.user || null, { - transaction, - }); - + static get RANGE_FIELDS() { + return ['requested_size_mb', 'expires_at']; + } - + static get ENUM_FIELDS() { + return ['purpose', 'asset_type']; + } - + static get CSV_FIELDS() { + return ['id', 'purpose', 'asset_type', 'requested_key', 'mime_type', 'status', 'createdAt']; + } - return presigned_url_requests; - } - + static get AUTOCOMPLETE_FIELD() { + return 'requested_key'; + } - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get ASSOCIATIONS() { + return [ + { field: 'project', setter: 'setProject', isArray: false }, + { field: 'user', setter: 'setUser', isArray: false }, + ]; + } - // Prepare data - wrapping individual data transformations in a map() method - const presigned_url_requestsData = data.map((item, index) => ({ - id: item.id || undefined, - - purpose: item.purpose - || - null - , - - asset_type: item.asset_type - || - null - , - - requested_key: item.requested_key - || - null - , - - mime_type: item.mime_type - || - null - , - - requested_size_mb: item.requested_size_mb - || - null - , - - expires_at: item.expires_at - || - null - , - - status: item.status - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); + static get FIND_BY_INCLUDES() { + return [ + { association: 'project' }, + { association: 'user' }, + ]; + } - // Bulk create items - const presigned_url_requests = await db.presigned_url_requests.bulkCreate(presigned_url_requestsData, { transaction }); + static getFieldMapping(data) { + return { + id: data.id || undefined, + purpose: data.purpose || null, + asset_type: data.asset_type || null, + requested_key: data.requested_key || null, + mime_type: data.mime_type || null, + requested_size_mb: data.requested_size_mb || null, + expires_at: data.expires_at || null, + status: data.status || null, + }; + } - // For each item created, replace relation files - + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - return presigned_url_requests; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const presigned_url_requests = await db.presigned_url_requests.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.purpose !== undefined) updatePayload.purpose = data.purpose; - - - if (data.asset_type !== undefined) updatePayload.asset_type = data.asset_type; - - - if (data.requested_key !== undefined) updatePayload.requested_key = data.requested_key; - - - if (data.mime_type !== undefined) updatePayload.mime_type = data.mime_type; - - - if (data.requested_size_mb !== undefined) updatePayload.requested_size_mb = data.requested_size_mb; - - - if (data.expires_at !== undefined) updatePayload.expires_at = data.expires_at; - - - if (data.status !== undefined) updatePayload.status = data.status; - - - updatePayload.updatedById = currentUser.id; - - await presigned_url_requests.update(updatePayload, {transaction}); - - - - if (data.project !== undefined) { - await presigned_url_requests.setProject( - - data.project, - - { transaction } - ); - } - - if (data.user !== undefined) { - await presigned_url_requests.setUser( - - data.user, - - { transaction } - ); - } - - - - - - - - return presigned_url_requests; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const presigned_url_requests = await db.presigned_url_requests.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of presigned_url_requests) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of presigned_url_requests) { - await record.destroy({transaction}); - } - }); - - - return presigned_url_requests; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const presigned_url_requests = await db.presigned_url_requests.findByPk(id, options); - - await presigned_url_requests.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await presigned_url_requests.destroy({ - transaction - }); - - return presigned_url_requests; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const presigned_url_requests = await db.presigned_url_requests.findOne({ - where, - transaction, - }); - - if (!presigned_url_requests) { - return presigned_url_requests; - } - - const output = presigned_url_requests.get({plain: true}); - - - - - - - - - - - - - - - - - - - - output.project = await presigned_url_requests.getProject({ - transaction - }); - - - output.user = await presigned_url_requests.getUser({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - { model: db.projects, as: 'project', - where: filter.project ? { [Op.or]: [ { id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } }, @@ -328,13 +83,10 @@ module.exports = class Presigned_url_requestsDBApi { }, ] } : {}, - }, - { model: db.users, as: 'user', - where: filter.user ? { [Op.or]: [ { id: { [Op.in]: filter.user.split('|').map(term => Utils.uuid(term)) } }, @@ -345,227 +97,78 @@ module.exports = class Presigned_url_requestsDBApi { }, ] } : {}, - }, - - - ]; - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.requested_key) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'presigned_url_requests', - 'requested_key', - filter.requested_key, - ), - }; - } - - if (filter.mime_type) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'presigned_url_requests', - 'mime_type', - filter.mime_type, - ), - }; - } - - if (filter.status) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'presigned_url_requests', - 'status', - filter.status, - ), - }; - } - - - - - - - if (filter.requested_size_mbRange) { - const [start, end] = filter.requested_size_mbRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - requested_size_mb: { - ...where.requested_size_mb, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - requested_size_mb: { - ...where.requested_size_mb, - [Op.lte]: end, - }, - }; - } - } - - if (filter.expires_atRange) { - const [start, end] = filter.expires_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - expires_at: { - ...where.expires_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - expires_at: { - ...where.expires_at, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.purpose) { - where = { - ...where, - purpose: filter.purpose, - }; - } - - if (filter.asset_type) { - where = { - ...where, - asset_type: filter.asset_type, - }; - } - - - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.presigned_url_requests.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (filter.id) { + where.id = Utils.uuid(filter.id); } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'presigned_url_requests', - 'requested_key', - query, - ), - ], - }; - } - - const records = await db.presigned_url_requests.findAll({ - attributes: [ 'id', 'requested_key' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['requested_key', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.requested_key, - })); + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } } - -}; + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = Presigned_url_requestsDBApi; diff --git a/backend/src/db/api/project_audio_tracks.js b/backend/src/db/api/project_audio_tracks.js index 96445e3..9d2859a 100644 --- a/backend/src/db/api/project_audio_tracks.js +++ b/backend/src/db/api/project_audio_tracks.js @@ -1,4 +1,4 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); const { @@ -6,342 +6,89 @@ const { applyRuntimeProjectFilter, } = require('./runtime-context'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class Project_audio_tracksDBApi { - +class Project_audio_tracksDBApi extends GenericDBApi { + static get MODEL() { + return db.project_audio_tracks; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'project_audio_tracks'; + } - const project_audio_tracks = await db.project_audio_tracks.create( - { - id: data.id || undefined, - - environment: data.environment - || - null - , - - source_key: data.source_key - || - null - , - - name: data.name - || - null - , - - slug: data.slug - || - null - , - - url: data.url - || - null - , - - loop: data.loop - || - false - - , - - volume: data.volume - || - null - , - - sort_order: data.sort_order - || - null - , - - is_enabled: data.is_enabled - || - false - - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, + static get SEARCHABLE_FIELDS() { + return ['source_key', 'name', 'slug', 'url']; + } + + static get RANGE_FIELDS() { + return ['volume', 'sort_order']; + } + + static get ENUM_FIELDS() { + return ['environment', 'loop', 'is_enabled']; + } + + static get CSV_FIELDS() { + return ['id', 'environment', 'source_key', 'name', 'slug', 'url', 'loop', 'volume', 'createdAt']; + } + + static get AUTOCOMPLETE_FIELD() { + return 'name'; + } + + static get ASSOCIATIONS() { + return [ + { field: 'project', setter: 'setProject', isArray: false }, + ]; + } + + static getFieldMapping(data) { + return { + id: data.id || undefined, + environment: data.environment || null, + source_key: data.source_key || null, + name: data.name || null, + slug: data.slug || null, + url: data.url || null, + loop: data.loop || false, + volume: data.volume || null, + sort_order: data.sort_order || null, + is_enabled: data.is_enabled || false, + }; + } + + static async findBy(where, options = {}) { + const transaction = options.transaction; + const queryWhere = applyRuntimeEnvironment({ ...where }, options); + const projectInclude = applyRuntimeProjectFilter( + { model: db.projects, as: 'project' }, + options ); - - await project_audio_tracks.setProject( data.project || null, { - transaction, - }); - + const record = await this.MODEL.findOne({ + where: queryWhere, + transaction, + include: [projectInclude], + }); - + if (!record) return null; + return record.get({ plain: true }); + } - + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - return project_audio_tracks; - } - - - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - // Prepare data - wrapping individual data transformations in a map() method - const project_audio_tracksData = data.map((item, index) => ({ - id: item.id || undefined, - - environment: item.environment - || - null - , - - source_key: item.source_key - || - null - , - - name: item.name - || - null - , - - slug: item.slug - || - null - , - - url: item.url - || - null - , - - loop: item.loop - || - false - - , - - volume: item.volume - || - null - , - - sort_order: item.sort_order - || - null - , - - is_enabled: item.is_enabled - || - false - - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const project_audio_tracks = await db.project_audio_tracks.bulkCreate(project_audio_tracksData, { transaction }); - - // For each item created, replace relation files - - - return project_audio_tracks; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const project_audio_tracks = await db.project_audio_tracks.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.environment !== undefined) updatePayload.environment = data.environment; - - - if (data.source_key !== undefined) updatePayload.source_key = data.source_key; - - - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.slug !== undefined) updatePayload.slug = data.slug; - - - if (data.url !== undefined) updatePayload.url = data.url; - - - if (data.loop !== undefined) updatePayload.loop = data.loop; - - - if (data.volume !== undefined) updatePayload.volume = data.volume; - - - if (data.sort_order !== undefined) updatePayload.sort_order = data.sort_order; - - - if (data.is_enabled !== undefined) updatePayload.is_enabled = data.is_enabled; - - - updatePayload.updatedById = currentUser.id; - - await project_audio_tracks.update(updatePayload, {transaction}); - - - - if (data.project !== undefined) { - await project_audio_tracks.setProject( - - data.project, - - { transaction } - ); - } - - - - - - - - return project_audio_tracks; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const project_audio_tracks = await db.project_audio_tracks.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of project_audio_tracks) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of project_audio_tracks) { - await record.destroy({transaction}); - } - }); - - - return project_audio_tracks; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const project_audio_tracks = await db.project_audio_tracks.findByPk(id, options); - - await project_audio_tracks.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await project_audio_tracks.destroy({ - transaction - }); - - return project_audio_tracks; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - const queryWhere = applyRuntimeEnvironment({ ...where }, options); - const projectInclude = applyRuntimeProjectFilter( - { - model: db.projects, - as: 'project', - }, - options, - ); - - const project_audio_tracks = await db.project_audio_tracks.findOne( - { where: queryWhere, include: [projectInclude], transaction }, - ); - - if (!project_audio_tracks) { - return project_audio_tracks; - } - - const output = project_audio_tracks.get({plain: true}); - - - - - - - - - - - - - - - - - - - - output.project = await project_audio_tracks.getProject({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - filter = filter || {}; - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - { model: db.projects, as: 'project', - where: filter.project ? { [Op.or]: [ { id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } }, @@ -352,245 +99,82 @@ module.exports = class Project_audio_tracksDBApi { }, ] } : {}, - }, - - - ]; - include[0] = applyRuntimeProjectFilter(include[0], options); - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } + include[0] = applyRuntimeProjectFilter(include[0], options); - - if (filter.source_key) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'project_audio_tracks', - 'source_key', - filter.source_key, - ), - }; - } - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'project_audio_tracks', - 'name', - filter.name, - ), - }; - } - - if (filter.slug) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'project_audio_tracks', - 'slug', - filter.slug, - ), - }; - } - - if (filter.url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'project_audio_tracks', - 'url', - filter.url, - ), - }; - } - - - - - - - if (filter.volumeRange) { - const [start, end] = filter.volumeRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - volume: { - ...where.volume, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - volume: { - ...where.volume, - [Op.lte]: end, - }, - }; - } - } - - if (filter.sort_orderRange) { - const [start, end] = filter.sort_orderRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - sort_order: { - ...where.sort_order, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - sort_order: { - ...where.sort_order, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.environment) { - where = { - ...where, - environment: filter.environment, - }; - } - - if (filter.loop) { - where = { - ...where, - loop: filter.loop, - }; - } - - if (filter.is_enabled) { - where = { - ...where, - is_enabled: filter.is_enabled, - }; - } - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - where = applyRuntimeEnvironment(where, options); - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.project_audio_tracks.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (filter.id) { + where.id = Utils.uuid(filter.id); } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'project_audio_tracks', - 'name', - query, - ), - ], - }; - } - - const records = await db.project_audio_tracks.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } } - -}; + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + where = applyRuntimeEnvironment(where, options); + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = Project_audio_tracksDBApi; diff --git a/backend/src/db/api/project_memberships.js b/backend/src/db/api/project_memberships.js index bbfe8a2..39226d5 100644 --- a/backend/src/db/api/project_memberships.js +++ b/backend/src/db/api/project_memberships.js @@ -1,286 +1,75 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class Project_membershipsDBApi { - +class Project_membershipsDBApi extends GenericDBApi { + static get MODEL() { + return db.project_memberships; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'project_memberships'; + } - const project_memberships = await db.project_memberships.create( - { - id: data.id || undefined, - - access_level: data.access_level - || - null - , - - is_active: data.is_active - || - false - - , - - invited_at: data.invited_at - || - null - , - - accepted_at: data.accepted_at - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); + static get SEARCHABLE_FIELDS() { + return []; + } - - await project_memberships.setProject( data.project || null, { - transaction, - }); - - await project_memberships.setUser( data.user || null, { - transaction, - }); - + static get RANGE_FIELDS() { + return ['invited_at', 'accepted_at']; + } - + static get ENUM_FIELDS() { + return ['access_level', 'is_active']; + } - + static get CSV_FIELDS() { + return ['id', 'access_level', 'is_active', 'invited_at', 'accepted_at', 'createdAt']; + } - return project_memberships; - } - + static get AUTOCOMPLETE_FIELD() { + return 'access_level'; + } - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get ASSOCIATIONS() { + return [ + { field: 'project', setter: 'setProject', isArray: false }, + { field: 'user', setter: 'setUser', isArray: false }, + ]; + } - // Prepare data - wrapping individual data transformations in a map() method - const project_membershipsData = data.map((item, index) => ({ - id: item.id || undefined, - - access_level: item.access_level - || - null - , - - is_active: item.is_active - || - false - - , - - invited_at: item.invited_at - || - null - , - - accepted_at: item.accepted_at - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); + static get FIND_BY_INCLUDES() { + return [ + { association: 'project' }, + { association: 'user' }, + ]; + } - // Bulk create items - const project_memberships = await db.project_memberships.bulkCreate(project_membershipsData, { transaction }); + static getFieldMapping(data) { + return { + id: data.id || undefined, + access_level: data.access_level || null, + is_active: data.is_active || false, + invited_at: data.invited_at || null, + accepted_at: data.accepted_at || null, + }; + } - // For each item created, replace relation files - + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - return project_memberships; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const project_memberships = await db.project_memberships.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.access_level !== undefined) updatePayload.access_level = data.access_level; - - - if (data.is_active !== undefined) updatePayload.is_active = data.is_active; - - - if (data.invited_at !== undefined) updatePayload.invited_at = data.invited_at; - - - if (data.accepted_at !== undefined) updatePayload.accepted_at = data.accepted_at; - - - updatePayload.updatedById = currentUser.id; - - await project_memberships.update(updatePayload, {transaction}); - - - - if (data.project !== undefined) { - await project_memberships.setProject( - - data.project, - - { transaction } - ); - } - - if (data.user !== undefined) { - await project_memberships.setUser( - - data.user, - - { transaction } - ); - } - - - - - - - - return project_memberships; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const project_memberships = await db.project_memberships.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of project_memberships) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of project_memberships) { - await record.destroy({transaction}); - } - }); - - - return project_memberships; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const project_memberships = await db.project_memberships.findByPk(id, options); - - await project_memberships.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await project_memberships.destroy({ - transaction - }); - - return project_memberships; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const project_memberships = await db.project_memberships.findOne({ - where, - transaction, - }); - - if (!project_memberships) { - return project_memberships; - } - - const output = project_memberships.get({plain: true}); - - - - - - - - - - - - - - - - - - - - output.project = await project_memberships.getProject({ - transaction - }); - - - output.user = await project_memberships.getUser({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - { model: db.projects, as: 'project', - where: filter.project ? { [Op.or]: [ { id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } }, @@ -291,13 +80,10 @@ module.exports = class Project_membershipsDBApi { }, ] } : {}, - }, - { model: db.users, as: 'user', - where: filter.user ? { [Op.or]: [ { id: { [Op.in]: filter.user.split('|').map(term => Utils.uuid(term)) } }, @@ -308,194 +94,72 @@ module.exports = class Project_membershipsDBApi { }, ] } : {}, - }, - - - ]; - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - - - - - - if (filter.invited_atRange) { - const [start, end] = filter.invited_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - invited_at: { - ...where.invited_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - invited_at: { - ...where.invited_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.accepted_atRange) { - const [start, end] = filter.accepted_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - accepted_at: { - ...where.accepted_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - accepted_at: { - ...where.accepted_at, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.access_level) { - where = { - ...where, - access_level: filter.access_level, - }; - } - - if (filter.is_active) { - where = { - ...where, - is_active: filter.is_active, - }; - } - - - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.project_memberships.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (filter.id) { + where.id = Utils.uuid(filter.id); } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'project_memberships', - 'access_level', - query, - ), - ], - }; + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; } - - const records = await db.project_memberships.findAll({ - attributes: [ 'id', 'access_level' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['access_level', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.access_level, - })); + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } } - -}; + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = Project_membershipsDBApi; diff --git a/backend/src/db/api/projects.js b/backend/src/db/api/projects.js index 83c372b..655c9f9 100644 --- a/backend/src/db/api/projects.js +++ b/backend/src/db/api/projects.js @@ -1,4 +1,4 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); const { @@ -6,694 +6,186 @@ const { getRuntimeProjectSlug, } = require('./runtime-context'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class ProjectsDBApi { - +class ProjectsDBApi extends GenericDBApi { + static get MODEL() { + return db.projects; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'projects'; + } - const projects = await db.projects.create( - { - id: data.id || undefined, - - name: data.name - || - null - , - - slug: data.slug - || - null - , - - description: data.description - || - null - , - - phase: data.phase - || - null - , - - logo_url: data.logo_url - || - null - , - - favicon_url: data.favicon_url - || - null - , - - og_image_url: data.og_image_url - || - null - , - - theme_config_json: data.theme_config_json - || - null - , - - custom_css_json: data.custom_css_json - || - null - , - - cdn_base_url: data.cdn_base_url - || - null - , - - entry_page_slug: data.entry_page_slug - || - null - , - - is_deleted: data.is_deleted - || - false - - , - - deleted_at_time: data.deleted_at_time - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); + static get SEARCHABLE_FIELDS() { + return ['name', 'slug', 'description', 'logo_url', 'favicon_url', 'og_image_url', 'theme_config_json', 'custom_css_json', 'cdn_base_url', 'entry_page_slug']; + } - + static get RANGE_FIELDS() { + return ['deleted_at_time']; + } - + static get ENUM_FIELDS() { + return ['phase', 'is_deleted']; + } - + static get CSV_FIELDS() { + return ['id', 'name', 'slug', 'description', 'phase', 'logo_url', 'cdn_base_url', 'createdAt']; + } - return projects; - } - + static get AUTOCOMPLETE_FIELD() { + return 'name'; + } - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get ASSOCIATIONS() { + return []; + } - // Prepare data - wrapping individual data transformations in a map() method - const projectsData = data.map((item, index) => ({ - id: item.id || undefined, - - name: item.name - || - null - , - - slug: item.slug - || - null - , - - description: item.description - || - null - , - - phase: item.phase - || - null - , - - logo_url: item.logo_url - || - null - , - - favicon_url: item.favicon_url - || - null - , - - og_image_url: item.og_image_url - || - null - , - - theme_config_json: item.theme_config_json - || - null - , - - custom_css_json: item.custom_css_json - || - null - , - - cdn_base_url: item.cdn_base_url - || - null - , - - entry_page_slug: item.entry_page_slug - || - null - , - - is_deleted: item.is_deleted - || - false - - , - - deleted_at_time: item.deleted_at_time - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); + static getFieldMapping(data) { + return { + id: data.id || undefined, + name: data.name || null, + slug: data.slug || null, + description: data.description || null, + phase: data.phase || null, + logo_url: data.logo_url || null, + favicon_url: data.favicon_url || null, + og_image_url: data.og_image_url || null, + theme_config_json: data.theme_config_json || null, + custom_css_json: data.custom_css_json || null, + cdn_base_url: data.cdn_base_url || null, + entry_page_slug: data.entry_page_slug || null, + is_deleted: data.is_deleted || false, + deleted_at_time: data.deleted_at_time || null, + }; + } - // Bulk create items - const projects = await db.projects.bulkCreate(projectsData, { transaction }); + static async findBy(where, options = {}) { + const transaction = options.transaction; + const runtimeEnvironment = getRuntimeEnvironment(options); + const runtimeProjectSlug = getRuntimeProjectSlug(options); + const queryWhere = { ...where }; - // For each item created, replace relation files - - - return projects; + if (runtimeEnvironment) { + queryWhere.phase = runtimeEnvironment === 'production' + ? 'production' + : { [Op.in]: ['stage', 'production'] }; } - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const projects = await db.projects.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.slug !== undefined) updatePayload.slug = data.slug; - - - if (data.description !== undefined) updatePayload.description = data.description; - - - if (data.phase !== undefined) updatePayload.phase = data.phase; - - - if (data.logo_url !== undefined) updatePayload.logo_url = data.logo_url; - - - if (data.favicon_url !== undefined) updatePayload.favicon_url = data.favicon_url; - - - if (data.og_image_url !== undefined) updatePayload.og_image_url = data.og_image_url; - - - if (data.theme_config_json !== undefined) updatePayload.theme_config_json = data.theme_config_json; - - - if (data.custom_css_json !== undefined) updatePayload.custom_css_json = data.custom_css_json; - - - if (data.cdn_base_url !== undefined) updatePayload.cdn_base_url = data.cdn_base_url; - - - if (data.entry_page_slug !== undefined) updatePayload.entry_page_slug = data.entry_page_slug; - - - if (data.is_deleted !== undefined) updatePayload.is_deleted = data.is_deleted; - - - if (data.deleted_at_time !== undefined) updatePayload.deleted_at_time = data.deleted_at_time; - - - updatePayload.updatedById = currentUser.id; - - await projects.update(updatePayload, {transaction}); - - - - - - - - - - return projects; + if (runtimeProjectSlug) { + queryWhere.slug = runtimeProjectSlug; } - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + const record = await this.MODEL.findOne({ + where: queryWhere, + transaction, + include: [ + { association: 'project_memberships_project' }, + { association: 'assets_project' }, + { association: 'presigned_url_requests_project' }, + { association: 'tour_pages_project' }, + { association: 'transitions_project' }, + { association: 'project_audio_tracks_project' }, + { association: 'publish_events_project' }, + { association: 'pwa_caches_project' }, + { association: 'access_logs_project' }, + ], + }); - const projects = await db.projects.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); + if (!record) return null; + return record.get({ plain: true }); + } - await db.sequelize.transaction(async (transaction) => { - for (const record of projects) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of projects) { - await record.destroy({transaction}); - } - }); + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; + let where = {}; + let include = []; - return projects; + if (filter.id) { + where.id = Utils.uuid(filter.id); } - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const projects = await db.projects.findByPk(id, options); - - await projects.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await projects.destroy({ - transaction - }); - - return projects; + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } } - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - const runtimeEnvironment = getRuntimeEnvironment(options); - const runtimeProjectSlug = getRuntimeProjectSlug(options); - const queryWhere = { ...where }; - - if (runtimeEnvironment) { - queryWhere.phase = runtimeEnvironment === 'production' - ? 'production' - : { [Op.in]: ['stage', 'production'] }; + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; } - - if (runtimeProjectSlug) { - queryWhere.slug = runtimeProjectSlug; + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; } - - const projects = await db.projects.findOne( - { where: queryWhere, transaction }, - ); - - if (!projects) { - return projects; - } - - const output = projects.get({plain: true}); - - - - - - - output.project_memberships_project = await projects.getProject_memberships_project({ - transaction - }); - - - output.assets_project = await projects.getAssets_project({ - transaction - }); - - - - output.presigned_url_requests_project = await projects.getPresigned_url_requests_project({ - transaction - }); - - - output.tour_pages_project = await projects.getTour_pages_project({ - transaction - }); - - - - - output.transitions_project = await projects.getTransitions_project({ - transaction - }); - - - output.project_audio_tracks_project = await projects.getProject_audio_tracks_project({ - transaction - }); - - - output.publish_events_project = await projects.getPublish_events_project({ - transaction - }); - - - output.pwa_caches_project = await projects.getPwa_caches_project({ - transaction - }); - - - output.access_logs_project = await projects.getAccess_logs_project({ - transaction - }); - - - - - return output; + } } - static async findAll( - filter, - options - ) { - filter = filter || {}; - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; - - let include = [ - - - - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'projects', - 'name', - filter.name, - ), - }; - } - - if (filter.slug) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'projects', - 'slug', - filter.slug, - ), - }; - } - - if (filter.description) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'projects', - 'description', - filter.description, - ), - }; - } - - if (filter.logo_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'projects', - 'logo_url', - filter.logo_url, - ), - }; - } - - if (filter.favicon_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'projects', - 'favicon_url', - filter.favicon_url, - ), - }; - } - - if (filter.og_image_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'projects', - 'og_image_url', - filter.og_image_url, - ), - }; - } - - if (filter.theme_config_json) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'projects', - 'theme_config_json', - filter.theme_config_json, - ), - }; - } - - if (filter.custom_css_json) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'projects', - 'custom_css_json', - filter.custom_css_json, - ), - }; - } - - if (filter.cdn_base_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'projects', - 'cdn_base_url', - filter.cdn_base_url, - ), - }; - } - - if (filter.entry_page_slug) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'projects', - 'entry_page_slug', - filter.entry_page_slug, - ), - }; - } - - - - - - - if (filter.deleted_at_timeRange) { - const [start, end] = filter.deleted_at_timeRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - deleted_at_time: { - ...where.deleted_at_time, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - deleted_at_time: { - ...where.deleted_at_time, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.phase) { - where = { - ...where, - phase: filter.phase, - }; - } - - if (filter.is_deleted) { - where = { - ...where, - is_deleted: filter.is_deleted, - }; - } - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - const runtimeEnvironment = getRuntimeEnvironment(options); - const runtimeProjectSlug = getRuntimeProjectSlug(options); - - if (runtimeEnvironment) { - where = { - ...where, - phase: runtimeEnvironment, - }; - } - - if (runtimeProjectSlug) { - where = { - ...where, - slug: runtimeProjectSlug, - }; - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.projects.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'projects', - 'name', - query, - ), - ], - }; - } - - const records = await db.projects.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; } - -}; + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + const runtimeEnvironment = getRuntimeEnvironment(options); + const runtimeProjectSlug = getRuntimeProjectSlug(options); + + if (runtimeEnvironment) { + where.phase = runtimeEnvironment; + } + + if (runtimeProjectSlug) { + where.slug = runtimeProjectSlug; + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = ProjectsDBApi; diff --git a/backend/src/db/api/publish_events.js b/backend/src/db/api/publish_events.js index 7a236fc..e61fa66 100644 --- a/backend/src/db/api/publish_events.js +++ b/backend/src/db/api/publish_events.js @@ -1,375 +1,82 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class Publish_eventsDBApi { - +class Publish_eventsDBApi extends GenericDBApi { + static get MODEL() { + return db.publish_events; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'publish_events'; + } - const publish_events = await db.publish_events.create( - { - id: data.id || undefined, - - title: data.title - || - null - , - - description: data.description - || - null - , - - from_environment: data.from_environment - || - null - , - - to_environment: data.to_environment - || - null - , - - started_at: data.started_at - || - null - , - - finished_at: data.finished_at - || - null - , - - status: data.status - || - null - , - - error_message: data.error_message - || - null - , - - pages_copied: data.pages_copied - || - null - , - - transitions_copied: data.transitions_copied - || - null - , - - audios_copied: data.audios_copied - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); + static get SEARCHABLE_FIELDS() { + return ['title', 'description', 'error_message']; + } - - await publish_events.setProject( data.project || null, { - transaction, - }); - - await publish_events.setUser( data.user || null, { - transaction, - }); - + static get RANGE_FIELDS() { + return ['started_at', 'finished_at', 'pages_copied', 'transitions_copied', 'audios_copied']; + } - + static get ENUM_FIELDS() { + return ['from_environment', 'to_environment', 'status']; + } - + static get CSV_FIELDS() { + return ['id', 'title', 'description', 'from_environment', 'to_environment', 'status', 'pages_copied', 'createdAt']; + } - return publish_events; - } - + static get AUTOCOMPLETE_FIELD() { + return 'status'; + } - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get ASSOCIATIONS() { + return [ + { field: 'project', setter: 'setProject', isArray: false }, + { field: 'user', setter: 'setUser', isArray: false }, + ]; + } - // Prepare data - wrapping individual data transformations in a map() method - const publish_eventsData = data.map((item, index) => ({ - id: item.id || undefined, - - title: item.title - || - null - , - - description: item.description - || - null - , - - from_environment: item.from_environment - || - null - , - - to_environment: item.to_environment - || - null - , - - started_at: item.started_at - || - null - , - - finished_at: item.finished_at - || - null - , - - status: item.status - || - null - , - - error_message: item.error_message - || - null - , - - pages_copied: item.pages_copied - || - null - , - - transitions_copied: item.transitions_copied - || - null - , - - audios_copied: item.audios_copied - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); + static get FIND_BY_INCLUDES() { + return [ + { association: 'project' }, + { association: 'user' }, + ]; + } - // Bulk create items - const publish_events = await db.publish_events.bulkCreate(publish_eventsData, { transaction }); + static getFieldMapping(data) { + return { + id: data.id || undefined, + title: data.title || null, + description: data.description || null, + from_environment: data.from_environment || null, + to_environment: data.to_environment || null, + started_at: data.started_at || null, + finished_at: data.finished_at || null, + status: data.status || null, + error_message: data.error_message || null, + pages_copied: data.pages_copied || null, + transitions_copied: data.transitions_copied || null, + audios_copied: data.audios_copied || null, + }; + } - // For each item created, replace relation files - + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - return publish_events; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const publish_events = await db.publish_events.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.title !== undefined) updatePayload.title = data.title; - - - if (data.description !== undefined) updatePayload.description = data.description; - - - if (data.from_environment !== undefined) updatePayload.from_environment = data.from_environment; - - - if (data.to_environment !== undefined) updatePayload.to_environment = data.to_environment; - - - if (data.started_at !== undefined) updatePayload.started_at = data.started_at; - - - if (data.finished_at !== undefined) updatePayload.finished_at = data.finished_at; - - - if (data.status !== undefined) updatePayload.status = data.status; - - - if (data.error_message !== undefined) updatePayload.error_message = data.error_message; - - - if (data.pages_copied !== undefined) updatePayload.pages_copied = data.pages_copied; - - - if (data.transitions_copied !== undefined) updatePayload.transitions_copied = data.transitions_copied; - - - if (data.audios_copied !== undefined) updatePayload.audios_copied = data.audios_copied; - - - updatePayload.updatedById = currentUser.id; - - await publish_events.update(updatePayload, {transaction}); - - - - if (data.project !== undefined) { - await publish_events.setProject( - - data.project, - - { transaction } - ); - } - - if (data.user !== undefined) { - await publish_events.setUser( - - data.user, - - { transaction } - ); - } - - - - - - - - return publish_events; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const publish_events = await db.publish_events.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of publish_events) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of publish_events) { - await record.destroy({transaction}); - } - }); - - - return publish_events; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const publish_events = await db.publish_events.findByPk(id, options); - - await publish_events.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await publish_events.destroy({ - transaction - }); - - return publish_events; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const publish_events = await db.publish_events.findOne({ - where, - transaction, - }); - - if (!publish_events) { - return publish_events; - } - - const output = publish_events.get({plain: true}); - - - - - - - - - - - - - - - - - - - - output.project = await publish_events.getProject({ - transaction - }); - - - output.user = await publish_events.getUser({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - { model: db.projects, as: 'project', - where: filter.project ? { [Op.or]: [ { id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } }, @@ -380,13 +87,10 @@ module.exports = class Publish_eventsDBApi { }, ] } : {}, - }, - { model: db.users, as: 'user', - where: filter.user ? { [Op.or]: [ { id: { [Op.in]: filter.user.split('|').map(term => Utils.uuid(term)) } }, @@ -397,307 +101,78 @@ module.exports = class Publish_eventsDBApi { }, ] } : {}, - }, - - - ]; - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.title) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'publish_events', - 'title', - filter.title, - ), - }; - } - - if (filter.description) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'publish_events', - 'description', - filter.description, - ), - }; - } - - - if (filter.error_message) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'publish_events', - 'error_message', - filter.error_message, - ), - }; - } - - - - - - - if (filter.started_atRange) { - const [start, end] = filter.started_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - started_at: { - ...where.started_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - started_at: { - ...where.started_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.finished_atRange) { - const [start, end] = filter.finished_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - finished_at: { - ...where.finished_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - finished_at: { - ...where.finished_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.pages_copiedRange) { - const [start, end] = filter.pages_copiedRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - pages_copied: { - ...where.pages_copied, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - pages_copied: { - ...where.pages_copied, - [Op.lte]: end, - }, - }; - } - } - - if (filter.transitions_copiedRange) { - const [start, end] = filter.transitions_copiedRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - transitions_copied: { - ...where.transitions_copied, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - transitions_copied: { - ...where.transitions_copied, - [Op.lte]: end, - }, - }; - } - } - - if (filter.audios_copiedRange) { - const [start, end] = filter.audios_copiedRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - audios_copied: { - ...where.audios_copied, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - audios_copied: { - ...where.audios_copied, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.from_environment) { - where = { - ...where, - from_environment: filter.from_environment, - }; - } - - if (filter.to_environment) { - where = { - ...where, - to_environment: filter.to_environment, - }; - } - - if (filter.status) { - where = { - ...where, - status: filter.status, - }; - } - - - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.publish_events.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (filter.id) { + where.id = Utils.uuid(filter.id); } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'publish_events', - 'status', - query, - ), - ], - }; - } - - const records = await db.publish_events.findAll({ - attributes: [ 'id', 'status' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['status', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.status, - })); + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } } - -}; + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = Publish_eventsDBApi; diff --git a/backend/src/db/api/pwa_caches.js b/backend/src/db/api/pwa_caches.js index 696149b..e722cd8 100644 --- a/backend/src/db/api/pwa_caches.js +++ b/backend/src/db/api/pwa_caches.js @@ -1,294 +1,73 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class Pwa_cachesDBApi { - +class Pwa_cachesDBApi extends GenericDBApi { + static get MODEL() { + return db.pwa_caches; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'pwa_caches'; + } - const pwa_caches = await db.pwa_caches.create( - { - id: data.id || undefined, - - environment: data.environment - || - null - , - - cache_version: data.cache_version - || - null - , - - manifest_json: data.manifest_json - || - null - , - - asset_list_json: data.asset_list_json - || - null - , - - generated_at: data.generated_at - || - null - , - - is_active: data.is_active - || - false - - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); + static get SEARCHABLE_FIELDS() { + return ['cache_version', 'manifest_json', 'asset_list_json']; + } - - await pwa_caches.setProject( data.project || null, { - transaction, - }); - + static get RANGE_FIELDS() { + return ['generated_at']; + } - + static get ENUM_FIELDS() { + return ['environment', 'is_active']; + } - + static get CSV_FIELDS() { + return ['id', 'environment', 'cache_version', 'is_active', 'generated_at', 'createdAt']; + } - return pwa_caches; - } - + static get AUTOCOMPLETE_FIELD() { + return 'cache_version'; + } - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get ASSOCIATIONS() { + return [ + { field: 'project', setter: 'setProject', isArray: false }, + ]; + } - // Prepare data - wrapping individual data transformations in a map() method - const pwa_cachesData = data.map((item, index) => ({ - id: item.id || undefined, - - environment: item.environment - || - null - , - - cache_version: item.cache_version - || - null - , - - manifest_json: item.manifest_json - || - null - , - - asset_list_json: item.asset_list_json - || - null - , - - generated_at: item.generated_at - || - null - , - - is_active: item.is_active - || - false - - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); + static get FIND_BY_INCLUDES() { + return [{ association: 'project' }]; + } - // Bulk create items - const pwa_caches = await db.pwa_caches.bulkCreate(pwa_cachesData, { transaction }); + static getFieldMapping(data) { + return { + id: data.id || undefined, + environment: data.environment || null, + cache_version: data.cache_version || null, + manifest_json: data.manifest_json || null, + asset_list_json: data.asset_list_json || null, + generated_at: data.generated_at || null, + is_active: data.is_active || false, + }; + } - // For each item created, replace relation files - + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - return pwa_caches; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const pwa_caches = await db.pwa_caches.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.environment !== undefined) updatePayload.environment = data.environment; - - - if (data.cache_version !== undefined) updatePayload.cache_version = data.cache_version; - - - if (data.manifest_json !== undefined) updatePayload.manifest_json = data.manifest_json; - - - if (data.asset_list_json !== undefined) updatePayload.asset_list_json = data.asset_list_json; - - - if (data.generated_at !== undefined) updatePayload.generated_at = data.generated_at; - - - if (data.is_active !== undefined) updatePayload.is_active = data.is_active; - - - updatePayload.updatedById = currentUser.id; - - await pwa_caches.update(updatePayload, {transaction}); - - - - if (data.project !== undefined) { - await pwa_caches.setProject( - - data.project, - - { transaction } - ); - } - - - - - - - - return pwa_caches; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const pwa_caches = await db.pwa_caches.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of pwa_caches) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of pwa_caches) { - await record.destroy({transaction}); - } - }); - - - return pwa_caches; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const pwa_caches = await db.pwa_caches.findByPk(id, options); - - await pwa_caches.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await pwa_caches.destroy({ - transaction - }); - - return pwa_caches; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const pwa_caches = await db.pwa_caches.findOne({ - where, - transaction, - }); - - if (!pwa_caches) { - return pwa_caches; - } - - const output = pwa_caches.get({plain: true}); - - - - - - - - - - - - - - - - - - - - output.project = await pwa_caches.getProject({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - { model: db.projects, as: 'project', - where: filter.project ? { [Op.or]: [ { id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } }, @@ -299,201 +78,78 @@ module.exports = class Pwa_cachesDBApi { }, ] } : {}, - }, - - - ]; - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.cache_version) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'pwa_caches', - 'cache_version', - filter.cache_version, - ), - }; - } - - if (filter.manifest_json) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'pwa_caches', - 'manifest_json', - filter.manifest_json, - ), - }; - } - - if (filter.asset_list_json) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'pwa_caches', - 'asset_list_json', - filter.asset_list_json, - ), - }; - } - - - - - - - if (filter.generated_atRange) { - const [start, end] = filter.generated_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - generated_at: { - ...where.generated_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - generated_at: { - ...where.generated_at, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.environment) { - where = { - ...where, - environment: filter.environment, - }; - } - - if (filter.is_active) { - where = { - ...where, - is_active: filter.is_active, - }; - } - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.pwa_caches.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (filter.id) { + where.id = Utils.uuid(filter.id); } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'pwa_caches', - 'cache_version', - query, - ), - ], - }; - } - - const records = await db.pwa_caches.findAll({ - attributes: [ 'id', 'cache_version' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['cache_version', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.cache_version, - })); + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } } - -}; + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = Pwa_cachesDBApi; diff --git a/backend/src/db/api/roles.js b/backend/src/db/api/roles.js index f149043..595812b 100644 --- a/backend/src/db/api/roles.js +++ b/backend/src/db/api/roles.js @@ -1,405 +1,148 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class RolesDBApi { - +class RolesDBApi extends GenericDBApi { + static get MODEL() { + return db.roles; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'roles'; + } - const roles = await db.roles.create( - { - id: data.id || undefined, - - name: data.name - || - null - , - - role_customization: data.role_customization - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); + static get SEARCHABLE_FIELDS() { + return ['name', 'role_customization']; + } - + static get RANGE_FIELDS() { + return []; + } - - await roles.setPermissions(data.permissions || [], { - transaction, - }); - + static get ENUM_FIELDS() { + return []; + } - + static get CSV_FIELDS() { + return ['id', 'name', 'role_customization', 'createdAt']; + } - return roles; - } - + static get AUTOCOMPLETE_FIELD() { + return 'name'; + } - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get ASSOCIATIONS() { + return [ + { field: 'permissions', setter: 'setPermissions', isArray: true }, + ]; + } - // Prepare data - wrapping individual data transformations in a map() method - const rolesData = data.map((item, index) => ({ - id: item.id || undefined, - - name: item.name - || - null - , - - role_customization: item.role_customization - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); + static get FIND_BY_INCLUDES() { + return [ + { association: 'users_app_role' }, + { association: 'permissions' }, + ]; + } - // Bulk create items - const roles = await db.roles.bulkCreate(rolesData, { transaction }); + static getFieldMapping(data) { + return { + id: data.id || undefined, + name: data.name || null, + role_customization: data.role_customization || null, + }; + } - // For each item created, replace relation files - + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - return roles; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const roles = await db.roles.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.role_customization !== undefined) updatePayload.role_customization = data.role_customization; - - - updatePayload.updatedById = currentUser.id; - - await roles.update(updatePayload, {transaction}); - - - - - - - if (data.permissions !== undefined) { - await roles.setPermissions(data.permissions, { transaction }); - } - - - - - return roles; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const roles = await db.roles.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of roles) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of roles) { - await record.destroy({transaction}); - } - }); - - - return roles; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const roles = await db.roles.findByPk(id, options); - - await roles.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await roles.destroy({ - transaction - }); - - return roles; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const roles = await db.roles.findOne({ - where, - transaction, - }); - - if (!roles) { - return roles; - } - - const output = roles.get({plain: true}); - - - output.users_app_role = await roles.getUsers_app_role({ - transaction - }); - - - - - - - - - - - - - - - - - - - output.permissions = await roles.getPermissions({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - - { model: db.permissions, as: 'permissions', required: false, }, - - ]; - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } + if (filter.id) { + where.id = Utils.uuid(filter.id); + } - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'roles', - 'name', - filter.name, - ), - }; - } - - if (filter.role_customization) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'roles', - 'role_customization', - filter.role_customization, - ), - }; - } - - - - - - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - - - - if (filter.permissions) { - const searchTerms = filter.permissions.split('|'); - - include = [ - { - model: db.permissions, - as: 'permissions_filter', - required: searchTerms.length > 0, - where: searchTerms.length > 0 ? { - [Op.or]: [ - { id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } }, - { - name: { - [Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` })) - } - } - ] - } : undefined - }, - ...include, - ] + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); } - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.roles.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'roles', - 'name', - query, - ), - ], - }; - } - - const records = await db.roles.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; } - -}; + if (filter.permissions) { + const searchTerms = filter.permissions.split('|'); + include = [ + { + model: db.permissions, + as: 'permissions_filter', + required: searchTerms.length > 0, + where: searchTerms.length > 0 ? { + [Op.or]: [ + { id: { [Op.in]: searchTerms.map(term => Utils.uuid(term)) } }, + { + name: { + [Op.or]: searchTerms.map(term => ({ [Op.iLike]: `%${term}%` })) + } + } + ] + } : undefined + }, + ...include, + ]; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = RolesDBApi; diff --git a/backend/src/db/api/tour_pages.js b/backend/src/db/api/tour_pages.js index 6509b37..596f0b0 100644 --- a/backend/src/db/api/tour_pages.js +++ b/backend/src/db/api/tour_pages.js @@ -1,4 +1,4 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); const { @@ -6,382 +6,120 @@ const { applyRuntimeProjectFilter, } = require('./runtime-context'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class Tour_pagesDBApi { - +class Tour_pagesDBApi extends GenericDBApi { + static get MODEL() { + return db.tour_pages; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - const projectId = data.project || data.projectId || null; + static get TABLE_NAME() { + return 'tour_pages'; + } - const tour_pages = await db.tour_pages.create( - { - id: data.id || undefined, - - environment: data.environment - || - null - , - - source_key: data.source_key - || - null - , - - name: data.name - || - null - , - - slug: data.slug - || - null - , - - sort_order: data.sort_order - || - null - , - - background_image_url: data.background_image_url - || - null - , - - background_video_url: data.background_video_url - || - null - , - - background_audio_url: data.background_audio_url - || - null - , - - background_loop: data.background_loop - || - false - - , - - requires_auth: data.requires_auth - || - false - - , - - ui_schema_json: data.ui_schema_json - || - null - , - projectId, - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, + static get SEARCHABLE_FIELDS() { + return ['source_key', 'name', 'slug', 'background_image_url', 'background_video_url', 'background_audio_url', 'ui_schema_json']; + } + + static get RANGE_FIELDS() { + return ['sort_order']; + } + + static get ENUM_FIELDS() { + return ['environment', 'background_loop', 'requires_auth']; + } + + static get CSV_FIELDS() { + return ['id', 'environment', 'source_key', 'name', 'slug', 'sort_order', 'createdAt']; + } + + static get AUTOCOMPLETE_FIELD() { + return 'name'; + } + + static get ASSOCIATIONS() { + return [ + { field: 'project', setter: 'setProject', isArray: false }, + ]; + } + + static getFieldMapping(data) { + return { + id: data.id || undefined, + environment: data.environment || null, + source_key: data.source_key || null, + name: data.name || null, + slug: data.slug || null, + sort_order: data.sort_order || null, + background_image_url: data.background_image_url || null, + background_video_url: data.background_video_url || null, + background_audio_url: data.background_audio_url || null, + background_loop: data.background_loop || false, + requires_auth: data.requires_auth || false, + ui_schema_json: data.ui_schema_json || null, + }; + } + + static async create(data, options = {}) { + const currentUser = options.currentUser || { id: null }; + const transaction = options.transaction; + const projectId = data.project || data.projectId || null; + + const record = await this.MODEL.create( + { + ...this.getFieldMapping(data), + projectId, + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { transaction } ); - - await tour_pages.setProject(projectId, { - transaction, - }); - + await record.setProject(projectId, { transaction }); - - - - - return tour_pages; - } - - - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - // Prepare data - wrapping individual data transformations in a map() method - const tour_pagesData = data.map((item, index) => ({ - id: item.id || undefined, - - environment: item.environment - || - null - , - - source_key: item.source_key - || - null - , - - name: item.name - || - null - , - - slug: item.slug - || - null - , - - sort_order: item.sort_order - || - null - , - - background_image_url: item.background_image_url - || - null - , - - background_video_url: item.background_video_url - || - null - , - - background_audio_url: item.background_audio_url - || - null - , - - background_loop: item.background_loop - || - false - - , - - requires_auth: item.requires_auth - || - false - - , - - ui_schema_json: item.ui_schema_json - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const tour_pages = await db.tour_pages.bulkCreate(tour_pagesData, { transaction }); - - // For each item created, replace relation files - - - return tour_pages; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const tour_pages = await db.tour_pages.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.environment !== undefined) updatePayload.environment = data.environment; - - - if (data.source_key !== undefined) updatePayload.source_key = data.source_key; - - - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.slug !== undefined) updatePayload.slug = data.slug; - - - if (data.sort_order !== undefined) updatePayload.sort_order = data.sort_order; - - - if (data.background_image_url !== undefined) updatePayload.background_image_url = data.background_image_url; - - - if (data.background_video_url !== undefined) updatePayload.background_video_url = data.background_video_url; - - - if (data.background_audio_url !== undefined) updatePayload.background_audio_url = data.background_audio_url; - - - if (data.background_loop !== undefined) updatePayload.background_loop = data.background_loop; - - - if (data.requires_auth !== undefined) updatePayload.requires_auth = data.requires_auth; - - - if (data.ui_schema_json !== undefined) updatePayload.ui_schema_json = data.ui_schema_json; - - - updatePayload.updatedById = currentUser.id; - - await tour_pages.update(updatePayload, {transaction}); - - - - if (data.project !== undefined) { - await tour_pages.setProject( - - data.project, - - { transaction } - ); - } - - - - - - - - return tour_pages; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const tour_pages = await db.tour_pages.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of tour_pages) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of tour_pages) { - await record.destroy({transaction}); - } - }); - - - return tour_pages; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const tour_pages = await db.tour_pages.findByPk(id, options); - - await tour_pages.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await tour_pages.destroy({ - transaction - }); - - return tour_pages; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - const queryWhere = applyRuntimeEnvironment({ ...where }, options); - const projectInclude = applyRuntimeProjectFilter( - { - model: db.projects, - as: 'project', - }, - options, - ); - - const tour_pages = await db.tour_pages.findOne( - { where: queryWhere, include: [projectInclude], transaction }, - ); - - if (!tour_pages) { - return tour_pages; - } - - const output = tour_pages.get({plain: true}); - - - - - - - - - - - - output.page_elements_page = await tour_pages.getPage_elements_page({ - transaction - }); - - - output.page_links_from_page = await tour_pages.getPage_links_from_page({ - transaction - }); - - output.page_links_to_page = await tour_pages.getPage_links_to_page({ - transaction - }); - - - - - - - - - output.project = await tour_pages.getProject({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - filter = filter || {}; - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; - - let include = [ + return record; + } + static async findBy(where, options = {}) { + const transaction = options.transaction; + const queryWhere = applyRuntimeEnvironment({ ...where }, options); + const projectInclude = applyRuntimeProjectFilter( + { + model: db.projects, + as: 'project', + }, + options, + ); + + const record = await this.MODEL.findOne({ + where: queryWhere, + transaction, + include: [ + projectInclude, + { association: 'page_elements_page' }, + { association: 'page_links_from_page' }, + { association: 'page_links_to_page' }, + ], + }); + + if (!record) return null; + return record.get({ plain: true }); + } + + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; + + let where = {}; + + let include = [ { model: db.projects, as: 'project', - where: filter.project ? { [Op.or]: [ { id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } }, @@ -392,254 +130,82 @@ module.exports = class Tour_pagesDBApi { }, ] } : {}, - }, - - - ]; - include[0] = applyRuntimeProjectFilter(include[0], options); - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } + include[0] = applyRuntimeProjectFilter(include[0], options); - - if (filter.source_key) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'tour_pages', - 'source_key', - filter.source_key, - ), - }; - } - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'tour_pages', - 'name', - filter.name, - ), - }; - } - - if (filter.slug) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'tour_pages', - 'slug', - filter.slug, - ), - }; - } - - if (filter.background_image_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'tour_pages', - 'background_image_url', - filter.background_image_url, - ), - }; - } - - if (filter.background_video_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'tour_pages', - 'background_video_url', - filter.background_video_url, - ), - }; - } - - if (filter.background_audio_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'tour_pages', - 'background_audio_url', - filter.background_audio_url, - ), - }; - } - - if (filter.ui_schema_json) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'tour_pages', - 'ui_schema_json', - filter.ui_schema_json, - ), - }; - } - - - - - - - if (filter.sort_orderRange) { - const [start, end] = filter.sort_orderRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - sort_order: { - ...where.sort_order, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - sort_order: { - ...where.sort_order, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.environment) { - where = { - ...where, - environment: filter.environment, - }; - } - - if (filter.background_loop) { - where = { - ...where, - background_loop: filter.background_loop, - }; - } - - if (filter.requires_auth) { - where = { - ...where, - requires_auth: filter.requires_auth, - }; - } - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - where = applyRuntimeEnvironment(where, options); - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.tour_pages.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (filter.id) { + where.id = Utils.uuid(filter.id); } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'tour_pages', - 'name', - query, - ), - ], - }; - } - - const records = await db.tour_pages.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } } - -}; + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + where = applyRuntimeEnvironment(where, options); + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = Tour_pagesDBApi; diff --git a/backend/src/db/api/transitions.js b/backend/src/db/api/transitions.js index 8352373..c7acfaa 100644 --- a/backend/src/db/api/transitions.js +++ b/backend/src/db/api/transitions.js @@ -1,4 +1,4 @@ - +const GenericDBApi = require('./base.api'); const db = require('../models'); const Utils = require('../utils'); const { @@ -6,331 +6,91 @@ const { applyRuntimeProjectFilter, } = require('./runtime-context'); - - const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class TransitionsDBApi { - +class TransitionsDBApi extends GenericDBApi { + static get MODEL() { + return db.transitions; + } - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; + static get TABLE_NAME() { + return 'transitions'; + } - const transitions = await db.transitions.create( - { - id: data.id || undefined, - - environment: data.environment - || - null - , - - source_key: data.source_key - || - null - , - - name: data.name - || - null - , - - slug: data.slug - || - null - , - - video_url: data.video_url - || - null - , - - audio_url: data.audio_url - || - null - , - - supports_reverse: data.supports_reverse - || - false - - , - - duration_sec: data.duration_sec - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, + static get SEARCHABLE_FIELDS() { + return ['source_key', 'name', 'slug', 'video_url', 'audio_url']; + } + + static get RANGE_FIELDS() { + return ['duration_sec']; + } + + static get ENUM_FIELDS() { + return ['environment', 'supports_reverse']; + } + + static get CSV_FIELDS() { + return ['id', 'source_key', 'name', 'slug', 'video_url', 'audio_url', 'duration_sec', 'createdAt']; + } + + static get AUTOCOMPLETE_FIELD() { + return 'name'; + } + + static get ASSOCIATIONS() { + return [ + { field: 'project', setter: 'setProject', isArray: false }, + ]; + } + + static getFieldMapping(data) { + return { + id: data.id || undefined, + environment: data.environment || null, + source_key: data.source_key || null, + name: data.name || null, + slug: data.slug || null, + video_url: data.video_url || null, + audio_url: data.audio_url || null, + supports_reverse: data.supports_reverse || false, + duration_sec: data.duration_sec || null, + }; + } + + static async findBy(where, options = {}) { + const transaction = options.transaction; + const queryWhere = applyRuntimeEnvironment({ ...where }, options); + const projectInclude = applyRuntimeProjectFilter( + { model: db.projects, as: 'project' }, + options ); - - await transitions.setProject( data.project || null, { - transaction, - }); - + const record = await this.MODEL.findOne({ + where: queryWhere, + transaction, + include: [ + projectInclude, + { association: 'page_links_transition' }, + ], + }); - + if (!record) return null; + return record.get({ plain: true }); + } - + static async findAll(filter = {}, options = {}) { + filter = filter || {}; + const limit = filter.limit || 0; + const currentPage = +filter.page || 0; + const offset = currentPage * limit; - return transitions; - } - - - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - // Prepare data - wrapping individual data transformations in a map() method - const transitionsData = data.map((item, index) => ({ - id: item.id || undefined, - - environment: item.environment - || - null - , - - source_key: item.source_key - || - null - , - - name: item.name - || - null - , - - slug: item.slug - || - null - , - - video_url: item.video_url - || - null - , - - audio_url: item.audio_url - || - null - , - - supports_reverse: item.supports_reverse - || - false - - , - - duration_sec: item.duration_sec - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const transitions = await db.transitions.bulkCreate(transitionsData, { transaction }); - - // For each item created, replace relation files - - - return transitions; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const transitions = await db.transitions.findByPk(id, {transaction}); - - - - - const updatePayload = {}; - - if (data.environment !== undefined) updatePayload.environment = data.environment; - - - if (data.source_key !== undefined) updatePayload.source_key = data.source_key; - - - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.slug !== undefined) updatePayload.slug = data.slug; - - - if (data.video_url !== undefined) updatePayload.video_url = data.video_url; - - - if (data.audio_url !== undefined) updatePayload.audio_url = data.audio_url; - - - if (data.supports_reverse !== undefined) updatePayload.supports_reverse = data.supports_reverse; - - - if (data.duration_sec !== undefined) updatePayload.duration_sec = data.duration_sec; - - - updatePayload.updatedById = currentUser.id; - - await transitions.update(updatePayload, {transaction}); - - - - if (data.project !== undefined) { - await transitions.setProject( - - data.project, - - { transaction } - ); - } - - - - - - - - return transitions; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const transitions = await db.transitions.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of transitions) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of transitions) { - await record.destroy({transaction}); - } - }); - - - return transitions; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const transitions = await db.transitions.findByPk(id, options); - - await transitions.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await transitions.destroy({ - transaction - }); - - return transitions; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - const queryWhere = applyRuntimeEnvironment({ ...where }, options); - const projectInclude = applyRuntimeProjectFilter( - { - model: db.projects, - as: 'project', - }, - options, - ); - - const transitions = await db.transitions.findOne( - { where: queryWhere, include: [projectInclude], transaction }, - ); - - if (!transitions) { - return transitions; - } - - const output = transitions.get({plain: true}); - - - - - - - - - - - - - output.page_links_transition = await transitions.getPage_links_transition({ - transaction - }); - - - - - - - - - output.project = await transitions.getProject({ - transaction - }); - - - - return output; - } - - static async findAll( - filter, - options - ) { - filter = filter || {}; - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - - - - - offset = currentPage * limit; + let where = {}; let include = [ - { model: db.projects, as: 'project', - where: filter.project ? { [Op.or]: [ { id: { [Op.in]: filter.project.split('|').map(term => Utils.uuid(term)) } }, @@ -341,225 +101,82 @@ module.exports = class TransitionsDBApi { }, ] } : {}, - }, - - - ]; - include[0] = applyRuntimeProjectFilter(include[0], options); - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } + include[0] = applyRuntimeProjectFilter(include[0], options); - - if (filter.source_key) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'transitions', - 'source_key', - filter.source_key, - ), - }; - } - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'transitions', - 'name', - filter.name, - ), - }; - } - - if (filter.slug) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'transitions', - 'slug', - filter.slug, - ), - }; - } - - if (filter.video_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'transitions', - 'video_url', - filter.video_url, - ), - }; - } - - if (filter.audio_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'transitions', - 'audio_url', - filter.audio_url, - ), - }; - } - - - - - - - if (filter.duration_secRange) { - const [start, end] = filter.duration_secRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - duration_sec: { - ...where.duration_sec, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - duration_sec: { - ...where.duration_sec, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.environment) { - where = { - ...where, - environment: filter.environment, - }; - } - - if (filter.supports_reverse) { - where = { - ...where, - supports_reverse: filter.supports_reverse, - }; - } - - - - - - - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - where = applyRuntimeEnvironment(where, options); - - - - const queryOptions = { - where, - include, - distinct: true, - order: filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.transitions.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } + if (filter.id) { + where.id = Utils.uuid(filter.id); } - static async findAllAutocomplete(query, limit, offset, ) { - let where = {}; - - - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike( - 'transitions', - 'name', - query, - ), - ], - }; - } - - const records = await db.transitions.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); + for (const field of this.SEARCHABLE_FIELDS) { + if (filter[field]) { + where[Op.and] = Utils.ilike(this.TABLE_NAME, field, filter[field]); + } } - -}; + for (const field of this.RANGE_FIELDS) { + const rangeKey = `${field}Range`; + if (filter[rangeKey]) { + const [start, end] = filter[rangeKey]; + if (start !== undefined && start !== null && start !== '') { + where[field] = { ...where[field], [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where[field] = { ...where[field], [Op.lte]: end }; + } + } + } + + for (const field of this.ENUM_FIELDS) { + if (filter[field] !== undefined) { + where[field] = filter[field]; + } + } + + if (filter.active !== undefined) { + where.active = filter.active === true || filter.active === 'true'; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + if (start !== undefined && start !== null && start !== '') { + where.createdAt = { ...where.createdAt, [Op.gte]: start }; + } + if (end !== undefined && end !== null && end !== '') { + where.createdAt = { ...where.createdAt, [Op.lte]: end }; + } + } + + where = applyRuntimeEnvironment(where, options); + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options.transaction, + }; + + if (!options.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await this.MODEL.findAndCountAll(queryOptions); + return { + rows: options.countOnly ? [] : rows, + count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } +} + +module.exports = TransitionsDBApi; diff --git a/backend/src/db/api/ui_elements.js b/backend/src/db/api/ui_elements.js deleted file mode 100644 index 6fcc437..0000000 --- a/backend/src/db/api/ui_elements.js +++ /dev/null @@ -1,233 +0,0 @@ -const db = require('../models'); -const Utils = require('../utils'); - -const Sequelize = db.Sequelize; -const Op = Sequelize.Op; - -module.exports = class Ui_elementsDBApi { - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const ui_elements = await db.ui_elements.create( - { - id: data.id || undefined, - element_type: data.element_type ?? null, - name: data.name ?? null, - settings_json: data.settings_json ?? null, - sort_order: data.sort_order ?? 0, - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - return ui_elements; - } - - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const uiElementsData = data.map((item, index) => ({ - id: item.id || undefined, - element_type: item.element_type ?? null, - name: item.name ?? null, - settings_json: item.settings_json ?? null, - sort_order: item.sort_order ?? 0, - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - const ui_elements = await db.ui_elements.bulkCreate(uiElementsData, { transaction }); - - return ui_elements; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const ui_elements = await db.ui_elements.findByPk(id, { transaction }); - - const updatePayload = {}; - - if (data.element_type !== undefined) updatePayload.element_type = data.element_type; - if (data.name !== undefined) updatePayload.name = data.name; - if (data.settings_json !== undefined) updatePayload.settings_json = data.settings_json; - if (data.sort_order !== undefined) updatePayload.sort_order = data.sort_order; - - updatePayload.updatedById = currentUser.id; - - await ui_elements.update(updatePayload, { transaction }); - - return ui_elements; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const ui_elements = await db.ui_elements.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (innerTransaction) => { - for (const record of ui_elements) { - await record.update({ deletedBy: currentUser.id }, { transaction: innerTransaction }); - } - for (const record of ui_elements) { - await record.destroy({ transaction: innerTransaction }); - } - }); - - return ui_elements; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const ui_elements = await db.ui_elements.findByPk(id, options); - - await ui_elements.update( - { - deletedBy: currentUser.id, - }, - { - transaction, - }, - ); - - await ui_elements.destroy({ - transaction, - }); - - return ui_elements; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const ui_elements = await db.ui_elements.findOne({ where, transaction }); - - if (!ui_elements) { - return ui_elements; - } - - return ui_elements.get({ plain: true }); - } - - static async findAll(filter, options) { - filter = filter || {}; - const limit = Number(filter.limit) || 0; - const currentPage = Number(filter.page) || 0; - const offset = limit ? currentPage * limit : undefined; - let where = {}; - - if (filter.id) { - where = { - ...where, - id: Utils.uuid(filter.id), - }; - } - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike('ui_elements', 'name', filter.name), - }; - } - - if (filter.element_type) { - where = { - ...where, - element_type: filter.element_type, - }; - } - - if (filter.sort_orderRange) { - const [start, end] = filter.sort_orderRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - sort_order: { - ...where.sort_order, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - sort_order: { - ...where.sort_order, - [Op.lte]: end, - }, - }; - } - } - - let { orderBy = null } = options || {}; - if (!orderBy) { - const sort = filter.sort || 'desc'; - const field = filter.field || 'createdAt'; - orderBy = [[field, sort]]; - } - - const { rows, count } = await db.ui_elements.findAndCountAll({ - where, - limit: limit || undefined, - offset, - order: orderBy, - }); - - return { - rows, - count, - }; - } - - static async findAllAutocomplete(query, limit) { - let where = {}; - - if (query) { - where = { - [Op.or]: [ - { - id: { - [Op.eq]: Utils.uuid(query), - }, - }, - { - name: { - [Op.iLike]: `%${query}%`, - }, - }, - ], - }; - } - - const records = await db.ui_elements.findAll({ - attributes: ['id', 'name'], - where, - limit: Number(limit) || undefined, - order: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); - } -}; diff --git a/backend/src/db/api/users.js b/backend/src/db/api/users.js index 124bd77..03519dc 100644 --- a/backend/src/db/api/users.js +++ b/backend/src/db/api/users.js @@ -391,6 +391,18 @@ module.exports = class UsersDBApi { const users = await db.users.findOne({ where, transaction, + include: [ + { association: 'project_memberships_user' }, + { association: 'presigned_url_requests_user' }, + { association: 'publish_events_user' }, + { association: 'access_logs_user' }, + { association: 'avatar' }, + { + association: 'app_role', + include: [{ association: 'permissions' }], + }, + { association: 'custom_permissions' }, + ], }); if (!users) { @@ -399,61 +411,10 @@ module.exports = class UsersDBApi { const output = users.get({plain: true}); - - - - - - output.project_memberships_user = await users.getProject_memberships_user({ - transaction - }); - - - - - output.presigned_url_requests_user = await users.getPresigned_url_requests_user({ - transaction - }); - - - - - - - - output.publish_events_user = await users.getPublish_events_user({ - transaction - }); - - - - output.access_logs_user = await users.getAccess_logs_user({ - transaction - }); - - - - output.avatar = await users.getAvatar({ - transaction - }); - - - output.app_role = await users.getApp_role({ - transaction - }); - + // Map nested permissions from app_role for backward compatibility if (output.app_role) { - output.app_role_permissions = await output.app_role.getPermissions({ - transaction, - }); + output.app_role_permissions = output.app_role.permissions || []; } - - - output.custom_permissions = await users.getCustom_permissions({ - transaction - }); - - return output; } @@ -744,8 +705,7 @@ module.exports = class UsersDBApi { order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log + transaction: options?.transaction }; if (!options?.countOnly) { @@ -921,7 +881,9 @@ module.exports = class UsersDBApi { const token = crypto .randomBytes(20) .toString('hex'); - const tokenExpiresAt = Date.now() + 360000; + // Token expires in 24 hours (was 6 minutes - too short for email verification flows) + const TOKEN_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours + const tokenExpiresAt = Date.now() + TOKEN_EXPIRY_MS; if(users){ await users.update( diff --git a/backend/src/db/db.config.js b/backend/src/db/db.config.js index 5519deb..2471d98 100644 --- a/backend/src/db/db.config.js +++ b/backend/src/db/db.config.js @@ -8,7 +8,7 @@ module.exports = { database: process.env.DB_NAME, host: process.env.DB_HOST, port: process.env.DB_PORT, - logging: console.log, + logging: false, seederStorage: 'sequelize', }, development: { @@ -20,14 +20,14 @@ module.exports = { logging: console.log, seederStorage: 'sequelize', }, - dev_stage: { - dialect: 'postgres', - username: process.env.DB_USER, - password: process.env.DB_PASS, - database: process.env.DB_NAME, - host: process.env.DB_HOST, - port: process.env.DB_PORT, - logging: console.log, - seederStorage: 'sequelize', - } + dev_stage: { + dialect: 'postgres', + username: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME, + host: process.env.DB_HOST, + port: process.env.DB_PORT, + logging: console.log, + seederStorage: 'sequelize', + } }; diff --git a/backend/src/db/migrations/1773663122940.js b/backend/src/db/migrations/1773663122940.js deleted file mode 100644 index 12e978f..0000000 --- a/backend/src/db/migrations/1773663122940.js +++ /dev/null @@ -1,4341 +0,0 @@ -module.exports = { - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async up(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - - - await queryInterface.createTable('users', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('roles', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('permissions', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('projects', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('project_memberships', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('assets', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('asset_variants', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('presigned_url_requests', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('tour_pages', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('page_elements', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('page_links', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('transitions', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('project_audio_tracks', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('publish_events', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('pwa_caches', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - await queryInterface.createTable('access_logs', { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, { transaction }); - - - - - await queryInterface.addColumn( - 'users', - 'firstName', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'lastName', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'phoneNumber', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'email', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'disabled', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - - - await queryInterface.addColumn( - 'users', - 'password', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'emailVerified', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'emailVerificationToken', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'emailVerificationTokenExpiresAt', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'passwordResetToken', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'passwordResetTokenExpiresAt', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'provider', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'users', - 'app_roleId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'roles', - key: 'id', - }, - - }, - { transaction } - ); - - - - - - - await queryInterface.addColumn( - 'roles', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'roles', - 'role_customization', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - - - await queryInterface.addColumn( - 'permissions', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'slug', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'description', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'phase', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['dev','stage','production'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'logo_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'favicon_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'og_image_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'theme_config_json', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'custom_css_json', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'cdn_base_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'entry_page_slug', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'is_deleted', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'projects', - 'deleted_at_time', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_memberships', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'projects', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_memberships', - 'userId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'users', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_memberships', - 'access_level', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['owner','editor','reviewer','viewer'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_memberships', - 'is_active', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_memberships', - 'invited_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_memberships', - 'accepted_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'projects', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'asset_type', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['image','video','audio','file'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'cdn_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'storage_key', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'mime_type', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'size_mb', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'width_px', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'height_px', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'duration_sec', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'checksum', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'is_public', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'is_deleted', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'assets', - 'deleted_at_time', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'asset_variants', - 'assetId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'assets', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'asset_variants', - 'variant_type', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['thumbnail','preview','webp','mp4_low','mp4_high','original'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'asset_variants', - 'cdn_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'asset_variants', - 'width_px', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'asset_variants', - 'height_px', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'asset_variants', - 'size_mb', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'presigned_url_requests', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'projects', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'presigned_url_requests', - 'userId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'users', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'presigned_url_requests', - 'purpose', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['upload','download'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'presigned_url_requests', - 'asset_type', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['image','video','audio','file'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'presigned_url_requests', - 'requested_key', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'presigned_url_requests', - 'mime_type', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'presigned_url_requests', - 'requested_size_mb', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'presigned_url_requests', - 'expires_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'presigned_url_requests', - 'status', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'projects', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'environment', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['dev','stage','production'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'source_key', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'slug', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'sort_order', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'background_image_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'background_video_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'background_audio_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'background_loop', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'requires_auth', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'tour_pages', - 'ui_schema_json', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'pageId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'tour_pages', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'element_type', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['nav_button','spot','description','tooltip','gallery','carousel','logo','video_player','popup'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'sort_order', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'is_visible', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'x_percent', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'y_percent', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'width_percent', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'height_percent', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'rotation_deg', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'style_json', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_elements', - 'content_json', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_links', - 'from_pageId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'tour_pages', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_links', - 'to_pageId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'tour_pages', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_links', - 'direction', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['forward','back','external'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_links', - 'external_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_links', - 'transitionId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'transitions', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_links', - 'is_active', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'page_links', - 'trigger_selector', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'transitions', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'projects', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'transitions', - 'environment', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['dev','stage','production'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'transitions', - 'source_key', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'transitions', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'transitions', - 'slug', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'transitions', - 'video_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'transitions', - 'audio_url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'transitions', - 'supports_reverse', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'transitions', - 'duration_sec', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_audio_tracks', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'projects', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_audio_tracks', - 'environment', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['dev','stage','production'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_audio_tracks', - 'source_key', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_audio_tracks', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_audio_tracks', - 'slug', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_audio_tracks', - 'url', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_audio_tracks', - 'loop', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_audio_tracks', - 'volume', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_audio_tracks', - 'sort_order', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'project_audio_tracks', - 'is_enabled', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'publish_events', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'projects', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'publish_events', - 'userId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'users', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'publish_events', - 'from_environment', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['dev','stage','production'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'publish_events', - 'to_environment', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['dev','stage','production'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'publish_events', - 'started_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'publish_events', - 'finished_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'publish_events', - 'status', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['queued','running','success','failed'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'publish_events', - 'error_message', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'publish_events', - 'pages_copied', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'publish_events', - 'transitions_copied', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'publish_events', - 'audios_copied', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pwa_caches', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'projects', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pwa_caches', - 'environment', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['stage','production'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pwa_caches', - 'cache_version', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pwa_caches', - 'manifest_json', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pwa_caches', - 'asset_list_json', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pwa_caches', - 'generated_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'pwa_caches', - 'is_active', - { - type: Sequelize.DataTypes.BOOLEAN, - - defaultValue: false, - allowNull: false, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'access_logs', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'projects', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'access_logs', - 'environment', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['admin','stage','production'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'access_logs', - 'userId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'users', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'access_logs', - 'path', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'access_logs', - 'ip_address', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'access_logs', - 'user_agent', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'access_logs', - 'accessed_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - // Hardening pass: constraints + indexes for core runtime tables. - await queryInterface.changeColumn( - 'users', - 'email', - { - type: Sequelize.DataTypes.TEXT, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'users', - 'password', - { - type: Sequelize.DataTypes.TEXT, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'users', - 'provider', - { - type: Sequelize.DataTypes.TEXT, - allowNull: false, - defaultValue: 'local', - }, - { transaction }, - ); - - await queryInterface.changeColumn( - 'projects', - 'name', - { - type: Sequelize.DataTypes.TEXT, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'projects', - 'slug', - { - type: Sequelize.DataTypes.TEXT, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'projects', - 'phase', - { - type: Sequelize.DataTypes.ENUM, - values: ['dev', 'stage', 'production'], - allowNull: false, - defaultValue: 'dev', - }, - { transaction }, - ); - - await queryInterface.changeColumn( - 'project_memberships', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'project_memberships', - 'userId', - { - type: Sequelize.DataTypes.UUID, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'project_memberships', - 'access_level', - { - type: Sequelize.DataTypes.ENUM, - values: ['owner', 'editor', 'reviewer', 'viewer'], - allowNull: false, - defaultValue: 'viewer', - }, - { transaction }, - ); - - await queryInterface.changeColumn( - 'tour_pages', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'tour_pages', - 'environment', - { - type: Sequelize.DataTypes.ENUM, - values: ['dev', 'stage', 'production'], - allowNull: false, - defaultValue: 'dev', - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'tour_pages', - 'name', - { - type: Sequelize.DataTypes.TEXT, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'tour_pages', - 'slug', - { - type: Sequelize.DataTypes.TEXT, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'tour_pages', - 'sort_order', - { - type: Sequelize.DataTypes.INTEGER, - allowNull: false, - defaultValue: 0, - }, - { transaction }, - ); - - await queryInterface.changeColumn( - 'transitions', - 'projectId', - { - type: Sequelize.DataTypes.UUID, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'transitions', - 'environment', - { - type: Sequelize.DataTypes.ENUM, - values: ['dev', 'stage', 'production'], - allowNull: false, - defaultValue: 'dev', - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'transitions', - 'name', - { - type: Sequelize.DataTypes.TEXT, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'transitions', - 'slug', - { - type: Sequelize.DataTypes.TEXT, - allowNull: false, - }, - { transaction }, - ); - - await queryInterface.changeColumn( - 'page_elements', - 'pageId', - { - type: Sequelize.DataTypes.UUID, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'page_elements', - 'element_type', - { - type: Sequelize.DataTypes.ENUM, - values: ['nav_button', 'spot', 'description', 'tooltip', 'gallery', 'carousel', 'logo', 'video_player', 'popup'], - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'page_elements', - 'sort_order', - { - type: Sequelize.DataTypes.INTEGER, - allowNull: false, - defaultValue: 0, - }, - { transaction }, - ); - - await queryInterface.changeColumn( - 'page_links', - 'from_pageId', - { - type: Sequelize.DataTypes.UUID, - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'page_links', - 'direction', - { - type: Sequelize.DataTypes.ENUM, - values: ['forward', 'back', 'external'], - allowNull: false, - defaultValue: 'forward', - }, - { transaction }, - ); - - await queryInterface.changeColumn( - 'access_logs', - 'environment', - { - type: Sequelize.DataTypes.ENUM, - values: ['admin', 'stage', 'production'], - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'access_logs', - 'accessed_at', - { - type: Sequelize.DataTypes.DATE, - allowNull: false, - defaultValue: Sequelize.literal('NOW()'), - }, - { transaction }, - ); - - await queryInterface.changeColumn( - 'publish_events', - 'from_environment', - { - type: Sequelize.DataTypes.ENUM, - values: ['dev', 'stage', 'production'], - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'publish_events', - 'to_environment', - { - type: Sequelize.DataTypes.ENUM, - values: ['dev', 'stage', 'production'], - allowNull: false, - }, - { transaction }, - ); - await queryInterface.changeColumn( - 'publish_events', - 'status', - { - type: Sequelize.DataTypes.ENUM, - values: ['queued', 'running', 'success', 'failed'], - allowNull: false, - defaultValue: 'queued', - }, - { transaction }, - ); - - await queryInterface.addConstraint('users', { - fields: ['email'], - type: 'unique', - name: 'users_email_unique', - transaction, - }); - await queryInterface.addConstraint('projects', { - fields: ['slug'], - type: 'unique', - name: 'projects_slug_unique', - transaction, - }); - await queryInterface.addConstraint('project_memberships', { - fields: ['projectId', 'userId'], - type: 'unique', - name: 'project_memberships_project_user_unique', - transaction, - }); - await queryInterface.addConstraint('tour_pages', { - fields: ['projectId', 'environment', 'slug'], - type: 'unique', - name: 'tour_pages_project_env_slug_unique', - transaction, - }); - await queryInterface.addConstraint('transitions', { - fields: ['projectId', 'environment', 'slug'], - type: 'unique', - name: 'transitions_project_env_slug_unique', - transaction, - }); - - await queryInterface.addIndex('projects', ['phase'], { transaction }); - await queryInterface.addIndex('projects', ['deletedAt'], { transaction }); - await queryInterface.addIndex('project_memberships', ['projectId'], { transaction }); - await queryInterface.addIndex('project_memberships', ['userId'], { transaction }); - await queryInterface.addIndex('project_memberships', ['is_active'], { transaction }); - await queryInterface.addIndex('project_memberships', ['deletedAt'], { transaction }); - await queryInterface.addIndex('tour_pages', ['projectId'], { transaction }); - await queryInterface.addIndex('tour_pages', ['projectId', 'environment', 'sort_order'], { transaction }); - await queryInterface.addIndex('tour_pages', ['deletedAt'], { transaction }); - await queryInterface.addIndex('transitions', ['projectId'], { transaction }); - await queryInterface.addIndex('transitions', ['deletedAt'], { transaction }); - await queryInterface.addIndex('page_elements', ['pageId'], { transaction }); - await queryInterface.addIndex('page_elements', ['pageId', 'sort_order'], { transaction }); - await queryInterface.addIndex('page_elements', ['is_visible'], { transaction }); - await queryInterface.addIndex('page_elements', ['deletedAt'], { transaction }); - await queryInterface.addIndex('page_links', ['from_pageId'], { transaction }); - await queryInterface.addIndex('page_links', ['to_pageId'], { transaction }); - await queryInterface.addIndex('page_links', ['transitionId'], { transaction }); - await queryInterface.addIndex('page_links', ['is_active'], { transaction }); - await queryInterface.addIndex('page_links', ['deletedAt'], { transaction }); - await queryInterface.addIndex('assets', ['projectId'], { transaction }); - await queryInterface.addIndex('assets', ['asset_type'], { transaction }); - await queryInterface.addIndex('assets', ['is_public'], { transaction }); - await queryInterface.addIndex('assets', ['is_deleted'], { transaction }); - await queryInterface.addIndex('assets', ['deletedAt'], { transaction }); - await queryInterface.addIndex('publish_events', ['projectId'], { transaction }); - await queryInterface.addIndex('publish_events', ['userId'], { transaction }); - await queryInterface.addIndex('publish_events', ['status'], { transaction }); - await queryInterface.addIndex('publish_events', ['started_at'], { transaction }); - await queryInterface.addIndex('access_logs', ['projectId'], { transaction }); - await queryInterface.addIndex('access_logs', ['environment'], { transaction }); - await queryInterface.addIndex('access_logs', ['userId'], { transaction }); - await queryInterface.addIndex('access_logs', ['accessed_at'], { transaction }); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async down(queryInterface) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - - - await queryInterface.removeColumn( - 'access_logs', - 'accessed_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'access_logs', - 'user_agent', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'access_logs', - 'ip_address', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'access_logs', - 'path', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'access_logs', - 'userId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'access_logs', - 'environment', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'access_logs', - 'projectId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pwa_caches', - 'is_active', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pwa_caches', - 'generated_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pwa_caches', - 'asset_list_json', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pwa_caches', - 'manifest_json', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pwa_caches', - 'cache_version', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pwa_caches', - 'environment', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'pwa_caches', - 'projectId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'publish_events', - 'audios_copied', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'publish_events', - 'transitions_copied', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'publish_events', - 'pages_copied', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'publish_events', - 'error_message', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'publish_events', - 'status', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'publish_events', - 'finished_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'publish_events', - 'started_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'publish_events', - 'to_environment', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'publish_events', - 'from_environment', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'publish_events', - 'userId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'publish_events', - 'projectId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_audio_tracks', - 'is_enabled', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_audio_tracks', - 'sort_order', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_audio_tracks', - 'volume', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_audio_tracks', - 'loop', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_audio_tracks', - 'url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_audio_tracks', - 'slug', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_audio_tracks', - 'name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_audio_tracks', - 'source_key', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_audio_tracks', - 'environment', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_audio_tracks', - 'projectId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'transitions', - 'duration_sec', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'transitions', - 'supports_reverse', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'transitions', - 'audio_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'transitions', - 'video_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'transitions', - 'slug', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'transitions', - 'name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'transitions', - 'source_key', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'transitions', - 'environment', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'transitions', - 'projectId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_links', - 'trigger_selector', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_links', - 'is_active', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_links', - 'transitionId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_links', - 'external_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_links', - 'direction', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_links', - 'to_pageId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_links', - 'from_pageId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'content_json', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'style_json', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'rotation_deg', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'height_percent', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'width_percent', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'y_percent', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'x_percent', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'is_visible', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'sort_order', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'element_type', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'page_elements', - 'pageId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'ui_schema_json', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'requires_auth', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'background_loop', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'background_audio_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'background_video_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'background_image_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'sort_order', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'slug', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'source_key', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'environment', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'tour_pages', - 'projectId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'presigned_url_requests', - 'status', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'presigned_url_requests', - 'expires_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'presigned_url_requests', - 'requested_size_mb', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'presigned_url_requests', - 'mime_type', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'presigned_url_requests', - 'requested_key', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'presigned_url_requests', - 'asset_type', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'presigned_url_requests', - 'purpose', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'presigned_url_requests', - 'userId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'presigned_url_requests', - 'projectId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'asset_variants', - 'size_mb', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'asset_variants', - 'height_px', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'asset_variants', - 'width_px', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'asset_variants', - 'cdn_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'asset_variants', - 'variant_type', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'asset_variants', - 'assetId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'deleted_at_time', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'is_deleted', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'is_public', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'checksum', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'duration_sec', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'height_px', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'width_px', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'size_mb', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'mime_type', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'storage_key', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'cdn_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'asset_type', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'assets', - 'projectId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_memberships', - 'accepted_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_memberships', - 'invited_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_memberships', - 'is_active', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_memberships', - 'access_level', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_memberships', - 'userId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'project_memberships', - 'projectId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'deleted_at_time', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'is_deleted', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'entry_page_slug', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'cdn_base_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'custom_css_json', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'theme_config_json', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'og_image_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'favicon_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'logo_url', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'phase', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'description', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'slug', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'projects', - 'name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'permissions', - 'name', - { transaction } - ); - - - - - - await queryInterface.removeColumn( - 'roles', - 'role_customization', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'roles', - 'name', - { transaction } - ); - - - - - - await queryInterface.removeColumn( - 'users', - 'app_roleId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'provider', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'passwordResetTokenExpiresAt', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'passwordResetToken', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'emailVerificationTokenExpiresAt', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'emailVerificationToken', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'emailVerified', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'password', - { transaction } - ); - - - - - - await queryInterface.removeColumn( - 'users', - 'disabled', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'email', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'phoneNumber', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'lastName', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'users', - 'firstName', - { transaction } - ); - - - - await queryInterface.dropTable('access_logs', { transaction }); - - - - await queryInterface.dropTable('pwa_caches', { transaction }); - - - - await queryInterface.dropTable('publish_events', { transaction }); - - - - await queryInterface.dropTable('project_audio_tracks', { transaction }); - - - - await queryInterface.dropTable('transitions', { transaction }); - - - - await queryInterface.dropTable('page_links', { transaction }); - - - - await queryInterface.dropTable('page_elements', { transaction }); - - - - await queryInterface.dropTable('tour_pages', { transaction }); - - - - await queryInterface.dropTable('presigned_url_requests', { transaction }); - - - - await queryInterface.dropTable('asset_variants', { transaction }); - - - - await queryInterface.dropTable('assets', { transaction }); - - - - await queryInterface.dropTable('project_memberships', { transaction }); - - - - await queryInterface.dropTable('projects', { transaction }); - - - - await queryInterface.dropTable('permissions', { transaction }); - - - - await queryInterface.dropTable('roles', { transaction }); - - - - await queryInterface.dropTable('users', { transaction }); - - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - } -}; diff --git a/backend/src/db/migrations/20260316000000-create-files.js b/backend/src/db/migrations/20260316000000-create-files.js deleted file mode 100644 index 69e9594..0000000 --- a/backend/src/db/migrations/20260316000000-create-files.js +++ /dev/null @@ -1,124 +0,0 @@ -module.exports = { - async up(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const rows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.files') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - const tableName = rows[0].regclass_name; - - if (tableName) { - await transaction.commit(); - return; - } - - await queryInterface.createTable( - 'files', - { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - belongsTo: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - }, - belongsToId: { - type: Sequelize.DataTypes.UUID, - allowNull: true, - }, - belongsToColumn: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - }, - name: { - type: Sequelize.DataTypes.STRING(2083), - allowNull: false, - }, - sizeInBytes: { - type: Sequelize.DataTypes.INTEGER, - allowNull: true, - }, - privateUrl: { - type: Sequelize.DataTypes.STRING(2083), - allowNull: true, - }, - publicUrl: { - type: Sequelize.DataTypes.STRING(2083), - allowNull: false, - }, - createdAt: { - type: Sequelize.DataTypes.DATE, - allowNull: false, - }, - updatedAt: { - type: Sequelize.DataTypes.DATE, - allowNull: false, - }, - deletedAt: { - type: Sequelize.DataTypes.DATE, - allowNull: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - allowNull: true, - references: { - key: 'id', - model: 'users', - }, - onDelete: 'SET NULL', - onUpdate: 'CASCADE', - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - allowNull: true, - references: { - key: 'id', - model: 'users', - }, - onDelete: 'SET NULL', - onUpdate: 'CASCADE', - }, - }, - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - - async down(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const rows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.files') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - const tableName = rows[0].regclass_name; - - if (!tableName) { - await transaction.commit(); - return; - } - - await queryInterface.dropTable('files', { transaction }); - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; diff --git a/backend/src/db/migrations/20260316001000-create-users-custom-permissions-join-table.js b/backend/src/db/migrations/20260316001000-create-users-custom-permissions-join-table.js deleted file mode 100644 index b7a0a6b..0000000 --- a/backend/src/db/migrations/20260316001000-create-users-custom-permissions-join-table.js +++ /dev/null @@ -1,95 +0,0 @@ -module.exports = { - async up(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const rows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.\"usersCustom_permissionsPermissions\"') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - const tableName = rows[0].regclass_name; - - if (tableName) { - await transaction.commit(); - return; - } - - await queryInterface.createTable( - 'usersCustom_permissionsPermissions', - { - createdAt: { - type: Sequelize.DataTypes.DATE, - allowNull: false, - }, - updatedAt: { - type: Sequelize.DataTypes.DATE, - allowNull: false, - }, - users_custom_permissionsId: { - type: Sequelize.DataTypes.UUID, - allowNull: false, - primaryKey: true, - references: { - model: 'users', - key: 'id', - }, - onDelete: 'CASCADE', - onUpdate: 'CASCADE', - }, - permissionId: { - type: Sequelize.DataTypes.UUID, - allowNull: false, - primaryKey: true, - references: { - model: 'permissions', - key: 'id', - }, - onDelete: 'CASCADE', - onUpdate: 'CASCADE', - }, - }, - { transaction }, - ); - - await queryInterface.addIndex( - 'usersCustom_permissionsPermissions', - ['permissionId'], - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - - async down(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const rows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.\"usersCustom_permissionsPermissions\"') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - const tableName = rows[0].regclass_name; - - if (!tableName) { - await transaction.commit(); - return; - } - - await queryInterface.dropTable('usersCustom_permissionsPermissions', { transaction }); - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; diff --git a/backend/src/db/migrations/20260317090000-make-page-elements-pageid-nullable.js b/backend/src/db/migrations/20260317090000-make-page-elements-pageid-nullable.js deleted file mode 100644 index 07e3793..0000000 --- a/backend/src/db/migrations/20260317090000-make-page-elements-pageid-nullable.js +++ /dev/null @@ -1,123 +0,0 @@ -module.exports = { - async up(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const tableRows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.page_elements') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!tableRows[0]?.regclass_name) { - await transaction.commit(); - return; - } - - const nullableRows = await queryInterface.sequelize.query( - `SELECT is_nullable - FROM information_schema.columns - WHERE table_schema = 'public' - AND table_name = 'page_elements' - AND column_name = 'pageId';`, - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!nullableRows.length || nullableRows[0].is_nullable === 'YES') { - await transaction.commit(); - return; - } - - await queryInterface.changeColumn( - 'page_elements', - 'pageId', - { - type: Sequelize.DataTypes.UUID, - allowNull: true, - references: { - model: 'tour_pages', - key: 'id', - }, - }, - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - - async down(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const tableRows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.page_elements') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!tableRows[0]?.regclass_name) { - await transaction.commit(); - return; - } - - const nullableRows = await queryInterface.sequelize.query( - `SELECT is_nullable - FROM information_schema.columns - WHERE table_schema = 'public' - AND table_name = 'page_elements' - AND column_name = 'pageId';`, - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!nullableRows.length || nullableRows[0].is_nullable === 'NO') { - await transaction.commit(); - return; - } - - const nullCountRows = await queryInterface.sequelize.query( - 'SELECT COUNT(*)::int AS count FROM "page_elements" WHERE "pageId" IS NULL;', - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (Number(nullCountRows[0]?.count || 0) > 0) { - throw new Error('Cannot make page_elements.pageId NOT NULL because NULL values exist.'); - } - - await queryInterface.changeColumn( - 'page_elements', - 'pageId', - { - type: Sequelize.DataTypes.UUID, - allowNull: false, - references: { - model: 'tour_pages', - key: 'id', - }, - }, - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; diff --git a/backend/src/db/migrations/20260317091000-drop-not-null-page-elements-pageid.js b/backend/src/db/migrations/20260317091000-drop-not-null-page-elements-pageid.js deleted file mode 100644 index 367f9bf..0000000 --- a/backend/src/db/migrations/20260317091000-drop-not-null-page-elements-pageid.js +++ /dev/null @@ -1,105 +0,0 @@ -module.exports = { - async up(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const tableRows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.page_elements') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!tableRows[0]?.regclass_name) { - await transaction.commit(); - return; - } - - const nullableRows = await queryInterface.sequelize.query( - `SELECT is_nullable - FROM information_schema.columns - WHERE table_schema = 'public' - AND table_name = 'page_elements' - AND column_name = 'pageId';`, - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!nullableRows.length || nullableRows[0].is_nullable === 'YES') { - await transaction.commit(); - return; - } - - await queryInterface.sequelize.query( - 'ALTER TABLE "page_elements" ALTER COLUMN "pageId" DROP NOT NULL;', - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - - async down(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const tableRows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.page_elements') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!tableRows[0]?.regclass_name) { - await transaction.commit(); - return; - } - - const nullableRows = await queryInterface.sequelize.query( - `SELECT is_nullable - FROM information_schema.columns - WHERE table_schema = 'public' - AND table_name = 'page_elements' - AND column_name = 'pageId';`, - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!nullableRows.length || nullableRows[0].is_nullable === 'NO') { - await transaction.commit(); - return; - } - - const nullCountRows = await queryInterface.sequelize.query( - 'SELECT COUNT(*)::int AS count FROM "page_elements" WHERE "pageId" IS NULL;', - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (Number(nullCountRows[0]?.count || 0) > 0) { - throw new Error('Cannot make page_elements.pageId NOT NULL because NULL values exist.'); - } - - await queryInterface.sequelize.query( - 'ALTER TABLE "page_elements" ALTER COLUMN "pageId" SET NOT NULL;', - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; diff --git a/backend/src/db/migrations/20260317100000-create-ui-elements.js b/backend/src/db/migrations/20260317100000-create-ui-elements.js deleted file mode 100644 index caf2294..0000000 --- a/backend/src/db/migrations/20260317100000-create-ui-elements.js +++ /dev/null @@ -1,108 +0,0 @@ -module.exports = { - async up(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const tableRows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.ui_elements') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (tableRows[0]?.regclass_name) { - await transaction.commit(); - return; - } - - await queryInterface.createTable( - 'ui_elements', - { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - element_type: { - type: Sequelize.DataTypes.TEXT, - allowNull: false, - }, - name: { - type: Sequelize.DataTypes.TEXT, - allowNull: true, - }, - settings_json: { - type: Sequelize.DataTypes.TEXT, - allowNull: true, - }, - sort_order: { - type: Sequelize.DataTypes.INTEGER, - allowNull: false, - defaultValue: 0, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - allowNull: true, - references: { - model: 'users', - key: 'id', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - allowNull: true, - references: { - model: 'users', - key: 'id', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { transaction }, - ); - - await queryInterface.addIndex('ui_elements', ['element_type'], { transaction }); - await queryInterface.addIndex('ui_elements', ['sort_order'], { transaction }); - await queryInterface.addIndex('ui_elements', ['deletedAt'], { transaction }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - }, - - async down(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const tableRows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.ui_elements') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!tableRows[0]?.regclass_name) { - await transaction.commit(); - return; - } - - await queryInterface.dropTable('ui_elements', { transaction }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - }, -}; diff --git a/backend/src/db/migrations/20260317113000-add-title-description-to-publish-events.js b/backend/src/db/migrations/20260317113000-add-title-description-to-publish-events.js deleted file mode 100644 index 39a25f4..0000000 --- a/backend/src/db/migrations/20260317113000-add-title-description-to-publish-events.js +++ /dev/null @@ -1,85 +0,0 @@ -module.exports = { - async up(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const tableRows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.publish_events') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!tableRows[0]?.regclass_name) { - await transaction.commit(); - return; - } - - const tableDefinition = await queryInterface.describeTable('publish_events', { transaction }); - - if (!tableDefinition.title) { - await queryInterface.addColumn( - 'publish_events', - 'title', - { - type: Sequelize.DataTypes.STRING, - allowNull: true, - }, - { transaction }, - ); - } - - if (!tableDefinition.description) { - await queryInterface.addColumn( - 'publish_events', - 'description', - { - type: Sequelize.DataTypes.TEXT, - allowNull: true, - }, - { transaction }, - ); - } - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - }, - - async down(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const tableRows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.publish_events') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!tableRows[0]?.regclass_name) { - await transaction.commit(); - return; - } - - const tableDefinition = await queryInterface.describeTable('publish_events', { transaction }); - - if (tableDefinition.description) { - await queryInterface.removeColumn('publish_events', 'description', { transaction }); - } - - if (tableDefinition.title) { - await queryInterface.removeColumn('publish_events', 'title', { transaction }); - } - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - }, -}; diff --git a/backend/src/db/migrations/20260318102000-add-type-to-assets.js b/backend/src/db/migrations/20260318102000-add-type-to-assets.js deleted file mode 100644 index 717ec2d..0000000 --- a/backend/src/db/migrations/20260318102000-add-type-to-assets.js +++ /dev/null @@ -1,105 +0,0 @@ -module.exports = { - async up(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const tableRows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.assets') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!tableRows[0]?.regclass_name) { - await transaction.commit(); - return; - } - - const tableDefinition = await queryInterface.describeTable('assets', { transaction }); - - if (!tableDefinition.type) { - await queryInterface.addColumn( - 'assets', - 'type', - { - type: Sequelize.DataTypes.ENUM, - values: [ - 'icon', - 'background_image', - 'audio', - 'video', - 'transition', - 'logo', - 'favicon', - 'document', - 'general', - ], - allowNull: false, - defaultValue: 'general', - }, - { transaction }, - ); - - await queryInterface.sequelize.query( - ` - UPDATE assets - SET type = ( - CASE - WHEN name ILIKE '[ICON] %' OR name ILIKE '[IMAGE] %' THEN 'icon' - WHEN name ILIKE '[BACKGROUND] %' THEN 'background_image' - WHEN name ILIKE '[AUDIO] %' THEN 'audio' - WHEN name ILIKE '[VIDEO] %' THEN 'video' - WHEN name ILIKE '[TRANSITION] %' THEN 'transition' - WHEN name ILIKE '[LOGO] %' THEN 'logo' - WHEN asset_type = 'audio' THEN 'audio' - WHEN asset_type = 'video' THEN 'video' - ELSE 'general' - END - )::"enum_assets_type" - WHERE type = 'general'; - `, - { transaction }, - ); - - await queryInterface.addIndex('assets', ['type'], { transaction }); - } - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - }, - - async down(queryInterface, Sequelize) { - const transaction = await queryInterface.sequelize.transaction(); - - try { - const tableRows = await queryInterface.sequelize.query( - "SELECT to_regclass('public.assets') AS regclass_name;", - { - transaction, - type: Sequelize.QueryTypes.SELECT, - }, - ); - - if (!tableRows[0]?.regclass_name) { - await transaction.commit(); - return; - } - - const tableDefinition = await queryInterface.describeTable('assets', { transaction }); - - if (tableDefinition.type) { - await queryInterface.removeIndex('assets', ['type'], { transaction }); - await queryInterface.removeColumn('assets', 'type', { transaction }); - } - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - }, -}; diff --git a/backend/src/db/models/access_logs.js b/backend/src/db/models/access_logs.js index a5939d2..4f18f1e 100644 --- a/backend/src/db/models/access_logs.js +++ b/backend/src/db/models/access_logs.js @@ -28,23 +28,23 @@ environment: { path: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 2048], msg: 'Path must be at most 2048 characters' }, + }, }, ip_address: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 45], msg: 'IP address must be at most 45 characters' }, + }, }, user_agent: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 1024], msg: 'User agent must be at most 1024 characters' }, + }, }, accessed_at: { diff --git a/backend/src/db/models/asset_variants.js b/backend/src/db/models/asset_variants.js index 8ce119a..d593589 100644 --- a/backend/src/db/models/asset_variants.js +++ b/backend/src/db/models/asset_variants.js @@ -38,30 +38,35 @@ variant_type: { cdn_url: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 2048], msg: 'CDN URL must be at most 2048 characters' }, + isUrlOrEmpty(value) { + if (value && value.length > 0 && !/^https?:\/\/.+/.test(value)) { + throw new Error('CDN URL must be a valid URL'); + } + }, + }, }, width_px: { type: DataTypes.INTEGER, - - - + validate: { + min: { args: [0], msg: 'Width must be a non-negative integer' }, + }, }, height_px: { type: DataTypes.INTEGER, - - - + validate: { + min: { args: [0], msg: 'Height must be a non-negative integer' }, + }, }, size_mb: { type: DataTypes.DECIMAL, - - - + validate: { + min: { args: [0], msg: 'Size must be a non-negative number' }, + }, }, importHash: { diff --git a/backend/src/db/models/assets.js b/backend/src/db/models/assets.js index c92f6a4..5617e88 100644 --- a/backend/src/db/models/assets.js +++ b/backend/src/db/models/assets.js @@ -10,9 +10,9 @@ module.exports = function(sequelize, DataTypes) { name: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 255], msg: 'Asset name must be at most 255 characters' }, + }, }, asset_type: { @@ -89,9 +89,9 @@ storage_key: { mime_type: { type: DataTypes.TEXT, - - - + validate: { + is: { args: /^[a-z0-9]+\/[a-z0-9.+-]+$/i, msg: 'Invalid MIME type format' }, + }, }, size_mb: { diff --git a/backend/src/db/models/page_elements.js b/backend/src/db/models/page_elements.js index 02bcc09..ea47c60 100644 --- a/backend/src/db/models/page_elements.js +++ b/backend/src/db/models/page_elements.js @@ -46,9 +46,9 @@ element_type: { name: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 255], msg: 'Element name must be at most 255 characters' }, + }, }, sort_order: { @@ -104,17 +104,11 @@ rotation_deg: { }, style_json: { - type: DataTypes.TEXT, - - - + type: DataTypes.JSON, }, content_json: { - type: DataTypes.TEXT, - - - + type: DataTypes.JSON, }, importHash: { @@ -167,6 +161,7 @@ content_json: { as: 'page', foreignKey: { name: 'pageId', + allowNull: false, }, constraints: false, }); diff --git a/backend/src/db/models/page_links.js b/backend/src/db/models/page_links.js index 36d47ab..7c95eb3 100644 --- a/backend/src/db/models/page_links.js +++ b/backend/src/db/models/page_links.js @@ -29,26 +29,27 @@ direction: { external_url: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 2048], msg: 'External URL must be at most 2048 characters' }, + isUrlOrEmpty(value) { + if (value && value.length > 0 && !/^https?:\/\/.+/.test(value)) { + throw new Error('External URL must be a valid URL'); + } + }, + }, }, is_active: { type: DataTypes.BOOLEAN, - allowNull: false, defaultValue: false, - - - }, trigger_selector: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 1024], msg: 'Trigger selector must be at most 1024 characters' }, + }, }, importHash: { diff --git a/backend/src/db/models/permissions.js b/backend/src/db/models/permissions.js index e546934..f50552a 100644 --- a/backend/src/db/models/permissions.js +++ b/backend/src/db/models/permissions.js @@ -10,9 +10,12 @@ module.exports = function(sequelize, DataTypes) { name: { type: DataTypes.TEXT, - - - + allowNull: false, + unique: true, + validate: { + notEmpty: { msg: 'Permission name is required' }, + len: { args: [1, 100], msg: 'Permission name must be between 1 and 100 characters' }, + }, }, importHash: { diff --git a/backend/src/db/models/presigned_url_requests.js b/backend/src/db/models/presigned_url_requests.js index 8909fde..06d0aa5 100644 --- a/backend/src/db/models/presigned_url_requests.js +++ b/backend/src/db/models/presigned_url_requests.js @@ -48,23 +48,28 @@ asset_type: { requested_key: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 1024], msg: 'Requested key must be at most 1024 characters' }, + }, }, mime_type: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 255], msg: 'MIME type must be at most 255 characters' }, + isMimeTypeOrEmpty(value) { + if (value && value.length > 0 && !/^[\w.-]+\/[\w.+-]+$/.test(value)) { + throw new Error('MIME type must be in format type/subtype'); + } + }, + }, }, requested_size_mb: { type: DataTypes.DECIMAL, - - - + validate: { + min: { args: [0], msg: 'Requested size must be a non-negative number' }, + }, }, expires_at: { diff --git a/backend/src/db/models/project_audio_tracks.js b/backend/src/db/models/project_audio_tracks.js index 6558bbf..ec1348f 100644 --- a/backend/src/db/models/project_audio_tracks.js +++ b/backend/src/db/models/project_audio_tracks.js @@ -36,9 +36,9 @@ source_key: { name: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 255], msg: 'Audio track name must be at most 255 characters' }, + }, }, slug: { @@ -67,9 +67,10 @@ loop: { volume: { type: DataTypes.DECIMAL, - - - + validate: { + min: { args: [0], msg: 'Volume must be at least 0' }, + max: { args: [1], msg: 'Volume must be at most 1' }, + }, }, sort_order: { diff --git a/backend/src/db/models/projects.js b/backend/src/db/models/projects.js index 5e47fbc..9de1e4e 100644 --- a/backend/src/db/models/projects.js +++ b/backend/src/db/models/projects.js @@ -11,14 +11,21 @@ module.exports = function(sequelize, DataTypes) { name: { type: DataTypes.TEXT, allowNull: false, - + validate: { + notEmpty: { msg: 'Project name is required' }, + len: { args: [1, 255], msg: 'Project name must be between 1 and 255 characters' }, + }, }, slug: { type: DataTypes.TEXT, allowNull: false, unique: true, - + validate: { + notEmpty: { msg: 'Slug is required' }, + is: { args: /^[a-z0-9_-]+$/i, msg: 'Slug can only contain letters, numbers, dashes, and underscores' }, + len: { args: [1, 255], msg: 'Slug must be between 1 and 255 characters' }, + }, }, description: { @@ -69,17 +76,11 @@ og_image_url: { }, theme_config_json: { - type: DataTypes.TEXT, - - - + type: DataTypes.JSON, }, custom_css_json: { - type: DataTypes.TEXT, - - - + type: DataTypes.JSON, }, cdn_base_url: { diff --git a/backend/src/db/models/publish_events.js b/backend/src/db/models/publish_events.js index c06689f..2bff2b5 100644 --- a/backend/src/db/models/publish_events.js +++ b/backend/src/db/models/publish_events.js @@ -11,13 +11,17 @@ module.exports = function(sequelize, DataTypes) { title: { type: DataTypes.STRING, allowNull: true, - + validate: { + len: { args: [0, 255], msg: 'Title must be at most 255 characters' }, + }, }, description: { type: DataTypes.TEXT, allowNull: true, - + validate: { + len: { args: [0, 5000], msg: 'Description must be at most 5000 characters' }, + }, }, from_environment: { @@ -101,23 +105,23 @@ error_message: { pages_copied: { type: DataTypes.INTEGER, - - - + validate: { + min: { args: [0], msg: 'Pages copied must be a non-negative integer' }, + }, }, transitions_copied: { type: DataTypes.INTEGER, - - - + validate: { + min: { args: [0], msg: 'Transitions copied must be a non-negative integer' }, + }, }, audios_copied: { type: DataTypes.INTEGER, - - - + validate: { + min: { args: [0], msg: 'Audios copied must be a non-negative integer' }, + }, }, importHash: { diff --git a/backend/src/db/models/pwa_caches.js b/backend/src/db/models/pwa_caches.js index a047f7e..570f0aa 100644 --- a/backend/src/db/models/pwa_caches.js +++ b/backend/src/db/models/pwa_caches.js @@ -15,6 +15,9 @@ environment: { values: [ +"dev", + + "stage", @@ -26,23 +29,17 @@ environment: { cache_version: { type: DataTypes.TEXT, - - - + validate: { + len: { args: [0, 255], msg: 'Cache version must be at most 255 characters' }, + }, }, manifest_json: { - type: DataTypes.TEXT, - - - + type: DataTypes.JSON, }, asset_list_json: { - type: DataTypes.TEXT, - - - + type: DataTypes.JSON, }, generated_at: { diff --git a/backend/src/db/models/roles.js b/backend/src/db/models/roles.js index e5057a4..7958fb0 100644 --- a/backend/src/db/models/roles.js +++ b/backend/src/db/models/roles.js @@ -10,9 +10,11 @@ module.exports = function(sequelize, DataTypes) { name: { type: DataTypes.TEXT, - - - + allowNull: false, + validate: { + notEmpty: { msg: 'Role name is required' }, + len: { args: [1, 100], msg: 'Role name must be between 1 and 100 characters' }, + }, }, role_customization: { diff --git a/backend/src/db/models/tour_pages.js b/backend/src/db/models/tour_pages.js index 04dd74a..060ca95 100644 --- a/backend/src/db/models/tour_pages.js +++ b/backend/src/db/models/tour_pages.js @@ -37,13 +37,20 @@ source_key: { name: { type: DataTypes.TEXT, allowNull: false, - + validate: { + notEmpty: { msg: 'Page name is required' }, + len: { args: [1, 255], msg: 'Page name must be between 1 and 255 characters' }, + }, }, slug: { type: DataTypes.TEXT, allowNull: false, - + validate: { + notEmpty: { msg: 'Slug is required' }, + is: { args: /^[a-z0-9_-]+$/i, msg: 'Slug can only contain letters, numbers, dashes, and underscores' }, + len: { args: [1, 255], msg: 'Slug must be between 1 and 255 characters' }, + }, }, sort_order: { @@ -95,10 +102,7 @@ requires_auth: { }, ui_schema_json: { - type: DataTypes.TEXT, - - - + type: DataTypes.JSON, }, importHash: { diff --git a/backend/src/db/models/transitions.js b/backend/src/db/models/transitions.js index c48aed8..af66c4d 100644 --- a/backend/src/db/models/transitions.js +++ b/backend/src/db/models/transitions.js @@ -37,13 +37,20 @@ source_key: { name: { type: DataTypes.TEXT, allowNull: false, - + validate: { + notEmpty: { msg: 'Transition name is required' }, + len: { args: [1, 255], msg: 'Transition name must be between 1 and 255 characters' }, + }, }, slug: { type: DataTypes.TEXT, allowNull: false, - + validate: { + notEmpty: { msg: 'Slug is required' }, + is: { args: /^[a-z0-9_-]+$/i, msg: 'Slug can only contain letters, numbers, dashes, and underscores' }, + len: { args: [1, 255], msg: 'Slug must be between 1 and 255 characters' }, + }, }, video_url: { diff --git a/backend/src/db/models/ui_elements.js b/backend/src/db/models/ui_elements.js deleted file mode 100644 index d0b9573..0000000 --- a/backend/src/db/models/ui_elements.js +++ /dev/null @@ -1,50 +0,0 @@ -module.exports = function (sequelize, DataTypes) { - const ui_elements = sequelize.define( - 'ui_elements', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - element_type: { - type: DataTypes.TEXT, - allowNull: false, - }, - name: { - type: DataTypes.TEXT, - }, - settings_json: { - type: DataTypes.TEXT, - }, - sort_order: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 0, - }, - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - indexes: [{ fields: ['element_type'] }, { fields: ['sort_order'] }, { fields: ['deletedAt'] }], - }, - ); - - ui_elements.associate = (db) => { - db.ui_elements.belongsTo(db.users, { - as: 'createdBy', - }); - - db.ui_elements.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - return ui_elements; -}; diff --git a/backend/src/db/models/users.js b/backend/src/db/models/users.js index f51bce1..f8f8390 100644 --- a/backend/src/db/models/users.js +++ b/backend/src/db/models/users.js @@ -38,7 +38,10 @@ email: { type: DataTypes.TEXT, allowNull: false, unique: true, - + validate: { + isEmail: { msg: 'Must be a valid email address' }, + notEmpty: { msg: 'Email is required' }, + }, }, disabled: { diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js index 88d7497..4241c83 100644 --- a/backend/src/db/seeders/20200430130760-user-roles.js +++ b/backend/src/db/seeders/20200430130760-user-roles.js @@ -68,7 +68,7 @@ await queryInterface.bulkInsert("permissions", [{ id: getId(`READ_API_DOCS`), cr await queryInterface.bulkInsert("permissions", [{ id: getId(`CREATE_SEARCH`), createdAt, updatedAt, name: `CREATE_SEARCH`}]); -await queryInterface.sequelize.query(`create table "rolesPermissionsPermissions" +await queryInterface.sequelize.query(`CREATE TABLE IF NOT EXISTS "rolesPermissionsPermissions" ( "createdAt" timestamp with time zone not null, "updatedAt" timestamp with time zone not null, @@ -84,7 +84,7 @@ constraint "rolesPermissionsPermissions_permission_fk" );`); await queryInterface.sequelize.query( - 'create index "rolesPermissionsPermissions_permission_idx" on "rolesPermissionsPermissions" ("permissionId");', + 'CREATE INDEX IF NOT EXISTS "rolesPermissionsPermissions_permission_idx" ON "rolesPermissionsPermissions" ("permissionId");', ); diff --git a/backend/src/db/sync.js b/backend/src/db/sync.js new file mode 100644 index 0000000..c31db9a --- /dev/null +++ b/backend/src/db/sync.js @@ -0,0 +1,15 @@ +const db = require('./models'); + +async function syncDatabase() { + try { + console.log('Syncing database...'); + await db.sequelize.sync({ force: true }); + console.log('Database synced successfully!'); + process.exit(0); + } catch (error) { + console.error('Error syncing database:', error); + process.exit(1); + } +} + +syncDatabase(); diff --git a/backend/src/factories/router.factory.js b/backend/src/factories/router.factory.js new file mode 100644 index 0000000..699e399 --- /dev/null +++ b/backend/src/factories/router.factory.js @@ -0,0 +1,101 @@ +const express = require('express'); +const { wrapAsync, commonErrorHandler } = require('../helpers'); +const { checkCrudPermissions } = require('../middlewares/check-permissions'); +const { parse } = require('json2csv'); + +const isUuidV4 = (value) => + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value); + +function createEntityRouter(entityName, Service, DBApi, options = {}) { + const router = express.Router(); + + const permissionEntity = options.permissionEntity || entityName; + router.use(checkCrudPermissions(permissionEntity)); + + router.post('/', wrapAsync(async (req, res) => { + const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + const payload = await Service.create(req.body.data, req.currentUser, true, link.host); + res.status(200).send(payload); + })); + + router.post('/bulk-import', wrapAsync(async (req, res) => { + const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + await Service.bulkImport(req, res, true, link.host); + res.status(200).send(true); + })); + + router.put('/:id', wrapAsync(async (req, res) => { + await Service.update(req.body.data, req.body.id, req.currentUser); + res.status(200).send(true); + })); + + router.delete('/:id', wrapAsync(async (req, res) => { + await Service.remove(req.params.id, req.currentUser); + res.status(200).send(true); + })); + + router.post('/deleteByIds', wrapAsync(async (req, res) => { + await Service.deleteByIds(req.body.data, req.currentUser); + res.status(200).send(true); + })); + + router.get('/', wrapAsync(async (req, res) => { + const filetype = req.query.filetype; + const currentUser = req.currentUser; + const runtimeContext = req.runtimeContext; + + const payload = await DBApi.findAll(req.query, { currentUser, runtimeContext }); + + if (filetype === 'csv') { + const fields = options.csvFields || DBApi.CSV_FIELDS || ['id', 'createdAt']; + const opts = { fields }; + try { + const csv = parse(payload.rows, opts); + res.status(200).attachment('export.csv').send(csv); + } catch (err) { + console.error(err); + res.status(500).send('CSV export error'); + } + } else { + res.status(200).send(payload); + } + })); + + router.get('/count', wrapAsync(async (req, res) => { + const currentUser = req.currentUser; + const runtimeContext = req.runtimeContext; + const payload = await DBApi.findAll(req.query, { countOnly: true, currentUser, runtimeContext }); + res.status(200).send(payload); + })); + + router.get('/autocomplete', async (req, res) => { + const payload = await DBApi.findAllAutocomplete( + req.query.query, + req.query.limit, + req.query.offset + ); + res.status(200).send(payload); + }); + + router.get('/:id', wrapAsync(async (req, res) => { + if (!isUuidV4(req.params.id)) { + return res.status(400).send(`Invalid ${entityName} id`); + } + + const runtimeContext = req.runtimeContext; + const payload = await DBApi.findBy({ id: req.params.id }, { runtimeContext }); + res.status(200).send(payload); + })); + + if (options.customRoutes) { + options.customRoutes(router, Service, DBApi); + } + + router.use('/', commonErrorHandler); + + return router; +} + +module.exports = { createEntityRouter, isUuidV4 }; diff --git a/backend/src/factories/service.factory.js b/backend/src/factories/service.factory.js new file mode 100644 index 0000000..fb4f100 --- /dev/null +++ b/backend/src/factories/service.factory.js @@ -0,0 +1,97 @@ +const db = require('../db/models'); +const processFile = require('../middlewares/upload'); +const ValidationError = require('../services/notifications/errors/validation'); +const csv = require('csv-parser'); +const stream = require('stream'); + +function createEntityService(DBApi, options = {}) { + const entityName = options.entityName || 'Entity'; + + return class GenericService { + static async create(data, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + const record = await DBApi.create(data, { currentUser, transaction }); + await transaction.commit(); + return record; + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async bulkImport(req, res) { + const transaction = await db.sequelize.transaction(); + + try { + await processFile(req, res); + const bufferStream = new stream.PassThrough(); + const results = []; + + await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); + + await new Promise((resolve, reject) => { + bufferStream + .pipe(csv()) + .on('data', (data) => results.push(data)) + .on('end', () => resolve()) + .on('error', (error) => reject(error)); + }); + + await DBApi.bulkImport(results, { + transaction, + ignoreDuplicates: true, + validate: true, + currentUser: req.currentUser, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async update(data, id, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + const record = await DBApi.findBy({ id }, { transaction }); + + if (!record) { + throw new ValidationError(`${entityName}NotFound`); + } + + const updated = await DBApi.update(id, data, { currentUser, transaction }); + await transaction.commit(); + return updated; + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async deleteByIds(ids, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + await DBApi.deleteByIds(ids, { currentUser, transaction }); + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async remove(id, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + await DBApi.remove(id, { currentUser, transaction }); + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + }; +} + +module.exports = { createEntityService }; diff --git a/backend/src/index.js b/backend/src/index.js index d744d6b..3f2acfa 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -9,6 +9,7 @@ const bodyParser = require('body-parser'); const config = require('./config'); const swaggerUI = require('swagger-ui-express'); const swaggerJsDoc = require('swagger-jsdoc'); +const { logger, requestLogger } = require('./utils/logger'); const authRoutes = require('./routes/auth'); const fileRoutes = require('./routes/file'); @@ -39,7 +40,6 @@ const presigned_url_requestsRoutes = require('./routes/presigned_url_requests'); const tour_pagesRoutes = require('./routes/tour_pages'); const page_elementsRoutes = require('./routes/page_elements'); -const ui_elementsRoutes = require('./routes/ui_elements'); const page_linksRoutes = require('./routes/page_links'); @@ -120,6 +120,7 @@ require('./auth/auth'); app.use(bodyParser.json({ limit: '1mb' })); app.use(bodyParser.urlencoded({ extended: true, limit: '1mb' })); +app.use(requestLogger); app.use(runtimeContextMiddleware); const jwtAuth = passport.authenticate('jwt', { session: false }); @@ -137,6 +138,29 @@ const requireRuntimeReadOrAuth = (req, res, next) => { return jwtAuth(req, res, next); }; +// Health check endpoint (no auth required) +app.get('/api/health', async (req, res) => { + const db = require('./db/models'); + const health = { + status: 'ok', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + environment: process.env.NODE_ENV || 'development', + }; + + try { + await db.sequelize.authenticate(); + health.database = 'connected'; + } catch (error) { + health.status = 'degraded'; + health.database = 'disconnected'; + health.databaseError = error.message; + } + + const statusCode = health.status === 'ok' ? 200 : 503; + res.status(statusCode).json(health); +}); + app.use('/api/auth', authRoutes); app.use('/api/file', fileRoutes); app.use('/api/pexels', pexelsRoutes); @@ -172,7 +196,6 @@ app.use('/api/presigned_url_requests', jwtAuth, presigned_url_requestsRoutes); mountRuntimeEntityRoute('/api/tour_pages', 'tour_pages', tour_pagesRoutes); mountRuntimeEntityRoute('/api/page_elements', 'page_elements', page_elementsRoutes); -app.use('/api/ui-elements', jwtAuth, ui_elementsRoutes); mountRuntimeEntityRoute('/api/page_links', 'page_links', page_linksRoutes); @@ -227,7 +250,7 @@ if (fs.existsSync(publicDir)) { const PORT = process.env.NODE_ENV === 'dev_stage' ? 3000 : 8080; app.listen(PORT, () => { - console.log(`Listening on port ${PORT}`); + logger.info({ port: PORT, env: process.env.NODE_ENV || 'development' }, 'Server started'); }); module.exports = app; diff --git a/backend/src/middlewares/validate.js b/backend/src/middlewares/validate.js new file mode 100644 index 0000000..026d88f --- /dev/null +++ b/backend/src/middlewares/validate.js @@ -0,0 +1,103 @@ +const { body, param, query, validationResult } = require('express-validator'); + +const handleValidationErrors = (req, res, next) => { + const errors = validationResult(req); + if (!errors.isEmpty()) { + return res.status(400).json({ + error: 'Validation failed', + details: errors.array().map(err => ({ + field: err.path, + message: err.msg, + value: err.value, + })), + }); + } + next(); +}; + +const validators = { + uuid: (field, location = 'param') => { + const validator = location === 'param' ? param(field) : body(field); + return validator.isUUID().withMessage(`${field} must be a valid UUID`); + }, + + requiredString: (field, min = 1, max = 255) => + body(field) + .trim() + .notEmpty().withMessage(`${field} is required`) + .isLength({ min, max }).withMessage(`${field} must be ${min}-${max} characters`), + + optionalString: (field, max = 255) => + body(field) + .optional() + .trim() + .isLength({ max }).withMessage(`${field} must be at most ${max} characters`), + + slug: (field) => + body(field) + .optional() + .trim() + .matches(/^[a-z0-9_-]+$/i).withMessage(`${field} can only contain letters, numbers, dashes, underscores`), + + email: (field) => + body(field) + .trim() + .isEmail().withMessage('Must be a valid email') + .normalizeEmail(), + + optionalEmail: (field) => + body(field) + .optional() + .trim() + .isEmail().withMessage('Must be a valid email') + .normalizeEmail(), + + enum: (field, values) => + body(field) + .optional() + .isIn(values).withMessage(`${field} must be one of: ${values.join(', ')}`), + + boolean: (field) => + body(field) + .optional() + .isBoolean().withMessage(`${field} must be a boolean`), + + integer: (field, min, max) => { + let validator = body(field).optional().isInt(); + if (min !== undefined) validator = validator.custom(val => val >= min).withMessage(`${field} must be at least ${min}`); + if (max !== undefined) validator = validator.custom(val => val <= max).withMessage(`${field} must be at most ${max}`); + return validator; + }, + + pagination: () => [ + query('page').optional().isInt({ min: 0 }).toInt(), + query('limit').optional().isInt({ min: 1, max: 100 }).toInt(), + ], + + url: (field) => + body(field) + .optional() + .trim() + .isURL().withMessage(`${field} must be a valid URL`), +}; + +function createEntityValidation(entityConfig = {}) { + const { fields = [], requiredFields = [] } = entityConfig; + + const fieldValidators = fields.map(field => { + if (requiredFields.includes(field.name)) { + return validators.requiredString(`data.${field.name}`, field.min || 1, field.max || 255); + } + return validators.optionalString(`data.${field.name}`, field.max || 255); + }); + + return { + create: [...fieldValidators, handleValidationErrors], + update: [validators.uuid('id'), ...fieldValidators, handleValidationErrors], + delete: [validators.uuid('id'), handleValidationErrors], + get: [validators.uuid('id'), handleValidationErrors], + list: [...validators.pagination(), handleValidationErrors], + }; +} + +module.exports = { handleValidationErrors, validators, createEntityValidation }; diff --git a/backend/src/routes/access_logs.js b/backend/src/routes/access_logs.js index fa9ac70..3798435 100644 --- a/backend/src/routes/access_logs.js +++ b/backend/src/routes/access_logs.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const Access_logsService = require('../services/access_logs'); const Access_logsDBApi = require('../db/api/access_logs'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('access_logs')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,20 +9,12 @@ router.use(checkCrudPermissions('access_logs')); * Access_logs: * type: object * properties: - * path: * type: string - * default: path * ip_address: * type: string - * default: ip_address * user_agent: * type: string - * default: user_agent - - - - * */ /** @@ -48,389 +24,118 @@ router.use(checkCrudPermissions('access_logs')); * description: The Access_logs managing API */ -/** -* @swagger -* /api/access_logs: -* post: -* security: -* - bearerAuth: [] -* tags: [Access_logs] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Access_logs" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Access_logs" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Access_logsService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Access_logs] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Access_logs" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Access_logs" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Access_logsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/access_logs/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Access_logs] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Access_logs" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Access_logs" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await Access_logsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/access_logs/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Access_logs] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Access_logs" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await Access_logsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/access_logs/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Access_logs] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Access_logs" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Access_logsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/access_logs: - * get: - * security: - * - bearerAuth: [] - * tags: [Access_logs] - * summary: Get all access_logs - * description: Get all access_logs - * responses: - * 200: - * description: Access_logs list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Access_logs" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const payload = await Access_logsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','path','ip_address','user_agent', - - - 'accessed_at', - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/access_logs/count: - * get: + * /api/access_logs: + * post: * security: * - bearerAuth: [] * tags: [Access_logs] - * summary: Count all access_logs - * description: Count all access_logs + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Access_logs" * responses: * 200: - * description: Access_logs count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Access_logs" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const payload = await Access_logsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/access_logs/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Access_logs] - * summary: Find all access_logs that match search criteria - * description: Find all access_logs that match search criteria + * summary: Get all access_logs * responses: * 200: * description: Access_logs list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Access_logs" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Access_logsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/access_logs/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Access_logs] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Access_logs" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const payload = await Access_logsDBApi.findBy( - { id: req.params.id }, - ); - - + * @swagger + * /api/access_logs/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Access_logs] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Access_logs" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Access_logs] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Access_logs] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('access_logs', Access_logsService, Access_logsDBApi); diff --git a/backend/src/routes/asset_variants.js b/backend/src/routes/asset_variants.js index dde6505..161c05c 100644 --- a/backend/src/routes/asset_variants.js +++ b/backend/src/routes/asset_variants.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const Asset_variantsService = require('../services/asset_variants'); const Asset_variantsDBApi = require('../db/api/asset_variants'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('asset_variants')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,23 +9,14 @@ router.use(checkCrudPermissions('asset_variants')); * Asset_variants: * type: object * properties: - * cdn_url: * type: string - * default: cdn_url - * width_px: * type: integer - * format: int64 * height_px: * type: integer - * format: int64 - * size_mb: - * type: integer - * format: int64 - - * + * type: number */ /** @@ -51,389 +26,118 @@ router.use(checkCrudPermissions('asset_variants')); * description: The Asset_variants managing API */ -/** -* @swagger -* /api/asset_variants: -* post: -* security: -* - bearerAuth: [] -* tags: [Asset_variants] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Asset_variants" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Asset_variants" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Asset_variantsService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Asset_variants] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Asset_variants" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Asset_variants" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Asset_variantsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/asset_variants/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Asset_variants] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Asset_variants" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Asset_variants" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await Asset_variantsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/asset_variants/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Asset_variants] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Asset_variants" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await Asset_variantsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/asset_variants/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Asset_variants] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Asset_variants" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Asset_variantsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/asset_variants: - * get: - * security: - * - bearerAuth: [] - * tags: [Asset_variants] - * summary: Get all asset_variants - * description: Get all asset_variants - * responses: - * 200: - * description: Asset_variants list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Asset_variants" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const payload = await Asset_variantsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','cdn_url', - 'width_px','height_px', - 'size_mb', - - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/asset_variants/count: - * get: + * /api/asset_variants: + * post: * security: * - bearerAuth: [] * tags: [Asset_variants] - * summary: Count all asset_variants - * description: Count all asset_variants + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Asset_variants" * responses: * 200: - * description: Asset_variants count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Asset_variants" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const payload = await Asset_variantsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/asset_variants/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Asset_variants] - * summary: Find all asset_variants that match search criteria - * description: Find all asset_variants that match search criteria + * summary: Get all asset variants * responses: * 200: * description: Asset_variants list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Asset_variants" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Asset_variantsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/asset_variants/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Asset_variants] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Asset_variants" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const payload = await Asset_variantsDBApi.findBy( - { id: req.params.id }, - ); - - + * @swagger + * /api/asset_variants/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Asset_variants] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Asset_variants" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Asset_variants] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Asset_variants] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('asset_variants', Asset_variantsService, Asset_variantsDBApi); diff --git a/backend/src/routes/assets.js b/backend/src/routes/assets.js index 8f783bf..e25e2af 100644 --- a/backend/src/routes/assets.js +++ b/backend/src/routes/assets.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const AssetsService = require('../services/assets'); const AssetsDBApi = require('../db/api/assets'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('assets')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,38 +9,24 @@ router.use(checkCrudPermissions('assets')); * Assets: * type: object * properties: - * name: * type: string - * default: name * cdn_url: * type: string - * default: cdn_url * storage_key: * type: string - * default: storage_key * mime_type: * type: string - * default: mime_type * checksum: * type: string - * default: checksum - * width_px: * type: integer - * format: int64 * height_px: * type: integer - * format: int64 - * size_mb: - * type: integer - * format: int64 + * type: number * duration_sec: - * type: integer - * format: int64 - - * + * type: number */ /** @@ -66,389 +36,118 @@ router.use(checkCrudPermissions('assets')); * description: The Assets managing API */ -/** -* @swagger -* /api/assets: -* post: -* security: -* - bearerAuth: [] -* tags: [Assets] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Assets" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Assets" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await AssetsService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Assets] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Assets" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Assets" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await AssetsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/assets/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Assets] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Assets" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Assets" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await AssetsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/assets/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Assets] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Assets" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await AssetsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/assets/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Assets] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Assets" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await AssetsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/assets: - * get: - * security: - * - bearerAuth: [] - * tags: [Assets] - * summary: Get all assets - * description: Get all assets - * responses: - * 200: - * description: Assets list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Assets" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const payload = await AssetsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','name','cdn_url','storage_key','mime_type','checksum', - 'width_px','height_px', - 'size_mb','duration_sec', - 'deleted_at_time', - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/assets/count: - * get: + * /api/assets: + * post: * security: * - bearerAuth: [] * tags: [Assets] - * summary: Count all assets - * description: Count all assets + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Assets" * responses: * 200: - * description: Assets count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Assets" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const payload = await AssetsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/assets/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Assets] - * summary: Find all assets that match search criteria - * description: Find all assets that match search criteria + * summary: Get all assets * responses: * 200: * description: Assets list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Assets" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await AssetsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/assets/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Assets] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Assets" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const payload = await AssetsDBApi.findBy( - { id: req.params.id }, - ); - - + * @swagger + * /api/assets/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Assets] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Assets" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Assets] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Assets] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('assets', AssetsService, AssetsDBApi); diff --git a/backend/src/routes/page_elements.js b/backend/src/routes/page_elements.js index 2a94c82..eedf2cf 100644 --- a/backend/src/routes/page_elements.js +++ b/backend/src/routes/page_elements.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const Page_elementsService = require('../services/page_elements'); const Page_elementsDBApi = require('../db/api/page_elements'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('page_elements')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,38 +9,24 @@ router.use(checkCrudPermissions('page_elements')); * Page_elements: * type: object * properties: - * name: * type: string - * default: name * style_json: * type: string - * default: style_json * content_json: * type: string - * default: content_json - * sort_order: * type: integer - * format: int64 - * x_percent: - * type: integer - * format: int64 + * type: number * y_percent: - * type: integer - * format: int64 + * type: number * width_percent: - * type: integer - * format: int64 + * type: number * height_percent: - * type: integer - * format: int64 + * type: number * rotation_deg: - * type: integer - * format: int64 - - * + * type: number */ /** @@ -66,393 +36,118 @@ router.use(checkCrudPermissions('page_elements')); * description: The Page_elements managing API */ -/** -* @swagger -* /api/page_elements: -* post: -* security: -* - bearerAuth: [] -* tags: [Page_elements] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Page_elements" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Page_elements" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Page_elementsService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Page_elements] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Page_elements" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Page_elements" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Page_elementsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/page_elements/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Page_elements] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Page_elements" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Page_elements" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await Page_elementsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/page_elements/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Page_elements] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Page_elements" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await Page_elementsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/page_elements/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Page_elements] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Page_elements" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Page_elementsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/page_elements: - * get: - * security: - * - bearerAuth: [] - * tags: [Page_elements] - * summary: Get all page_elements - * description: Get all page_elements - * responses: - * 200: - * description: Page_elements list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Page_elements" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const runtimeContext = req.runtimeContext; - const payload = await Page_elementsDBApi.findAll( - req.query, { currentUser, runtimeContext } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','name','style_json','content_json', - 'sort_order', - 'x_percent','y_percent','width_percent','height_percent','rotation_deg', - - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/page_elements/count: - * get: + * /api/page_elements: + * post: * security: * - bearerAuth: [] * tags: [Page_elements] - * summary: Count all page_elements - * description: Count all page_elements + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Page_elements" * responses: * 200: - * description: Page_elements count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Page_elements" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const runtimeContext = req.runtimeContext; - const payload = await Page_elementsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser, runtimeContext } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/page_elements/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Page_elements] - * summary: Find all page_elements that match search criteria - * description: Find all page_elements that match search criteria + * summary: Get all page_elements * responses: * 200: * description: Page_elements list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Page_elements" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Page_elementsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/page_elements/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Page_elements] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Page_elements" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const runtimeContext = req.runtimeContext; - const payload = await Page_elementsDBApi.findBy( - { id: req.params.id }, - { runtimeContext }, - ); - - + * @swagger + * /api/page_elements/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Page_elements] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Page_elements" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Page_elements] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Page_elements] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('page_elements', Page_elementsService, Page_elementsDBApi); diff --git a/backend/src/routes/page_links.js b/backend/src/routes/page_links.js index 6304139..14e7245 100644 --- a/backend/src/routes/page_links.js +++ b/backend/src/routes/page_links.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const Page_linksService = require('../services/page_links'); const Page_linksDBApi = require('../db/api/page_links'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('page_links')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,17 +9,10 @@ router.use(checkCrudPermissions('page_links')); * Page_links: * type: object * properties: - * external_url: * type: string - * default: external_url * trigger_selector: * type: string - * default: trigger_selector - - - - * */ /** @@ -45,393 +22,118 @@ router.use(checkCrudPermissions('page_links')); * description: The Page_links managing API */ -/** -* @swagger -* /api/page_links: -* post: -* security: -* - bearerAuth: [] -* tags: [Page_links] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Page_links" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Page_links" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Page_linksService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Page_links] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Page_links" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Page_links" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Page_linksService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/page_links/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Page_links] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Page_links" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Page_links" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await Page_linksService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/page_links/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Page_links] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Page_links" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await Page_linksService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/page_links/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Page_links] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Page_links" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Page_linksService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/page_links: - * get: - * security: - * - bearerAuth: [] - * tags: [Page_links] - * summary: Get all page_links - * description: Get all page_links - * responses: - * 200: - * description: Page_links list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Page_links" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const runtimeContext = req.runtimeContext; - const payload = await Page_linksDBApi.findAll( - req.query, { currentUser, runtimeContext } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','external_url','trigger_selector', - - - - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/page_links/count: - * get: + * /api/page_links: + * post: * security: * - bearerAuth: [] * tags: [Page_links] - * summary: Count all page_links - * description: Count all page_links + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Page_links" * responses: * 200: - * description: Page_links count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Page_links" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const runtimeContext = req.runtimeContext; - const payload = await Page_linksDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser, runtimeContext } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/page_links/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Page_links] - * summary: Find all page_links that match search criteria - * description: Find all page_links that match search criteria + * summary: Get all page_links * responses: * 200: * description: Page_links list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Page_links" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Page_linksDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/page_links/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Page_links] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Page_links" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const runtimeContext = req.runtimeContext; - const payload = await Page_linksDBApi.findBy( - { id: req.params.id }, - { runtimeContext }, - ); - - + * @swagger + * /api/page_links/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Page_links] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Page_links" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Page_links] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Page_links] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('page_links', Page_linksService, Page_linksDBApi); diff --git a/backend/src/routes/permissions.js b/backend/src/routes/permissions.js index b569a78..98edd5a 100644 --- a/backend/src/routes/permissions.js +++ b/backend/src/routes/permissions.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const PermissionsService = require('../services/permissions'); const PermissionsDBApi = require('../db/api/permissions'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('permissions')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,13 +9,9 @@ router.use(checkCrudPermissions('permissions')); * Permissions: * type: object * properties: - * name: * type: string * default: name - - - */ /** @@ -41,319 +21,44 @@ router.use(checkCrudPermissions('permissions')); * description: The Permissions managing API */ -/** -* @swagger -* /api/permissions: -* post: -* security: -* - bearerAuth: [] -* tags: [Permissions] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Permissions" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Permissions" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await PermissionsService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Permissions" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Permissions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await PermissionsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/permissions/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Permissions" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Permissions" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await PermissionsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/permissions/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Permissions" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await PermissionsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/permissions/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Permissions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await PermissionsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/permissions: - * get: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Get all permissions - * description: Get all permissions - * responses: - * 200: - * description: Permissions list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Permissions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const payload = await PermissionsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','name', - - - - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/permissions/count: - * get: + * /api/permissions: + * post: * security: * - bearerAuth: [] * tags: [Permissions] - * summary: Count all permissions - * description: Count all permissions + * summary: Add new item + * description: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Permissions" * responses: * 200: - * description: Permissions count successfully received + * description: The item was successfully added * content: * application/json: * schema: - * type: array - * items: - * $ref: "#/components/schemas/Permissions" + * $ref: "#/components/schemas/Permissions" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found + * 405: + * description: Invalid input data * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const payload = await PermissionsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/permissions/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Permissions] - * summary: Find all permissions that match search criteria - * description: Find all permissions that match search criteria + * summary: Get all permissions + * description: Get all permissions * responses: * 200: * description: Permissions list successfully received @@ -370,60 +75,110 @@ router.get('/count', wrapAsync(async (req, res) => { * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await PermissionsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/permissions/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Permissions" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const payload = await PermissionsDBApi.findBy( - { id: req.params.id }, - ); - - + * @swagger + * /api/permissions/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Permissions] + * summary: Update the data of the selected item + * description: Update the data of the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to update + * required: true + * schema: + * type: string + * requestBody: + * description: Set new item data + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * description: ID of the updated item + * type: string + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Permissions" + * required: + * - id + * responses: + * 200: + * description: The item data was successfully updated + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Permissions" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Permissions] + * summary: Delete the selected item + * description: Delete the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to delete + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Permissions" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Permissions] + * summary: Get selected item + * description: Get selected item + * parameters: + * - in: path + * name: id + * description: ID of item to get + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Permissions" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('permissions', PermissionsService, PermissionsDBApi); diff --git a/backend/src/routes/presigned_url_requests.js b/backend/src/routes/presigned_url_requests.js index 95f9464..2752a7f 100644 --- a/backend/src/routes/presigned_url_requests.js +++ b/backend/src/routes/presigned_url_requests.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const Presigned_url_requestsService = require('../services/presigned_url_requests'); const Presigned_url_requestsDBApi = require('../db/api/presigned_url_requests'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('presigned_url_requests')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,24 +9,17 @@ router.use(checkCrudPermissions('presigned_url_requests')); * Presigned_url_requests: * type: object * properties: - * requested_key: * type: string - * default: requested_key * mime_type: * type: string - * default: mime_type * status: * type: string - * default: status - - * requested_size_mb: - * type: integer - * format: int64 - - * - * + * type: number + * expires_at: + * type: string + * format: date-time */ /** @@ -52,389 +29,118 @@ router.use(checkCrudPermissions('presigned_url_requests')); * description: The Presigned_url_requests managing API */ -/** -* @swagger -* /api/presigned_url_requests: -* post: -* security: -* - bearerAuth: [] -* tags: [Presigned_url_requests] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Presigned_url_requests" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Presigned_url_requests" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Presigned_url_requestsService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Presigned_url_requests] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Presigned_url_requests" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Presigned_url_requests" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Presigned_url_requestsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/presigned_url_requests/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Presigned_url_requests] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Presigned_url_requests" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Presigned_url_requests" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await Presigned_url_requestsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/presigned_url_requests/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Presigned_url_requests] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Presigned_url_requests" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await Presigned_url_requestsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/presigned_url_requests/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Presigned_url_requests] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Presigned_url_requests" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Presigned_url_requestsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/presigned_url_requests: - * get: - * security: - * - bearerAuth: [] - * tags: [Presigned_url_requests] - * summary: Get all presigned_url_requests - * description: Get all presigned_url_requests - * responses: - * 200: - * description: Presigned_url_requests list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Presigned_url_requests" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const payload = await Presigned_url_requestsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','requested_key','mime_type','status', - - 'requested_size_mb', - 'expires_at', - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/presigned_url_requests/count: - * get: + * /api/presigned_url_requests: + * post: * security: * - bearerAuth: [] * tags: [Presigned_url_requests] - * summary: Count all presigned_url_requests - * description: Count all presigned_url_requests + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Presigned_url_requests" * responses: * 200: - * description: Presigned_url_requests count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Presigned_url_requests" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const payload = await Presigned_url_requestsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/presigned_url_requests/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Presigned_url_requests] - * summary: Find all presigned_url_requests that match search criteria - * description: Find all presigned_url_requests that match search criteria + * summary: Get all presigned_url_requests * responses: * 200: * description: Presigned_url_requests list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Presigned_url_requests" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Presigned_url_requestsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/presigned_url_requests/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Presigned_url_requests] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Presigned_url_requests" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const payload = await Presigned_url_requestsDBApi.findBy( - { id: req.params.id }, - ); - - + * @swagger + * /api/presigned_url_requests/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Presigned_url_requests] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Presigned_url_requests" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Presigned_url_requests] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Presigned_url_requests] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('presigned_url_requests', Presigned_url_requestsService, Presigned_url_requestsDBApi); diff --git a/backend/src/routes/project_audio_tracks.js b/backend/src/routes/project_audio_tracks.js index b8f4f0f..a148ca2 100644 --- a/backend/src/routes/project_audio_tracks.js +++ b/backend/src/routes/project_audio_tracks.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const Project_audio_tracksService = require('../services/project_audio_tracks'); const Project_audio_tracksDBApi = require('../db/api/project_audio_tracks'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('project_audio_tracks')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,29 +9,18 @@ router.use(checkCrudPermissions('project_audio_tracks')); * Project_audio_tracks: * type: object * properties: - * source_key: * type: string - * default: source_key * name: * type: string - * default: name * slug: * type: string - * default: slug * url: * type: string - * default: url - * sort_order: * type: integer - * format: int64 - * volume: - * type: integer - * format: int64 - - * + * type: number */ /** @@ -57,393 +30,118 @@ router.use(checkCrudPermissions('project_audio_tracks')); * description: The Project_audio_tracks managing API */ -/** -* @swagger -* /api/project_audio_tracks: -* post: -* security: -* - bearerAuth: [] -* tags: [Project_audio_tracks] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Project_audio_tracks" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Project_audio_tracks" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Project_audio_tracksService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Project_audio_tracks] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Project_audio_tracks" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Project_audio_tracks" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Project_audio_tracksService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/project_audio_tracks/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Project_audio_tracks] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Project_audio_tracks" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Project_audio_tracks" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await Project_audio_tracksService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/project_audio_tracks/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Project_audio_tracks] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Project_audio_tracks" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await Project_audio_tracksService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/project_audio_tracks/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Project_audio_tracks] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Project_audio_tracks" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Project_audio_tracksService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/project_audio_tracks: - * get: - * security: - * - bearerAuth: [] - * tags: [Project_audio_tracks] - * summary: Get all project_audio_tracks - * description: Get all project_audio_tracks - * responses: - * 200: - * description: Project_audio_tracks list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Project_audio_tracks" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const runtimeContext = req.runtimeContext; - const payload = await Project_audio_tracksDBApi.findAll( - req.query, { currentUser, runtimeContext } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','source_key','name','slug','url', - 'sort_order', - 'volume', - - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/project_audio_tracks/count: - * get: + * /api/project_audio_tracks: + * post: * security: * - bearerAuth: [] * tags: [Project_audio_tracks] - * summary: Count all project_audio_tracks - * description: Count all project_audio_tracks + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Project_audio_tracks" * responses: * 200: - * description: Project_audio_tracks count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Project_audio_tracks" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const runtimeContext = req.runtimeContext; - const payload = await Project_audio_tracksDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser, runtimeContext } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/project_audio_tracks/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Project_audio_tracks] - * summary: Find all project_audio_tracks that match search criteria - * description: Find all project_audio_tracks that match search criteria + * summary: Get all project_audio_tracks * responses: * 200: * description: Project_audio_tracks list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Project_audio_tracks" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Project_audio_tracksDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/project_audio_tracks/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Project_audio_tracks] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Project_audio_tracks" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const runtimeContext = req.runtimeContext; - const payload = await Project_audio_tracksDBApi.findBy( - { id: req.params.id }, - { runtimeContext }, - ); - - + * @swagger + * /api/project_audio_tracks/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Project_audio_tracks] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Project_audio_tracks" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Project_audio_tracks] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Project_audio_tracks] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('project_audio_tracks', Project_audio_tracksService, Project_audio_tracksDBApi); diff --git a/backend/src/routes/project_memberships.js b/backend/src/routes/project_memberships.js index 30454a9..b7f22e2 100644 --- a/backend/src/routes/project_memberships.js +++ b/backend/src/routes/project_memberships.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const Project_membershipsService = require('../services/project_memberships'); const Project_membershipsDBApi = require('../db/api/project_memberships'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('project_memberships')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,11 +9,12 @@ router.use(checkCrudPermissions('project_memberships')); * Project_memberships: * type: object * properties: - - - - - * + * invited_at: + * type: string + * format: date-time + * accepted_at: + * type: string + * format: date-time */ /** @@ -39,389 +24,118 @@ router.use(checkCrudPermissions('project_memberships')); * description: The Project_memberships managing API */ -/** -* @swagger -* /api/project_memberships: -* post: -* security: -* - bearerAuth: [] -* tags: [Project_memberships] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Project_memberships" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Project_memberships" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Project_membershipsService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Project_memberships] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Project_memberships" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Project_memberships" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Project_membershipsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/project_memberships/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Project_memberships] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Project_memberships" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Project_memberships" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await Project_membershipsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/project_memberships/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Project_memberships] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Project_memberships" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await Project_membershipsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/project_memberships/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Project_memberships] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Project_memberships" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Project_membershipsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/project_memberships: - * get: - * security: - * - bearerAuth: [] - * tags: [Project_memberships] - * summary: Get all project_memberships - * description: Get all project_memberships - * responses: - * 200: - * description: Project_memberships list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Project_memberships" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const payload = await Project_membershipsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id', - - - 'invited_at','accepted_at', - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/project_memberships/count: - * get: + * /api/project_memberships: + * post: * security: * - bearerAuth: [] * tags: [Project_memberships] - * summary: Count all project_memberships - * description: Count all project_memberships + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Project_memberships" * responses: * 200: - * description: Project_memberships count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Project_memberships" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const payload = await Project_membershipsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/project_memberships/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Project_memberships] - * summary: Find all project_memberships that match search criteria - * description: Find all project_memberships that match search criteria + * summary: Get all project_memberships * responses: * 200: * description: Project_memberships list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Project_memberships" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Project_membershipsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/project_memberships/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Project_memberships] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Project_memberships" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const payload = await Project_membershipsDBApi.findBy( - { id: req.params.id }, - ); - - + * @swagger + * /api/project_memberships/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Project_memberships] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Project_memberships" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Project_memberships] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Project_memberships] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('project_memberships', Project_membershipsService, Project_membershipsDBApi); diff --git a/backend/src/routes/publish_events.js b/backend/src/routes/publish_events.js index 296c3e9..1f9a197 100644 --- a/backend/src/routes/publish_events.js +++ b/backend/src/routes/publish_events.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const Publish_eventsService = require('../services/publish_events'); const Publish_eventsDBApi = require('../db/api/publish_events'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('publish_events')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,25 +9,24 @@ router.use(checkCrudPermissions('publish_events')); * Publish_events: * type: object * properties: - + * title: + * type: string + * description: + * type: string * error_message: * type: string - * default: error_message - * pages_copied: * type: integer - * format: int64 * transitions_copied: * type: integer - * format: int64 * audios_copied: * type: integer - * format: int64 - - - * - * - * + * started_at: + * type: string + * format: date-time + * finished_at: + * type: string + * format: date-time */ /** @@ -53,389 +36,118 @@ router.use(checkCrudPermissions('publish_events')); * description: The Publish_events managing API */ -/** -* @swagger -* /api/publish_events: -* post: -* security: -* - bearerAuth: [] -* tags: [Publish_events] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Publish_events" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Publish_events" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Publish_eventsService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Publish_events] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Publish_events" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Publish_events" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Publish_eventsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/publish_events/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Publish_events] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Publish_events" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Publish_events" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await Publish_eventsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/publish_events/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Publish_events] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Publish_events" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await Publish_eventsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/publish_events/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Publish_events] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Publish_events" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Publish_eventsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/publish_events: - * get: - * security: - * - bearerAuth: [] - * tags: [Publish_events] - * summary: Get all publish_events - * description: Get all publish_events - * responses: - * 200: - * description: Publish_events list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Publish_events" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const payload = await Publish_eventsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','title','description','error_message', - 'pages_copied','transitions_copied','audios_copied', - - 'started_at','finished_at', - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/publish_events/count: - * get: + * /api/publish_events: + * post: * security: * - bearerAuth: [] * tags: [Publish_events] - * summary: Count all publish_events - * description: Count all publish_events + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Publish_events" * responses: * 200: - * description: Publish_events count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Publish_events" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const payload = await Publish_eventsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/publish_events/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Publish_events] - * summary: Find all publish_events that match search criteria - * description: Find all publish_events that match search criteria + * summary: Get all publish_events * responses: * 200: * description: Publish_events list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Publish_events" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Publish_eventsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/publish_events/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Publish_events] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Publish_events" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const payload = await Publish_eventsDBApi.findBy( - { id: req.params.id }, - ); - - + * @swagger + * /api/publish_events/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Publish_events] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Publish_events" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Publish_events] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Publish_events] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('publish_events', Publish_eventsService, Publish_eventsDBApi); diff --git a/backend/src/routes/pwa_caches.js b/backend/src/routes/pwa_caches.js index 44e1cf6..d4c7135 100644 --- a/backend/src/routes/pwa_caches.js +++ b/backend/src/routes/pwa_caches.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const Pwa_cachesService = require('../services/pwa_caches'); const Pwa_cachesDBApi = require('../db/api/pwa_caches'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('pwa_caches')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,20 +9,15 @@ router.use(checkCrudPermissions('pwa_caches')); * Pwa_caches: * type: object * properties: - * cache_version: * type: string - * default: cache_version * manifest_json: * type: string - * default: manifest_json * asset_list_json: * type: string - * default: asset_list_json - - - - * + * generated_at: + * type: string + * format: date-time */ /** @@ -48,389 +27,118 @@ router.use(checkCrudPermissions('pwa_caches')); * description: The Pwa_caches managing API */ -/** -* @swagger -* /api/pwa_caches: -* post: -* security: -* - bearerAuth: [] -* tags: [Pwa_caches] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Pwa_caches" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Pwa_caches" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Pwa_cachesService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Pwa_caches] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Pwa_caches" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Pwa_caches" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Pwa_cachesService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/pwa_caches/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Pwa_caches] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Pwa_caches" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Pwa_caches" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await Pwa_cachesService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/pwa_caches/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Pwa_caches] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Pwa_caches" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await Pwa_cachesService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/pwa_caches/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Pwa_caches] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Pwa_caches" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Pwa_cachesService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/pwa_caches: - * get: - * security: - * - bearerAuth: [] - * tags: [Pwa_caches] - * summary: Get all pwa_caches - * description: Get all pwa_caches - * responses: - * 200: - * description: Pwa_caches list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Pwa_caches" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const payload = await Pwa_cachesDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','cache_version','manifest_json','asset_list_json', - - - 'generated_at', - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/pwa_caches/count: - * get: + * /api/pwa_caches: + * post: * security: * - bearerAuth: [] * tags: [Pwa_caches] - * summary: Count all pwa_caches - * description: Count all pwa_caches + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Pwa_caches" * responses: * 200: - * description: Pwa_caches count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Pwa_caches" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const payload = await Pwa_cachesDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/pwa_caches/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Pwa_caches] - * summary: Find all pwa_caches that match search criteria - * description: Find all pwa_caches that match search criteria + * summary: Get all pwa_caches * responses: * 200: * description: Pwa_caches list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Pwa_caches" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Pwa_cachesDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/pwa_caches/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Pwa_caches] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Pwa_caches" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const payload = await Pwa_cachesDBApi.findBy( - { id: req.params.id }, - ); - - + * @swagger + * /api/pwa_caches/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Pwa_caches] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Pwa_caches" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Pwa_caches] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Pwa_caches] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('pwa_caches', Pwa_cachesService, Pwa_cachesDBApi); diff --git a/backend/src/routes/roles.js b/backend/src/routes/roles.js index 42f1121..74a1929 100644 --- a/backend/src/routes/roles.js +++ b/backend/src/routes/roles.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const RolesService = require('../services/roles'); const RolesDBApi = require('../db/api/roles'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('roles')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,13 +9,9 @@ router.use(checkCrudPermissions('roles')); * Roles: * type: object * properties: - * name: * type: string * default: name - - - */ /** @@ -41,389 +21,121 @@ router.use(checkCrudPermissions('roles')); * description: The Roles managing API */ -/** -* @swagger -* /api/roles: -* post: -* security: -* - bearerAuth: [] -* tags: [Roles] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Roles" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Roles" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await RolesService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Roles" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Roles" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await RolesService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/roles/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Roles" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Roles" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await RolesService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/roles/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Roles" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await RolesService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/roles/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Roles" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await RolesService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/roles: - * get: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Get all roles - * description: Get all roles - * responses: - * 200: - * description: Roles list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Roles" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const payload = await RolesDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','name', - - - - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/roles/count: - * get: + * /api/roles: + * post: * security: * - bearerAuth: [] * tags: [Roles] - * summary: Count all roles - * description: Count all roles + * summary: Add new item + * description: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the item + * type: object + * $ref: "#/components/schemas/Roles" * responses: * 200: - * description: Roles count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Roles" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const payload = await RolesDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/roles/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Roles] - * summary: Find all roles that match search criteria - * description: Find all roles that match search criteria + * summary: Get all roles + * description: Get all roles * responses: * 200: * description: Roles list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Roles" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await RolesDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/roles/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Roles" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const payload = await RolesDBApi.findBy( - { id: req.params.id }, - ); - - + * @swagger + * /api/roles/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Roles] + * summary: Update the data of the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Roles" + * responses: + * 200: + * description: The item data was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Roles] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Roles] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('roles', RolesService, RolesDBApi); diff --git a/backend/src/routes/tour_pages.js b/backend/src/routes/tour_pages.js index e7bd980..ed1da62 100644 --- a/backend/src/routes/tour_pages.js +++ b/backend/src/routes/tour_pages.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const Tour_pagesService = require('../services/tour_pages'); const Tour_pagesDBApi = require('../db/api/tour_pages'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('tour_pages')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,35 +9,22 @@ router.use(checkCrudPermissions('tour_pages')); * Tour_pages: * type: object * properties: - * source_key: * type: string - * default: source_key * name: * type: string - * default: name * slug: * type: string - * default: slug * background_image_url: * type: string - * default: background_image_url * background_video_url: * type: string - * default: background_video_url * background_audio_url: * type: string - * default: background_audio_url * ui_schema_json: * type: string - * default: ui_schema_json - * sort_order: * type: integer - * format: int64 - - - * */ /** @@ -63,393 +34,118 @@ router.use(checkCrudPermissions('tour_pages')); * description: The Tour_pages managing API */ -/** -* @swagger -* /api/tour_pages: -* post: -* security: -* - bearerAuth: [] -* tags: [Tour_pages] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Tour_pages" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Tour_pages" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Tour_pagesService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Tour_pages] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Tour_pages" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Tour_pages" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await Tour_pagesService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/tour_pages/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Tour_pages] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Tour_pages" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Tour_pages" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await Tour_pagesService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/tour_pages/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Tour_pages] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Tour_pages" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await Tour_pagesService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/tour_pages/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Tour_pages] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Tour_pages" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Tour_pagesService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/tour_pages: - * get: - * security: - * - bearerAuth: [] - * tags: [Tour_pages] - * summary: Get all tour_pages - * description: Get all tour_pages - * responses: - * 200: - * description: Tour_pages list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Tour_pages" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const runtimeContext = req.runtimeContext; - const payload = await Tour_pagesDBApi.findAll( - req.query, { currentUser, runtimeContext } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','source_key','name','slug','background_image_url','background_video_url','background_audio_url','ui_schema_json', - 'sort_order', - - - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/tour_pages/count: - * get: + * /api/tour_pages: + * post: * security: * - bearerAuth: [] * tags: [Tour_pages] - * summary: Count all tour_pages - * description: Count all tour_pages + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Tour_pages" * responses: * 200: - * description: Tour_pages count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Tour_pages" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const runtimeContext = req.runtimeContext; - const payload = await Tour_pagesDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser, runtimeContext } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/tour_pages/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Tour_pages] - * summary: Find all tour_pages that match search criteria - * description: Find all tour_pages that match search criteria + * summary: Get all tour_pages * responses: * 200: * description: Tour_pages list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Tour_pages" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Tour_pagesDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/tour_pages/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Tour_pages] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Tour_pages" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const runtimeContext = req.runtimeContext; - const payload = await Tour_pagesDBApi.findBy( - { id: req.params.id }, - { runtimeContext }, - ); - - + * @swagger + * /api/tour_pages/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Tour_pages] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Tour_pages" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Tour_pages] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Tour_pages] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('tour_pages', Tour_pagesService, Tour_pagesDBApi); diff --git a/backend/src/routes/transitions.js b/backend/src/routes/transitions.js index 19271f8..ecd213a 100644 --- a/backend/src/routes/transitions.js +++ b/backend/src/routes/transitions.js @@ -1,22 +1,6 @@ - -const express = require('express'); - const TransitionsService = require('../services/transitions'); const TransitionsDBApi = require('../db/api/transitions'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('transitions')); - +const { createEntityRouter } = require('../factories/router.factory'); /** * @swagger @@ -25,29 +9,18 @@ router.use(checkCrudPermissions('transitions')); * Transitions: * type: object * properties: - * source_key: * type: string - * default: source_key * name: * type: string - * default: name * slug: * type: string - * default: slug * video_url: * type: string - * default: video_url * audio_url: * type: string - * default: audio_url - - * duration_sec: - * type: integer - * format: int64 - - * + * type: number */ /** @@ -57,393 +30,118 @@ router.use(checkCrudPermissions('transitions')); * description: The Transitions managing API */ -/** -* @swagger -* /api/transitions: -* post: -* security: -* - bearerAuth: [] -* tags: [Transitions] -* summary: Add new item -* description: Add new item -* requestBody: -* required: true -* content: -* application/json: -* schema: -* properties: -* data: -* description: Data of the updated item -* type: object -* $ref: "#/components/schemas/Transitions" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Transitions" -* 401: -* $ref: "#/components/responses/UnauthorizedError" -* 405: -* description: Invalid input data -* 500: -* description: Some server error -*/ -router.post('/', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await TransitionsService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Transitions] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Transitions" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Transitions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post('/bulk-import', wrapAsync(async (req, res) => { - const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await TransitionsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/transitions/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Transitions] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Transitions" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Transitions" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put('/:id', wrapAsync(async (req, res) => { - await TransitionsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/transitions/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Transitions] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Transitions" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete('/:id', wrapAsync(async (req, res) => { - await TransitionsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/transitions/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Transitions] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Transitions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await TransitionsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/transitions: - * get: - * security: - * - bearerAuth: [] - * tags: [Transitions] - * summary: Get all transitions - * description: Get all transitions - * responses: - * 200: - * description: Transitions list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Transitions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error -*/ -router.get('/', wrapAsync(async (req, res) => { - const filetype = req.query.filetype - - const currentUser = req.currentUser; - const runtimeContext = req.runtimeContext; - const payload = await TransitionsDBApi.findAll( - req.query, { currentUser, runtimeContext } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','source_key','name','slug','video_url','audio_url', - - 'duration_sec', - - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv) - - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - -})); - /** * @swagger - * /api/transitions/count: - * get: + * /api/transitions: + * post: * security: * - bearerAuth: [] * tags: [Transitions] - * summary: Count all transitions - * description: Count all transitions + * summary: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * type: object + * $ref: "#/components/schemas/Transitions" * responses: * 200: - * description: Transitions count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Transitions" + * description: The item was successfully added * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error - */ -router.get('/count', wrapAsync(async (req, res) => { - - const currentUser = req.currentUser; - const runtimeContext = req.runtimeContext; - const payload = await TransitionsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser, runtimeContext } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/transitions/autocomplete: * get: * security: * - bearerAuth: [] * tags: [Transitions] - * summary: Find all transitions that match search criteria - * description: Find all transitions that match search criteria + * summary: Get all transitions * responses: * 200: * description: Transitions list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Transitions" * 401: * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found * 500: * description: Some server error */ -router.get('/autocomplete', async (req, res) => { - - const payload = await TransitionsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); /** - * @swagger - * /api/transitions/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Transitions] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Transitions" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get('/:id', wrapAsync(async (req, res) => { - const runtimeContext = req.runtimeContext; - const payload = await TransitionsDBApi.findBy( - { id: req.params.id }, - { runtimeContext }, - ); - - + * @swagger + * /api/transitions/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Transitions] + * summary: Update the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * type: string + * data: + * type: object + * $ref: "#/components/schemas/Transitions" + * responses: + * 200: + * description: The item was successfully updated + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * delete: + * security: + * - bearerAuth: [] + * tags: [Transitions] + * summary: Delete the selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + * get: + * security: + * - bearerAuth: [] + * tags: [Transitions] + * summary: Get selected item + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; +module.exports = createEntityRouter('transitions', TransitionsService, TransitionsDBApi); diff --git a/backend/src/routes/ui_elements.js b/backend/src/routes/ui_elements.js deleted file mode 100644 index efceefa..0000000 --- a/backend/src/routes/ui_elements.js +++ /dev/null @@ -1,103 +0,0 @@ -const express = require('express'); - -const Ui_elementsService = require('../services/ui_elements'); -const Ui_elementsDBApi = require('../db/api/ui_elements'); -const wrapAsync = require('../helpers').wrapAsync; - -const router = express.Router(); -const { parse } = require('json2csv'); - -const { checkCrudPermissions } = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('page_elements')); - -router.post( - '/', - wrapAsync(async (req, res) => { - await Ui_elementsService.create(req.body.data, req.currentUser); - res.status(200).send(true); - }), -); - -router.post( - '/bulk-import', - wrapAsync(async (req, res) => { - await Ui_elementsService.bulkImport(req, res); - res.status(200).send(true); - }), -); - -router.put( - '/:id', - wrapAsync(async (req, res) => { - await Ui_elementsService.update(req.body.data, req.body.id, req.currentUser); - res.status(200).send(true); - }), -); - -router.delete( - '/:id', - wrapAsync(async (req, res) => { - await Ui_elementsService.remove(req.params.id, req.currentUser); - res.status(200).send(true); - }), -); - -router.post( - '/deleteByIds', - wrapAsync(async (req, res) => { - await Ui_elementsService.deleteByIds(req.body.data, req.currentUser); - res.status(200).send(true); - }), -); - -router.get( - '/', - wrapAsync(async (req, res) => { - const filetype = req.query.filetype; - - const payload = await Ui_elementsDBApi.findAll(req.query, { - currentUser: req.currentUser, - }); - - if (filetype && filetype === 'csv') { - const fields = ['id', 'name', 'element_type', 'settings_json', 'sort_order']; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv); - } catch (err) { - console.error(err); - throw err; - } - } else { - res.status(200).send(payload); - } - }), -); - -router.get( - '/count', - wrapAsync(async (req, res) => { - const payload = await Ui_elementsDBApi.findAll(req.query, { countOnly: true, currentUser: req.currentUser }); - res.status(200).send(payload); - }), -); - -router.get('/autocomplete', async (req, res) => { - const payload = await Ui_elementsDBApi.findAllAutocomplete(req.query.query, req.query.limit, req.query.offset); - res.status(200).send(payload); -}); - -router.get( - '/:id', - wrapAsync(async (req, res) => { - const payload = await Ui_elementsDBApi.findBy({ id: req.params.id }); - res.status(200).send(payload); - }), -); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/services/ui_elements.js b/backend/src/services/ui_elements.js deleted file mode 100644 index 5b02983..0000000 --- a/backend/src/services/ui_elements.js +++ /dev/null @@ -1,114 +0,0 @@ -const db = require('../db/models'); -const Ui_elementsDBApi = require('../db/api/ui_elements'); -const processFile = require('../middlewares/upload'); -const ValidationError = require('./notifications/errors/validation'); -const csv = require('csv-parser'); -const stream = require('stream'); - -module.exports = class Ui_elementsService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await Ui_elementsDBApi.create(data, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async bulkImport(req, res) { - const transaction = await db.sequelize.transaction(); - - try { - await processFile(req, res); - const bufferStream = new stream.PassThrough(); - const results = []; - - await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); - - await new Promise((resolve, reject) => { - bufferStream - .pipe(csv()) - .on('data', (data) => results.push(data)) - .on('end', async () => { - resolve(); - }) - .on('error', (error) => reject(error)); - }); - - await Ui_elementsDBApi.bulkImport(results, { - transaction, - ignoreDuplicates: true, - validate: true, - currentUser: req.currentUser, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async update(data, id, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - const ui_elements = await Ui_elementsDBApi.findBy( - { id }, - { transaction }, - ); - - if (!ui_elements) { - throw new ValidationError('ui_elementsNotFound'); - } - - const updatedUiElements = await Ui_elementsDBApi.update(id, data, { - currentUser, - transaction, - }); - - await transaction.commit(); - return updatedUiElements; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await Ui_elementsDBApi.deleteByIds(ids, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async remove(id, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await Ui_elementsDBApi.remove(id, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } -}; diff --git a/backend/src/utils/circuit-breaker.js b/backend/src/utils/circuit-breaker.js new file mode 100644 index 0000000..72b0b94 --- /dev/null +++ b/backend/src/utils/circuit-breaker.js @@ -0,0 +1,79 @@ +const { logger } = require('./logger'); + +class CircuitBreaker { + constructor(options = {}) { + this.name = options.name || 'default'; + this.failureThreshold = options.failureThreshold || 5; + this.resetTimeout = options.resetTimeout || 30000; + this.failures = 0; + this.state = 'CLOSED'; + this.nextAttempt = Date.now(); + } + + async execute(fn) { + if (this.state === 'OPEN') { + if (Date.now() < this.nextAttempt) { + logger.warn({ circuitBreaker: this.name, state: this.state }, 'Circuit breaker is OPEN, rejecting request'); + throw new Error(`Circuit breaker ${this.name} is OPEN`); + } + this.state = 'HALF-OPEN'; + logger.info({ circuitBreaker: this.name }, 'Circuit breaker moved to HALF-OPEN'); + } + + try { + const result = await fn(); + this.onSuccess(); + return result; + } catch (error) { + this.onFailure(error); + throw error; + } + } + + onSuccess() { + if (this.state === 'HALF-OPEN') { + logger.info({ circuitBreaker: this.name }, 'Circuit breaker recovered, moving to CLOSED'); + } + this.failures = 0; + this.state = 'CLOSED'; + } + + onFailure(error) { + this.failures++; + logger.warn({ + circuitBreaker: this.name, + failures: this.failures, + threshold: this.failureThreshold, + error: error.message, + }, 'Circuit breaker recorded failure'); + + if (this.failures >= this.failureThreshold) { + this.state = 'OPEN'; + this.nextAttempt = Date.now() + this.resetTimeout; + logger.error({ + circuitBreaker: this.name, + resetAt: new Date(this.nextAttempt).toISOString(), + }, 'Circuit breaker tripped to OPEN'); + } + } + + getState() { + return { + name: this.name, + state: this.state, + failures: this.failures, + nextAttempt: this.state === 'OPEN' ? new Date(this.nextAttempt).toISOString() : null, + }; + } +} + +const circuitBreakers = new Map(); + +function getCircuitBreaker(name, options = {}) { + if (!circuitBreakers.has(name)) { + circuitBreakers.set(name, new CircuitBreaker({ name, ...options })); + } + return circuitBreakers.get(name); +} + +module.exports = { CircuitBreaker, getCircuitBreaker }; diff --git a/backend/src/utils/env-validation.js b/backend/src/utils/env-validation.js new file mode 100644 index 0000000..e2e862c --- /dev/null +++ b/backend/src/utils/env-validation.js @@ -0,0 +1,65 @@ +const Joi = require('joi'); + +const envSchema = Joi.object({ + NODE_ENV: Joi.string() + .valid('development', 'test', 'production', 'dev_stage') + .default('development'), + + PORT: Joi.number().default(8080), + + DB_HOST: Joi.string().default('localhost'), + DB_PORT: Joi.number().default(5432), + DB_NAME: Joi.string().default('db_tour_builder_platform'), + DB_USER: Joi.string().default('postgres'), + DB_PASS: Joi.string().allow('').default(''), + + SECRET_KEY: Joi.string().min(16).default('88dbeaf8-e906-405e-9e41-c3baadeda5c6'), + + ADMIN_PASS: Joi.string().default('88dbeaf8'), + USER_PASS: Joi.string().default('c3baadeda5c6'), + ADMIN_EMAIL: Joi.string().email().default('admin@flatlogic.com'), + + GOOGLE_CLIENT_ID: Joi.string().allow('').default(''), + GOOGLE_CLIENT_SECRET: Joi.string().allow('').default(''), + MS_CLIENT_ID: Joi.string().allow('').default(''), + MS_CLIENT_SECRET: Joi.string().allow('').default(''), + + AWS_ACCESS_KEY_ID: Joi.string().allow('').default(''), + AWS_SECRET_ACCESS_KEY: Joi.string().allow('').default(''), + AWS_S3_BUCKET: Joi.string().allow('').default(''), + AWS_S3_REGION: Joi.string().default('us-east-1'), + AWS_S3_PREFIX: Joi.string().default('afeefb9d49f5b7977577876b99532ac7'), + + EMAIL_USER: Joi.string().allow('').default(''), + EMAIL_PASS: Joi.string().allow('').default(''), + EMAIL_TLS_REJECT_UNAUTHORIZED: Joi.string().valid('true', 'false').default('true'), + + GPT_KEY: Joi.string().allow('').default(''), + PEXELS_KEY: Joi.string().allow('').default(''), + + LOG_LEVEL: Joi.string() + .valid('fatal', 'error', 'warn', 'info', 'debug', 'trace') + .default('info'), +}).unknown(true); + +function validateEnv() { + const { error, value } = envSchema.validate(process.env, { + abortEarly: false, + stripUnknown: false, + }); + + if (error) { + const messages = error.details.map(d => ` - ${d.message}`).join('\n'); + console.error('Environment validation failed:\n' + messages); + + if (process.env.NODE_ENV === 'production') { + process.exit(1); + } else { + console.warn('Continuing with default values in non-production mode'); + } + } + + return value; +} + +module.exports = { validateEnv, envSchema }; diff --git a/backend/src/utils/errors.js b/backend/src/utils/errors.js new file mode 100644 index 0000000..97da127 --- /dev/null +++ b/backend/src/utils/errors.js @@ -0,0 +1,48 @@ +class AppError extends Error { + constructor(message, statusCode = 500, details = null) { + super(message); + this.statusCode = statusCode; + this.details = details; + this.isOperational = true; + Error.captureStackTrace(this, this.constructor); + } +} + +class NotFoundError extends AppError { + constructor(resource = 'Resource') { + super(`${resource} not found`, 404); + } +} + +class ValidationError extends AppError { + constructor(message, details = null) { + super(message, 400, details); + } +} + +class ForbiddenError extends AppError { + constructor(message = 'Access denied') { + super(message, 403); + } +} + +class UnauthorizedError extends AppError { + constructor(message = 'Unauthorized') { + super(message, 401); + } +} + +class ConflictError extends AppError { + constructor(message = 'Resource conflict') { + super(message, 409); + } +} + +module.exports = { + AppError, + NotFoundError, + ValidationError, + ForbiddenError, + UnauthorizedError, + ConflictError, +}; diff --git a/backend/src/utils/events.js b/backend/src/utils/events.js new file mode 100644 index 0000000..1388a0f --- /dev/null +++ b/backend/src/utils/events.js @@ -0,0 +1,53 @@ +const EventEmitter = require('events'); +const { logger } = require('./logger'); + +class AppEventEmitter extends EventEmitter { + emit(event, data) { + logger.debug({ event, hasListeners: this.listenerCount(event) > 0 }, 'Event emitted'); + return super.emit(event, data); + } + + async emitAsync(event, data) { + logger.debug({ event, listenerCount: this.listenerCount(event) }, 'Async event emitted'); + const results = await Promise.allSettled( + this.listeners(event).map(listener => listener(data)) + ); + + const failures = results.filter(r => r.status === 'rejected'); + if (failures.length > 0) { + logger.warn({ event, failures: failures.map(f => f.reason?.message) }, 'Some event listeners failed'); + } + + return results; + } + + onAsync(event, listener) { + this.on(event, async (data) => { + try { + await listener(data); + } catch (error) { + logger.error({ event, error: error.message }, 'Async event listener error'); + } + }); + } +} + +const appEvents = new AppEventEmitter(); + +appEvents.on('user.created', (user) => { + logger.info({ userId: user.id, email: user.email }, 'User created event received'); +}); + +appEvents.on('project.created', (project) => { + logger.info({ projectId: project.id, name: project.name }, 'Project created event received'); +}); + +appEvents.on('project.published', (project) => { + logger.info({ projectId: project.id, slug: project.slug }, 'Project published event received'); +}); + +appEvents.on('error', (error) => { + logger.error({ error: error.message }, 'Application event error'); +}); + +module.exports = { appEvents, AppEventEmitter }; diff --git a/backend/src/utils/index.js b/backend/src/utils/index.js new file mode 100644 index 0000000..04b4e18 --- /dev/null +++ b/backend/src/utils/index.js @@ -0,0 +1,7 @@ +module.exports = { + ...require('./errors'), + ...require('./logger'), + ...require('./circuit-breaker'), + ...require('./events'), + envValidation: require('./env-validation'), +}; diff --git a/backend/src/utils/logger.js b/backend/src/utils/logger.js new file mode 100644 index 0000000..7380316 --- /dev/null +++ b/backend/src/utils/logger.js @@ -0,0 +1,46 @@ +const pino = require('pino'); +const crypto = require('crypto'); + +const isDevelopment = process.env.NODE_ENV === 'development'; + +const logger = pino({ + level: process.env.LOG_LEVEL || 'info', + transport: isDevelopment + ? { target: 'pino-pretty', options: { colorize: true } } + : undefined, + base: { + service: 'tour-builder-api', + env: process.env.NODE_ENV || 'development', + }, +}); + +function requestLogger(req, res, next) { + const requestId = req.headers['x-request-id'] || crypto.randomUUID(); + req.log = logger.child({ requestId }); + req.requestId = requestId; + res.setHeader('X-Request-Id', requestId); + + const start = Date.now(); + res.on('finish', () => { + const duration = Date.now() - start; + const logData = { + method: req.method, + url: req.originalUrl || req.url, + status: res.statusCode, + duration, + userAgent: req.headers['user-agent'], + }; + + if (res.statusCode >= 500) { + req.log.error(logData, 'Request completed with server error'); + } else if (res.statusCode >= 400) { + req.log.warn(logData, 'Request completed with client error'); + } else { + req.log.info(logData, 'Request completed'); + } + }); + + next(); +} + +module.exports = { logger, requestLogger }; diff --git a/backend/yarn.lock b/backend/yarn.lock index 1ee5959..27b2f9e 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -77,7 +77,7 @@ "@smithy/util-utf8" "^2.0.0" tslib "^2.6.2" -"@aws-crypto/sha256-js@5.2.0", "@aws-crypto/sha256-js@^5.2.0": +"@aws-crypto/sha256-js@^5.2.0", "@aws-crypto/sha256-js@5.2.0": version "5.2.0" resolved "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz" integrity sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA== @@ -93,7 +93,7 @@ dependencies: tslib "^2.6.2" -"@aws-crypto/util@5.2.0", "@aws-crypto/util@^5.2.0": +"@aws-crypto/util@^5.2.0", "@aws-crypto/util@5.2.0": version "5.2.0" resolved "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz" integrity sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ== @@ -104,7 +104,7 @@ "@aws-sdk/client-s3@^3.1011.0": version "3.1011.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.1011.0.tgz#fd9c2cf288136f5d2babc2a51564c6bc1eaceacb" + resolved "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1011.0.tgz" integrity sha512-jY7CGX+vfM/DSi4K8UwaZKoXnhqchmAbKFB1kIuHMfPPqW7l3jC/fUVDb95/njMsB2ymYOTusZEzoCTeUB/4qA== dependencies: "@aws-crypto/sha1-browser" "5.2.0" @@ -607,7 +607,7 @@ "@azure/core-auth@^1.7.2": version "1.10.1" - resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.10.1.tgz#68a17fa861ebd14f6fd314055798355ef6bedf1b" + resolved "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz" integrity sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg== dependencies: "@azure/abort-controller" "^2.1.2" @@ -684,7 +684,7 @@ "@azure/core-util@^1.13.0": version "1.13.1" - resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.13.1.tgz#6dff2ff6d3c9c6430c6f4d3b3e65de531f10bafe" + resolved "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz" integrity sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A== dependencies: "@azure/abort-controller" "^2.1.2" @@ -758,19 +758,19 @@ "@eslint-community/eslint-utils@^4.2.0": version "4.9.1" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz" integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: eslint-visitor-keys "^3.4.3" "@eslint-community/regexpp@^4.6.1": version "4.12.2" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz" integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== "@eslint/eslintrc@^2.1.4": version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz" integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== dependencies: ajv "^6.12.4" @@ -785,12 +785,12 @@ "@eslint/js@8.57.1": version "8.57.1" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== "@google-cloud/paginator@^5.0.0": version "5.0.2" - resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-5.0.2.tgz#86ad773266ce9f3b82955a8f75e22cd012ccc889" + resolved "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz" integrity sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg== dependencies: arrify "^2.0.0" @@ -798,17 +798,17 @@ "@google-cloud/projectify@^4.0.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-4.0.0.tgz#d600e0433daf51b88c1fa95ac7f02e38e80a07be" + resolved "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz" integrity sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA== "@google-cloud/promisify@<4.1.0": version "4.0.0" - resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-4.0.0.tgz#a906e533ebdd0f754dca2509933334ce58b8c8b1" + resolved "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz" integrity sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g== "@google-cloud/storage@^7.0.0": version "7.19.0" - resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-7.19.0.tgz#34fb7cc4eacede5a2f1f0d6cefa0c70a22078c5b" + resolved "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.19.0.tgz" integrity sha512-n2FjE7NAOYyshogdc7KQOl/VZb4sneqPjWouSyia9CMDdMhRX5+RIbqalNmC7LOLzuLAN89VlF2HvG8na9G+zQ== dependencies: "@google-cloud/paginator" "^5.0.0" @@ -827,9 +827,21 @@ teeny-request "^9.0.0" uuid "^8.0.0" +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + "@humanwhocodes/config-array@^0.13.0": version "0.13.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz" integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== dependencies: "@humanwhocodes/object-schema" "^2.0.3" @@ -838,12 +850,12 @@ "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/object-schema@^2.0.3": version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== "@isaacs/cliui@^8.0.2": @@ -870,7 +882,7 @@ "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" @@ -878,12 +890,12 @@ "@nodelib/fs.stat@2.0.5": version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.8": version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" @@ -894,6 +906,11 @@ resolved "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz" integrity sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw== +"@pinojs/redact@^0.4.0": + version "0.4.0" + resolved "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz" + integrity sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" @@ -901,9 +918,26 @@ "@rtsao/scc@^1.1.0": version "1.1.0" - resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" + resolved "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@smithy/abort-controller@^4.2.12": version "4.2.12" resolved "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.12.tgz" @@ -1412,7 +1446,7 @@ "@types/caseless@*": version "0.12.5" - resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.5.tgz#db9468cb1b1b5a925b8f34822f1669df0c5472f5" + resolved "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz" integrity sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg== "@types/debug@^4.1.8": @@ -1429,7 +1463,7 @@ "@types/json5@^0.0.29": version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/ms@*": @@ -1454,7 +1488,7 @@ "@types/request@^2.48.8": version "2.48.13" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.13.tgz#abdf4256524e801ea8fdda54320f083edb5a6b80" + resolved "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz" integrity sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg== dependencies: "@types/caseless" "*" @@ -1464,7 +1498,7 @@ "@types/tough-cookie@*": version "4.0.5" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" + resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz" integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== "@types/validator@^13.7.17": @@ -1474,7 +1508,7 @@ "@typespec/ts-http-runtime@^0.3.0": version "0.3.4" - resolved "https://registry.yarnpkg.com/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.4.tgz#c5f236ea5924c85ad8ff96d60ecdf0a22585411c" + resolved "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.4.tgz" integrity sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ== dependencies: http-proxy-agent "^7.0.0" @@ -1483,7 +1517,7 @@ "@ungap/structured-clone@^1.2.0": version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== abbrev@^2.0.0: @@ -1508,22 +1542,22 @@ accepts@^1.3.7, accepts@~1.3.8: acorn-jsx@^5.3.2: version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.9.0: +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0: version "8.16.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz" integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== -agent-base@6: - version "6.0.2" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== +agent-base@^7.0.2: + version "7.1.1" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== dependencies: - debug "4" + debug "^4.3.4" -agent-base@^7.0.2, agent-base@^7.1.0: +agent-base@^7.1.0: version "7.1.1" resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz" integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== @@ -1532,12 +1566,19 @@ agent-base@^7.0.2, agent-base@^7.1.0: agent-base@^7.1.2: version "7.1.4" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz" integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== +agent-base@6: + version "6.0.2" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv@^6.12.4: version "6.14.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz" integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== dependencies: fast-deep-equal "^3.1.1" @@ -1547,7 +1588,7 @@ ajv@^6.12.4: ansi-colors@^4.1.3: version "4.1.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-regex@^5.0.1: @@ -1600,7 +1641,7 @@ array-buffer-byte-length@^1.0.1: array-buffer-byte-length@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz" integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== dependencies: call-bound "^1.0.3" @@ -1613,7 +1654,7 @@ array-flatten@1.1.1: array-includes@^3.1.9: version "3.1.9" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.9.tgz#1f0ccaa08e90cdbc3eb433210f903ad0f17c3f3a" + resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz" integrity sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ== dependencies: call-bind "^1.0.8" @@ -1627,7 +1668,7 @@ array-includes@^3.1.9: array.prototype.findlastindex@^1.2.6: version "1.2.6" - resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz#cfa1065c81dcb64e34557c9b81d012f6a421c564" + resolved "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz" integrity sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ== dependencies: call-bind "^1.0.8" @@ -1640,7 +1681,7 @@ array.prototype.findlastindex@^1.2.6: array.prototype.flat@^1.3.3: version "1.3.3" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5" + resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz" integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== dependencies: call-bind "^1.0.8" @@ -1650,7 +1691,7 @@ array.prototype.flat@^1.3.3: array.prototype.flatmap@^1.3.3: version "1.3.3" - resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" + resolved "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz" integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== dependencies: call-bind "^1.0.8" @@ -1674,7 +1715,7 @@ arraybuffer.prototype.slice@^1.0.3: arraybuffer.prototype.slice@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + resolved "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz" integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== dependencies: array-buffer-byte-length "^1.0.1" @@ -1692,7 +1733,7 @@ arrify@^2.0.0: async-function@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz" integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== async-retry@^1.3.3: @@ -1712,6 +1753,11 @@ at-least-node@^1.0.0: resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz" @@ -1721,7 +1767,7 @@ available-typed-arrays@^1.0.7: axios@^1.13.0: version "1.13.6" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.6.tgz#c3f92da917dc209a15dd29936d20d5089b6b6c98" + resolved "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz" integrity sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ== dependencies: follow-redirects "^1.15.11" @@ -1735,7 +1781,7 @@ balanced-match@^1.0.0: balanced-match@^4.0.2: version "4.0.4" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz" integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== base64-js@^1.3.0, base64-js@^1.3.1: @@ -1750,7 +1796,7 @@ base64url@3.x.x: bcrypt@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-6.0.0.tgz#86643fddde9bcd0ad91400b063003fa4b0312835" + resolved "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz" integrity sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg== dependencies: node-addon-api "^8.3.0" @@ -1821,7 +1867,7 @@ brace-expansion@^2.0.1: brace-expansion@^5.0.2: version "5.0.4" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.4.tgz#614daaecd0a688f660bbbc909a8748c3d80d4336" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz" integrity sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg== dependencies: balanced-match "^4.0.2" @@ -1835,7 +1881,7 @@ braces@~3.0.2: browser-stdout@^1.3.1: version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + resolved "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== buffer-equal-constant-time@1.0.1: @@ -1858,7 +1904,7 @@ buffer@^6.0.3: busboy@^1.6.0: version "1.6.0" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz" integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== dependencies: streamsearch "^1.1.0" @@ -1870,7 +1916,7 @@ bytes@3.1.2: call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: es-errors "^1.3.0" @@ -1889,7 +1935,7 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: call-bind@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz" integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== dependencies: call-bind-apply-helpers "^1.0.0" @@ -1899,7 +1945,7 @@ call-bind@^1.0.8: call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== dependencies: call-bind-apply-helpers "^1.0.2" @@ -1912,12 +1958,12 @@ call-me-maybe@^1.0.1: callsites@^3.0.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase@^6.0.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== chalk@^4.0.0, chalk@^4.1.0: @@ -1928,9 +1974,24 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@^3.5.2, chokidar@^3.5.3: +chokidar@^3.5.2: version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== dependencies: anymatch "~3.1.2" @@ -1945,7 +2006,7 @@ chokidar@^3.5.2, chokidar@^3.5.3: chokidar@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz" integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== dependencies: readdirp "^4.0.1" @@ -1971,6 +2032,11 @@ color-name@~1.1.4: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colorette@^2.0.7: + version "2.0.20" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" @@ -1978,11 +2044,6 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -commander@6.2.0: - version "6.2.0" - resolved "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz" - integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q== - commander@^10.0.0: version "10.0.1" resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz" @@ -1993,6 +2054,11 @@ commander@^6.1.0: resolved "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== +commander@6.2.0: + version "6.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz" + integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -2000,7 +2066,7 @@ concat-map@0.0.1: concat-stream@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" + resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz" integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== dependencies: buffer-from "^1.0.0" @@ -2016,7 +2082,7 @@ config-chain@^1.1.13: ini "^1.3.4" proto-list "~1.2.1" -content-disposition@0.5.4, content-disposition@^0.5.3: +content-disposition@^0.5.3, content-disposition@0.5.4: version "0.5.4" resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== @@ -2040,7 +2106,7 @@ cookie@0.5.0: cors@^2.8.6: version "2.8.6" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96" + resolved "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz" integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== dependencies: object-assign "^4" @@ -2048,7 +2114,7 @@ cors@^2.8.6: cross-env@^7.0.3: version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz" integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== dependencies: cross-spawn "^7.0.1" @@ -2064,7 +2130,7 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1: cross-spawn@^7.0.2: version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" @@ -2073,7 +2139,7 @@ cross-spawn@^7.0.2: csv-parser@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/csv-parser/-/csv-parser-3.2.0.tgz#7e5515e3763e963dc8660dc9dcfc3f0eaf72b0a9" + resolved "https://registry.npmjs.org/csv-parser/-/csv-parser-3.2.0.tgz" integrity sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA== data-view-buffer@^1.0.1: @@ -2087,7 +2153,7 @@ data-view-buffer@^1.0.1: data-view-buffer@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + resolved "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz" integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== dependencies: call-bound "^1.0.3" @@ -2105,7 +2171,7 @@ data-view-byte-length@^1.0.1: data-view-byte-length@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + resolved "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz" integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== dependencies: call-bound "^1.0.3" @@ -2123,13 +2189,60 @@ data-view-byte-offset@^1.0.0: data-view-byte-offset@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + resolved "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz" integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== dependencies: call-bound "^1.0.2" es-errors "^1.3.0" is-data-view "^1.0.1" +dateformat@^4.6.3: + version "4.6.3" + resolved "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz" + integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +debug@^4.3.1: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +debug@^4.3.2: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +debug@^4.3.4, debug@4: + version "4.3.5" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +debug@^4.3.5: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + debug@2.6.9: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" @@ -2137,35 +2250,14 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.3.4: - version "4.3.5" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz" - integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== - dependencies: - ms "2.1.2" - -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.5: - version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - decamelize@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== deep-is@^0.1.3: version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== define-data-property@^1.0.1, define-data-property@^1.1.4: @@ -2201,16 +2293,16 @@ denque@^1.4.1: resolved "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz" integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== -depd@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - depd@^1.1.0: version "1.1.2" resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== +depd@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + destroy@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" @@ -2218,22 +2310,27 @@ destroy@1.2.0: diff@^5.2.0: version "5.2.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.2.tgz#0a4742797281d09cfa699b79ea32d27723623bad" + resolved "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz" integrity sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A== -doctrine@3.0.0, doctrine@^3.0.0: +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0, doctrine@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" +dotenv@^16.4.0: + version "16.6.1" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== dottie@^2.0.6: version "2.0.6" @@ -2242,7 +2339,7 @@ dottie@^2.0.6: dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== dependencies: call-bind-apply-helpers "^1.0.1" @@ -2251,7 +2348,7 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1: duplexify@^4.1.3: version "4.1.3" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.3.tgz#a07e1c0d0a2c001158563d32592ba58bddb0236f" + resolved "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz" integrity sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA== dependencies: end-of-stream "^1.4.1" @@ -2264,7 +2361,7 @@ eastasianwidth@^0.2.0: resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: +ecdsa-sig-formatter@^1.0.11, ecdsa-sig-formatter@1.0.11: version "1.0.11" resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz" integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== @@ -2301,7 +2398,7 @@ encodeurl@~1.0.2: resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -end-of-stream@^1.4.1: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -2362,7 +2459,7 @@ es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23 es-abstract@^1.23.5, es-abstract@^1.23.9, es-abstract@^1.24.0: version "1.24.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.1.tgz#f0c131ed5ea1bb2411134a8dd94def09c46c7899" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz" integrity sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw== dependencies: array-buffer-byte-length "^1.0.2" @@ -2429,7 +2526,7 @@ es-define-property@^1.0.0: es-define-property@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.2.1, es-errors@^1.3.0: @@ -2446,7 +2543,7 @@ es-object-atoms@^1.0.0: es-object-atoms@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== dependencies: es-errors "^1.3.0" @@ -2462,7 +2559,7 @@ es-set-tostringtag@^2.0.3: es-set-tostringtag@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz" integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== dependencies: es-errors "^1.3.0" @@ -2472,7 +2569,7 @@ es-set-tostringtag@^2.1.0: es-shim-unscopables@^1.0.2, es-shim-unscopables@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz#438df35520dac5d105f3943d927549ea3b00f4b5" + resolved "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz" integrity sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== dependencies: hasown "^2.0.2" @@ -2488,7 +2585,7 @@ es-to-primitive@^1.2.1: es-to-primitive@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz" integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== dependencies: is-callable "^1.2.7" @@ -2512,7 +2609,7 @@ escape-string-regexp@^4.0.0: eslint-import-resolver-node@^0.3.9: version "0.3.9" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz" integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== dependencies: debug "^3.2.7" @@ -2521,14 +2618,14 @@ eslint-import-resolver-node@^0.3.9: eslint-module-utils@^2.12.1: version "2.12.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz#f76d3220bfb83c057651359295ab5854eaad75ff" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz" integrity sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw== dependencies: debug "^3.2.7" eslint-plugin-import@^2.29.1: version "2.32.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz#602b55faa6e4caeaa5e970c198b5c00a37708980" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz" integrity sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA== dependencies: "@rtsao/scc" "^1.1.0" @@ -2553,7 +2650,7 @@ eslint-plugin-import@^2.29.1: eslint-scope@^7.2.2: version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz" integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" @@ -2561,12 +2658,12 @@ eslint-scope@^7.2.2: eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.57.0: +"eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@^8.57.0: version "8.57.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz" integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" @@ -2610,7 +2707,7 @@ eslint@^8.57.0: espree@^9.6.0, espree@^9.6.1: version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz" integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: acorn "^8.9.0" @@ -2619,21 +2716,21 @@ espree@^9.6.0, espree@^9.6.1: esquery@^1.4.2: version "1.7.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz" integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: @@ -2656,7 +2753,15 @@ events@^3.0.0, events@^3.3.0: resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -express@4.18.2: +express-validator@^7.0.0: + version "7.3.1" + resolved "https://registry.npmjs.org/express-validator/-/express-validator-7.3.1.tgz" + integrity sha512-IGenaSf+DnWc69lKuqlRE9/i/2t5/16VpH5bXoqdxWz1aCpRvEdrBuu1y95i/iL5QP8ZYVATiwLFhwk3EDl5vg== + dependencies: + lodash "^4.17.21" + validator "~13.15.23" + +"express@>=4.0.0 || >=5.0.0-beta", express@4.18.2: version "4.18.2" resolved "https://registry.npmjs.org/express/-/express-4.18.2.tgz" integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== @@ -2698,21 +2803,31 @@ extend@^3.0.2: resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +fast-copy@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz" + integrity sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-json-stable-stringify@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-safe-stringify@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + fast-xml-builder@^1.0.0, fast-xml-builder@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz" @@ -2720,6 +2835,15 @@ fast-xml-builder@^1.0.0, fast-xml-builder@^1.1.4: dependencies: path-expression-matcher "^1.1.3" +fast-xml-parser@^5.3.4: + version "5.5.6" + resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz" + integrity sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw== + dependencies: + fast-xml-builder "^1.1.4" + path-expression-matcher "^1.1.3" + strnum "^2.1.2" + fast-xml-parser@5.4.1: version "5.4.1" resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz" @@ -2728,25 +2852,16 @@ fast-xml-parser@5.4.1: fast-xml-builder "^1.0.0" strnum "^2.1.2" -fast-xml-parser@^5.3.4: - version "5.5.6" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz#6fc61f5ae06a55a1f058abd6a4f4b5d3e9972cd0" - integrity sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw== - dependencies: - fast-xml-builder "^1.1.4" - path-expression-matcher "^1.1.3" - strnum "^2.1.2" - fastq@^1.6.0: version "1.20.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz" integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== dependencies: reusify "^1.0.4" file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" @@ -2773,7 +2888,7 @@ finalhandler@1.2.0: find-up@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" @@ -2781,7 +2896,7 @@ find-up@^5.0.0: flat-cache@^3.0.4: version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz" integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== dependencies: flatted "^3.2.9" @@ -2790,17 +2905,17 @@ flat-cache@^3.0.4: flat@^5.0.2: version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatted@^3.2.9: version "3.4.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz" integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== follow-redirects@^1.15.11: version "1.15.11" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz" integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== for-each@^0.3.3: @@ -2812,7 +2927,7 @@ for-each@^0.3.3: for-each@^0.3.5: version "0.3.5" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz" integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== dependencies: is-callable "^1.2.7" @@ -2827,7 +2942,7 @@ foreground-child@^3.1.0: form-data@^2.5.5: version "2.5.5" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.5.tgz#a5f6364ad7e4e67e95b4a07e2d8c6f711c74f624" + resolved "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz" integrity sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A== dependencies: asynckit "^0.4.0" @@ -2839,7 +2954,7 @@ form-data@^2.5.5: form-data@^4.0.5: version "4.0.5" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz" integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== dependencies: asynckit "^0.4.0" @@ -2858,7 +2973,7 @@ forwarded@0.2.0: resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fresh@0.5.2, fresh@^0.5.2: +fresh@^0.5.2, fresh@0.5.2: version "0.5.2" resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== @@ -2880,7 +2995,7 @@ fs.realpath@^1.0.0: fsevents@~2.3.2: version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: @@ -2900,7 +3015,7 @@ function.prototype.name@^1.1.6: function.prototype.name@^1.1.8: version "1.1.8" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz" integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== dependencies: call-bind "^1.0.8" @@ -2917,7 +3032,7 @@ functions-have-names@^1.2.3: gaxios@^6.0.0, gaxios@^6.0.2, gaxios@^6.1.1: version "6.7.1" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.7.1.tgz#ebd9f7093ede3ba502685e73390248bb5b7f71fb" + resolved "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz" integrity sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ== dependencies: extend "^3.0.2" @@ -2928,7 +3043,7 @@ gaxios@^6.0.0, gaxios@^6.0.2, gaxios@^6.1.1: gcp-metadata@^6.1.0: version "6.1.1" - resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-6.1.1.tgz#f65aa69f546bc56e116061d137d3f5f90bdec494" + resolved "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz" integrity sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A== dependencies: gaxios "^6.1.1" @@ -2944,7 +3059,7 @@ generate-function@^2.3.1: generator-function@^2.0.0: version "2.0.1" - resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2" + resolved "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz" integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g== get-caller-file@^2.0.5: @@ -2952,7 +3067,29 @@ get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: +get-intrinsic@^1.1.3: + version "1.2.4" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-intrinsic@^1.2.1: + version "1.2.4" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-intrinsic@^1.2.3, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz" integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== @@ -2965,7 +3102,7 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@ get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: call-bind-apply-helpers "^1.0.2" @@ -2981,7 +3118,7 @@ get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@ get-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== dependencies: dunder-proto "^1.0.1" @@ -2998,7 +3135,7 @@ get-symbol-description@^1.0.2: get-symbol-description@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz" integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== dependencies: call-bound "^1.0.3" @@ -3007,7 +3144,7 @@ get-symbol-description@^1.1.0: glob-parent@^6.0.2: version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: is-glob "^4.0.3" @@ -3019,21 +3156,9 @@ glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob@7.1.6: - version "7.1.6" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^10.4.2: version "10.5.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" + resolved "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz" integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== dependencies: foreground-child "^3.1.0" @@ -3057,7 +3182,7 @@ glob@^7.1.3: glob@^8.1.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== dependencies: fs.realpath "^1.0.0" @@ -3066,9 +3191,21 @@ glob@^8.1.0: minimatch "^5.0.1" once "^1.3.0" +glob@7.1.6: + version "7.1.6" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + globals@^13.19.0: version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + resolved "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz" integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2" @@ -3083,7 +3220,7 @@ globalthis@^1.0.3, globalthis@^1.0.4: google-auth-library@^9.6.3: version "9.15.1" - resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-9.15.1.tgz#0c5d84ed1890b2375f1cd74f03ac7b806b392928" + resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz" integrity sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng== dependencies: base64-js "^1.3.0" @@ -3095,7 +3232,7 @@ google-auth-library@^9.6.3: google-logging-utils@^0.0.2: version "0.0.2" - resolved "https://registry.yarnpkg.com/google-logging-utils/-/google-logging-utils-0.0.2.tgz#5fd837e06fa334da450433b9e3e1870c1594466a" + resolved "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz" integrity sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ== gopd@^1.0.1: @@ -3107,7 +3244,7 @@ gopd@^1.0.1: gopd@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== graceful-fs@^4.1.6, graceful-fs@^4.2.0: @@ -3117,12 +3254,12 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0: graphemer@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== gtoken@^7.0.0: version "7.1.0" - resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26" + resolved "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz" integrity sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw== dependencies: gaxios "^6.0.0" @@ -3157,7 +3294,7 @@ has-proto@^1.0.1, has-proto@^1.0.3: has-proto@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz" integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== dependencies: dunder-proto "^1.0.0" @@ -3169,7 +3306,7 @@ has-symbols@^1.0.2, has-symbols@^1.0.3: has-symbols@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: @@ -3188,17 +3325,22 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: he@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== helmet@^8.0.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-8.1.0.tgz#f96d23fedc89e9476ecb5198181009c804b8b38c" + resolved "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz" integrity sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg== +help-me@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz" + integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg== + html-entities@^2.5.2: version "2.6.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.6.0.tgz#7c64f1ea3b36818ccae3d3fb48b6974208e984f8" + resolved "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz" integrity sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ== http-errors@2.0.0: @@ -3247,12 +3389,26 @@ https-proxy-agent@^7.0.0: https-proxy-agent@^7.0.1: version "7.0.6" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz" integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== dependencies: agent-base "^7.1.2" debug "4" +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" @@ -3260,13 +3416,6 @@ iconv-lite@0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2, iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" @@ -3279,12 +3428,12 @@ ignore-by-default@^1.0.1: ignore@^5.2.0: version "5.3.2" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== import-fresh@^3.2.1: version "3.3.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz" integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== dependencies: parent-module "^1.0.0" @@ -3308,7 +3457,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: +inherits@^2.0.3, inherits@^2.0.4, inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3329,7 +3478,7 @@ internal-slot@^1.0.7: internal-slot@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz" integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== dependencies: es-errors "^1.3.0" @@ -3351,7 +3500,7 @@ is-array-buffer@^3.0.4: is-array-buffer@^3.0.5: version "3.0.5" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz" integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== dependencies: call-bind "^1.0.8" @@ -3360,7 +3509,7 @@ is-array-buffer@^3.0.5: is-async-function@^2.0.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + resolved "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz" integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== dependencies: async-function "^1.0.0" @@ -3378,7 +3527,7 @@ is-bigint@^1.0.1: is-bigint@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz" integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== dependencies: has-bigints "^1.0.2" @@ -3400,7 +3549,7 @@ is-boolean-object@^1.1.0: is-boolean-object@^1.2.1: version "1.2.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz" integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== dependencies: call-bound "^1.0.3" @@ -3420,7 +3569,7 @@ is-core-module@^2.13.0: is-core-module@^2.16.1: version "2.16.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== dependencies: hasown "^2.0.2" @@ -3434,7 +3583,7 @@ is-data-view@^1.0.1: is-data-view@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + resolved "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz" integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== dependencies: call-bound "^1.0.2" @@ -3450,7 +3599,7 @@ is-date-object@^1.0.1: is-date-object@^1.0.5, is-date-object@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz" integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== dependencies: call-bound "^1.0.2" @@ -3468,7 +3617,7 @@ is-extglob@^2.1.1: is-finalizationregistry@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + resolved "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz" integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== dependencies: call-bound "^1.0.3" @@ -3480,7 +3629,7 @@ is-fullwidth-code-point@^3.0.0: is-generator-function@^1.0.10: version "1.1.2" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5" + resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz" integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA== dependencies: call-bound "^1.0.4" @@ -3515,7 +3664,7 @@ is-number-object@^1.0.4: is-number-object@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz" integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== dependencies: call-bound "^1.0.3" @@ -3533,7 +3682,7 @@ is-path-inside@^3.0.3: is-plain-obj@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== is-property@^1.0.2: @@ -3551,7 +3700,7 @@ is-regex@^1.1.4: is-regex@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz" integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== dependencies: call-bound "^1.0.2" @@ -3573,7 +3722,7 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: is-shared-array-buffer@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz" integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== dependencies: call-bound "^1.0.3" @@ -3592,7 +3741,7 @@ is-string@^1.0.5, is-string@^1.0.7: is-string@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + resolved "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz" integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== dependencies: call-bound "^1.0.3" @@ -3605,9 +3754,18 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-symbol@^1.0.4, is-symbol@^1.1.1: +is-symbol@^1.0.4: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz" integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== dependencies: call-bound "^1.0.2" @@ -3621,21 +3779,28 @@ is-typed-array@^1.1.13: dependencies: which-typed-array "^1.1.14" -is-typed-array@^1.1.14, is-typed-array@^1.1.15: +is-typed-array@^1.1.14: version "1.1.15" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + +is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz" integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== dependencies: which-typed-array "^1.1.16" is-unicode-supported@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== is-weakmap@^2.0.2: version "2.0.2" - resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz" integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== is-weakref@^1.0.2: @@ -3647,14 +3812,14 @@ is-weakref@^1.0.2: is-weakref@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz" integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== dependencies: call-bound "^1.0.3" is-weakset@^2.0.3: version "2.0.4" - resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + resolved "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz" integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== dependencies: call-bound "^1.0.3" @@ -3686,9 +3851,25 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +joi@^17.13.0: + version "17.13.3" + resolved "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz" + integrity sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + +joycon@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz" + integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== + js-beautify@1.15.4: version "1.15.4" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.15.4.tgz#f579f977ed4c930cef73af8f98f3f0a608acd51e" + resolved "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz" integrity sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA== dependencies: config-chain "^1.1.13" @@ -3723,17 +3904,17 @@ json-bigint@^1.0.0: json-buffer@3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json2csv@^5.0.7: @@ -3747,7 +3928,7 @@ json2csv@^5.0.7: json5@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + resolved "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz" integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" @@ -3818,14 +3999,14 @@ jws@^4.0.0: keyv@^4.5.3: version "4.5.4" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== dependencies: json-buffer "3.0.1" levn@^0.4.1: version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" @@ -3880,7 +4061,7 @@ lodash.isstring@^4.0.1: lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash.mergewith@^4.6.2: @@ -3893,19 +4074,14 @@ lodash.once@^4.0.0: resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -lodash@^4.17.23: +lodash@^4.17.21, lodash@^4.17.23: version "4.17.23" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz" integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== log-symbols@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: chalk "^4.1.0" @@ -3935,7 +4111,7 @@ lru-cache@^7.14.1: math-intrinsics@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + resolved "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz" integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== media-typer@0.3.0: @@ -3943,16 +4119,16 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - merge-descriptors@^1.0.1: version "1.0.3" resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz" integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" @@ -3965,12 +4141,12 @@ mime-db@1.52.0: mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" -mime@1.6.0, mime@^1.3.4: +mime@^1.3.4, mime@1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -3980,16 +4156,9 @@ mime@^3.0.0: resolved "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz" integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== -minimatch@9.0.1: - version "9.0.1" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz" - integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w== - dependencies: - brace-expansion "^2.0.1" - minimatch@^10.2.1: version "10.2.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz" integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== dependencies: brace-expansion "^5.0.2" @@ -4003,7 +4172,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: minimatch@^5.0.1, minimatch@^5.1.6: version "5.1.9" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.9.tgz#1293ef15db0098b394540e8f9f744f9fda8dee4b" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz" integrity sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw== dependencies: brace-expansion "^2.0.1" @@ -4015,6 +4184,13 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" +minimatch@9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz" + integrity sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" @@ -4027,7 +4203,7 @@ minimist@^1.2.0, minimist@^1.2.6: mocha@^10.0.0: version "10.8.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.8.2.tgz#8d8342d016ed411b12a429eb731b825f961afb96" + resolved "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz" integrity sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg== dependencies: ansi-colors "^4.1.3" @@ -4058,11 +4234,16 @@ moment-timezone@^0.5.43: dependencies: moment "^2.29.4" -moment@2.30.1, moment@^2.29.4: +moment@^2.29.4, moment@2.30.1: version "2.30.1" resolved "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz" integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== +ms@^2.1.1, ms@^2.1.3, ms@2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + ms@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" @@ -4073,14 +4254,9 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - multer@^2.0.0: version "2.1.1" - resolved "https://registry.yarnpkg.com/multer/-/multer-2.1.1.tgz#122d819244fbdfee1efddd9147426691014385b7" + resolved "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz" integrity sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A== dependencies: append-field "^1.0.0" @@ -4116,7 +4292,7 @@ native-duplexpair@^1.0.0: natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== negotiator@0.6.3: @@ -4126,24 +4302,24 @@ negotiator@0.6.3: node-addon-api@^8.3.0: version "8.6.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.6.0.tgz#b22497201b465cd0a92ef2c01074ee5068c79a6d" + resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.6.0.tgz" integrity sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q== node-fetch@^2.6.9: version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" node-gyp-build@^4.8.4: version "4.8.4" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8" + resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz" integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ== node-mocks-http@^1.17.0: version "1.17.2" - resolved "https://registry.yarnpkg.com/node-mocks-http/-/node-mocks-http-1.17.2.tgz#e947d9b94defb13e3775414a8200c848f6b2fc74" + resolved "https://registry.npmjs.org/node-mocks-http/-/node-mocks-http-1.17.2.tgz" integrity sha512-HVxSnjNzE9NzoWMx9T9z4MLqwMpLwVvA0oVZ+L+gXskYXEJ6tFn3Kx4LargoB6ie7ZlCLplv7QbWO6N+MysWGA== dependencies: accepts "^1.3.7" @@ -4164,7 +4340,7 @@ nodemailer@6.9.9: nodemon@^3.0.0: version "3.1.14" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.14.tgz#8487ca379c515301d221ec007f27f24ecafa2b51" + resolved "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz" integrity sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw== dependencies: chokidar "^3.5.2" @@ -4180,7 +4356,7 @@ nodemon@^3.0.0: nopt@^7.2.1: version "7.2.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-7.2.1.tgz#1cac0eab9b8e97c9093338446eddd40b2c8ca1e7" + resolved "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz" integrity sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w== dependencies: abbrev "^2.0.0" @@ -4207,7 +4383,7 @@ object-inspect@^1.13.1: object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== object-keys@^1.1.1: @@ -4227,7 +4403,7 @@ object.assign@^4.1.5: object.assign@^4.1.7: version "4.1.7" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz" integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== dependencies: call-bind "^1.0.8" @@ -4239,7 +4415,7 @@ object.assign@^4.1.7: object.fromentries@^2.0.8: version "2.0.8" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz" integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== dependencies: call-bind "^1.0.7" @@ -4249,7 +4425,7 @@ object.fromentries@^2.0.8: object.groupby@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + resolved "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz" integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== dependencies: call-bind "^1.0.7" @@ -4258,7 +4434,7 @@ object.groupby@^1.0.3: object.values@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" + resolved "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz" integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== dependencies: call-bind "^1.0.8" @@ -4266,6 +4442,11 @@ object.values@^1.2.1: define-properties "^1.2.1" es-object-atoms "^1.0.0" +on-exit-leak-free@^2.1.0: + version "2.1.2" + resolved "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz" + integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== + on-finished@2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" @@ -4273,7 +4454,7 @@ on-finished@2.4.1: dependencies: ee-first "1.1.1" -once@^1.3.0, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -4289,9 +4470,14 @@ open@^8.0.0: is-docker "^2.1.1" is-wsl "^2.2.0" +openapi-types@>=7: + version "12.1.3" + resolved "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz" + integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== + optionator@^0.9.3: version "0.9.4" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== dependencies: deep-is "^0.1.3" @@ -4303,7 +4489,7 @@ optionator@^0.9.3: own-keys@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + resolved "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz" integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== dependencies: get-intrinsic "^1.2.6" @@ -4331,7 +4517,7 @@ package-json-from-dist@^1.0.0: parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" @@ -4358,12 +4544,12 @@ passport-jwt@^4.0.1: passport-microsoft@^2.0.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/passport-microsoft/-/passport-microsoft-2.1.0.tgz#7ff80219fd9fd3f572f0900d7dd3f477e37d66cb" + resolved "https://registry.npmjs.org/passport-microsoft/-/passport-microsoft-2.1.0.tgz" integrity sha512-7bOcjEmZCHg5qD55iHaMD/mgBxPtXLbqAwmKox5IsqOSEU50WJk5nQKK4lxKdBHLZ0hf+gzrFgDsTybJP18/JA== dependencies: passport-oauth2 "1.8.0" -passport-oauth2@1.8.0, passport-oauth2@^1.1.2: +passport-oauth2@^1.1.2, passport-oauth2@1.8.0: version "1.8.0" resolved "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz" integrity sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA== @@ -4374,7 +4560,7 @@ passport-oauth2@1.8.0, passport-oauth2@^1.1.2: uid2 "0.0.x" utils-merge "1.x.x" -passport-strategy@1.x.x, passport-strategy@^1.0.0: +passport-strategy@^1.0.0, passport-strategy@1.x.x: version "1.0.0" resolved "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz" integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== @@ -4433,12 +4619,12 @@ pause@0.0.1: pg-cloudflare@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz#386035d4bfcf1a7045b026f8b21acf5353f14d65" + resolved "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz" integrity sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ== pg-connection-string@^2.12.0: version "2.12.0" - resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.12.0.tgz#4084f917902bb2daae3dc1376fe24ac7b4eaccf2" + resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz" integrity sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ== pg-connection-string@^2.6.1: @@ -4460,17 +4646,17 @@ pg-int8@1.0.1: pg-pool@^3.13.0: version "3.13.0" - resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.13.0.tgz#416482e9700e8f80c685a6ae5681697a413c13a3" + resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz" integrity sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA== pg-protocol@^1.13.0: version "1.13.0" - resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.13.0.tgz#fdaf6d020bca590d58bb991b4b16fc448efe0511" + resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz" integrity sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w== pg-types@2.2.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + resolved "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz" integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== dependencies: pg-int8 "1.0.1" @@ -4479,9 +4665,9 @@ pg-types@2.2.0: postgres-date "~1.0.4" postgres-interval "^1.1.0" -pg@^8.20.0: +pg@^8.20.0, pg@>=8.0: version "8.20.0" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.20.0.tgz#1a274de944cb329fd6dd77a6d371a005ba6b136d" + resolved "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz" integrity sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA== dependencies: pg-connection-string "^2.12.0" @@ -4494,14 +4680,14 @@ pg@^8.20.0: pgpass@1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + resolved "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz" integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== dependencies: split2 "^4.1.0" picocolors@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.1: @@ -4509,6 +4695,55 @@ picomatch@^2.0.4, picomatch@^2.2.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pino-abstract-transport@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz" + integrity sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw== + dependencies: + split2 "^4.0.0" + +pino-pretty@^11.0.0: + version "11.3.0" + resolved "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.3.0.tgz" + integrity sha512-oXwn7ICywaZPHmu3epHGU2oJX4nPmKvHvB/bwrJHlGcbEWaVcotkpyVHMKLKmiVryWYByNp0jpgAcXpFJDXJzA== + dependencies: + colorette "^2.0.7" + dateformat "^4.6.3" + fast-copy "^3.0.2" + fast-safe-stringify "^2.1.1" + help-me "^5.0.0" + joycon "^3.1.1" + minimist "^1.2.6" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^2.0.0" + pump "^3.0.0" + readable-stream "^4.0.0" + secure-json-parse "^2.4.0" + sonic-boom "^4.0.1" + strip-json-comments "^3.1.1" + +pino-std-serializers@^7.0.0: + version "7.1.0" + resolved "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz" + integrity sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw== + +pino@^9.0.0: + version "9.14.0" + resolved "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz" + integrity sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w== + dependencies: + "@pinojs/redact" "^0.4.0" + atomic-sleep "^1.0.0" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^2.0.0" + pino-std-serializers "^7.0.0" + process-warning "^5.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^4.0.1" + thread-stream "^3.0.0" + possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz" @@ -4538,9 +4773,14 @@ postgres-interval@^1.1.0: prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +process-warning@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz" + integrity sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA== + process@^0.11.10: version "0.11.10" resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" @@ -4566,12 +4806,20 @@ proxy-from-env@^1.1.0: pstree.remy@^1.1.8: version "1.1.8" - resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" + resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz" integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== +pump@^3.0.0: + version "3.0.4" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz" + integrity sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0: version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== qs@6.11.0: @@ -4583,9 +4831,14 @@ qs@6.11.0: queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" @@ -4617,6 +4870,17 @@ readable-stream@^3.0.2, readable-stream@^3.1.1: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@^4.0.0: + version "4.7.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz" + integrity sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + readable-stream@^4.2.0: version "4.5.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz" @@ -4630,7 +4894,7 @@ readable-stream@^4.2.0: readdirp@^4.0.1: version "4.1.2" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz" integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== readdirp@~3.6.0: @@ -4640,9 +4904,14 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +real-require@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz" + integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== + reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: version "1.0.10" - resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz" integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== dependencies: call-bind "^1.0.8" @@ -4666,7 +4935,7 @@ regexp.prototype.flags@^1.5.2: regexp.prototype.flags@^1.5.4: version "1.5.4" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz" integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== dependencies: call-bind "^1.0.8" @@ -4683,7 +4952,7 @@ require-directory@^2.1.1: resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== resolve@^1.22.1: @@ -4697,7 +4966,7 @@ resolve@^1.22.1: resolve@^1.22.4: version "1.22.11" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz" integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: is-core-module "^2.16.1" @@ -4711,7 +4980,7 @@ retry-as-promised@^7.0.4: retry-request@^7.0.0: version "7.0.2" - resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-7.0.2.tgz#60bf48cfb424ec01b03fca6665dee91d06dd95f3" + resolved "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz" integrity sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w== dependencies: "@types/request" "^2.48.8" @@ -4725,7 +4994,7 @@ retry@0.13.1: reusify@^1.0.4: version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz" integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== rimraf@^3.0.2: @@ -4737,7 +5006,7 @@ rimraf@^3.0.2: run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" @@ -4754,7 +5023,7 @@ safe-array-concat@^1.1.2: safe-array-concat@^1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz" integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== dependencies: call-bind "^1.0.8" @@ -4763,7 +5032,7 @@ safe-array-concat@^1.1.3: has-symbols "^1.1.0" isarray "^2.0.5" -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0, safe-buffer@5.2.1: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -4775,7 +5044,7 @@ safe-buffer@~5.1.1: safe-push-apply@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + resolved "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz" integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== dependencies: es-errors "^1.3.0" @@ -4792,18 +5061,28 @@ safe-regex-test@^1.0.3: safe-regex-test@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz" integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== dependencies: call-bound "^1.0.2" es-errors "^1.3.0" is-regex "^1.2.1" +safe-stable-stringify@^2.3.1: + version "2.5.0" + resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" + integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +secure-json-parse@^2.4.0: + version "2.7.0" + resolved "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz" + integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== + semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" @@ -4840,7 +5119,7 @@ seq-queue@^0.0.5: sequelize-cli@^6.6.5: version "6.6.5" - resolved "https://registry.yarnpkg.com/sequelize-cli/-/sequelize-cli-6.6.5.tgz#5377d42116f10c793780132f7423ffc5285711fb" + resolved "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-6.6.5.tgz" integrity sha512-DqyISCULOaEbTM+rRQH4YvcUWeOC1XDiSKcjsC6TfAnT7W837mNkChJhtB/Z4FdCFHRCojmiP7zsrA4pARmacA== dependencies: fs-extra "^9.1.0" @@ -4861,9 +5140,9 @@ sequelize-pool@^7.1.0: resolved "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz" integrity sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg== -sequelize@^6.37.0: +sequelize@^6.37.0, "sequelize@>= 4": version "6.37.8" - resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-6.37.8.tgz#70e62c9e682a2009005093104591e59ec2d0ed2c" + resolved "https://registry.npmjs.org/sequelize/-/sequelize-6.37.8.tgz" integrity sha512-HJ0IQFqcTsTiqbEgiuioYFMSD00TP6Cz7zoTti+zVVBwVe9fEhev9cH6WnM3XU31+ABS356durAb99ZuOthnKw== dependencies: "@types/debug" "^4.1.8" @@ -4885,7 +5164,7 @@ sequelize@^6.37.0: serialize-javascript@^6.0.2: version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz" integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" @@ -4924,7 +5203,7 @@ set-function-name@^2.0.1, set-function-name@^2.0.2: set-proto@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + resolved "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz" integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== dependencies: dunder-proto "^1.0.1" @@ -4950,7 +5229,7 @@ shebang-regex@^3.0.0: side-channel-list@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz" integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: es-errors "^1.3.0" @@ -4958,7 +5237,7 @@ side-channel-list@^1.0.0: side-channel-map@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + resolved "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz" integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== dependencies: call-bound "^1.0.2" @@ -4968,7 +5247,7 @@ side-channel-map@^1.0.1: side-channel-weakmap@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + resolved "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz" integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== dependencies: call-bound "^1.0.2" @@ -4989,7 +5268,7 @@ side-channel@^1.0.4: side-channel@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz" integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== dependencies: es-errors "^1.3.0" @@ -5005,12 +5284,19 @@ signal-exit@^4.0.1: simple-update-notifier@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + resolved "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz" integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== dependencies: semver "^7.5.3" -split2@^4.1.0: +sonic-boom@^4.0.1: + version "4.2.1" + resolved "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz" + integrity sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q== + dependencies: + atomic-sleep "^1.0.0" + +split2@^4.0.0, split2@^4.1.0: version "4.2.0" resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== @@ -5037,7 +5323,7 @@ statuses@2.0.1: stop-iteration-iterator@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz" integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== dependencies: es-errors "^1.3.0" @@ -5062,9 +5348,16 @@ stream-shift@^1.0.2: streamsearch@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== +string_decoder@^1.1.1, string_decoder@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" @@ -5094,7 +5387,7 @@ string-width@^5.0.1, string-width@^5.1.2: string.prototype.trim@^1.2.10: version "1.2.10" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + resolved "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz" integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== dependencies: call-bind "^1.0.8" @@ -5126,7 +5419,7 @@ string.prototype.trimend@^1.0.8: string.prototype.trimend@^1.0.9: version "1.0.9" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz" integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== dependencies: call-bind "^1.0.8" @@ -5143,13 +5436,6 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.1.1, string_decoder@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - "strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -5173,12 +5459,12 @@ strip-ansi@^7.0.1: strip-bom@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== strnum@^2.1.2: @@ -5207,7 +5493,7 @@ supports-color@^7.1.0: supports-color@^8.1.1: version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" @@ -5250,7 +5536,7 @@ swagger-ui-express@^5.0.0: tedious@^18.6.0: version "18.6.2" - resolved "https://registry.yarnpkg.com/tedious/-/tedious-18.6.2.tgz#c7da62ae11b0961177559b1c4f1c4d8dfa75ec2c" + resolved "https://registry.npmjs.org/tedious/-/tedious-18.6.2.tgz" integrity sha512-g7jC56o3MzLkE3lHkaFe2ZdOVFBahq5bsB60/M4NYUbocw/MCrS89IOEQUFr+ba6pb8ZHczZ/VqCyYeYq0xBAg== dependencies: "@azure/core-auth" "^1.7.2" @@ -5266,7 +5552,7 @@ tedious@^18.6.0: teeny-request@^9.0.0: version "9.0.0" - resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-9.0.0.tgz#18140de2eb6595771b1b02203312dfad79a4716d" + resolved "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz" integrity sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g== dependencies: http-proxy-agent "^5.0.0" @@ -5277,9 +5563,16 @@ teeny-request@^9.0.0: text-table@^0.2.0: version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thread-stream@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz" + integrity sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A== + dependencies: + real-require "^0.2.0" + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" @@ -5309,7 +5602,7 @@ tr46@~0.0.3: tsconfig-paths@^3.15.0: version "3.15.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz" integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== dependencies: "@types/json5" "^0.0.29" @@ -5324,14 +5617,14 @@ tslib@^2.2.0, tslib@^2.6.2: type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== type-is@^1.6.18, type-is@~1.6.18: @@ -5353,7 +5646,7 @@ typed-array-buffer@^1.0.2: typed-array-buffer@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz" integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== dependencies: call-bound "^1.0.3" @@ -5373,7 +5666,7 @@ typed-array-byte-length@^1.0.1: typed-array-byte-length@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + resolved "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz" integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== dependencies: call-bind "^1.0.8" @@ -5396,7 +5689,7 @@ typed-array-byte-offset@^1.0.2: typed-array-byte-offset@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + resolved "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz" integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== dependencies: available-typed-arrays "^1.0.7" @@ -5421,7 +5714,7 @@ typed-array-length@^1.0.6: typed-array-length@^1.0.7: version "1.0.7" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz" integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== dependencies: call-bind "^1.0.7" @@ -5460,7 +5753,7 @@ unbox-primitive@^1.0.2: unbox-primitive@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz" integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== dependencies: call-bound "^1.0.3" @@ -5470,7 +5763,7 @@ unbox-primitive@^1.1.0: undefsafe@^2.0.5: version "2.0.5" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" + resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== underscore@^1.13.1: @@ -5488,14 +5781,14 @@ universalify@^2.0.0: resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== -unpipe@1.0.0, unpipe@~1.0.0: +unpipe@~1.0.0, unpipe@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" @@ -5505,7 +5798,7 @@ util-deprecate@^1.0.1: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -utils-merge@1.0.1, utils-merge@1.x.x, utils-merge@^1.0.1: +utils-merge@^1.0.1, utils-merge@1.0.1, utils-merge@1.x.x: version "1.0.1" resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== @@ -5515,15 +5808,20 @@ uuid@^8.0.0, uuid@^8.3.0, uuid@^8.3.2: resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@^9.0.0, uuid@^9.0.1: +uuid@^9.0.0: version "9.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== -validator@^13.7.0, validator@^13.9.0: - version "13.12.0" - resolved "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz" - integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + +validator@^13.7.0, validator@^13.9.0, validator@~13.15.23: + version "13.15.26" + resolved "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz" + integrity sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA== vary@^1, vary@~1.1.2: version "1.1.2" @@ -5556,7 +5854,7 @@ which-boxed-primitive@^1.0.2: which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz" integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== dependencies: is-bigint "^1.1.0" @@ -5567,7 +5865,7 @@ which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: which-builtin-type@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + resolved "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz" integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== dependencies: call-bound "^1.0.2" @@ -5586,7 +5884,7 @@ which-builtin-type@^1.2.1: which-collection@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz" integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== dependencies: is-map "^2.0.3" @@ -5594,7 +5892,18 @@ which-collection@^1.0.2: is-weakmap "^2.0.2" is-weakset "^2.0.3" -which-typed-array@^1.1.14, which-typed-array@^1.1.15: +which-typed-array@^1.1.14: + version "1.1.15" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz" + integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.2" + +which-typed-array@^1.1.15: version "1.1.15" resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz" integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== @@ -5607,7 +5916,7 @@ which-typed-array@^1.1.14, which-typed-array@^1.1.15: which-typed-array@^1.1.16, which-typed-array@^1.1.19: version "1.1.20" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.20.tgz#3fdb7adfafe0ea69157b1509f3a1cd892bd1d122" + resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz" integrity sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg== dependencies: available-typed-arrays "^1.0.7" @@ -5634,12 +5943,12 @@ wkx@^0.5.0: word-wrap@^1.2.5: version "1.2.5" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== workerpool@^6.5.1: version "6.5.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": @@ -5701,7 +6010,7 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.9: yargs-unparser@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== dependencies: camelcase "^6.0.0" diff --git a/frontend/package.json b/frontend/package.json index ade1134..94ac57a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -52,7 +52,8 @@ "react-switch": "^7.0.0", "react-toastify": "^11.0.2", "swr": "^2.0.0", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "zod": "^4.3.6" }, "devDependencies": { "@tailwindcss/forms": "^0.5.7", diff --git a/frontend/public/offline.html b/frontend/public/offline.html new file mode 100644 index 0000000..42dac6f --- /dev/null +++ b/frontend/public/offline.html @@ -0,0 +1,99 @@ + + + + + + Offline - Tour Builder + + + +
+ + + + + + +

You're Offline

+ +

+ It looks like you've lost your internet connection. + Some features may not be available until you're back online. +

+ + +
+ + + + diff --git a/frontend/public/sw.js b/frontend/public/sw.js new file mode 100644 index 0000000..d5a09ec --- /dev/null +++ b/frontend/public/sw.js @@ -0,0 +1,210 @@ +/** + * Tour Builder Platform - Service Worker + * + * Provides offline caching for PWA functionality. + * Caches tour assets (images, videos, audio) for offline viewing. + */ + +const CACHE_NAME = 'tour-builder-v1'; +const STATIC_CACHE_NAME = 'tour-builder-static-v1'; +const DYNAMIC_CACHE_NAME = 'tour-builder-dynamic-v1'; + +// Static assets to cache on install +const STATIC_ASSETS = [ + '/', + '/runtime', + '/offline.html', +]; + +// Asset types to cache +const CACHEABLE_EXTENSIONS = [ + '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico', + '.mp4', '.webm', '.mov', + '.mp3', '.wav', '.ogg', '.m4a', + '.woff', '.woff2', '.ttf', '.eot', + '.css', '.js', +]; + +// Check if request should be cached +const isCacheableRequest = (request) => { + const url = new URL(request.url); + + // Don't cache API requests (except static assets from /file/download) + if (url.pathname.startsWith('/api/') && !url.pathname.includes('/file/download')) { + return false; + } + + // Cache known asset extensions + const hasExtension = CACHEABLE_EXTENSIONS.some((ext) => url.pathname.toLowerCase().endsWith(ext)); + if (hasExtension) { + return true; + } + + // Cache file downloads (S3 presigned URLs, CDN assets) + if (url.pathname.includes('/file/download') || url.hostname.includes('amazonaws.com') || url.hostname.includes('cloudfront.net')) { + return true; + } + + return false; +}; + +// Install event - cache static assets +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(STATIC_CACHE_NAME).then((cache) => { + console.log('[SW] Caching static assets'); + return cache.addAll(STATIC_ASSETS).catch((error) => { + console.warn('[SW] Failed to cache some static assets:', error); + }); + }) + ); + self.skipWaiting(); +}); + +// Activate event - clean up old caches +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames + .filter((name) => { + return name.startsWith('tour-builder-') && name !== STATIC_CACHE_NAME && name !== DYNAMIC_CACHE_NAME; + }) + .map((name) => { + console.log('[SW] Deleting old cache:', name); + return caches.delete(name); + }) + ); + }) + ); + self.clients.claim(); +}); + +// Fetch event - serve from cache or network +self.addEventListener('fetch', (event) => { + const { request } = event; + + // Only handle GET requests + if (request.method !== 'GET') { + return; + } + + // Skip non-http(s) requests + if (!request.url.startsWith('http')) { + return; + } + + event.respondWith( + caches.match(request).then((cachedResponse) => { + // Return cached response if available + if (cachedResponse) { + // Optionally update cache in background (stale-while-revalidate) + if (isCacheableRequest(request)) { + event.waitUntil( + fetch(request) + .then((networkResponse) => { + if (networkResponse && networkResponse.status === 200) { + const responseToCache = networkResponse.clone(); + caches.open(DYNAMIC_CACHE_NAME).then((cache) => { + cache.put(request, responseToCache); + }); + } + }) + .catch(() => { + // Network failed, but we have cache - that's fine + }) + ); + } + return cachedResponse; + } + + // Fetch from network + return fetch(request) + .then((networkResponse) => { + // Cache successful responses for cacheable requests + if (networkResponse && networkResponse.status === 200 && isCacheableRequest(request)) { + const responseToCache = networkResponse.clone(); + caches.open(DYNAMIC_CACHE_NAME).then((cache) => { + cache.put(request, responseToCache); + }); + } + return networkResponse; + }) + .catch((error) => { + console.warn('[SW] Fetch failed:', error); + + // Return offline page for navigation requests + if (request.mode === 'navigate') { + return caches.match('/offline.html'); + } + + // Return empty response for assets + return new Response('', { + status: 503, + statusText: 'Service Unavailable', + }); + }); + }) + ); +}); + +// Message event - handle commands from main thread +self.addEventListener('message', (event) => { + const { type, payload } = event.data || {}; + + switch (type) { + case 'CACHE_ASSETS': + // Cache specific assets for a project/page + if (Array.isArray(payload?.urls)) { + event.waitUntil( + caches.open(DYNAMIC_CACHE_NAME).then((cache) => { + return Promise.all( + payload.urls.map((url) => + fetch(url) + .then((response) => { + if (response.status === 200) { + return cache.put(url, response); + } + }) + .catch((error) => { + console.warn('[SW] Failed to cache asset:', url, error); + }) + ) + ); + }) + ); + } + break; + + case 'CLEAR_CACHE': + // Clear all dynamic caches + event.waitUntil( + caches.delete(DYNAMIC_CACHE_NAME).then(() => { + console.log('[SW] Dynamic cache cleared'); + }) + ); + break; + + case 'GET_CACHE_STATUS': + // Return current cache status + event.waitUntil( + caches.open(DYNAMIC_CACHE_NAME).then((cache) => { + return cache.keys().then((keys) => { + event.source.postMessage({ + type: 'CACHE_STATUS', + payload: { + cachedCount: keys.length, + urls: keys.map((request) => request.url), + }, + }); + }); + }) + ); + break; + + default: + break; + } +}); + +console.log('[SW] Service worker loaded'); diff --git a/frontend/src/colors.ts b/frontend/src/colors.ts index 71e116a..0fea7b8 100644 --- a/frontend/src/colors.ts +++ b/frontend/src/colors.ts @@ -1,21 +1,22 @@ -import type { ColorButtonKey } from './interfaces' +import type { ColorButtonKey } from './interfaces'; -export const gradientBgBase = 'bg-gradient-to-tr' -export const colorBgBase = "bg-violet-50/50" -export const gradientBgPurplePink = `${gradientBgBase} from-purple-400 via-pink-500 to-red-500` -export const gradientBgViolet = `${gradientBgBase} ${colorBgBase}` +export const gradientBgBase = 'bg-gradient-to-tr'; +export const colorBgBase = 'bg-violet-50/50'; +export const gradientBgPurplePink = `${gradientBgBase} from-purple-400 via-pink-500 to-red-500`; +export const gradientBgViolet = `${gradientBgBase} ${colorBgBase}`; export const gradientBgDark = `${gradientBgBase} from-dark-700 via-dark-900 to-dark-800`; -export const gradientBgPinkRed = `${gradientBgBase} from-pink-400 via-red-500 to-yellow-500` +export const gradientBgPinkRed = `${gradientBgBase} from-pink-400 via-red-500 to-yellow-500`; export const colorsBgLight = { white: 'bg-white text-black', light: ' bg-white text-black text-black dark:bg-dark-900 dark:text-white', contrast: 'bg-gray-800 text-white dark:bg-white dark:text-black', - success: 'bg-emerald-500 border-emerald-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white', + success: + 'bg-emerald-500 border-emerald-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white', danger: 'bg-red-500 border-red-500 text-white', warning: 'bg-yellow-500 border-yellow-500 text-white', info: 'bg-blue-500 border-blue-500 dark:bg-pavitra-blue dark:border-pavitra-blue text-white', -} +}; export const colorsText = { white: 'text-black dark:text-slate-100', @@ -30,7 +31,9 @@ export const colorsText = { export const colorsOutline = { white: [colorsText.white, 'border-gray-100'].join(' '), light: [colorsText.light, 'border-gray-100'].join(' '), - contrast: [colorsText.contrast, 'border-gray-900 dark:border-slate-100'].join(' '), + contrast: [colorsText.contrast, 'border-gray-900 dark:border-slate-100'].join( + ' ', + ), success: [colorsText.success, 'border-emerald-500'].join(' '), danger: [colorsText.danger, 'border-red-500'].join(' '), warning: [colorsText.warning, 'border-yellow-500'].join(' '), @@ -41,10 +44,10 @@ export const getButtonColor = ( color: ColorButtonKey, isOutlined: boolean, hasHover: boolean, - isActive = false + isActive = false, ) => { if (color === 'void') { - return '' + return ''; } const colors = { @@ -56,7 +59,7 @@ export const getButtonColor = ( success: 'ring-emerald-300 dark:ring-pavitra-blue', danger: 'ring-red-300 dark:ring-red-700', warning: 'ring-yellow-300 dark:ring-yellow-700', - info: "ring-blue-300 dark:ring-pavitra-blue", + info: 'ring-blue-300 dark:ring-pavitra-blue', }, active: { white: 'bg-gray-100', @@ -76,7 +79,7 @@ export const getButtonColor = ( success: 'bg-emerald-600 dark:bg-pavitra-blue text-white', danger: 'bg-red-600 text-white dark:bg-red-500 ', warning: 'bg-yellow-600 dark:bg-yellow-500 text-white', - info: " bg-blue-600 dark:bg-pavitra-blue text-white ", + info: ' bg-blue-600 dark:bg-pavitra-blue text-white ', }, bgHover: { white: 'hover:bg-gray-100', @@ -84,12 +87,12 @@ export const getButtonColor = ( lightDark: 'hover:bg-gray-200 hover:dark:bg-slate-700', contrast: 'hover:bg-gray-700 hover:dark:bg-slate-100', success: - 'hover:bg-emerald-700 hover:border-emerald-700 hover:dark:bg-pavitra-blue hover:dark:border-pavitra-blue', + 'hover:bg-emerald-700 hover:border-emerald-700 hover:dark:bg-pavitra-blue hover:dark:border-pavitra-blue', danger: 'hover:bg-red-700 hover:border-red-700 hover:dark:bg-red-600 hover:dark:border-red-600', warning: 'hover:bg-yellow-700 hover:border-yellow-700 hover:dark:bg-yellow-600 hover:dark:border-yellow-600', - info: "hover:bg-blue-700 hover:border-blue-700 hover:dark:bg-pavitra-blue/80 hover:dark:border-pavitra-blue/80", + info: 'hover:bg-blue-700 hover:border-blue-700 hover:dark:bg-pavitra-blue/80 hover:dark:border-pavitra-blue/80', }, borders: { white: 'border-white', @@ -99,7 +102,7 @@ export const getButtonColor = ( success: 'border-emerald-600 dark:border-pavitra-blue', danger: 'border-red-600 dark:border-red-500', warning: 'border-yellow-600 dark:border-yellow-500', - info: "border-blue-600 border-blue-600 dark:border-pavitra-blue", + info: 'border-blue-600 border-blue-600 dark:border-pavitra-blue', }, text: { contrast: 'dark:text-slate-100', @@ -111,28 +114,32 @@ export const getButtonColor = ( outlineHover: { contrast: 'hover:bg-gray-800 hover:text-gray-100 hover:dark:bg-slate-100 hover:dark:text-black', - success: 'hover:bg-emerald-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue', + success: + 'hover:bg-emerald-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue', danger: 'hover:bg-red-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-red-600', warning: 'hover:bg-yellow-600 hover:text-white hover:text-white hover:dark:text-white hover:dark:border-yellow-600', - info: "hover:bg-blue-600 hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue", + info: 'hover:bg-blue-600 hover:bg-blue-600 hover:text-white hover:dark:text-white hover:dark:border-pavitra-blue', }, - } + }; - const isOutlinedProcessed = isOutlined && ['white', 'whiteDark', 'lightDark'].indexOf(color) < 0 + const isOutlinedProcessed = + isOutlined && ['white', 'whiteDark', 'lightDark'].indexOf(color) < 0; - const base = [colors.borders[color], colors.ring[color]] + const base = [colors.borders[color], colors.ring[color]]; if (isActive) { - base.push(colors.active[color]) + base.push(colors.active[color]); } else { - base.push(isOutlinedProcessed ? colors.text[color] : colors.bg[color]) + base.push(isOutlinedProcessed ? colors.text[color] : colors.bg[color]); } if (hasHover) { - base.push(isOutlinedProcessed ? colors.outlineHover[color] : colors.bgHover[color]) + base.push( + isOutlinedProcessed ? colors.outlineHover[color] : colors.bgHover[color], + ); } - return base.join(' ') -} + return base.join(' '); +}; diff --git a/frontend/src/components/Access_logs/CardAccess_logs.tsx b/frontend/src/components/Access_logs/CardAccess_logs.tsx index 212a834..bc66956 100644 --- a/frontend/src/components/Access_logs/CardAccess_logs.tsx +++ b/frontend/src/components/Access_logs/CardAccess_logs.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { access_logs: any[]; @@ -28,17 +27,16 @@ const CardAccess_logs = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACCESS_LOGS') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACCESS_LOGS'); return (
@@ -47,122 +45,108 @@ const CardAccess_logs = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && access_logs.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.path} + }`} + > +
    + + {item.path} - -
    - +
    + +
    -
    -
    - - +
    -
    Project
    -
    -
    - { dataFormatter.projectsOneListFormatter(item.project) } -
    -
    +
    + Project +
    +
    +
    + {dataFormatter.projectsOneListFormatter(item.project)} +
    +
    - - -
    -
    Environment
    -
    -
    - { item.environment } -
    -
    +
    + Environment +
    +
    +
    + {item.environment} +
    +
    - - -
    -
    User
    -
    -
    - { dataFormatter.usersOneListFormatter(item.user) } -
    -
    +
    User
    +
    +
    + {dataFormatter.usersOneListFormatter(item.user)} +
    +
    - - -
    -
    Path
    -
    -
    - { item.path } -
    -
    +
    Path
    +
    +
    {item.path}
    +
    - - -
    -
    IPaddress
    -
    -
    - { item.ip_address } -
    -
    +
    + IPaddress +
    +
    +
    + {item.ip_address} +
    +
    - - -
    -
    Useragent
    -
    -
    - { item.user_agent } -
    -
    +
    + Useragent +
    +
    +
    + {item.user_agent} +
    +
    - - -
    -
    Accessedat
    -
    -
    - { dataFormatter.dateTimeFormatter(item.accessed_at) } -
    -
    +
    + Accessedat +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.accessed_at)} +
    +
    - - - -
    -
  • - ))} + + + ))} {!loading && access_logs.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Access_logs/ListAccess_logs.tsx b/frontend/src/components/Access_logs/ListAccess_logs.tsx index 39c1239..3ec09ca 100644 --- a/frontend/src/components/Access_logs/ListAccess_logs.tsx +++ b/frontend/src/components/Access_logs/ListAccess_logs.tsx @@ -2,135 +2,122 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - access_logs: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + access_logs: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListAccess_logs = ({ access_logs, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACCESS_LOGS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListAccess_logs = ({ + access_logs, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACCESS_LOGS'); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && access_logs.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Project

    -

    { dataFormatter.projectsOneListFormatter(item.project) }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + access_logs.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Project

    +

    + {dataFormatter.projectsOneListFormatter(item.project)} +

    +
    - - -
    -

    Environment

    -

    { item.environment }

    -
    - +
    +

    Environment

    +

    {item.environment}

    +
    - - -
    -

    User

    -

    { dataFormatter.usersOneListFormatter(item.user) }

    -
    - +
    +

    User

    +

    + {dataFormatter.usersOneListFormatter(item.user)} +

    +
    - - -
    -

    Path

    -

    { item.path }

    -
    - +
    +

    Path

    +

    {item.path}

    +
    - - -
    -

    IPaddress

    -

    { item.ip_address }

    -
    - +
    +

    IPaddress

    +

    {item.ip_address}

    +
    - - -
    -

    Useragent

    -

    { item.user_agent }

    -
    - +
    +

    Useragent

    +

    {item.user_agent}

    +
    - - -
    -

    Accessedat

    -

    { dataFormatter.dateTimeFormatter(item.accessed_at) }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && access_logs.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    Accessedat

    +

    + {dataFormatter.dateTimeFormatter(item.accessed_at)} +

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && access_logs.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListAccess_logs \ No newline at end of file +export default ListAccess_logs; diff --git a/frontend/src/components/Access_logs/TableAccess_logs.tsx b/frontend/src/components/Access_logs/TableAccess_logs.tsx index 5da6d63..3c48d97 100644 --- a/frontend/src/components/Access_logs/TableAccess_logs.tsx +++ b/frontend/src/components/Access_logs/TableAccess_logs.tsx @@ -1,463 +1,48 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/access_logs/access_logsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * Access Logs Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureAccess_logsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/access_logs/access_logsSlice'; +import { loadColumns } from './configureAccess_logsCols'; +import type { AccessLog } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSampleAccess_logs = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { access_logs, loading, count, notify: access_logsNotify, refetch } = useAppSelector((state) => state.access_logs) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (access_logsNotify.showNotification) { - notify(access_logsNotify.typeNotification, access_logsNotify.textNotification); - } - }, [access_logsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `access_logs`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={access_logs ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TableAccess_logsProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSampleAccess_logs +const TableAccess_logs: React.FC = ({ + filterItems, + setFilterItems, + filters, +}) => { + return ( + + entityName='access_logs' + sliceSelector={(state: RootState) => state.access_logs} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TableAccess_logs; diff --git a/frontend/src/components/Access_logs/configureAccess_logsCols.tsx b/frontend/src/components/Access_logs/configureAccess_logsCols.tsx index 60a4fd8..190b93a 100644 --- a/frontend/src/components/Access_logs/configureAccess_logsCols.tsx +++ b/frontend/src/components/Access_logs/configureAccess_logsCols.tsx @@ -3,188 +3,164 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ACCESS_LOGS') - - return [ - - { - field: 'project', - headerName: 'Project', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('projects'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'environment', - headerName: 'Environment', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'user', - headerName: 'User', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('users'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'path', - headerName: 'Path', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'ip_address', - headerName: 'IPaddress', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'user_agent', - headerName: 'Useragent', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'accessed_at', - headerName: 'Accessedat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.accessed_at), - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_ACCESS_LOGS'); + + return [ + { + field: 'project', + headerName: 'Project', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('projects'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'environment', + headerName: 'Environment', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'user', + headerName: 'User', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('users'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'path', + headerName: 'Path', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'ip_address', + headerName: 'IPaddress', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'user_agent', + headerName: 'Useragent', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'accessed_at', + headerName: 'Accessedat', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.accessed_at), + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/AsideMenu.tsx b/frontend/src/components/AsideMenu.tsx index 6376786..4ce5173 100644 --- a/frontend/src/components/AsideMenu.tsx +++ b/frontend/src/components/AsideMenu.tsx @@ -1,14 +1,14 @@ -import React from 'react' -import { MenuAsideItem } from '../interfaces' -import AsideMenuLayer from './AsideMenuLayer' -import OverlayLayer from './OverlayLayer' +import React from 'react'; +import { MenuAsideItem } from '../interfaces'; +import AsideMenuLayer from './AsideMenuLayer'; +import OverlayLayer from './OverlayLayer'; type Props = { - menu: MenuAsideItem[] - isAsideMobileExpanded: boolean - isAsideLgActive: boolean - onAsideLgClose: () => void -} + menu: MenuAsideItem[]; + isAsideMobileExpanded: boolean; + isAsideLgActive: boolean; + onAsideLgClose: () => void; +}; export default function AsideMenu({ isAsideMobileExpanded = false, @@ -24,7 +24,9 @@ export default function AsideMenu({ }`} onAsideLgCloseClick={props.onAsideLgClose} /> - {isAsideLgActive && } + {isAsideLgActive && ( + + )} - ) + ); } diff --git a/frontend/src/components/AsideMenuItem.tsx b/frontend/src/components/AsideMenuItem.tsx index dbb09b2..c5a8b8a 100644 --- a/frontend/src/components/AsideMenuItem.tsx +++ b/frontend/src/components/AsideMenuItem.tsx @@ -1,49 +1,60 @@ -import React, { useEffect, useState } from 'react' -import { mdiMinus, mdiPlus } from '@mdi/js' -import BaseIcon from './BaseIcon' -import Link from 'next/link' -import { getButtonColor } from '../colors' -import AsideMenuList from './AsideMenuList' -import { MenuAsideItem } from '../interfaces' -import { useAppSelector } from '../stores/hooks' -import { useRouter } from 'next/router' +import React, { useEffect, useState } from 'react'; +import { mdiMinus, mdiPlus } from '@mdi/js'; +import BaseIcon from './BaseIcon'; +import Link from 'next/link'; +import { getButtonColor } from '../colors'; +import AsideMenuList from './AsideMenuList'; +import { MenuAsideItem } from '../interfaces'; +import { useAppSelector } from '../stores/hooks'; +import { useRouter } from 'next/router'; type Props = { - item: MenuAsideItem - isDropdownList?: boolean -} + item: MenuAsideItem; + isDropdownList?: boolean; +}; const AsideMenuItem = ({ item, isDropdownList = false }: Props) => { - const [isLinkActive, setIsLinkActive] = useState(false) - const [isDropdownActive, setIsDropdownActive] = useState(false) + const [isLinkActive, setIsLinkActive] = useState(false); + const [isDropdownActive, setIsDropdownActive] = useState(false); - const asideMenuItemStyle = useAppSelector((state) => state.style.asideMenuItemStyle) - const asideMenuDropdownStyle = useAppSelector((state) => state.style.asideMenuDropdownStyle) - const asideMenuItemActiveStyle = useAppSelector((state) => state.style.asideMenuItemActiveStyle) + const asideMenuItemStyle = useAppSelector( + (state) => state.style.asideMenuItemStyle, + ); + const asideMenuDropdownStyle = useAppSelector( + (state) => state.style.asideMenuDropdownStyle, + ); + const asideMenuItemActiveStyle = useAppSelector( + (state) => state.style.asideMenuItemActiveStyle, + ); const borders = useAppSelector((state) => state.style.borders); const activeLinkColor = useAppSelector( (state) => state.style.activeLinkColor, ); - const activeClassAddon = !item.color && isLinkActive ? asideMenuItemActiveStyle : '' + const activeClassAddon = + !item.color && isLinkActive ? asideMenuItemActiveStyle : ''; - const { asPath, isReady } = useRouter() + const { asPath, isReady } = useRouter(); useEffect(() => { if (item.href && isReady) { const linkPathName = new URL(item.href, location.href).pathname + '/'; - const activePathname = new URL(asPath, location.href).pathname + const activePathname = new URL(asPath, location.href).pathname; const activeView = activePathname.split('/')[1]; const linkPathNameView = linkPathName.split('/')[1]; setIsLinkActive(linkPathNameView === activeView); } - }, [item.href, isReady, asPath]) + }, [item.href, isReady, asPath]); const asideMenuItemInnerContents = ( <> {item.icon && ( - + )} { )} - ) + ); const componentClass = [ 'flex cursor-pointer py-1.5 ', isDropdownList ? 'px-6 text-sm' : '', item.color - ? getButtonColor(item.color, false, true) - : `${asideMenuItemStyle}`, + ? getButtonColor(item.color, false, true) + : `${asideMenuItemStyle}`, isLinkActive - ? `text-black ${activeLinkColor} dark:text-white dark:bg-dark-800` - : '', + ? `text-black ${activeLinkColor} dark:text-white dark:bg-dark-800` + : '', ].join(' '); return ( @@ -82,7 +93,10 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => { )} {!item.href && ( -
    setIsDropdownActive(!isDropdownActive)}> +
    setIsDropdownActive(!isDropdownActive)} + > {asideMenuItemInnerContents}
    )} @@ -96,7 +110,7 @@ const AsideMenuItem = ({ item, isDropdownList = false }: Props) => { /> )} - ) -} + ); +}; -export default AsideMenuItem +export default AsideMenuItem; diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx index a320eba..46c303a 100644 --- a/frontend/src/components/AsideMenuLayer.tsx +++ b/frontend/src/components/AsideMenuLayer.tsx @@ -1,30 +1,36 @@ -import React from 'react' -import { mdiLogout, mdiClose } from '@mdi/js' -import BaseIcon from './BaseIcon' -import AsideMenuList from './AsideMenuList' -import { MenuAsideItem } from '../interfaces' -import { useAppSelector } from '../stores/hooks' +import React from 'react'; +import { mdiLogout, mdiClose } from '@mdi/js'; +import BaseIcon from './BaseIcon'; +import AsideMenuList from './AsideMenuList'; +import { MenuAsideItem } from '../interfaces'; +import { useAppSelector } from '../stores/hooks'; import Link from 'next/link'; - type Props = { - menu: MenuAsideItem[] - className?: string - onAsideLgCloseClick: () => void -} + menu: MenuAsideItem[]; + className?: string; + onAsideLgCloseClick: () => void; +}; -export default function AsideMenuLayer({ menu, className = '', ...props }: Props) { +export default function AsideMenuLayer({ + menu, + className = '', + ...props +}: Props) { const corners = useAppSelector((state) => state.style.corners); - const asideStyle = useAppSelector((state) => state.style.asideStyle) - const asideBrandStyle = useAppSelector((state) => state.style.asideBrandStyle) - const asideScrollbarsStyle = useAppSelector((state) => state.style.asideScrollbarsStyle) - const darkMode = useAppSelector((state) => state.style.darkMode) + const asideStyle = useAppSelector((state) => state.style.asideStyle); + const asideBrandStyle = useAppSelector( + (state) => state.style.asideBrandStyle, + ); + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const darkMode = useAppSelector((state) => state.style.darkMode); const handleAsideLgCloseClick = (e: React.MouseEvent) => { - e.preventDefault() - props.onAsideLgCloseClick() - } - + e.preventDefault(); + props.onAsideLgCloseClick(); + }; return ( - ) + ); } diff --git a/frontend/src/components/AsideMenuList.tsx b/frontend/src/components/AsideMenuList.tsx index 9e33ea1..9f0434e 100644 --- a/frontend/src/components/AsideMenuList.tsx +++ b/frontend/src/components/AsideMenuList.tsx @@ -1,16 +1,20 @@ -import React from 'react' -import { MenuAsideItem } from '../interfaces' -import AsideMenuItem from './AsideMenuItem' -import {useAppSelector} from "../stores/hooks"; -import {hasPermission} from "../helpers/userPermissions"; +import React from 'react'; +import { MenuAsideItem } from '../interfaces'; +import AsideMenuItem from './AsideMenuItem'; +import { useAppSelector } from '../stores/hooks'; +import { hasPermission } from '../helpers/userPermissions'; type Props = { - menu: MenuAsideItem[] - isDropdownList?: boolean - className?: string -} + menu: MenuAsideItem[]; + isDropdownList?: boolean; + className?: string; +}; -export default function AsideMenuList({ menu, isDropdownList = false, className = '' }: Props) { +export default function AsideMenuList({ + menu, + isDropdownList = false, + className = '', +}: Props) { const { currentUser } = useAppSelector((state) => state.auth); if (!currentUser) return null; @@ -18,18 +22,14 @@ export default function AsideMenuList({ menu, isDropdownList = false, className return (
      {menu.map((item, index) => { - - if (!hasPermission(currentUser, item.permissions)) return null; - + if (!hasPermission(currentUser, item.permissions)) return null; + return (
      - +
      - ) + ); })}
    - ) + ); } diff --git a/frontend/src/components/Asset_variants/CardAsset_variants.tsx b/frontend/src/components/Asset_variants/CardAsset_variants.tsx index d6e99d1..bbc97e8 100644 --- a/frontend/src/components/Asset_variants/CardAsset_variants.tsx +++ b/frontend/src/components/Asset_variants/CardAsset_variants.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { asset_variants: any[]; @@ -28,17 +27,19 @@ const CardAsset_variants = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSET_VARIANTS') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_ASSET_VARIANTS', + ); return (
    @@ -47,110 +48,101 @@ const CardAsset_variants = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && asset_variants.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.variant_type} + }`} + > +
    + + {item.variant_type} - -
    - +
    + +
    -
    -
    - - +
    -
    Asset
    -
    -
    - { dataFormatter.assetsOneListFormatter(item.asset) } -
    -
    +
    Asset
    +
    +
    + {dataFormatter.assetsOneListFormatter(item.asset)} +
    +
    - - -
    -
    Varianttype
    -
    -
    - { item.variant_type } -
    -
    +
    + Varianttype +
    +
    +
    + {item.variant_type} +
    +
    - - -
    -
    CDNURL
    -
    -
    - { item.cdn_url } -
    -
    +
    + CDNURL +
    +
    +
    + {item.cdn_url} +
    +
    - - -
    -
    Width(px)
    -
    -
    - { item.width_px } -
    -
    +
    + Width(px) +
    +
    +
    + {item.width_px} +
    +
    - - -
    -
    Height(px)
    -
    -
    - { item.height_px } -
    -
    +
    + Height(px) +
    +
    +
    + {item.height_px} +
    +
    - - -
    -
    Size(MB)
    -
    -
    - { item.size_mb } -
    -
    +
    + Size(MB) +
    +
    +
    + {item.size_mb} +
    +
    - - - -
    -
  • - ))} + + + ))} {!loading && asset_variants.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Asset_variants/ListAsset_variants.tsx b/frontend/src/components/Asset_variants/ListAsset_variants.tsx index 9e84561..b0c022b 100644 --- a/frontend/src/components/Asset_variants/ListAsset_variants.tsx +++ b/frontend/src/components/Asset_variants/ListAsset_variants.tsx @@ -2,127 +2,116 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - asset_variants: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + asset_variants: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListAsset_variants = ({ asset_variants, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSET_VARIANTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListAsset_variants = ({ + asset_variants, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_ASSET_VARIANTS', + ); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && asset_variants.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Asset

    -

    { dataFormatter.assetsOneListFormatter(item.asset) }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + asset_variants.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Asset

    +

    + {dataFormatter.assetsOneListFormatter(item.asset)} +

    +
    - - -
    -

    Varianttype

    -

    { item.variant_type }

    -
    - +
    +

    Varianttype

    +

    {item.variant_type}

    +
    - - -
    -

    CDNURL

    -

    { item.cdn_url }

    -
    - +
    +

    CDNURL

    +

    {item.cdn_url}

    +
    - - -
    -

    Width(px)

    -

    { item.width_px }

    -
    - +
    +

    Width(px)

    +

    {item.width_px}

    +
    - - -
    -

    Height(px)

    -

    { item.height_px }

    -
    - +
    +

    Height(px)

    +

    {item.height_px}

    +
    - - -
    -

    Size(MB)

    -

    { item.size_mb }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && asset_variants.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    Size(MB)

    +

    {item.size_mb}

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && asset_variants.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListAsset_variants \ No newline at end of file +export default ListAsset_variants; diff --git a/frontend/src/components/Asset_variants/TableAsset_variants.tsx b/frontend/src/components/Asset_variants/TableAsset_variants.tsx index f5d2ba1..e51af53 100644 --- a/frontend/src/components/Asset_variants/TableAsset_variants.tsx +++ b/frontend/src/components/Asset_variants/TableAsset_variants.tsx @@ -1,463 +1,48 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/asset_variants/asset_variantsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * Asset Variants Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureAsset_variantsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/asset_variants/asset_variantsSlice'; +import { loadColumns } from './configureAsset_variantsCols'; +import type { AssetVariant } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSampleAsset_variants = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { asset_variants, loading, count, notify: asset_variantsNotify, refetch } = useAppSelector((state) => state.asset_variants) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (asset_variantsNotify.showNotification) { - notify(asset_variantsNotify.typeNotification, asset_variantsNotify.textNotification); - } - }, [asset_variantsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `asset_variants`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={asset_variants ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TableAsset_variantsProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSampleAsset_variants +const TableAsset_variants: React.FC = ({ + filterItems, + setFilterItems, + filters, +}) => { + return ( + + entityName='asset_variants' + sliceSelector={(state: RootState) => state.asset_variants} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TableAsset_variants; diff --git a/frontend/src/components/Asset_variants/configureAsset_variantsCols.tsx b/frontend/src/components/Asset_variants/configureAsset_variantsCols.tsx index 067d8d8..5c9672b 100644 --- a/frontend/src/components/Asset_variants/configureAsset_variantsCols.tsx +++ b/frontend/src/components/Asset_variants/configureAsset_variantsCols.tsx @@ -3,166 +3,146 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ASSET_VARIANTS') - - return [ - - { - field: 'asset', - headerName: 'Asset', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('assets'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'variant_type', - headerName: 'Varianttype', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'cdn_url', - headerName: 'CDNURL', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'width_px', - headerName: 'Width(px)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'height_px', - headerName: 'Height(px)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'size_mb', - headerName: 'Size(MB)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_ASSET_VARIANTS'); + + return [ + { + field: 'asset', + headerName: 'Asset', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('assets'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'variant_type', + headerName: 'Varianttype', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'cdn_url', + headerName: 'CDNURL', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'width_px', + headerName: 'Width(px)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'height_px', + headerName: 'Height(px)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'size_mb', + headerName: 'Size(MB)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/Assets/CardAssets.tsx b/frontend/src/components/Assets/CardAssets.tsx index 9600324..cefdc25 100644 --- a/frontend/src/components/Assets/CardAssets.tsx +++ b/frontend/src/components/Assets/CardAssets.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { assets: any[]; @@ -28,17 +27,16 @@ const CardAssets = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSETS') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSETS'); return (
    @@ -47,215 +45,196 @@ const CardAssets = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && assets.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.name} + }`} + > +
    + + {item.name} - -
    - +
    + +
    -
    -
    - - +
    -
    Project
    -
    -
    - { dataFormatter.projectsOneListFormatter(item.project) } -
    -
    -
    - - - - -
    -
    Name
    -
    -
    - { item.name } -
    -
    -
    - - - - -
    -
    Asset format
    -
    -
    - { item.asset_type } -
    -
    +
    + Project +
    +
    +
    + {dataFormatter.projectsOneListFormatter(item.project)} +
    +
    -
    Type
    -
    -
    - { item.type || 'general' } -
    -
    +
    Name
    +
    +
    {item.name}
    +
    - - -
    -
    CDNURL
    -
    -
    - { item.cdn_url } -
    -
    +
    + Asset format +
    +
    +
    + {item.asset_type} +
    +
    - - -
    -
    Storagekey
    -
    -
    - { item.storage_key } -
    -
    +
    Type
    +
    +
    + {item.type || 'general'} +
    +
    - - -
    -
    MIMEtype
    -
    -
    - { item.mime_type } -
    -
    +
    + CDNURL +
    +
    +
    + {item.cdn_url} +
    +
    - - -
    -
    Size(MB)
    -
    -
    - { item.size_mb } -
    -
    +
    + Storagekey +
    +
    +
    + {item.storage_key} +
    +
    - - -
    -
    Width(px)
    -
    -
    - { item.width_px } -
    -
    +
    + MIMEtype +
    +
    +
    + {item.mime_type} +
    +
    - - -
    -
    Height(px)
    -
    -
    - { item.height_px } -
    -
    +
    + Size(MB) +
    +
    +
    + {item.size_mb} +
    +
    - - -
    -
    Duration(sec)
    -
    -
    - { item.duration_sec } -
    -
    +
    + Width(px) +
    +
    +
    + {item.width_px} +
    +
    - - -
    -
    Checksum
    -
    -
    - { item.checksum } -
    -
    +
    + Height(px) +
    +
    +
    + {item.height_px} +
    +
    - - -
    -
    Ispublic
    -
    -
    - { dataFormatter.booleanFormatter(item.is_public) } -
    -
    +
    + Duration(sec) +
    +
    +
    + {item.duration_sec} +
    +
    - - -
    -
    Isdeleted
    -
    -
    - { dataFormatter.booleanFormatter(item.is_deleted) } -
    -
    +
    + Checksum +
    +
    +
    + {item.checksum} +
    +
    - - -
    -
    Deletedat
    -
    -
    - { dataFormatter.dateTimeFormatter(item.deleted_at_time) } -
    -
    +
    + Ispublic +
    +
    +
    + {dataFormatter.booleanFormatter(item.is_public)} +
    +
    - - -
    -
  • - ))} +
    +
    + Isdeleted +
    +
    +
    + {dataFormatter.booleanFormatter(item.is_deleted)} +
    +
    +
    + +
    +
    + Deletedat +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.deleted_at_time)} +
    +
    +
    + + + ))} {!loading && assets.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Assets/ListAssets.tsx b/frontend/src/components/Assets/ListAssets.tsx index f68b646..7a5fbae 100644 --- a/frontend/src/components/Assets/ListAssets.tsx +++ b/frontend/src/components/Assets/ListAssets.tsx @@ -2,196 +2,166 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - assets: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + assets: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListAssets = ({ assets, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSETS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListAssets = ({ + assets, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ASSETS'); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && assets.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Project

    -

    { dataFormatter.projectsOneListFormatter(item.project) }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + assets.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Project

    +

    + {dataFormatter.projectsOneListFormatter(item.project)} +

    +
    - - -
    -

    Name

    -

    { item.name }

    -
    - +
    +

    Name

    +

    {item.name}

    +
    - - -
    -

    Asset format

    -

    { item.asset_type }

    -
    +
    +

    Asset format

    +

    {item.asset_type}

    +
    -
    -

    Type

    -

    { item.type || 'general' }

    -
    - +
    +

    Type

    +

    {item.type || 'general'}

    +
    - - -
    -

    CDNURL

    -

    { item.cdn_url }

    -
    - +
    +

    CDNURL

    +

    {item.cdn_url}

    +
    - - -
    -

    Storagekey

    -

    { item.storage_key }

    -
    - +
    +

    Storagekey

    +

    {item.storage_key}

    +
    - - -
    -

    MIMEtype

    -

    { item.mime_type }

    -
    - +
    +

    MIMEtype

    +

    {item.mime_type}

    +
    - - -
    -

    Size(MB)

    -

    { item.size_mb }

    -
    - +
    +

    Size(MB)

    +

    {item.size_mb}

    +
    - - -
    -

    Width(px)

    -

    { item.width_px }

    -
    - +
    +

    Width(px)

    +

    {item.width_px}

    +
    - - -
    -

    Height(px)

    -

    { item.height_px }

    -
    - +
    +

    Height(px)

    +

    {item.height_px}

    +
    - - -
    -

    Duration(sec)

    -

    { item.duration_sec }

    -
    - +
    +

    + Duration(sec) +

    +

    {item.duration_sec}

    +
    - - -
    -

    Checksum

    -

    { item.checksum }

    -
    - +
    +

    Checksum

    +

    {item.checksum}

    +
    - - -
    -

    Ispublic

    -

    { dataFormatter.booleanFormatter(item.is_public) }

    -
    - +
    +

    Ispublic

    +

    + {dataFormatter.booleanFormatter(item.is_public)} +

    +
    - - -
    -

    Isdeleted

    -

    { dataFormatter.booleanFormatter(item.is_deleted) }

    -
    - +
    +

    Isdeleted

    +

    + {dataFormatter.booleanFormatter(item.is_deleted)} +

    +
    - - -
    -

    Deletedat

    -

    { dataFormatter.dateTimeFormatter(item.deleted_at_time) }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && assets.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    Deletedat

    +

    + {dataFormatter.dateTimeFormatter(item.deleted_at_time)} +

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && assets.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListAssets +export default ListAssets; diff --git a/frontend/src/components/Assets/TableAssets.tsx b/frontend/src/components/Assets/TableAssets.tsx index 0997833..9711905 100644 --- a/frontend/src/components/Assets/TableAssets.tsx +++ b/frontend/src/components/Assets/TableAssets.tsx @@ -1,463 +1,48 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/assets/assetsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * Assets Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureAssetsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/assets/assetsSlice'; +import { loadColumns } from './configureAssetsCols'; +import type { Asset } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSampleAssets = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { assets, loading, count, notify: assetsNotify, refetch } = useAppSelector((state) => state.assets) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (assetsNotify.showNotification) { - notify(assetsNotify.typeNotification, assetsNotify.textNotification); - } - }, [assetsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `assets`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={assets ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TableAssetsProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSampleAssets +const TableAssets: React.FC = ({ + filterItems, + setFilterItems, + filters, +}) => { + return ( + + entityName='assets' + sliceSelector={(state: RootState) => state.assets} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TableAssets; diff --git a/frontend/src/components/Assets/configureAssetsCols.tsx b/frontend/src/components/Assets/configureAssetsCols.tsx index 2a3a4f5..aff139d 100644 --- a/frontend/src/components/Assets/configureAssetsCols.tsx +++ b/frontend/src/components/Assets/configureAssetsCols.tsx @@ -3,307 +3,264 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ASSETS') - - return [ - - { - field: 'project', - headerName: 'Project', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('projects'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'name', - headerName: 'Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'asset_type', - headerName: 'Asset format', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'type', - headerName: 'Type', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'cdn_url', - headerName: 'CDNURL', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'storage_key', - headerName: 'Storagekey', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'mime_type', - headerName: 'MIMEtype', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'size_mb', - headerName: 'Size(MB)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'width_px', - headerName: 'Width(px)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'height_px', - headerName: 'Height(px)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'duration_sec', - headerName: 'Duration(sec)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'checksum', - headerName: 'Checksum', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'is_public', - headerName: 'Ispublic', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'is_deleted', - headerName: 'Isdeleted', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'deleted_at_time', - headerName: 'Deletedat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.deleted_at_time), - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_ASSETS'); + + return [ + { + field: 'project', + headerName: 'Project', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('projects'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'name', + headerName: 'Name', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'asset_type', + headerName: 'Asset format', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'type', + headerName: 'Type', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'cdn_url', + headerName: 'CDNURL', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'storage_key', + headerName: 'Storagekey', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'mime_type', + headerName: 'MIMEtype', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'size_mb', + headerName: 'Size(MB)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'width_px', + headerName: 'Width(px)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'height_px', + headerName: 'Height(px)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'duration_sec', + headerName: 'Duration(sec)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'checksum', + headerName: 'Checksum', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'is_public', + headerName: 'Ispublic', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'boolean', + }, + + { + field: 'is_deleted', + headerName: 'Isdeleted', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'boolean', + }, + + { + field: 'deleted_at_time', + headerName: 'Deletedat', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.deleted_at_time), + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/BaseButton.tsx b/frontend/src/components/BaseButton.tsx index a137dc2..cb87f90 100644 --- a/frontend/src/components/BaseButton.tsx +++ b/frontend/src/components/BaseButton.tsx @@ -1,28 +1,28 @@ -import React from 'react' -import Link from 'next/link' -import { getButtonColor } from '../colors' -import BaseIcon from './BaseIcon' -import type { ColorButtonKey } from '../interfaces' +import React from 'react'; +import Link from 'next/link'; +import { getButtonColor } from '../colors'; +import BaseIcon from './BaseIcon'; +import type { ColorButtonKey } from '../interfaces'; import { useAppSelector } from '../stores/hooks'; type Props = { - label?: string - icon?: string - iconSize?: string | number - href?: string - target?: string - type?: string - color?: ColorButtonKey - className?: string - iconClassName?: string - asAnchor?: boolean - small?: boolean - outline?: boolean - active?: boolean - disabled?: boolean - roundedFull?: boolean - onClick?: (e: React.MouseEvent) => void -} + label?: string; + icon?: string; + iconSize?: string | number; + href?: string; + target?: string; + type?: string; + color?: ColorButtonKey; + className?: string; + iconClassName?: string; + asAnchor?: boolean; + small?: boolean; + outline?: boolean; + active?: boolean; + disabled?: boolean; + roundedFull?: boolean; + onClick?: (e: React.MouseEvent) => void; +}; export default function BaseButton({ label, @@ -57,40 +57,50 @@ export default function BaseButton({ roundedFull ? 'rounded-full' : `${corners}`, getButtonColor(color, outline, !disabled, active), className, - ] + ]; if (!label && icon) { - componentClass.push('p-1') + componentClass.push('p-1'); } else if (small) { - componentClass.push('text-sm', roundedFull ? 'px-3 py-1' : 'p-1') + componentClass.push('text-sm', roundedFull ? 'px-3 py-1' : 'p-1'); } else { - componentClass.push('py-2', roundedFull ? 'px-6' : 'px-3') + componentClass.push('py-2', roundedFull ? 'px-6' : 'px-3'); } if (disabled) { - componentClass.push(outline ? 'opacity-50' : 'opacity-70') + componentClass.push(outline ? 'opacity-50' : 'opacity-70'); } - const componentClassString = componentClass.join(' ') + const componentClassString = componentClass.join(' '); const componentChildren = ( <> - {icon && } - {label && {label}} + {icon && ( + + )} + {label && ( + {label} + )} - ) + ); if (href && !disabled) { return ( {componentChildren} - ) + ); } return React.createElement( asAnchor ? 'a' : 'button', - { className: componentClassString, type: type ?? 'button', target, disabled, onClick }, - componentChildren - ) + { + className: componentClassString, + type: type ?? 'button', + target, + disabled, + onClick, + }, + componentChildren, + ); } diff --git a/frontend/src/components/BaseButtons.tsx b/frontend/src/components/BaseButtons.tsx index e81a102..bff8011 100644 --- a/frontend/src/components/BaseButtons.tsx +++ b/frontend/src/components/BaseButtons.tsx @@ -11,27 +11,27 @@ type Props = { }; const BaseButtons = ({ - type = 'justify-end', - mb = '-mb-3', - classAddon = 'mr-3 last:mr-0 mb-3', - noWrap = false, - children, - className, - }: Props) => { + type = 'justify-end', + mb = '-mb-3', + classAddon = 'mr-3 last:mr-0 mb-3', + noWrap = false, + children, + className, +}: Props) => { return ( -
    - {Children.map(children, (child: ReactElement) => - child - ? cloneElement(child as ReactElement<{ className?: string }>, { - className: `${classAddon} ${(child.props as { className?: string }).className || ''}`, - }) - : null, - )} -
    +
    + {Children.map(children, (child: ReactElement) => + child + ? cloneElement(child as ReactElement<{ className?: string }>, { + className: `${classAddon} ${(child.props as { className?: string }).className || ''}`, + }) + : null, + )} +
    ); }; diff --git a/frontend/src/components/BaseDivider.tsx b/frontend/src/components/BaseDivider.tsx index ac56fa6..52e7f29 100644 --- a/frontend/src/components/BaseDivider.tsx +++ b/frontend/src/components/BaseDivider.tsx @@ -1,14 +1,14 @@ -import React from 'react' +import React from 'react'; import { useAppSelector } from '../stores/hooks'; type Props = { - navBar?: boolean -} + navBar?: boolean; +}; export default function BaseDivider({ navBar = false }: Props) { const borders = useAppSelector((state) => state.style.borders); const classAddon = navBar ? 'hidden lg:block lg:my-0.5 dark:border-dark-700' - : 'my-6 -mx-6 dark:border-dark-800' + : 'my-6 -mx-6 dark:border-dark-800'; - return
    + return
    ; } diff --git a/frontend/src/components/BaseIcon.tsx b/frontend/src/components/BaseIcon.tsx index 98ec891..d26fe1c 100644 --- a/frontend/src/components/BaseIcon.tsx +++ b/frontend/src/components/BaseIcon.tsx @@ -1,14 +1,14 @@ -import React, { ReactNode } from 'react' +import React, { ReactNode } from 'react'; type Props = { - path: string - w?: string - h?: string + path: string; + w?: string; + h?: string; fill?: string; - size?: string | number | null - className?: string - children?: ReactNode -} + size?: string | number | null; + className?: string; + children?: ReactNode; +}; export default function BaseIcon({ path, @@ -19,14 +19,21 @@ export default function BaseIcon({ className = '', children, }: Props) { - const iconSize = size ?? 16 + const iconSize = size ?? 16; return ( - - + + {children} - ) + ); } diff --git a/frontend/src/components/BigCalendar.tsx b/frontend/src/components/BigCalendar.tsx index a457be6..0b26ffe 100644 --- a/frontend/src/components/BigCalendar.tsx +++ b/frontend/src/components/BigCalendar.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useMemo, useState, useRef } from 'react'; import { - Calendar, - Views, - momentLocalizer, - SlotInfo, - EventProps, + Calendar, + Views, + momentLocalizer, + SlotInfo, + EventProps, } from 'react-big-calendar'; import moment from 'moment'; import 'react-big-calendar/lib/css/react-big-calendar.css'; @@ -14,162 +14,158 @@ import Link from 'next/link'; import { useAppSelector } from '../stores/hooks'; import { hasPermission } from '../helpers/userPermissions'; - const localizer = momentLocalizer(moment); type TEvent = { - id: string; - title: string; - start: Date; - end: Date; + id: string; + title: string; + start: Date; + end: Date; }; type Props = { - events: any[]; - handleDeleteAction: (id: string) => void; - handleCreateEventAction: (slotInfo: SlotInfo) => void; - onDateRangeChange: (range: { start: string; end: string }) => void; - entityName: string; - showField: string; - pathEdit?: string; - pathView?: string; - 'start-data-key': string; - 'end-data-key': string; + events: any[]; + handleDeleteAction: (id: string) => void; + handleCreateEventAction: (slotInfo: SlotInfo) => void; + onDateRangeChange: (range: { start: string; end: string }) => void; + entityName: string; + showField: string; + pathEdit?: string; + pathView?: string; + 'start-data-key': string; + 'end-data-key': string; }; const BigCalendar = ({ - events, - handleDeleteAction, - handleCreateEventAction, - onDateRangeChange, - entityName, - showField, - pathEdit, - pathView, - 'start-data-key': startDataKey, - 'end-data-key': endDataKey, - }: Props) => { - const [myEvents, setMyEvents] = useState([]); - const prevRange = useRef<{ start: string; end: string } | null>(null); + events, + handleDeleteAction, + handleCreateEventAction, + onDateRangeChange, + entityName, + showField, + pathEdit, + pathView, + 'start-data-key': startDataKey, + 'end-data-key': endDataKey, +}: Props) => { + const [myEvents, setMyEvents] = useState([]); + const prevRange = useRef<{ start: string; end: string } | null>(null); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = - currentUser && - hasPermission(currentUser, `UPDATE_${entityName.toUpperCase()}`); - const hasCreatePermission = - currentUser && - hasPermission(currentUser, `CREATE_${entityName.toUpperCase()}`); - + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = + currentUser && + hasPermission(currentUser, `UPDATE_${entityName.toUpperCase()}`); + const hasCreatePermission = + currentUser && + hasPermission(currentUser, `CREATE_${entityName.toUpperCase()}`); - const { defaultDate, scrollToTime } = useMemo( - () => ({ - defaultDate: new Date(), - scrollToTime: new Date(1970, 1, 1, 6), - }), - [], - ); + const { defaultDate, scrollToTime } = useMemo( + () => ({ + defaultDate: new Date(), + scrollToTime: new Date(1970, 1, 1, 6), + }), + [], + ); - useEffect(() => { - if (!events || !Array.isArray(events) || !events?.length) return; + useEffect(() => { + if (!events || !Array.isArray(events) || !events?.length) return; - const formattedEvents = events.map((event) => ({ - ...event, - start: new Date(event[startDataKey]), - end: new Date(event[endDataKey]), - title: event[showField], - })); + const formattedEvents = events.map((event) => ({ + ...event, + start: new Date(event[startDataKey]), + end: new Date(event[endDataKey]), + title: event[showField], + })); - setMyEvents(formattedEvents); - }, [endDataKey, events, startDataKey, showField]); + setMyEvents(formattedEvents); + }, [endDataKey, events, startDataKey, showField]); - const onRangeChange = ( - range: Date[] | { start: Date; end: Date }, - ) => { - const newRange = { start: '', end: '' }; - const format = 'YYYY-MM-DDTHH:mm'; + const onRangeChange = (range: Date[] | { start: Date; end: Date }) => { + const newRange = { start: '', end: '' }; + const format = 'YYYY-MM-DDTHH:mm'; - if (Array.isArray(range)) { - newRange.start = moment(range[0]).format(format); - newRange.end = moment(range[range.length - 1]).format(format); - } else { - newRange.start = moment(range.start).format(format); - newRange.end = moment(range.end).format(format); - } + if (Array.isArray(range)) { + newRange.start = moment(range[0]).format(format); + newRange.end = moment(range[range.length - 1]).format(format); + } else { + newRange.start = moment(range.start).format(format); + newRange.end = moment(range.end).format(format); + } - if (newRange.start === newRange.end) { - newRange.end = moment(newRange.end).add(1, 'days').format(format); - } + if (newRange.start === newRange.end) { + newRange.end = moment(newRange.end).add(1, 'days').format(format); + } - // check if the range fits in the previous range - if ( - prevRange.current && - prevRange.current.start <= newRange.start && - prevRange.current.end >= newRange.end - ) { - return; - } + // check if the range fits in the previous range + if ( + prevRange.current && + prevRange.current.start <= newRange.start && + prevRange.current.end >= newRange.end + ) { + return; + } - prevRange.current = { start: newRange.start, end: newRange.end }; - onDateRangeChange(newRange); - }; + prevRange.current = { start: newRange.start, end: newRange.end }; + onDateRangeChange(newRange); + }; - return ( -
    - ( - - ), - }} + return ( +
    + ( + -
    - ); + ), + }} + /> +
    + ); }; const MyCustomEvent = ( - props: { - onDelete: (id: string) => void; - hasUpdatePermission: boolean; - pathEdit?: string; - pathView?: string; - } & EventProps, + props: { + onDelete: (id: string) => void; + hasUpdatePermission: boolean; + pathEdit?: string; + pathView?: string; + } & EventProps, ) => { - const { onDelete, hasUpdatePermission, title, event, pathEdit, pathView } = props; + const { onDelete, hasUpdatePermission, title, event, pathEdit, pathView } = + props; - return ( -
    - - {title} - - -
    - ); + return ( +
    + + {title} + + +
    + ); }; export default BigCalendar; diff --git a/frontend/src/components/CardBox.tsx b/frontend/src/components/CardBox.tsx index 988f162..dea7269 100644 --- a/frontend/src/components/CardBox.tsx +++ b/frontend/src/components/CardBox.tsx @@ -1,23 +1,23 @@ -import React, { ReactNode } from 'react' -import CardBoxComponentBody from './CardBoxComponentBody' -import CardBoxComponentFooter from './CardBoxComponentFooter' +import React, { ReactNode } from 'react'; +import CardBoxComponentBody from './CardBoxComponentBody'; +import CardBoxComponentFooter from './CardBoxComponentFooter'; import { useAppSelector } from '../stores/hooks'; type Props = { - rounded?: string - flex?: string - className?: string - hasComponentLayout?: boolean - cardBoxClassName?: string - hasTable?: boolean - isHoverable?: boolean - isModal?: boolean - children?: ReactNode - footer?: ReactNode - isList?:boolean + rounded?: string; + flex?: string; + className?: string; + hasComponentLayout?: boolean; + cardBoxClassName?: string; + hasTable?: boolean; + isHoverable?: boolean; + isModal?: boolean; + children?: ReactNode; + footer?: ReactNode; + isList?: boolean; id?: string; - onClick?: (e: React.MouseEvent) => void -} + onClick?: (e: React.MouseEvent) => void; +}; export default function CardBox({ rounded = 'rounded', @@ -31,7 +31,7 @@ export default function CardBox({ isModal = false, children, footer, - id ='', + id = '', onClick, }: Props) { const corners = useAppSelector((state) => state.style.corners); @@ -39,14 +39,14 @@ export default function CardBox({ const componentClass = [ `flex dark:border-dark-700 dark:bg-dark-900`, className, - corners !== 'rounded-full'? corners : 'rounded-3xl', + corners !== 'rounded-full' ? corners : 'rounded-3xl', flex, isList ? '' : `${cardsStyle}`, hasTable ? '' : `border-dark-700 dark:border-dark-700`, - ] + ]; if (isHoverable) { - componentClass.push('hover:shadow-lg transition-shadow duration-500') + componentClass.push('hover:shadow-lg transition-shadow duration-500'); } return React.createElement( @@ -56,9 +56,15 @@ export default function CardBox({ children ) : ( <> - {children} + + {children} + {footer && {footer}} - ) - ) + ), + ); } diff --git a/frontend/src/components/CardBoxComponentBody.tsx b/frontend/src/components/CardBoxComponentBody.tsx index 4c89896..12448d8 100644 --- a/frontend/src/components/CardBoxComponentBody.tsx +++ b/frontend/src/components/CardBoxComponentBody.tsx @@ -1,12 +1,21 @@ -import React, { ReactNode } from 'react' +import React, { ReactNode } from 'react'; type Props = { - noPadding?: boolean - className?: string - children?: ReactNode - id?: string -} + noPadding?: boolean; + className?: string; + children?: ReactNode; + id?: string; +}; -export default function CardBoxComponentBody({ noPadding = false, className, children, id }: Props) { - return
    {children}
    +export default function CardBoxComponentBody({ + noPadding = false, + className, + children, + id, +}: Props) { + return ( +
    + {children} +
    + ); } diff --git a/frontend/src/components/CardBoxComponentEmpty.tsx b/frontend/src/components/CardBoxComponentEmpty.tsx index c71413e..c9072bb 100644 --- a/frontend/src/components/CardBoxComponentEmpty.tsx +++ b/frontend/src/components/CardBoxComponentEmpty.tsx @@ -1,11 +1,11 @@ -import React from 'react' +import React from 'react'; const CardBoxComponentEmpty = () => { return ( -
    +

    Nothing's here…

    - ) -} + ); +}; -export default CardBoxComponentEmpty +export default CardBoxComponentEmpty; diff --git a/frontend/src/components/CardBoxComponentFooter.tsx b/frontend/src/components/CardBoxComponentFooter.tsx index 4b41bba..184a058 100644 --- a/frontend/src/components/CardBoxComponentFooter.tsx +++ b/frontend/src/components/CardBoxComponentFooter.tsx @@ -1,10 +1,10 @@ -import React, { ReactNode } from 'react' +import React, { ReactNode } from 'react'; type Props = { - className?: string - children?: ReactNode -} + className?: string; + children?: ReactNode; +}; export default function CardBoxComponentFooter({ className, children }: Props) { - return
    {children}
    + return
    {children}
    ; } diff --git a/frontend/src/components/CardBoxComponentTitle.tsx b/frontend/src/components/CardBoxComponentTitle.tsx index 4f92def..20990e6 100644 --- a/frontend/src/components/CardBoxComponentTitle.tsx +++ b/frontend/src/components/CardBoxComponentTitle.tsx @@ -1,17 +1,17 @@ -import React, { ReactNode } from 'react' +import React, { ReactNode } from 'react'; type Props = { - title: string - children?: ReactNode -} + title: string; + children?: ReactNode; +}; const CardBoxComponentTitle = ({ title, children }: Props) => { return ( -
    -

    {title}

    +
    +

    {title}

    {children}
    - ) -} + ); +}; -export default CardBoxComponentTitle +export default CardBoxComponentTitle; diff --git a/frontend/src/components/CardBoxModal.tsx b/frontend/src/components/CardBoxModal.tsx index ef1837b..9de915a 100644 --- a/frontend/src/components/CardBoxModal.tsx +++ b/frontend/src/components/CardBoxModal.tsx @@ -1,22 +1,22 @@ -import { mdiClose } from '@mdi/js' -import { ReactNode } from 'react' -import type { ColorButtonKey } from '../interfaces' -import BaseButton from './BaseButton' -import BaseButtons from './BaseButtons' -import CardBox from './CardBox' -import CardBoxComponentTitle from './CardBoxComponentTitle' -import OverlayLayer from './OverlayLayer' +import { mdiClose } from '@mdi/js'; +import { ReactNode } from 'react'; +import type { ColorButtonKey } from '../interfaces'; +import BaseButton from './BaseButton'; +import BaseButtons from './BaseButtons'; +import CardBox from './CardBox'; +import CardBoxComponentTitle from './CardBoxComponentTitle'; +import OverlayLayer from './OverlayLayer'; type Props = { - title: string - buttonColor: ColorButtonKey - buttonLabel: string - isConfirmDisabled?: boolean - isActive: boolean - children?: ReactNode - onConfirm: () => void - onCancel?: () => void -} + title: string; + buttonColor: ColorButtonKey; + buttonLabel: string; + isConfirmDisabled?: boolean; + isActive: boolean; + children?: ReactNode; + onConfirm: () => void; + onCancel?: () => void; +}; const CardBoxModal = ({ title, @@ -29,18 +29,33 @@ const CardBoxModal = ({ onCancel, }: Props) => { if (!isActive) { - return null + return null; } const footer = ( - - {!!onCancel && } + + {!!onCancel && ( + + )} - ) + ); return ( - + {!!onCancel && ( - + )} -
    {children}
    +
    {children}
    - ) -} + ); +}; -export default CardBoxModal +export default CardBoxModal; diff --git a/frontend/src/components/ChartLineSample/config.ts b/frontend/src/components/ChartLineSample/config.ts index cacce92..c29cbdd 100644 --- a/frontend/src/components/ChartLineSample/config.ts +++ b/frontend/src/components/ChartLineSample/config.ts @@ -4,17 +4,17 @@ export const chartColors = { info: '#209CEE', danger: '#FF3860', }, -} +}; const randomChartData = (n: number) => { - const data = [] + const data = []; for (let i = 0; i < n; i++) { - data.push(Math.round(Math.random() * 200)) + data.push(Math.round(Math.random() * 200)); } - return data -} + return data; +}; const datasetObject = (color: string, points: number) => { return { @@ -33,14 +33,14 @@ const datasetObject = (color: string, points: number) => { data: randomChartData(points), tension: 0.5, cubicInterpolationMode: 'default', - } -} + }; +}; export const sampleChartData = (points = 9) => { - const labels = [] + const labels = []; for (let i = 1; i <= points; i++) { - labels.push(`0${i}`) + labels.push(`0${i}`); } return { @@ -50,5 +50,5 @@ export const sampleChartData = (points = 9) => { datasetObject('info', points), datasetObject('danger', points), ], - } -} + }; +}; diff --git a/frontend/src/components/ChartLineSample/index.tsx b/frontend/src/components/ChartLineSample/index.tsx index c7f417b..0761549 100644 --- a/frontend/src/components/ChartLineSample/index.tsx +++ b/frontend/src/components/ChartLineSample/index.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React from 'react'; import { Chart, LineElement, @@ -7,10 +7,17 @@ import { LinearScale, CategoryScale, Tooltip, -} from 'chart.js' -import { Line } from 'react-chartjs-2' +} from 'chart.js'; +import { Line } from 'react-chartjs-2'; -Chart.register(LineElement, PointElement, LineController, LinearScale, CategoryScale, Tooltip) +Chart.register( + LineElement, + PointElement, + LineController, + LinearScale, + CategoryScale, + Tooltip, +); const options = { responsive: true, @@ -28,10 +35,10 @@ const options = { display: false, }, }, -} +}; const ChartLineSample = ({ data }) => { - return -} + return ; +}; -export default ChartLineSample +export default ChartLineSample; diff --git a/frontend/src/components/ClickOutside.tsx b/frontend/src/components/ClickOutside.tsx index 1673e29..4e031ad 100644 --- a/frontend/src/components/ClickOutside.tsx +++ b/frontend/src/components/ClickOutside.tsx @@ -1,35 +1,45 @@ -import React, { useCallback, useEffect, useRef, ReactNode, MutableRefObject } from 'react'; +import React, { + useCallback, + useEffect, + useRef, + ReactNode, + MutableRefObject, +} from 'react'; interface ClickOutsideProps { - children?: ReactNode; - onClickOutside: () => void; - excludedElements: MutableRefObject[]; + children?: ReactNode; + onClickOutside: () => void; + excludedElements: MutableRefObject[]; } -const ClickOutside = ({ children, onClickOutside, excludedElements }: ClickOutsideProps) => { - const wrapperRef = useRef(null); +const ClickOutside = ({ + children, + onClickOutside, + excludedElements, +}: ClickOutsideProps) => { + const wrapperRef = useRef(null); - const handleClickOutside = useCallback( - (event) => { - if ( - wrapperRef.current && - !wrapperRef.current.contains(event.target) && - !excludedElements.some((el) => el.current.contains(event.target)) - ) { - onClickOutside(); - } - }, - [wrapperRef, onClickOutside, ...excludedElements], - ); + const handleClickOutside = useCallback( + (event) => { + if ( + wrapperRef.current && + !wrapperRef.current.contains(event.target) && + !excludedElements.some((el) => el.current.contains(event.target)) + ) { + onClickOutside(); + } + }, + [wrapperRef, onClickOutside, ...excludedElements], + ); - useEffect(() => { - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [handleClickOutside]); + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [handleClickOutside]); - return
    {children}
    ; + return
    {children}
    ; }; export default ClickOutside; diff --git a/frontend/src/components/DevModeBadge.tsx b/frontend/src/components/DevModeBadge.tsx index bc238c4..2d226a0 100644 --- a/frontend/src/components/DevModeBadge.tsx +++ b/frontend/src/components/DevModeBadge.tsx @@ -1,150 +1,150 @@ import React, { useState, useEffect } from 'react'; import useDevCompilationStatus from '../hooks/useDevCompilationStatus'; const DevModeBadge: React.FC = () => { - const [isVisible, setIsVisible] = useState(false); - const [isCollapsed, setIsCollapsed] = useState(true); - const compilationStatus = useDevCompilationStatus(); + const [isVisible, setIsVisible] = useState(false); + const [isCollapsed, setIsCollapsed] = useState(true); + const compilationStatus = useDevCompilationStatus(); - const [badgeStyles, setBadgeStyles] = useState({ - position: 'fixed', - bottom: '20px', - left: '70px', - background: 'rgba(0, 0, 0, 0.85)', - color: 'white', - padding: '15px', - borderRadius: '8px', - fontFamily: 'sans-serif', - fontSize: '14px', - lineHeight: '1.5', - textAlign: 'left', - zIndex: 2147483647, - boxShadow: '0 4px 10px rgba(0, 0, 0, 0.3)', - whiteSpace: 'pre-wrap', - transition: 'width 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), padding 0.3s ease-in-out, opacity 0.3s ease-in-out, background-color 0.3s ease-in-out', // Improved transition for width - opacity: 0, - pointerEvents: 'none', - width: '340px', - maxWidth: '340px', - height: 'auto', - overflow: 'hidden', - cursor: 'pointer', - }); + const [badgeStyles, setBadgeStyles] = useState({ + position: 'fixed', + bottom: '20px', + left: '70px', + background: 'rgba(0, 0, 0, 0.85)', + color: 'white', + padding: '15px', + borderRadius: '8px', + fontFamily: 'sans-serif', + fontSize: '14px', + lineHeight: '1.5', + textAlign: 'left', + zIndex: 2147483647, + boxShadow: '0 4px 10px rgba(0, 0, 0, 0.3)', + whiteSpace: 'pre-wrap', + transition: + 'width 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), padding 0.3s ease-in-out, opacity 0.3s ease-in-out, background-color 0.3s ease-in-out', // Improved transition for width + opacity: 0, + pointerEvents: 'none', + width: '340px', + maxWidth: '340px', + height: 'auto', + overflow: 'hidden', + cursor: 'pointer', + }); - const fullText = `🚧 Your app is running in development mode. + const fullText = `🚧 Your app is running in development mode. Current request is compiling and may take a few moments. 💡 Tip: Set up a stable environment to run your app in production mode—pages will load instantly without compilation delays.`; - const collapsedText = '🚧 DEV stage'; + const collapsedText = '🚧 DEV stage'; - useEffect(() => { - if (compilationStatus === 'ready') { - setIsCollapsed(true); - } else { - setIsCollapsed(false); - } - - }, [compilationStatus]); - - useEffect(() => { - if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev_stage') { - setIsVisible(true); - - setBadgeStyles(prev => ({ - ...prev, - opacity: 1, - width: '120px', - maxWidth: '120px', - padding: '6px 10px', - borderRadius: '18px', - whiteSpace: 'nowrap', - fontSize: '12px', - cursor: 'pointer', - pointerEvents: 'auto', - })); - - } else { - setIsVisible(false); - setBadgeStyles(prev => ({ ...prev, opacity: 0 })); - } - }, []); - - useEffect(() => { - if (!isVisible) return; - - if (isCollapsed) { - setBadgeStyles(prev => ({ - ...prev, - width: '140px', - maxWidth: '160px', - padding: '6px 20px', - borderRadius: '18px', - whiteSpace: 'nowrap', - fontSize: '12px', - })); - } else { - setBadgeStyles(prev => ({ - ...prev, - width: '340px', - maxWidth: '340px', - padding: '15px', - borderRadius: '8px', - whiteSpace: 'pre-wrap', - fontSize: '14px', - })); - } - }, [isCollapsed, isVisible]); - - const handleToggleCollapse = (e: React.MouseEvent) => { - e.stopPropagation(); - setIsCollapsed(prev => !prev); - }; - - if (!isVisible) { - return null; + useEffect(() => { + if (compilationStatus === 'ready') { + setIsCollapsed(true); + } else { + setIsCollapsed(false); } + }, [compilationStatus]); - return ( -
    - + useEffect(() => { + if ( + process.env.NODE_ENV === 'development' || + process.env.NODE_ENV === 'dev_stage' + ) { + setIsVisible(true); - {!isCollapsed && ( -
    - {fullText} -
    - )} - {isCollapsed && ( -
    - {collapsedText} -
    - )} -
    - ); + setBadgeStyles((prev) => ({ + ...prev, + opacity: 1, + width: '120px', + maxWidth: '120px', + padding: '6px 10px', + borderRadius: '18px', + whiteSpace: 'nowrap', + fontSize: '12px', + cursor: 'pointer', + pointerEvents: 'auto', + })); + } else { + setIsVisible(false); + setBadgeStyles((prev) => ({ ...prev, opacity: 0 })); + } + }, []); + + useEffect(() => { + if (!isVisible) return; + + if (isCollapsed) { + setBadgeStyles((prev) => ({ + ...prev, + width: '140px', + maxWidth: '160px', + padding: '6px 20px', + borderRadius: '18px', + whiteSpace: 'nowrap', + fontSize: '12px', + })); + } else { + setBadgeStyles((prev) => ({ + ...prev, + width: '340px', + maxWidth: '340px', + padding: '15px', + borderRadius: '8px', + whiteSpace: 'pre-wrap', + fontSize: '14px', + })); + } + }, [isCollapsed, isVisible]); + + const handleToggleCollapse = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsCollapsed((prev) => !prev); + }; + + if (!isVisible) { + return null; + } + + return ( +
    + + + {!isCollapsed &&
    {fullText}
    } + {isCollapsed && ( +
    {collapsedText}
    + )} +
    + ); }; export default DevModeBadge; diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx index 93b2833..490bd95 100644 --- a/frontend/src/components/ErrorBoundary.tsx +++ b/frontend/src/components/ErrorBoundary.tsx @@ -37,9 +37,9 @@ class ErrorBoundary extends Component { } componentDidUpdate( - prevProps: Readonly, - prevState: Readonly, - snapshot?: any, + prevProps: Readonly, + prevState: Readonly, + snapshot?: any, ) { if (process.env.NODE_ENV !== 'production') { console.log('componentDidUpdate'); @@ -145,69 +145,69 @@ class ErrorBoundary extends Component { const { error, errorInfo, showStack } = this.state; const errorMessage = error?.message || 'An unexpected error occurred'; const stackTrace = - errorInfo?.componentStack || error?.stack || 'No stack trace available'; + errorInfo?.componentStack || error?.stack || 'No stack trace available'; return ( -
    -
    -
    -
    - -
    +
    +
    +
    +
    + +
    -
    -

    - Something went wrong -

    -

    - We're sorry, but we encountered an unexpected error. -

    -
    +
    +

    + Something went wrong +

    +

    + We're sorry, but we encountered an unexpected error. +

    +
    -
    -

    - {errorMessage} -

    +
    +

    + {errorMessage} +

    -
    - +
    + - {showStack && ( -
    +                  {showStack && (
    +                    
                           {stackTrace}
                         
    - )} -
    + )}
    +
    -
    - +
    + - -
    +
    +
    ); } diff --git a/frontend/src/components/FooterBar.tsx b/frontend/src/components/FooterBar.tsx index 0acc9c5..ec78468 100644 --- a/frontend/src/components/FooterBar.tsx +++ b/frontend/src/components/FooterBar.tsx @@ -1,21 +1,21 @@ -import React, { ReactNode } from 'react' -import { containerMaxW } from '../config' -import Logo from './Logo' +import React, { ReactNode } from 'react'; +import { containerMaxW } from '../config'; +import Logo from './Logo'; type Props = { - children?: ReactNode -} + children?: ReactNode; +}; export default function FooterBar({ children }: Props) { - const year = new Date().getFullYear() + const year = new Date().getFullYear(); return (
    -
    -
    +
    +
    ©{year},{` `} - + Flatlogic . @@ -24,12 +24,12 @@ export default function FooterBar({ children }: Props) { {children}
    -
    - - - -
    +
    + + + +
    - ) + ); } diff --git a/frontend/src/components/FormCheckRadio.tsx b/frontend/src/components/FormCheckRadio.tsx index 7a9bc31..17c00ea 100644 --- a/frontend/src/components/FormCheckRadio.tsx +++ b/frontend/src/components/FormCheckRadio.tsx @@ -1,20 +1,20 @@ -import { ReactNode } from 'react' +import { ReactNode } from 'react'; type Props = { - children: ReactNode - type: 'checkbox' | 'radio' | 'switch' - label?: string - className?: string -} + children: ReactNode; + type: 'checkbox' | 'radio' | 'switch'; + label?: string; + className?: string; +}; const FormCheckRadio = (props: Props) => { return ( - ) -} + ); +}; -export default FormCheckRadio +export default FormCheckRadio; diff --git a/frontend/src/components/FormCheckRadioGroup.tsx b/frontend/src/components/FormCheckRadioGroup.tsx index 90d98e0..5d23a75 100644 --- a/frontend/src/components/FormCheckRadioGroup.tsx +++ b/frontend/src/components/FormCheckRadioGroup.tsx @@ -1,20 +1,22 @@ -import { Children, cloneElement, ReactElement, ReactNode } from 'react' +import { Children, cloneElement, ReactElement, ReactNode } from 'react'; type Props = { - isColumn?: boolean - children: ReactNode -} + isColumn?: boolean; + children: ReactNode; +}; const FormCheckRadioGroup = (props: Props) => { return ( -
    +
    {Children.map(props.children, (child: ReactElement) => - cloneElement(child as ReactElement<{ className?: string }>, { - className: `mr-6 mb-3 last:mr-0 ${(child.props as { className?: string }).className || ''}`, - }), + cloneElement(child as ReactElement<{ className?: string }>, { + className: `mr-6 mb-3 last:mr-0 ${(child.props as { className?: string }).className || ''}`, + }), )}
    - ) -} + ); +}; -export default FormCheckRadioGroup +export default FormCheckRadioGroup; diff --git a/frontend/src/components/FormField.tsx b/frontend/src/components/FormField.tsx index 988ac39..006d938 100644 --- a/frontend/src/components/FormField.tsx +++ b/frontend/src/components/FormField.tsx @@ -1,36 +1,36 @@ -import { Children, cloneElement, ReactElement, ReactNode } from 'react' -import BaseIcon from './BaseIcon' +import { Children, cloneElement, ReactElement, ReactNode } from 'react'; +import BaseIcon from './BaseIcon'; import { useAppSelector } from '../stores/hooks'; type Props = { - label?: string - labelFor?: string - help?: string - icons?: string[] | null[] - isBorderless?: boolean - isTransparent?: boolean - hasTextareaHeight?: boolean - children: ReactNode - disabled?: boolean - borderButtom?: boolean - diversity?: boolean - websiteBg?: boolean -} + label?: string; + labelFor?: string; + help?: string; + icons?: string[] | null[]; + isBorderless?: boolean; + isTransparent?: boolean; + hasTextareaHeight?: boolean; + children: ReactNode; + disabled?: boolean; + borderButtom?: boolean; + diversity?: boolean; + websiteBg?: boolean; +}; const FormField = ({ icons = [], ...props }: Props) => { - const childrenCount = Children.count(props.children) + const childrenCount = Children.count(props.children); const bgColor = useAppSelector((state) => state.style.cardsColor); const focusRing = useAppSelector((state) => state.style.focusRingColor); const corners = useAppSelector((state) => state.style.corners); const bgWebsiteColor = useAppSelector((state) => state.style.bgLayoutColor); - let elementWrapperClass = '' + let elementWrapperClass = ''; switch (childrenCount) { case 2: - elementWrapperClass = 'grid grid-cols-1 gap-3 md:grid-cols-2' - break + elementWrapperClass = 'grid grid-cols-1 gap-3 md:grid-cols-2'; + break; case 3: - elementWrapperClass = 'grid grid-cols-1 gap-3 md:grid-cols-3' + elementWrapperClass = 'grid grid-cols-1 gap-3 md:grid-cols-3'; } const controlClassName = [ @@ -38,13 +38,17 @@ const FormField = ({ icons = [], ...props }: Props) => { `${focusRing}`, props.hasTextareaHeight ? 'h-24' : 'h-12', props.isBorderless ? 'border-0' : 'border', - props.isTransparent ? 'bg-transparent' : `${props.websiteBg ? ` bg-white` : bgColor} dark:bg-dark-800`, + props.isTransparent + ? 'bg-transparent' + : `${props.websiteBg ? ` bg-white` : bgColor} dark:bg-dark-800`, props.disabled ? 'bg-gray-200 text-gray-100 dark:bg-dark-900 disabled' : '', - props.borderButtom ? `border-0 border-b ${props.diversity ? "border-gray-400" : " placeholder-white border-gray-300/10 border-white "} rounded-none focus:ring-0` : '', + props.borderButtom + ? `border-0 border-b ${props.diversity ? 'border-gray-400' : ' placeholder-white border-gray-300/10 border-white '} rounded-none focus:ring-0` + : '', ].join(' '); return ( -
    +
    {props.label && (
    + ); }; export default KanbanCard; diff --git a/frontend/src/components/KanbanBoard/KanbanColumn.tsx b/frontend/src/components/KanbanBoard/KanbanColumn.tsx index 425a0d3..86f8c7e 100644 --- a/frontend/src/components/KanbanBoard/KanbanColumn.tsx +++ b/frontend/src/components/KanbanBoard/KanbanColumn.tsx @@ -8,202 +8,204 @@ import { useDrop } from 'react-dnd'; import KanbanCard from './KanbanCard'; type Props = { - column: { id: string; label: string }; - entityName: string; - columnFieldName: string; - showFieldName: string; - filtersQuery: any; - deleteThunk: AsyncThunk; - updateThunk: AsyncThunk; + column: { id: string; label: string }; + entityName: string; + columnFieldName: string; + showFieldName: string; + filtersQuery: any; + deleteThunk: AsyncThunk; + updateThunk: AsyncThunk; }; type DropResult = { - sourceColumn: { id: string; label: string }; - item: any; + sourceColumn: { id: string; label: string }; + item: any; }; const perPage = 10; const KanbanColumn = ({ - column, - entityName, - columnFieldName, - showFieldName, - filtersQuery, - deleteThunk, - updateThunk, - }: Props) => { - const [currentPage, setCurrentPage] = useState(0); - const [count, setCount] = useState(0); - const [data, setData] = useState(null); - const [loading, setLoading] = useState(false); - const [itemIdToDelete, setItemIdToDelete] = useState(''); - const currentUser = useAppSelector((state) => state.auth.currentUser); - const listInnerRef = useRef(null); - const dispatch = useAppDispatch(); + column, + entityName, + columnFieldName, + showFieldName, + filtersQuery, + deleteThunk, + updateThunk, +}: Props) => { + const [currentPage, setCurrentPage] = useState(0); + const [count, setCount] = useState(0); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [itemIdToDelete, setItemIdToDelete] = useState(''); + const currentUser = useAppSelector((state) => state.auth.currentUser); + const listInnerRef = useRef(null); + const dispatch = useAppDispatch(); - const [{ dropResult }, drop] = useDrop< - { - item: any; - column: { - id: string; - label: string; - }; - }, - unknown, - { - dropResult: DropResult; - } - >( - () => ({ - accept: 'box', - drop: ({ - item, - column: sourceColumn, - }: { - item: any; - column: { id: string; label: string }; - }) => { - if (sourceColumn.id === column.id) return; + const [{ dropResult }, drop] = useDrop< + { + item: any; + column: { + id: string; + label: string; + }; + }, + unknown, + { + dropResult: DropResult; + } + >( + () => ({ + accept: 'box', + drop: ({ + item, + column: sourceColumn, + }: { + item: any; + column: { id: string; label: string }; + }) => { + if (sourceColumn.id === column.id) return; - dispatch( - updateThunk({ - id: item.id, - data: { - [columnFieldName]: column.id, - }, - }), - ).then((res) => { - setData((prevState) => (prevState ? [...prevState, item] : [item])); - setCount((prevState) => prevState + 1); - }); - - return { sourceColumn, item }; + dispatch( + updateThunk({ + id: item.id, + data: { + [columnFieldName]: column.id, }, - collect: (monitor) => ({ - dropResult: monitor.getDropResult(), - }), - }), - [], - ); + }), + ).then((res) => { + setData((prevState) => (prevState ? [...prevState, item] : [item])); + setCount((prevState) => prevState + 1); + }); - const loadData = useCallback( - (page: number, filters = '') => { - const query = `?page=${page}&limit=${perPage}&field=createdAt&sort=desc&${columnFieldName}=${column.id}&${filters}`; - setLoading(true); - Axios.get(`${entityName}${query}`) - .then((res) => { - setData((prevState) => - page === 0 ? res.data.rows : [...prevState, ...res.data.rows], - ); - setCount(res.data.count); - setCurrentPage(page); - }) - .catch((err) => { - console.error(err); - }) - .finally(() => { - setLoading(false); - }); - }, - [currentUser, column], - ); + return { sourceColumn, item }; + }, + collect: (monitor) => ({ + dropResult: monitor.getDropResult(), + }), + }), + [], + ); - useEffect(() => { - if (!currentUser) return; - loadData(0, filtersQuery); - }, [currentUser, loadData, filtersQuery]); + const loadData = useCallback( + (page: number, filters = '') => { + const query = `?page=${page}&limit=${perPage}&field=createdAt&sort=desc&${columnFieldName}=${column.id}&${filters}`; + setLoading(true); + Axios.get(`${entityName}${query}`) + .then((res) => { + setData((prevState) => + page === 0 ? res.data.rows : [...prevState, ...res.data.rows], + ); + setCount(res.data.count); + setCurrentPage(page); + }) + .catch((err) => { + console.error(err); + }) + .finally(() => { + setLoading(false); + }); + }, + [currentUser, column], + ); - useEffect(() => { - loadData(0, filtersQuery); - }, [loadData, filtersQuery]); + useEffect(() => { + if (!currentUser) return; + loadData(0, filtersQuery); + }, [currentUser, loadData, filtersQuery]); - useEffect(() => { - if (dropResult?.sourceColumn && dropResult.sourceColumn.id === column.id) { - setData((prevState) => - prevState.filter((item) => item.id !== dropResult.item.id), - ); - setCount((prevState) => prevState - 1); + useEffect(() => { + loadData(0, filtersQuery); + }, [loadData, filtersQuery]); + + useEffect(() => { + if (dropResult?.sourceColumn && dropResult.sourceColumn.id === column.id) { + setData((prevState) => + prevState.filter((item) => item.id !== dropResult.item.id), + ); + setCount((prevState) => prevState - 1); + } + }, [dropResult]); + + const onScroll = () => { + if (listInnerRef.current) { + const { scrollTop, scrollHeight, clientHeight } = listInnerRef.current; + if (Math.floor(scrollTop + clientHeight) === scrollHeight) { + if (data.length < count && !loading) { + loadData(currentPage + 1, filtersQuery); } - }, [dropResult]); + } + } + }; - const onScroll = () => { - if (listInnerRef.current) { - const { scrollTop, scrollHeight, clientHeight } = listInnerRef.current; - if (Math.floor(scrollTop + clientHeight) === scrollHeight) { - if (data.length < count && !loading) { - loadData(currentPage + 1, filtersQuery); - } - } + const onDeleteConfirm = () => { + if (!itemIdToDelete) return; + + dispatch(deleteThunk(itemIdToDelete)) + .then((res) => { + if (res.meta.requestStatus === 'fulfilled') { + setItemIdToDelete(''); + loadData(0, filtersQuery); } - }; + }) + .catch((err) => { + console.error(err); + }) + .finally(() => { + setItemIdToDelete(''); + }); + }; - const onDeleteConfirm = () => { - if (!itemIdToDelete) return; - - dispatch(deleteThunk(itemIdToDelete)) - .then((res) => { - if (res.meta.requestStatus === 'fulfilled') { - setItemIdToDelete(''); - loadData(0, filtersQuery); - } - }) - .catch((err) => { - console.error(err); - }) - .finally(() => { - setItemIdToDelete(''); - }); - }; - - return ( - <> - -
    -

    {column.label}

    -

    {count}

    -
    -
    { - drop(node); - listInnerRef.current = node; - }} - className={'p-3 space-y-3 flex-1 overflow-y-auto max-h-[400px]'} - onScroll={onScroll} - > - {data?.map((item) => ( -
    - -
    - ))} - {!data?.length && ( -

    No data

    - )} -
    -
    - setItemIdToDelete('')} - > -

    Are you sure you want to delete this item?

    -
    - - ); + return ( + <> + +
    +

    {column.label}

    +

    {count}

    +
    +
    { + drop(node); + listInnerRef.current = node; + }} + className={'p-3 space-y-3 flex-1 overflow-y-auto max-h-[400px]'} + onScroll={onScroll} + > + {data?.map((item) => ( +
    + +
    + ))} + {!data?.length && ( +

    + No data +

    + )} +
    +
    + setItemIdToDelete('')} + > +

    Are you sure you want to delete this item?

    +
    + + ); }; export default KanbanColumn; diff --git a/frontend/src/components/LanguageSwitcher.tsx b/frontend/src/components/LanguageSwitcher.tsx index f2f373a..f2f0b4a 100644 --- a/frontend/src/components/LanguageSwitcher.tsx +++ b/frontend/src/components/LanguageSwitcher.tsx @@ -1,96 +1,100 @@ import React, { useEffect, useState } from 'react'; -import Select, { components, SingleValueProps, OptionProps } from 'react-select'; +import Select, { + components, + SingleValueProps, + OptionProps, +} from 'react-select'; type LanguageOption = { label: string; value: string }; const LANGS: LanguageOption[] = [ - { value: 'en', label: '🇬🇧 EN' }, - { value: 'fr', label: '🇫🇷 FR' }, - { value: 'es', label: '🇪🇸 ES' }, - { value: 'de', label: '🇩🇪 DE' }, + { value: 'en', label: '🇬🇧 EN' }, + { value: 'fr', label: '🇫🇷 FR' }, + { value: 'es', label: '🇪🇸 ES' }, + { value: 'de', label: '🇩🇪 DE' }, ]; const Option = (props: OptionProps) => ( - - {props.data.label} - + + {props.data.label} + ); const SingleVal = (props: SingleValueProps) => ( - - {props.data.label} - + + {props.data.label} + ); const LanguageSwitcher: React.FC = () => { - const [mounted, setMounted] = useState(false); - const [selected, setSelected] = useState(LANGS[0]); + const [mounted, setMounted] = useState(false); + const [selected, setSelected] = useState(LANGS[0]); - useEffect(() => { - setMounted(true); - }, []); + useEffect(() => { + setMounted(true); + }, []); - const handleChange = (opt: LanguageOption | null) => { - if (!opt) return; - setSelected(opt); - }; + const handleChange = (opt: LanguageOption | null) => { + if (!opt) return; + setSelected(opt); + }; - if (!mounted) return null; + if (!mounted) return null; - return ( -
    - null, + }} + styles={{ + control: (base) => ({ + ...base, + minHeight: 28, + height: 28, + paddingTop: 0, + paddingBottom: 0, + borderColor: '#d1d5db', + cursor: 'pointer', + }), + valueContainer: (base) => ({ + ...base, + paddingTop: 0, + paddingBottom: 0, + paddingLeft: 6, + }), + indicatorsContainer: (base) => ({ + ...base, + height: 24, + }), + dropdownIndicator: (base) => ({ + ...base, + padding: 2, + }), + option: (base, state) => ({ + ...base, + paddingTop: 4, + paddingBottom: 4, + height: 26, + fontSize: '0.875rem', + backgroundColor: state.isFocused ? '#f3f4f6' : 'white', + color: '#111827', + }), + menu: (base) => ({ + ...base, + zIndex: 9999, + }), + }} + /> +
    + ); }; export default LanguageSwitcher; diff --git a/frontend/src/components/ListActionsPopover.tsx b/frontend/src/components/ListActionsPopover.tsx index 0f93245..7465d91 100644 --- a/frontend/src/components/ListActionsPopover.tsx +++ b/frontend/src/components/ListActionsPopover.tsx @@ -3,113 +3,112 @@ import Link from 'next/link'; import Button from '@mui/material/Button'; import BaseIcon from './BaseIcon'; import { - mdiDotsVertical, - mdiEye, - mdiPencilOutline, - mdiTrashCan, + mdiDotsVertical, + mdiEye, + mdiPencilOutline, + mdiTrashCan, } from '@mdi/js'; import Popover from '@mui/material/Popover'; import { IconButton } from '@mui/material'; - type Props = { - itemId: string; - onDelete: (id: string) => void; - hasUpdatePermission: boolean; - className?: string; - iconClassName?: string; - pathEdit: string; - pathView: string; + itemId: string; + onDelete: (id: string) => void; + hasUpdatePermission: boolean; + className?: string; + iconClassName?: string; + pathEdit: string; + pathView: string; }; const ListActionsPopover = ({ - itemId, - onDelete, - hasUpdatePermission, - className, - iconClassName, - pathEdit, - pathView, - }: Props) => { - const [anchorEl, setAnchorEl] = React.useState(null); - const handleClick = (event) => { - setAnchorEl(event.currentTarget); - }; - const linkView = pathView; - const linkEdit = pathEdit; - const handleClose = () => { - setAnchorEl(null); - }; + itemId, + onDelete, + hasUpdatePermission, + className, + iconClassName, + pathEdit, + pathView, +}: Props) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + const linkView = pathView; + const linkEdit = pathEdit; + const handleClose = () => { + setAnchorEl(null); + }; - const open = Boolean(anchorEl); - const id = open ? 'simple-popover' : undefined; + const open = Boolean(anchorEl); + const id = open ? 'simple-popover' : undefined; - return ( - <> - + + + + +
    + + {hasUpdatePermission && ( + - {hasUpdatePermission && ( - - )} - {hasUpdatePermission && ( - - )} -
    -
    - - ); + Delete + + )} +
    + + + ); }; export default ListActionsPopover; diff --git a/frontend/src/components/LoadingSpinner.tsx b/frontend/src/components/LoadingSpinner.tsx index 6f288a0..2a56d8d 100644 --- a/frontend/src/components/LoadingSpinner.tsx +++ b/frontend/src/components/LoadingSpinner.tsx @@ -4,15 +4,11 @@ const LoadingSpinner = () => { return (
    -
    -
    +
    +
    ); }; -export default LoadingSpinner; \ No newline at end of file +export default LoadingSpinner; diff --git a/frontend/src/components/Logo/index.tsx b/frontend/src/components/Logo/index.tsx index 8d28b9b..d075aff 100644 --- a/frontend/src/components/Logo/index.tsx +++ b/frontend/src/components/Logo/index.tsx @@ -1,19 +1,19 @@ -import React from 'react' +import React from 'react'; import Image from 'next/image'; type Props = { - className?: string -} + className?: string; +}; export default function Logo({ className = '' }: Props) { return ( {'Flatlogic - ) + ); } diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx index c270ae0..aa08751 100644 --- a/frontend/src/components/NavBar.tsx +++ b/frontend/src/components/NavBar.tsx @@ -1,20 +1,20 @@ -import React, { ReactNode, useState, useEffect } from 'react' -import { mdiClose, mdiDotsVertical } from '@mdi/js' -import { containerMaxW } from '../config' -import BaseIcon from './BaseIcon' -import NavBarItemPlain from './NavBarItemPlain' -import NavBarMenuList from './NavBarMenuList' -import { MenuNavBarItem } from '../interfaces' -import { useAppSelector } from '../stores/hooks'; +import React, { ReactNode, useState, useEffect } from 'react'; +import { mdiClose, mdiDotsVertical } from '@mdi/js'; +import { containerMaxW } from '../config'; +import BaseIcon from './BaseIcon'; +import NavBarItemPlain from './NavBarItemPlain'; +import NavBarMenuList from './NavBarMenuList'; +import { MenuNavBarItem } from '../interfaces'; +import { useAppSelector } from '../stores/hooks'; type Props = { - menu: MenuNavBarItem[] - className: string - children: ReactNode -} + menu: MenuNavBarItem[]; + className: string; + children: ReactNode; +}; export default function NavBar({ menu, className = '', children }: Props) { - const [isMenuNavBarActive, setIsMenuNavBarActive] = useState(false) + const [isMenuNavBarActive, setIsMenuNavBarActive] = useState(false); const [isScrolled, setIsScrolled] = useState(false); const bgColor = useAppSelector((state) => state.style.bgLayoutColor); @@ -30,18 +30,23 @@ export default function NavBar({ menu, className = '', children }: Props) { }, []); const handleMenuNavBarToggleClick = () => { - setIsMenuNavBarActive(!isMenuNavBarActive) - } + setIsMenuNavBarActive(!isMenuNavBarActive); + }; return ( - ) + ); } diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index a47d445..4afd076 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,20 +1,20 @@ -import React, {useEffect, useRef, useState } from 'react' -import Link from 'next/link' -import { mdiChevronUp, mdiChevronDown } from '@mdi/js' -import BaseDivider from './BaseDivider' -import BaseIcon from './BaseIcon' -import UserAvatarCurrentUser from './UserAvatarCurrentUser' -import NavBarMenuList from './NavBarMenuList' -import { useAppDispatch, useAppSelector } from '../stores/hooks' -import { MenuNavBarItem } from '../interfaces' -import { setDarkMode } from '../stores/styleSlice' -import { logoutUser } from '../stores/authSlice' +import React, { useEffect, useRef, useState } from 'react'; +import Link from 'next/link'; +import { mdiChevronUp, mdiChevronDown } from '@mdi/js'; +import BaseDivider from './BaseDivider'; +import BaseIcon from './BaseIcon'; +import UserAvatarCurrentUser from './UserAvatarCurrentUser'; +import NavBarMenuList from './NavBarMenuList'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { MenuNavBarItem } from '../interfaces'; +import { setDarkMode } from '../stores/styleSlice'; +import { logoutUser } from '../stores/authSlice'; import { useRouter } from 'next/router'; -import ClickOutside from "./ClickOutside"; +import ClickOutside from './ClickOutside'; type Props = { - item: MenuNavBarItem -} + item: MenuNavBarItem; +}; export default function NavBarItem({ item }: Props) { const router = useRouter(); @@ -22,16 +22,20 @@ export default function NavBarItem({ item }: Props) { const excludedRef = useRef(null); const navBarItemLabelActiveColorStyle = useAppSelector( - (state) => state.style.navBarItemLabelActiveColorStyle - ) - const navBarItemLabelStyle = useAppSelector((state) => state.style.navBarItemLabelStyle) - const navBarItemLabelHoverStyle = useAppSelector((state) => state.style.navBarItemLabelHoverStyle) + (state) => state.style.navBarItemLabelActiveColorStyle, + ); + const navBarItemLabelStyle = useAppSelector( + (state) => state.style.navBarItemLabelStyle, + ); + const navBarItemLabelHoverStyle = useAppSelector( + (state) => state.style.navBarItemLabelHoverStyle, + ); const currentUser = useAppSelector((state) => state.auth.currentUser); - const userName = `${currentUser?.firstName ? currentUser?.firstName : ""} ${currentUser?.lastName ? currentUser?.lastName : ""}`; + const userName = `${currentUser?.firstName ? currentUser?.firstName : ''} ${currentUser?.lastName ? currentUser?.lastName : ''}`; - const [isDropdownActive, setIsDropdownActive] = useState(false) + const [isDropdownActive, setIsDropdownActive] = useState(false); useEffect(() => { return () => setIsDropdownActive(false); @@ -44,26 +48,26 @@ export default function NavBarItem({ item }: Props) { : `${navBarItemLabelStyle} dark:text-white dark:hover:text-slate-400 ${navBarItemLabelHoverStyle}`, item.menu ? 'lg:py-2 lg:px-3' : 'py-2 px-3', item.isDesktopNoLabel ? 'lg:w-16 lg:justify-center' : '', - ].join(' ') + ].join(' '); - const itemLabel = item.isCurrentUser ? userName : item.label + const itemLabel = item.isCurrentUser ? userName : item.label; const handleMenuClick = () => { if (item.menu) { - setIsDropdownActive(!isDropdownActive) + setIsDropdownActive(!isDropdownActive); } if (item.isToggleLightDark) { - dispatch(setDarkMode(null)) + dispatch(setDarkMode(null)); } - if(item.isLogout) { - dispatch(logoutUser()) - router.push('/login') + if (item.isLogout) { + dispatch(logoutUser()); + router.push('/login'); } - } + }; - const getItemId = (label) => { + const getItemId = (label) => { switch (label) { case 'Light/Dark': return 'themeToggle'; @@ -85,7 +89,9 @@ export default function NavBarItem({ item }: Props) { }`} onClick={handleMenuClick} > - {item.icon && } + {item.icon && ( + + )} {itemLabel} - {item.isCurrentUser && } + {item.isCurrentUser && ( + + )} {item.menu && ( )}
    @@ -107,16 +115,19 @@ export default function NavBarItem({ item }: Props) { !isDropdownActive ? 'lg:hidden' : '' } text-sm border-b border-gray-100 lg:border lg:bg-white lg:absolute lg:top-full lg:left-0 lg:min-w-full lg:z-20 lg:rounded-lg lg:shadow-lg lg:dark:bg-dark-900 dark:border-dark-700`} > - setIsDropdownActive(false)} excludedElements={[excludedRef]}> + setIsDropdownActive(false)} + excludedElements={[excludedRef]} + >
    )} - ) + ); if (item.isDivider) { - return + return ; } if (item.href) { @@ -124,8 +135,12 @@ export default function NavBarItem({ item }: Props) { {NavBarItemComponentContents} - ) + ); } - return
    {NavBarItemComponentContents}
    + return ( +
    + {NavBarItemComponentContents} +
    + ); } diff --git a/frontend/src/components/NavBarItemPlain.tsx b/frontend/src/components/NavBarItemPlain.tsx index 5589728..57f9679 100644 --- a/frontend/src/components/NavBarItemPlain.tsx +++ b/frontend/src/components/NavBarItemPlain.tsx @@ -1,12 +1,12 @@ -import React, { ReactNode } from 'react' -import { useAppSelector } from '../stores/hooks' +import React, { ReactNode } from 'react'; +import { useAppSelector } from '../stores/hooks'; type Props = { - display?: string - useMargin?: boolean - children?: ReactNode - onClick?: (e: React.MouseEvent) => void -} + display?: string; + useMargin?: boolean; + children?: ReactNode; + onClick?: (e: React.MouseEvent) => void; +}; export default function NavBarItemPlain({ display = 'flex', @@ -14,17 +14,22 @@ export default function NavBarItemPlain({ onClick, children, }: Props) { - const navBarItemLabelStyle = useAppSelector((state) => state.style.navBarItemLabelStyle) - const navBarItemLabelHoverStyle = useAppSelector((state) => state.style.navBarItemLabelHoverStyle) + const navBarItemLabelStyle = useAppSelector( + (state) => state.style.navBarItemLabelStyle, + ); + const navBarItemLabelHoverStyle = useAppSelector( + (state) => state.style.navBarItemLabelHoverStyle, + ); - const classBase = 'items-center cursor-pointer dark:text-white dark:hover:text-slate-400' + const classBase = + 'items-center cursor-pointer dark:text-white dark:hover:text-slate-400'; const classAddon = `${display} ${navBarItemLabelStyle} ${navBarItemLabelHoverStyle} ${ useMargin ? 'my-2 mx-3' : 'py-2 px-3' - }` + }`; return (
    {children}
    - ) + ); } diff --git a/frontend/src/components/NavBarMenuList.tsx b/frontend/src/components/NavBarMenuList.tsx index 0896428..1e8493c 100644 --- a/frontend/src/components/NavBarMenuList.tsx +++ b/frontend/src/components/NavBarMenuList.tsx @@ -1,19 +1,19 @@ -import React from 'react' -import { MenuNavBarItem } from '../interfaces' -import NavBarItem from './NavBarItem' +import React from 'react'; +import { MenuNavBarItem } from '../interfaces'; +import NavBarItem from './NavBarItem'; type Props = { - menu: MenuNavBarItem[] -} + menu: MenuNavBarItem[]; +}; export default function NavBarMenuList({ menu }: Props) { return ( <> {menu.map((item, index) => ( -
    - -
    +
    + +
    ))} - ) + ); } diff --git a/frontend/src/components/NotificationBar.tsx b/frontend/src/components/NotificationBar.tsx index a3ade58..e91f880 100644 --- a/frontend/src/components/NotificationBar.tsx +++ b/frontend/src/components/NotificationBar.tsx @@ -1,57 +1,65 @@ -import { mdiClose } from '@mdi/js' -import React, { ReactNode, useState } from 'react' -import { ColorKey } from '../interfaces' -import { colorsBgLight, colorsOutline } from '../colors' -import BaseButton from './BaseButton' -import BaseIcon from './BaseIcon' +import { mdiClose } from '@mdi/js'; +import React, { ReactNode, useState } from 'react'; +import { ColorKey } from '../interfaces'; +import { colorsBgLight, colorsOutline } from '../colors'; +import BaseButton from './BaseButton'; +import BaseIcon from './BaseIcon'; type Props = { - color: ColorKey - icon?: string - outline?: boolean - children: ReactNode - button?: ReactNode -} + color: ColorKey; + icon?: string; + outline?: boolean; + children: ReactNode; + button?: ReactNode; +}; const NotificationBar = ({ outline = false, children, ...props }: Props) => { - const componentColorClass = outline ? colorsOutline[props.color] : colorsBgLight[props.color] + const componentColorClass = outline + ? colorsOutline[props.color] + : colorsBgLight[props.color]; - const [isDismissed, setIsDismissed] = useState(false) + const [isDismissed, setIsDismissed] = useState(false); const dismiss = (e: React.MouseEvent) => { - e.preventDefault() + e.preventDefault(); - setIsDismissed(true) - } + setIsDismissed(true); + }; if (isDismissed) { - return null + return null; } return (
    -
    -
    +
    +
    {props.icon && ( )} - {children} + {children}
    {props.button} {!props.button && ( - + )}
    - ) -} + ); +}; -export default NotificationBar +export default NotificationBar; diff --git a/frontend/src/components/OverlayLayer.tsx b/frontend/src/components/OverlayLayer.tsx index 4cb9e6f..53c681d 100644 --- a/frontend/src/components/OverlayLayer.tsx +++ b/frontend/src/components/OverlayLayer.tsx @@ -1,13 +1,13 @@ -import React, { ReactNode } from 'react' -import { useAppSelector } from '../stores/hooks' +import React, { ReactNode } from 'react'; +import { useAppSelector } from '../stores/hooks'; type Props = { - zIndex?: string - type?: string - children?: ReactNode - className?: string - onClick: (e: React.MouseEvent) => void -} + zIndex?: string; + type?: string; + children?: ReactNode; + className?: string; + onClick: (e: React.MouseEvent) => void; +}; export default function OverlayLayer({ zIndex = 'z-50', @@ -16,15 +16,15 @@ export default function OverlayLayer({ className, ...props }: Props) { - const overlayStyle = useAppSelector((state) => state.style.overlayStyle) + const overlayStyle = useAppSelector((state) => state.style.overlayStyle); const handleClick = (e: React.MouseEvent) => { - e.preventDefault() + e.preventDefault(); if (props.onClick) { - props.onClick(e) + props.onClick(e); } - } + }; return (
    - ) + ); } diff --git a/frontend/src/components/PWALoadingOverlay.tsx b/frontend/src/components/PWALoadingOverlay.tsx new file mode 100644 index 0000000..e0b2bbc --- /dev/null +++ b/frontend/src/components/PWALoadingOverlay.tsx @@ -0,0 +1,122 @@ +/** + * PWA Loading Overlay Component + * + * Displays a "We prepare a demo" overlay during first load + * with pulsating animation and progress tracking for asset preloading. + * Uses project theme colors from theme_config_json when available. + */ + +import React, { useEffect, useState } from 'react'; + +type PWALoadingOverlayProps = { + isVisible: boolean; + progress: number; // 0-100 + projectName?: string; + themeConfig?: { + primaryColor?: string; + backgroundColor?: string; + textColor?: string; + }; + onComplete?: () => void; +}; + +const PWALoadingOverlay: React.FC = ({ + isVisible, + progress, + projectName, + themeConfig, + onComplete, +}) => { + const [shouldRender, setShouldRender] = useState(isVisible); + + // Handle fade-out animation before unmounting + useEffect(() => { + if (!isVisible && shouldRender) { + const timer = setTimeout(() => { + setShouldRender(false); + onComplete?.(); + }, 500); // Match CSS transition duration + return () => clearTimeout(timer); + } + if (isVisible) { + setShouldRender(true); + } + }, [isVisible, shouldRender, onComplete]); + + if (!shouldRender) return null; + + const primaryColor = themeConfig?.primaryColor || '#3b82f6'; + const backgroundColor = themeConfig?.backgroundColor || '#1f2937'; + const textColor = themeConfig?.textColor || '#ffffff'; + + return ( +
    + {/* Pulsating logo/spinner */} +
    + + {/* Project name */} + {projectName && ( +

    + {projectName} +

    + )} + + {/* Loading message */} +

    + We prepare a demo +

    + + {/* Progress bar */} +
    +
    +
    + + {/* Progress percentage */} +

    + {Math.round(progress)}% +

    + + {/* Loading indicator dots */} +
    + {[0, 1, 2].map((index) => ( +
    + ))} +
    +
    + ); +}; + +export default PWALoadingOverlay; diff --git a/frontend/src/components/Page_elements/CardPage_elements.tsx b/frontend/src/components/Page_elements/CardPage_elements.tsx index 554ee11..f8d9d1f 100644 --- a/frontend/src/components/Page_elements/CardPage_elements.tsx +++ b/frontend/src/components/Page_elements/CardPage_elements.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { page_elements: any[]; @@ -28,17 +27,19 @@ const CardPage_elements = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAGE_ELEMENTS') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_PAGE_ELEMENTS', + ); return (
    @@ -47,182 +48,159 @@ const CardPage_elements = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && page_elements.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.name} + }`} + > +
    + + {item.name} - -
    - +
    + +
    -
    -
    - - +
    -
    Page
    -
    -
    - { dataFormatter.tour_pagesOneListFormatter(item.page) } -
    -
    +
    Page
    +
    +
    + {dataFormatter.tour_pagesOneListFormatter(item.page)} +
    +
    - - -
    -
    Elementtype
    -
    -
    - { item.element_type } -
    -
    +
    + Elementtype +
    +
    +
    + {item.element_type} +
    +
    - - -
    -
    Name
    -
    -
    - { item.name } -
    -
    +
    Name
    +
    +
    {item.name}
    +
    - - -
    -
    Sortorder
    -
    -
    - { item.sort_order } -
    -
    +
    + Sortorder +
    +
    +
    + {item.sort_order} +
    +
    - - -
    -
    Isvisible
    -
    -
    - { dataFormatter.booleanFormatter(item.is_visible) } -
    -
    +
    + Isvisible +
    +
    +
    + {dataFormatter.booleanFormatter(item.is_visible)} +
    +
    - - -
    -
    X(%)
    -
    -
    - { item.x_percent } -
    -
    +
    X(%)
    +
    +
    + {item.x_percent} +
    +
    - - -
    -
    Y(%)
    -
    -
    - { item.y_percent } -
    -
    +
    Y(%)
    +
    +
    + {item.y_percent} +
    +
    - - -
    -
    Width(%)
    -
    -
    - { item.width_percent } -
    -
    +
    + Width(%) +
    +
    +
    + {item.width_percent} +
    +
    - - -
    -
    Height(%)
    -
    -
    - { item.height_percent } -
    -
    +
    + Height(%) +
    +
    +
    + {item.height_percent} +
    +
    - - -
    -
    Rotation(deg)
    -
    -
    - { item.rotation_deg } -
    -
    +
    + Rotation(deg) +
    +
    +
    + {item.rotation_deg} +
    +
    - - -
    -
    StyleJSON
    -
    -
    - { item.style_json } -
    -
    +
    + StyleJSON +
    +
    +
    + {item.style_json} +
    +
    - - -
    -
    ContentJSON
    -
    -
    - { item.content_json } -
    -
    +
    + ContentJSON +
    +
    +
    + {item.content_json} +
    +
    - - - -
    -
  • - ))} + + + ))} {!loading && page_elements.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Page_elements/ListPage_elements.tsx b/frontend/src/components/Page_elements/ListPage_elements.tsx index 79d2cfb..6f8bc11 100644 --- a/frontend/src/components/Page_elements/ListPage_elements.tsx +++ b/frontend/src/components/Page_elements/ListPage_elements.tsx @@ -2,175 +2,150 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - page_elements: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + page_elements: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListPage_elements = ({ page_elements, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAGE_ELEMENTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListPage_elements = ({ + page_elements, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_PAGE_ELEMENTS', + ); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && page_elements.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Page

    -

    { dataFormatter.tour_pagesOneListFormatter(item.page) }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + page_elements.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Page

    +

    + {dataFormatter.tour_pagesOneListFormatter(item.page)} +

    +
    - - -
    -

    Elementtype

    -

    { item.element_type }

    -
    - +
    +

    Elementtype

    +

    {item.element_type}

    +
    - - -
    -

    Name

    -

    { item.name }

    -
    - +
    +

    Name

    +

    {item.name}

    +
    - - -
    -

    Sortorder

    -

    { item.sort_order }

    -
    - +
    +

    Sortorder

    +

    {item.sort_order}

    +
    - - -
    -

    Isvisible

    -

    { dataFormatter.booleanFormatter(item.is_visible) }

    -
    - +
    +

    Isvisible

    +

    + {dataFormatter.booleanFormatter(item.is_visible)} +

    +
    - - -
    -

    X(%)

    -

    { item.x_percent }

    -
    - +
    +

    X(%)

    +

    {item.x_percent}

    +
    - - -
    -

    Y(%)

    -

    { item.y_percent }

    -
    - +
    +

    Y(%)

    +

    {item.y_percent}

    +
    - - -
    -

    Width(%)

    -

    { item.width_percent }

    -
    - +
    +

    Width(%)

    +

    {item.width_percent}

    +
    - - -
    -

    Height(%)

    -

    { item.height_percent }

    -
    - +
    +

    Height(%)

    +

    {item.height_percent}

    +
    - - -
    -

    Rotation(deg)

    -

    { item.rotation_deg }

    -
    - +
    +

    + Rotation(deg) +

    +

    {item.rotation_deg}

    +
    - - -
    -

    StyleJSON

    -

    { item.style_json }

    -
    - +
    +

    StyleJSON

    +

    {item.style_json}

    +
    - - -
    -

    ContentJSON

    -

    { item.content_json }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && page_elements.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    ContentJSON

    +

    {item.content_json}

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && page_elements.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListPage_elements \ No newline at end of file +export default ListPage_elements; diff --git a/frontend/src/components/Page_elements/TablePage_elements.tsx b/frontend/src/components/Page_elements/TablePage_elements.tsx index 187192c..f44e8c4 100644 --- a/frontend/src/components/Page_elements/TablePage_elements.tsx +++ b/frontend/src/components/Page_elements/TablePage_elements.tsx @@ -1,140 +1,63 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; +/** + * Page Elements Table Component + */ + +import React, { useEffect, useState, useMemo } from 'react'; import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/page_elements/page_elementsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; -import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configurePage_elementsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - +import CardBox from '../CardBox'; +import BaseButton from '../BaseButton'; +import GenericTable from '../Generic/GenericTable'; import KanbanBoard from '../KanbanBoard/KanbanBoard'; -import axios from 'axios'; +import { + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/page_elements/page_elementsSlice'; +import { loadColumns } from './configurePage_elementsCols'; +import { useAppSelector } from '../../stores/hooks'; +import { Field, Form, Formik } from 'formik'; +import type { PageElement } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; +interface TablePage_elementsProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; + extraQuery?: string; +} -const perPage = 10 +const KANBAN_COLUMNS = [ + { id: 'nav_button', label: 'nav_button' }, + { id: 'spot', label: 'spot' }, + { id: 'description', label: 'description' }, + { id: 'tooltip', label: 'tooltip' }, + { id: 'gallery', label: 'gallery' }, + { id: 'carousel', label: 'carousel' }, + { id: 'logo', label: 'logo' }, + { id: 'video_player', label: 'video_player' }, + { id: 'popup', label: 'popup' }, +]; -const TableSamplePage_elements = ({ filterItems, setFilterItems, filters, showGrid, extraQuery = '' }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const [kanbanColumns, setKanbanColumns] = useState | null>(null); - const [kanbanFilters, setKanbanFilters] = useState(''); - - const { page_elements, loading, count, notify: page_elementsNotify, refetch } = useAppSelector((state) => state.page_elements) - const { currentUser } = useAppSelector((state) => state.auth); +const TablePage_elements: React.FC = ({ + filterItems, + setFilterItems, + filters, + showGrid = false, + extraQuery = '', +}) => { + const [kanbanFilters, setKanbanFilters] = useState(''); const focusRing = useAppSelector((state) => state.style.focusRingColor); const bgColor = useAppSelector((state) => state.style.bgLayoutColor); const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}${extraQuery}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (page_elementsNotify.showNotification) { - notify(page_elementsNotify.typeNotification, page_elementsNotify.textNotification); - } - }, [page_elementsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - useEffect(() => { - - - - setKanbanColumns([ - - { id: "nav_button", label: "nav_button" }, - - { id: "spot", label: "spot" }, - - { id: "description", label: "description" }, - - { id: "tooltip", label: "tooltip" }, - - { id: "gallery", label: "gallery" }, - - { id: "carousel", label: "carousel" }, - - { id: "logo", label: "logo" }, - - { id: "video_player", label: "video_player" }, - - { id: "popup", label: "popup" }, - - ]); - - - }, []); - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; + const controlClasses = + 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + + ` ${bgColor} ${focusRing} ${corners} ` + + 'dark:bg-slate-800 border'; const generateFilterRequests = useMemo(() => { let request = '&'; @@ -144,374 +67,188 @@ const TableSamplePage_elements = ({ filterItems, setFilterItems, filters, showGr filter.title === item.fields.selectedField && (filter.number || filter.date), ); - if (isRangeFilter) { const from = item.fields.filterValueFrom; const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } + if (from) request += `${item.fields.selectedField}Range=${from}&`; + if (to) request += `${item.fields.selectedField}Range=${to}&`; } else { const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } + if (value) request += `${item.fields.selectedField}=${value}&`; } }); return request; }, [filterItems, filters]); - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); + const handleChange = + (id: string) => + (e: React.ChangeEvent) => { + const value = e.target.value; + const name = e.target.name; + setFilterItems( + filterItems.map((item) => { + if (item.id !== id) return item; + if (name === 'selectedField') + return { id, fields: { [name]: value } }; + return { id, fields: { ...item.fields, [name]: value } }; + }), + ); + }; - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setKanbanFilters(''); - - setFilterItems(newItems); + const deleteFilter = (value: string) => { + const newItems = filterItems.filter((item) => item.id !== value); + if (!newItems.length) { + setKanbanFilters(''); } + setFilterItems(newItems); }; const handleSubmit = () => { - loadData(0, generateFilterRequests); - setKanbanFilters(generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); }; const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - setKanbanFilters(''); - + setFilterItems([]); + setKanbanFilters(''); }; - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `page_elements`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={page_elements ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) + // Show grid view using GenericTable + if (showGrid) { + return ( + + entityName='page_elements' + sliceSelector={(state: RootState) => state.page_elements} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + extraQuery={extraQuery} + /> + ); + } + // Show kanban view with custom filter handling return ( <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? + {filterItems && Array.isArray(filterItems) && filterItems.length > 0 && ( - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - - {!showGrid && kanbanColumns && ( - - )} - - - - {showGrid && dataGrid} - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), + null} + > +
    + {filterItems.map((filterItem) => ( +
    +
    +
    Filter
    + + {filters.map((selectOption) => ( + + ))} + +
    + {filters.find( + (f) => f.title === filterItem?.fields?.selectedField, + )?.type === 'enum' ? ( +
    +
    Value
    + + + {filters + .find( + (f) => + f.title === filterItem?.fields?.selectedField, + ) + ?.options?.map((option) => ( + + ))} + +
    + ) : ( +
    +
    Contains
    + +
    + )} +
    +
    Action
    + deleteFilter(filterItem.id)} + /> +
    +
    + ))} +
    + + +
    +
    +
    + )} - + + - ) -} + ); +}; -export default TableSamplePage_elements +export default TablePage_elements; diff --git a/frontend/src/components/Page_elements/configurePage_elementsCols.tsx b/frontend/src/components/Page_elements/configurePage_elementsCols.tsx index 24d34ee..9fc4983 100644 --- a/frontend/src/components/Page_elements/configurePage_elementsCols.tsx +++ b/frontend/src/components/Page_elements/configurePage_elementsCols.tsx @@ -3,260 +3,226 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_PAGE_ELEMENTS') - - return [ - - { - field: 'page', - headerName: 'Page', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('tour_pages'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'element_type', - headerName: 'Elementtype', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'name', - headerName: 'Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'sort_order', - headerName: 'Sortorder', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'is_visible', - headerName: 'Isvisible', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'x_percent', - headerName: 'X(%)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'y_percent', - headerName: 'Y(%)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'width_percent', - headerName: 'Width(%)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'height_percent', - headerName: 'Height(%)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'rotation_deg', - headerName: 'Rotation(deg)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'style_json', - headerName: 'StyleJSON', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'content_json', - headerName: 'ContentJSON', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_PAGE_ELEMENTS'); + + return [ + { + field: 'page', + headerName: 'Page', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('tour_pages'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'element_type', + headerName: 'Elementtype', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'name', + headerName: 'Name', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'sort_order', + headerName: 'Sortorder', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'is_visible', + headerName: 'Isvisible', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'boolean', + }, + + { + field: 'x_percent', + headerName: 'X(%)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'y_percent', + headerName: 'Y(%)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'width_percent', + headerName: 'Width(%)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'height_percent', + headerName: 'Height(%)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'rotation_deg', + headerName: 'Rotation(deg)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'style_json', + headerName: 'StyleJSON', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'content_json', + headerName: 'ContentJSON', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/Page_links/CardPage_links.tsx b/frontend/src/components/Page_links/CardPage_links.tsx index aede92a..58f0d5d 100644 --- a/frontend/src/components/Page_links/CardPage_links.tsx +++ b/frontend/src/components/Page_links/CardPage_links.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { page_links: any[]; @@ -28,17 +27,16 @@ const CardPage_links = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAGE_LINKS') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAGE_LINKS'); return (
    @@ -47,122 +45,116 @@ const CardPage_links = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && page_links.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.direction} + }`} + > +
    + + {item.direction} - -
    - +
    + +
    -
    -
    - - +
    -
    Frompage
    -
    -
    - { dataFormatter.tour_pagesOneListFormatter(item.from_page) } -
    -
    +
    + Frompage +
    +
    +
    + {dataFormatter.tour_pagesOneListFormatter(item.from_page)} +
    +
    - - -
    -
    Topage
    -
    -
    - { dataFormatter.tour_pagesOneListFormatter(item.to_page) } -
    -
    +
    + Topage +
    +
    +
    + {dataFormatter.tour_pagesOneListFormatter(item.to_page)} +
    +
    - - -
    -
    Direction
    -
    -
    - { item.direction } -
    -
    +
    + Direction +
    +
    +
    + {item.direction} +
    +
    - - -
    -
    ExternalURL
    -
    -
    - { item.external_url } -
    -
    +
    + ExternalURL +
    +
    +
    + {item.external_url} +
    +
    - - -
    -
    Transition
    -
    -
    - { dataFormatter.transitionsOneListFormatter(item.transition) } -
    -
    +
    + Transition +
    +
    +
    + {dataFormatter.transitionsOneListFormatter( + item.transition, + )} +
    +
    - - -
    -
    Isactive
    -
    -
    - { dataFormatter.booleanFormatter(item.is_active) } -
    -
    +
    + Isactive +
    +
    +
    + {dataFormatter.booleanFormatter(item.is_active)} +
    +
    - - -
    -
    Triggerselector
    -
    -
    - { item.trigger_selector } -
    -
    +
    + Triggerselector +
    +
    +
    + {item.trigger_selector} +
    +
    - - - -
    -
  • - ))} + + + ))} {!loading && page_links.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Page_links/ListPage_links.tsx b/frontend/src/components/Page_links/ListPage_links.tsx index 2b8c1bc..f6adf58 100644 --- a/frontend/src/components/Page_links/ListPage_links.tsx +++ b/frontend/src/components/Page_links/ListPage_links.tsx @@ -2,135 +2,130 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - page_links: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + page_links: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListPage_links = ({ page_links, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAGE_LINKS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListPage_links = ({ + page_links, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAGE_LINKS'); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && page_links.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Frompage

    -

    { dataFormatter.tour_pagesOneListFormatter(item.from_page) }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + page_links.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Frompage

    +

    + {dataFormatter.tour_pagesOneListFormatter( + item.from_page, + )} +

    +
    - - -
    -

    Topage

    -

    { dataFormatter.tour_pagesOneListFormatter(item.to_page) }

    -
    - +
    +

    Topage

    +

    + {dataFormatter.tour_pagesOneListFormatter(item.to_page)} +

    +
    - - -
    -

    Direction

    -

    { item.direction }

    -
    - +
    +

    Direction

    +

    {item.direction}

    +
    - - -
    -

    ExternalURL

    -

    { item.external_url }

    -
    - +
    +

    ExternalURL

    +

    {item.external_url}

    +
    - - -
    -

    Transition

    -

    { dataFormatter.transitionsOneListFormatter(item.transition) }

    -
    - +
    +

    Transition

    +

    + {dataFormatter.transitionsOneListFormatter( + item.transition, + )} +

    +
    - - -
    -

    Isactive

    -

    { dataFormatter.booleanFormatter(item.is_active) }

    -
    - +
    +

    Isactive

    +

    + {dataFormatter.booleanFormatter(item.is_active)} +

    +
    - - -
    -

    Triggerselector

    -

    { item.trigger_selector }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && page_links.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    + Triggerselector +

    +

    {item.trigger_selector}

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && page_links.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListPage_links \ No newline at end of file +export default ListPage_links; diff --git a/frontend/src/components/Page_links/TablePage_links.tsx b/frontend/src/components/Page_links/TablePage_links.tsx index e803bbe..270652a 100644 --- a/frontend/src/components/Page_links/TablePage_links.tsx +++ b/frontend/src/components/Page_links/TablePage_links.tsx @@ -1,463 +1,48 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/page_links/page_linksSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * Page Links Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configurePage_linksCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/page_links/page_linksSlice'; +import { loadColumns } from './configurePage_linksCols'; +import type { PageLink } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSamplePage_links = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { page_links, loading, count, notify: page_linksNotify, refetch } = useAppSelector((state) => state.page_links) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (page_linksNotify.showNotification) { - notify(page_linksNotify.typeNotification, page_linksNotify.textNotification); - } - }, [page_linksNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `page_links`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={page_links ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TablePage_linksProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSamplePage_links +const TablePage_links: React.FC = ({ + filterItems, + setFilterItems, + filters, +}) => { + return ( + + entityName='page_links' + sliceSelector={(state: RootState) => state.page_links} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TablePage_links; diff --git a/frontend/src/components/Page_links/configurePage_linksCols.tsx b/frontend/src/components/Page_links/configurePage_linksCols.tsx index 4b3be0e..6a6644f 100644 --- a/frontend/src/components/Page_links/configurePage_linksCols.tsx +++ b/frontend/src/components/Page_links/configurePage_linksCols.tsx @@ -3,193 +3,170 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_PAGE_LINKS') - - return [ - - { - field: 'from_page', - headerName: 'Frompage', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('tour_pages'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'to_page', - headerName: 'Topage', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('tour_pages'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'direction', - headerName: 'Direction', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'external_url', - headerName: 'ExternalURL', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'transition', - headerName: 'Transition', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('transitions'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'is_active', - headerName: 'Isactive', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'trigger_selector', - headerName: 'Triggerselector', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_PAGE_LINKS'); + + return [ + { + field: 'from_page', + headerName: 'Frompage', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('tour_pages'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'to_page', + headerName: 'Topage', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('tour_pages'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'direction', + headerName: 'Direction', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'external_url', + headerName: 'ExternalURL', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'transition', + headerName: 'Transition', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('transitions'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'is_active', + headerName: 'Isactive', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'boolean', + }, + + { + field: 'trigger_selector', + headerName: 'Triggerselector', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/Pagination.tsx b/frontend/src/components/Pagination.tsx index bb6f7b3..8203969 100644 --- a/frontend/src/components/Pagination.tsx +++ b/frontend/src/components/Pagination.tsx @@ -41,7 +41,7 @@ export const Pagination = ({ />
    setCurrentPage(currentPage - 1)}> - diff --git a/frontend/src/components/PasswordSetOrReset.tsx b/frontend/src/components/PasswordSetOrReset.tsx index 07749ba..5fdf30a 100644 --- a/frontend/src/components/PasswordSetOrReset.tsx +++ b/frontend/src/components/PasswordSetOrReset.tsx @@ -1,113 +1,111 @@ import React from 'react'; -import {toast, ToastContainer} from 'react-toastify'; +import { toast, ToastContainer } from 'react-toastify'; import Head from 'next/head'; import CardBox from '../components/CardBox'; import SectionFullScreen from '../components/SectionFullScreen'; -import {useRouter} from 'next/router'; -import {getPageTitle} from '../config'; +import { useRouter } from 'next/router'; +import { getPageTitle } from '../config'; -import {Field, Form, Formik} from 'formik'; +import { Field, Form, Formik } from 'formik'; import FormField from '../components/FormField'; import BaseButtons from '../components/BaseButtons'; import BaseButton from '../components/BaseButton'; import { passwordReset } from '../stores/authSlice'; -import {useAppDispatch} from '../stores/hooks'; +import { useAppDispatch } from '../stores/hooks'; export default function PasswordSetOrReset() { - const [loading, setLoading] = React.useState(false); - const [isInvitation, setIsInvitation] = React.useState(false); - const router = useRouter(); - const {token, invitation} = router.query; + const [loading, setLoading] = React.useState(false); + const [isInvitation, setIsInvitation] = React.useState(false); + const router = useRouter(); + const { token, invitation } = router.query; - const notify = (type, msg) => toast(msg, {type}); + const notify = (type, msg) => toast(msg, { type }); - const dispatch = useAppDispatch(); - - React.useEffect(() => { - if (invitation) { - setIsInvitation(true); - } - }, [invitation]); + const dispatch = useAppDispatch(); - const handleSubmit = async (value) => { - setLoading(true); - if (typeof token === 'string') { - await dispatch( - passwordReset({ - token, - password: value.password, - type: isInvitation && 'invitation', - }), - ); - await router.push('/login'); - } + React.useEffect(() => { + if (invitation) { + setIsInvitation(true); + } + }, [invitation]); - setLoading(false); - }; + const handleSubmit = async (value) => { + setLoading(true); + if (typeof token === 'string') { + await dispatch( + passwordReset({ + token, + password: value.password, + type: isInvitation && 'invitation', + }), + ); + await router.push('/login'); + } - return ( - <> - - {isInvitation && {getPageTitle('Set Password')}} - {!isInvitation && {getPageTitle('Reset Password')}} - + setLoading(false); + }; - -
    - - {isInvitation &&

    Set Password

    } - {!isInvitation &&

    Reset Password

    } -

    Enter your new password

    + return ( + <> + + {isInvitation && {getPageTitle('Set Password')}} + {!isInvitation && {getPageTitle('Reset Password')}} + - handleSubmit(values)} - > - {({errors, touched}) => ( -
    - - - - - - + +
    + + {isInvitation &&

    Set Password

    } + {!isInvitation &&

    Reset Password

    } +

    Enter your new password

    - - - - - )} - -
    -
    -
    - - - ); + handleSubmit(values)} + > + {({ errors, touched }) => ( +
    + + + + + + + + + + +
    + )} +
    +
    +
    +
    + + + ); } diff --git a/frontend/src/components/Permissions/CardPermissions.tsx b/frontend/src/components/Permissions/CardPermissions.tsx index caff22d..b0bd673 100644 --- a/frontend/src/components/Permissions/CardPermissions.tsx +++ b/frontend/src/components/Permissions/CardPermissions.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { permissions: any[]; @@ -28,17 +27,16 @@ const CardPermissions = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PERMISSIONS') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PERMISSIONS'); return (
    @@ -47,50 +45,44 @@ const CardPermissions = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && permissions.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.name} + }`} + > +
    + + {item.name} - -
    - -
    -
    -
    - - -
    -
    Name
    -
    -
    - { item.name } -
    -
    +
    +
    - - - -
    -
  • - ))} +
    +
    +
    +
    Name
    +
    +
    {item.name}
    +
    +
    +
    + + ))} {!loading && permissions.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Permissions/ListPermissions.tsx b/frontend/src/components/Permissions/ListPermissions.tsx index 80239af..b74b0a0 100644 --- a/frontend/src/components/Permissions/ListPermissions.tsx +++ b/frontend/src/components/Permissions/ListPermissions.tsx @@ -2,87 +2,86 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - permissions: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + permissions: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListPermissions = ({ permissions, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PERMISSIONS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListPermissions = ({ + permissions, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PERMISSIONS'); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && permissions.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Name

    -

    { item.name }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && permissions.length === 0 && ( -
    -

    No data to display

    -
    - )} + return ( + <> +
    + {loading && } + {!loading && + permissions.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Name

    +

    {item.name}

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && permissions.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListPermissions \ No newline at end of file +export default ListPermissions; diff --git a/frontend/src/components/Permissions/TablePermissions.tsx b/frontend/src/components/Permissions/TablePermissions.tsx index 9b21466..0193067 100644 --- a/frontend/src/components/Permissions/TablePermissions.tsx +++ b/frontend/src/components/Permissions/TablePermissions.tsx @@ -1,463 +1,48 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/permissions/permissionsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * Permissions Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configurePermissionsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/permissions/permissionsSlice'; +import { loadColumns } from './configurePermissionsCols'; +import type { Permission } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSamplePermissions = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { permissions, loading, count, notify: permissionsNotify, refetch } = useAppSelector((state) => state.permissions) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (permissionsNotify.showNotification) { - notify(permissionsNotify.typeNotification, permissionsNotify.textNotification); - } - }, [permissionsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `permissions`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={permissions ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TablePermissionsProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSamplePermissions +const TablePermissions: React.FC = ({ + filterItems, + setFilterItems, + filters, +}) => { + return ( + + entityName='permissions' + sliceSelector={(state: RootState) => state.permissions} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TablePermissions; diff --git a/frontend/src/components/Permissions/configurePermissionsCols.tsx b/frontend/src/components/Permissions/configurePermissionsCols.tsx index b221e80..2d46f88 100644 --- a/frontend/src/components/Permissions/configurePermissionsCols.tsx +++ b/frontend/src/components/Permissions/configurePermissionsCols.tsx @@ -3,81 +3,72 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_PERMISSIONS') - - return [ - - { - field: 'name', - headerName: 'Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_PERMISSIONS'); + + return [ + { + field: 'name', + headerName: 'Name', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/Presigned_url_requests/CardPresigned_url_requests.tsx b/frontend/src/components/Presigned_url_requests/CardPresigned_url_requests.tsx index 266df20..55af3e8 100644 --- a/frontend/src/components/Presigned_url_requests/CardPresigned_url_requests.tsx +++ b/frontend/src/components/Presigned_url_requests/CardPresigned_url_requests.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { presigned_url_requests: any[]; @@ -28,17 +27,19 @@ const CardPresigned_url_requests = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PRESIGNED_URL_REQUESTS') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_PRESIGNED_URL_REQUESTS', + ); return (
    @@ -47,146 +48,134 @@ const CardPresigned_url_requests = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && presigned_url_requests.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.requested_key} + }`} + > +
    + + {item.requested_key} - -
    - +
    + +
    -
    -
    - - +
    -
    Project
    -
    -
    - { dataFormatter.projectsOneListFormatter(item.project) } -
    -
    +
    + Project +
    +
    +
    + {dataFormatter.projectsOneListFormatter(item.project)} +
    +
    - - -
    -
    User
    -
    -
    - { dataFormatter.usersOneListFormatter(item.user) } -
    -
    +
    User
    +
    +
    + {dataFormatter.usersOneListFormatter(item.user)} +
    +
    - - -
    -
    Purpose
    -
    -
    - { item.purpose } -
    -
    +
    + Purpose +
    +
    +
    + {item.purpose} +
    +
    - - -
    -
    Assettype
    -
    -
    - { item.asset_type } -
    -
    +
    + Assettype +
    +
    +
    + {item.asset_type} +
    +
    - - -
    -
    Requestedkey
    -
    -
    - { item.requested_key } -
    -
    +
    + Requestedkey +
    +
    +
    + {item.requested_key} +
    +
    - - -
    -
    MIMEtype
    -
    -
    - { item.mime_type } -
    -
    +
    + MIMEtype +
    +
    +
    + {item.mime_type} +
    +
    - - -
    -
    Requestedsize(MB)
    -
    -
    - { item.requested_size_mb } -
    -
    +
    + Requestedsize(MB) +
    +
    +
    + {item.requested_size_mb} +
    +
    - - -
    -
    Expiresat
    -
    -
    - { dataFormatter.dateTimeFormatter(item.expires_at) } -
    -
    +
    + Expiresat +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.expires_at)} +
    +
    - - -
    -
    Status
    -
    -
    - { item.status } -
    -
    +
    + Status +
    +
    +
    + {item.status} +
    +
    - - - -
    -
  • - ))} + + + ))} {!loading && presigned_url_requests.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Presigned_url_requests/ListPresigned_url_requests.tsx b/frontend/src/components/Presigned_url_requests/ListPresigned_url_requests.tsx index 3eeb0f2..6db827c 100644 --- a/frontend/src/components/Presigned_url_requests/ListPresigned_url_requests.tsx +++ b/frontend/src/components/Presigned_url_requests/ListPresigned_url_requests.tsx @@ -2,151 +2,137 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - presigned_url_requests: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + presigned_url_requests: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListPresigned_url_requests = ({ presigned_url_requests, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PRESIGNED_URL_REQUESTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListPresigned_url_requests = ({ + presigned_url_requests, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_PRESIGNED_URL_REQUESTS', + ); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && presigned_url_requests.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Project

    -

    { dataFormatter.projectsOneListFormatter(item.project) }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + presigned_url_requests.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Project

    +

    + {dataFormatter.projectsOneListFormatter(item.project)} +

    +
    - - -
    -

    User

    -

    { dataFormatter.usersOneListFormatter(item.user) }

    -
    - +
    +

    User

    +

    + {dataFormatter.usersOneListFormatter(item.user)} +

    +
    - - -
    -

    Purpose

    -

    { item.purpose }

    -
    - +
    +

    Purpose

    +

    {item.purpose}

    +
    - - -
    -

    Assettype

    -

    { item.asset_type }

    -
    - +
    +

    Assettype

    +

    {item.asset_type}

    +
    - - -
    -

    Requestedkey

    -

    { item.requested_key }

    -
    - +
    +

    Requestedkey

    +

    {item.requested_key}

    +
    - - -
    -

    MIMEtype

    -

    { item.mime_type }

    -
    - +
    +

    MIMEtype

    +

    {item.mime_type}

    +
    - - -
    -

    Requestedsize(MB)

    -

    { item.requested_size_mb }

    -
    - +
    +

    + Requestedsize(MB) +

    +

    {item.requested_size_mb}

    +
    - - -
    -

    Expiresat

    -

    { dataFormatter.dateTimeFormatter(item.expires_at) }

    -
    - +
    +

    Expiresat

    +

    + {dataFormatter.dateTimeFormatter(item.expires_at)} +

    +
    - - -
    -

    Status

    -

    { item.status }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && presigned_url_requests.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    Status

    +

    {item.status}

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && presigned_url_requests.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListPresigned_url_requests \ No newline at end of file +export default ListPresigned_url_requests; diff --git a/frontend/src/components/Presigned_url_requests/TablePresigned_url_requests.tsx b/frontend/src/components/Presigned_url_requests/TablePresigned_url_requests.tsx index fd35a86..18e42cd 100644 --- a/frontend/src/components/Presigned_url_requests/TablePresigned_url_requests.tsx +++ b/frontend/src/components/Presigned_url_requests/TablePresigned_url_requests.tsx @@ -1,463 +1,46 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/presigned_url_requests/presigned_url_requestsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * Presigned URL Requests Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configurePresigned_url_requestsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/presigned_url_requests/presigned_url_requestsSlice'; +import { loadColumns } from './configurePresigned_url_requestsCols'; +import type { PresignedUrlRequest } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSamplePresigned_url_requests = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { presigned_url_requests, loading, count, notify: presigned_url_requestsNotify, refetch } = useAppSelector((state) => state.presigned_url_requests) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (presigned_url_requestsNotify.showNotification) { - notify(presigned_url_requestsNotify.typeNotification, presigned_url_requestsNotify.textNotification); - } - }, [presigned_url_requestsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `presigned_url_requests`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={presigned_url_requests ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TablePresigned_url_requestsProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSamplePresigned_url_requests +const TablePresigned_url_requests: React.FC< + TablePresigned_url_requestsProps +> = ({ filterItems, setFilterItems, filters }) => { + return ( + + entityName='presigned_url_requests' + sliceSelector={(state: RootState) => state.presigned_url_requests} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TablePresigned_url_requests; diff --git a/frontend/src/components/Presigned_url_requests/configurePresigned_url_requestsCols.tsx b/frontend/src/components/Presigned_url_requests/configurePresigned_url_requestsCols.tsx index 524f1b7..196cbf5 100644 --- a/frontend/src/components/Presigned_url_requests/configurePresigned_url_requestsCols.tsx +++ b/frontend/src/components/Presigned_url_requests/configurePresigned_url_requestsCols.tsx @@ -3,219 +3,193 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_PRESIGNED_URL_REQUESTS') - - return [ - - { - field: 'project', - headerName: 'Project', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('projects'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'user', - headerName: 'User', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('users'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'purpose', - headerName: 'Purpose', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'asset_type', - headerName: 'Assettype', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'requested_key', - headerName: 'Requestedkey', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'mime_type', - headerName: 'MIMEtype', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'requested_size_mb', - headerName: 'Requestedsize(MB)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'expires_at', - headerName: 'Expiresat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.expires_at), - - }, - - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission( + user, + 'UPDATE_PRESIGNED_URL_REQUESTS', + ); + + return [ + { + field: 'project', + headerName: 'Project', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('projects'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'user', + headerName: 'User', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('users'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'purpose', + headerName: 'Purpose', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'asset_type', + headerName: 'Assettype', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'requested_key', + headerName: 'Requestedkey', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'mime_type', + headerName: 'MIMEtype', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'requested_size_mb', + headerName: 'Requestedsize(MB)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'expires_at', + headerName: 'Expiresat', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.expires_at), + }, + + { + field: 'status', + headerName: 'Status', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/Project_audio_tracks/CardProject_audio_tracks.tsx b/frontend/src/components/Project_audio_tracks/CardProject_audio_tracks.tsx index 5c64d3e..fcdc743 100644 --- a/frontend/src/components/Project_audio_tracks/CardProject_audio_tracks.tsx +++ b/frontend/src/components/Project_audio_tracks/CardProject_audio_tracks.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { project_audio_tracks: any[]; @@ -28,17 +27,19 @@ const CardProject_audio_tracks = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROJECT_AUDIO_TRACKS') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_PROJECT_AUDIO_TRACKS', + ); return (
    @@ -47,158 +48,133 @@ const CardProject_audio_tracks = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && project_audio_tracks.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.name} + }`} + > +
    + + {item.name} - -
    - +
    + +
    -
    -
    - - +
    -
    Project
    -
    -
    - { dataFormatter.projectsOneListFormatter(item.project) } -
    -
    +
    + Project +
    +
    +
    + {dataFormatter.projectsOneListFormatter(item.project)} +
    +
    - - -
    -
    Environment
    -
    -
    - { item.environment } -
    -
    +
    + Environment +
    +
    +
    + {item.environment} +
    +
    - - -
    -
    Sourcekey
    -
    -
    - { item.source_key } -
    -
    +
    + Sourcekey +
    +
    +
    + {item.source_key} +
    +
    - - -
    -
    Name
    -
    -
    - { item.name } -
    -
    +
    Name
    +
    +
    {item.name}
    +
    - - -
    -
    Slug
    -
    -
    - { item.slug } -
    -
    +
    Slug
    +
    +
    {item.slug}
    +
    - - -
    -
    URL
    -
    -
    - { item.url } -
    -
    +
    URL
    +
    +
    {item.url}
    +
    - - -
    -
    Loop
    -
    -
    - { dataFormatter.booleanFormatter(item.loop) } -
    -
    +
    Loop
    +
    +
    + {dataFormatter.booleanFormatter(item.loop)} +
    +
    - - -
    -
    Volume
    -
    -
    - { item.volume } -
    -
    +
    + Volume +
    +
    +
    + {item.volume} +
    +
    - - -
    -
    Sortorder
    -
    -
    - { item.sort_order } -
    -
    +
    + Sortorder +
    +
    +
    + {item.sort_order} +
    +
    - - -
    -
    Isenabled
    -
    -
    - { dataFormatter.booleanFormatter(item.is_enabled) } -
    -
    +
    + Isenabled +
    +
    +
    + {dataFormatter.booleanFormatter(item.is_enabled)} +
    +
    - - - -
    -
  • - ))} + + + ))} {!loading && project_audio_tracks.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Project_audio_tracks/ListProject_audio_tracks.tsx b/frontend/src/components/Project_audio_tracks/ListProject_audio_tracks.tsx index 81ceee0..7eaa3bc 100644 --- a/frontend/src/components/Project_audio_tracks/ListProject_audio_tracks.tsx +++ b/frontend/src/components/Project_audio_tracks/ListProject_audio_tracks.tsx @@ -2,159 +2,140 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - project_audio_tracks: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + project_audio_tracks: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListProject_audio_tracks = ({ project_audio_tracks, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROJECT_AUDIO_TRACKS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListProject_audio_tracks = ({ + project_audio_tracks, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_PROJECT_AUDIO_TRACKS', + ); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && project_audio_tracks.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Project

    -

    { dataFormatter.projectsOneListFormatter(item.project) }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + project_audio_tracks.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Project

    +

    + {dataFormatter.projectsOneListFormatter(item.project)} +

    +
    - - -
    -

    Environment

    -

    { item.environment }

    -
    - +
    +

    Environment

    +

    {item.environment}

    +
    - - -
    -

    Sourcekey

    -

    { item.source_key }

    -
    - +
    +

    Sourcekey

    +

    {item.source_key}

    +
    - - -
    -

    Name

    -

    { item.name }

    -
    - +
    +

    Name

    +

    {item.name}

    +
    - - -
    -

    Slug

    -

    { item.slug }

    -
    - +
    +

    Slug

    +

    {item.slug}

    +
    - - -
    -

    URL

    -

    { item.url }

    -
    - +
    +

    URL

    +

    {item.url}

    +
    - - -
    -

    Loop

    -

    { dataFormatter.booleanFormatter(item.loop) }

    -
    - +
    +

    Loop

    +

    + {dataFormatter.booleanFormatter(item.loop)} +

    +
    - - -
    -

    Volume

    -

    { item.volume }

    -
    - +
    +

    Volume

    +

    {item.volume}

    +
    - - -
    -

    Sortorder

    -

    { item.sort_order }

    -
    - +
    +

    Sortorder

    +

    {item.sort_order}

    +
    - - -
    -

    Isenabled

    -

    { dataFormatter.booleanFormatter(item.is_enabled) }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && project_audio_tracks.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    Isenabled

    +

    + {dataFormatter.booleanFormatter(item.is_enabled)} +

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && project_audio_tracks.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListProject_audio_tracks \ No newline at end of file +export default ListProject_audio_tracks; diff --git a/frontend/src/components/Project_audio_tracks/TableProject_audio_tracks.tsx b/frontend/src/components/Project_audio_tracks/TableProject_audio_tracks.tsx index 7c97db7..160f4f4 100644 --- a/frontend/src/components/Project_audio_tracks/TableProject_audio_tracks.tsx +++ b/frontend/src/components/Project_audio_tracks/TableProject_audio_tracks.tsx @@ -1,463 +1,48 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/project_audio_tracks/project_audio_tracksSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * Project Audio Tracks Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureProject_audio_tracksCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/project_audio_tracks/project_audio_tracksSlice'; +import { loadColumns } from './configureProject_audio_tracksCols'; +import type { ProjectAudioTrack } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSampleProject_audio_tracks = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { project_audio_tracks, loading, count, notify: project_audio_tracksNotify, refetch } = useAppSelector((state) => state.project_audio_tracks) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (project_audio_tracksNotify.showNotification) { - notify(project_audio_tracksNotify.typeNotification, project_audio_tracksNotify.textNotification); - } - }, [project_audio_tracksNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `project_audio_tracks`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={project_audio_tracks ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TableProject_audio_tracksProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSampleProject_audio_tracks +const TableProject_audio_tracks: React.FC = ({ + filterItems, + setFilterItems, + filters, +}) => { + return ( + + entityName='project_audio_tracks' + sliceSelector={(state: RootState) => state.project_audio_tracks} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TableProject_audio_tracks; diff --git a/frontend/src/components/Project_audio_tracks/configureProject_audio_tracksCols.tsx b/frontend/src/components/Project_audio_tracks/configureProject_audio_tracksCols.tsx index 95aa529..e8d7d55 100644 --- a/frontend/src/components/Project_audio_tracks/configureProject_audio_tracksCols.tsx +++ b/frontend/src/components/Project_audio_tracks/configureProject_audio_tracksCols.tsx @@ -3,227 +3,199 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_PROJECT_AUDIO_TRACKS') - - return [ - - { - field: 'project', - headerName: 'Project', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('projects'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'environment', - headerName: 'Environment', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'source_key', - headerName: 'Sourcekey', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'name', - headerName: 'Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'slug', - headerName: 'Slug', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'url', - headerName: 'URL', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'loop', - headerName: 'Loop', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'volume', - headerName: 'Volume', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'sort_order', - headerName: 'Sortorder', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'is_enabled', - headerName: 'Isenabled', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission( + user, + 'UPDATE_PROJECT_AUDIO_TRACKS', + ); + + return [ + { + field: 'project', + headerName: 'Project', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('projects'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'environment', + headerName: 'Environment', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'source_key', + headerName: 'Sourcekey', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'name', + headerName: 'Name', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'slug', + headerName: 'Slug', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'url', + headerName: 'URL', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'loop', + headerName: 'Loop', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'boolean', + }, + + { + field: 'volume', + headerName: 'Volume', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'sort_order', + headerName: 'Sortorder', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'is_enabled', + headerName: 'Isenabled', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'boolean', + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/Project_memberships/CardProject_memberships.tsx b/frontend/src/components/Project_memberships/CardProject_memberships.tsx index 193ec94..977d030 100644 --- a/frontend/src/components/Project_memberships/CardProject_memberships.tsx +++ b/frontend/src/components/Project_memberships/CardProject_memberships.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { project_memberships: any[]; @@ -28,17 +27,19 @@ const CardProject_memberships = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROJECT_MEMBERSHIPS') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_PROJECT_MEMBERSHIPS', + ); return (
    @@ -47,110 +48,101 @@ const CardProject_memberships = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && project_memberships.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.access_level} + }`} + > +
    + + {item.access_level} - -
    - +
    + +
    -
    -
    - - +
    -
    Project
    -
    -
    - { dataFormatter.projectsOneListFormatter(item.project) } -
    -
    +
    + Project +
    +
    +
    + {dataFormatter.projectsOneListFormatter(item.project)} +
    +
    - - -
    -
    User
    -
    -
    - { dataFormatter.usersOneListFormatter(item.user) } -
    -
    +
    User
    +
    +
    + {dataFormatter.usersOneListFormatter(item.user)} +
    +
    - - -
    -
    Accesslevel
    -
    -
    - { item.access_level } -
    -
    +
    + Accesslevel +
    +
    +
    + {item.access_level} +
    +
    - - -
    -
    Isactive
    -
    -
    - { dataFormatter.booleanFormatter(item.is_active) } -
    -
    +
    + Isactive +
    +
    +
    + {dataFormatter.booleanFormatter(item.is_active)} +
    +
    - - -
    -
    Invitedat
    -
    -
    - { dataFormatter.dateTimeFormatter(item.invited_at) } -
    -
    +
    + Invitedat +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.invited_at)} +
    +
    - - -
    -
    Acceptedat
    -
    -
    - { dataFormatter.dateTimeFormatter(item.accepted_at) } -
    -
    +
    + Acceptedat +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.accepted_at)} +
    +
    - - - -
    -
  • - ))} + + + ))} {!loading && project_memberships.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Project_memberships/ListProject_memberships.tsx b/frontend/src/components/Project_memberships/ListProject_memberships.tsx index 2175ace..1855219 100644 --- a/frontend/src/components/Project_memberships/ListProject_memberships.tsx +++ b/frontend/src/components/Project_memberships/ListProject_memberships.tsx @@ -2,127 +2,124 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - project_memberships: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + project_memberships: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListProject_memberships = ({ project_memberships, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROJECT_MEMBERSHIPS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListProject_memberships = ({ + project_memberships, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_PROJECT_MEMBERSHIPS', + ); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && project_memberships.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Project

    -

    { dataFormatter.projectsOneListFormatter(item.project) }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + project_memberships.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Project

    +

    + {dataFormatter.projectsOneListFormatter(item.project)} +

    +
    - - -
    -

    User

    -

    { dataFormatter.usersOneListFormatter(item.user) }

    -
    - +
    +

    User

    +

    + {dataFormatter.usersOneListFormatter(item.user)} +

    +
    - - -
    -

    Accesslevel

    -

    { item.access_level }

    -
    - +
    +

    Accesslevel

    +

    {item.access_level}

    +
    - - -
    -

    Isactive

    -

    { dataFormatter.booleanFormatter(item.is_active) }

    -
    - +
    +

    Isactive

    +

    + {dataFormatter.booleanFormatter(item.is_active)} +

    +
    - - -
    -

    Invitedat

    -

    { dataFormatter.dateTimeFormatter(item.invited_at) }

    -
    - +
    +

    Invitedat

    +

    + {dataFormatter.dateTimeFormatter(item.invited_at)} +

    +
    - - -
    -

    Acceptedat

    -

    { dataFormatter.dateTimeFormatter(item.accepted_at) }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && project_memberships.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    Acceptedat

    +

    + {dataFormatter.dateTimeFormatter(item.accepted_at)} +

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && project_memberships.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListProject_memberships \ No newline at end of file +export default ListProject_memberships; diff --git a/frontend/src/components/Project_memberships/TableProject_memberships.tsx b/frontend/src/components/Project_memberships/TableProject_memberships.tsx index aaa2b2c..1ea4cd1 100644 --- a/frontend/src/components/Project_memberships/TableProject_memberships.tsx +++ b/frontend/src/components/Project_memberships/TableProject_memberships.tsx @@ -1,463 +1,48 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/project_memberships/project_membershipsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * Project Memberships Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureProject_membershipsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/project_memberships/project_membershipsSlice'; +import { loadColumns } from './configureProject_membershipsCols'; +import type { ProjectMembership } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSampleProject_memberships = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { project_memberships, loading, count, notify: project_membershipsNotify, refetch } = useAppSelector((state) => state.project_memberships) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (project_membershipsNotify.showNotification) { - notify(project_membershipsNotify.typeNotification, project_membershipsNotify.textNotification); - } - }, [project_membershipsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `project_memberships`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={project_memberships ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TableProject_membershipsProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSampleProject_memberships +const TableProject_memberships: React.FC = ({ + filterItems, + setFilterItems, + filters, +}) => { + return ( + + entityName='project_memberships' + sliceSelector={(state: RootState) => state.project_memberships} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TableProject_memberships; diff --git a/frontend/src/components/Project_memberships/configureProject_membershipsCols.tsx b/frontend/src/components/Project_memberships/configureProject_membershipsCols.tsx index b224161..f852730 100644 --- a/frontend/src/components/Project_memberships/configureProject_membershipsCols.tsx +++ b/frontend/src/components/Project_memberships/configureProject_membershipsCols.tsx @@ -3,177 +3,158 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_PROJECT_MEMBERSHIPS') - - return [ - - { - field: 'project', - headerName: 'Project', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('projects'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'user', - headerName: 'User', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('users'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'access_level', - headerName: 'Accesslevel', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'is_active', - headerName: 'Isactive', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'invited_at', - headerName: 'Invitedat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.invited_at), - - }, - - { - field: 'accepted_at', - headerName: 'Acceptedat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.accepted_at), - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_PROJECT_MEMBERSHIPS'); + + return [ + { + field: 'project', + headerName: 'Project', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('projects'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'user', + headerName: 'User', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('users'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'access_level', + headerName: 'Accesslevel', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'is_active', + headerName: 'Isactive', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'boolean', + }, + + { + field: 'invited_at', + headerName: 'Invitedat', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.invited_at), + }, + + { + field: 'accepted_at', + headerName: 'Acceptedat', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.accepted_at), + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/Projects/CardProjects.tsx b/frontend/src/components/Projects/CardProjects.tsx index 87868f8..bffc544 100644 --- a/frontend/src/components/Projects/CardProjects.tsx +++ b/frontend/src/components/Projects/CardProjects.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { projects: any[]; @@ -28,17 +27,16 @@ const CardProjects = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROJECTS') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROJECTS'); return (
    @@ -47,194 +45,168 @@ const CardProjects = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && projects.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.name} + }`} + > +
    + + {item.name} - -
    - +
    + +
    -
    -
    - - +
    -
    Name
    -
    -
    - { item.name } -
    -
    +
    Name
    +
    +
    {item.name}
    +
    - - -
    -
    Slug
    -
    -
    - { item.slug } -
    -
    +
    Slug
    +
    +
    {item.slug}
    +
    - - -
    -
    Description
    -
    -
    - { item.description } -
    -
    +
    + Description +
    +
    +
    + {item.description} +
    +
    - - -
    -
    Phase
    -
    -
    - { item.phase } -
    -
    +
    Phase
    +
    +
    {item.phase}
    +
    - - -
    -
    LogoURL
    -
    -
    - { item.logo_url } -
    -
    +
    + LogoURL +
    +
    +
    + {item.logo_url} +
    +
    - - -
    -
    FaviconURL
    -
    -
    - { item.favicon_url } -
    -
    +
    + FaviconURL +
    +
    +
    + {item.favicon_url} +
    +
    - - -
    -
    OGImageURL
    -
    -
    - { item.og_image_url } -
    -
    +
    + OGImageURL +
    +
    +
    + {item.og_image_url} +
    +
    - - -
    -
    ThemeconfigJSON
    -
    -
    - { item.theme_config_json } -
    -
    +
    + ThemeconfigJSON +
    +
    +
    + {item.theme_config_json} +
    +
    - - -
    -
    CustomCSSJSON
    -
    -
    - { item.custom_css_json } -
    -
    +
    + CustomCSSJSON +
    +
    +
    + {item.custom_css_json} +
    +
    - - -
    -
    CDNbaseURL
    -
    -
    - { item.cdn_base_url } -
    -
    +
    + CDNbaseURL +
    +
    +
    + {item.cdn_base_url} +
    +
    - - -
    -
    Entrypageslug
    -
    -
    - { item.entry_page_slug } -
    -
    +
    + Entrypageslug +
    +
    +
    + {item.entry_page_slug} +
    +
    - - -
    -
    Isdeleted
    -
    -
    - { dataFormatter.booleanFormatter(item.is_deleted) } -
    -
    +
    + Isdeleted +
    +
    +
    + {dataFormatter.booleanFormatter(item.is_deleted)} +
    +
    - - -
    -
    Deletedat
    -
    -
    - { dataFormatter.dateTimeFormatter(item.deleted_at_time) } -
    -
    +
    + Deletedat +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.deleted_at_time)} +
    +
    - - - -
    -
  • - ))} + + + ))} {!loading && projects.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Projects/ListProjects.tsx b/frontend/src/components/Projects/ListProjects.tsx index 9ef574d..2a20bbc 100644 --- a/frontend/src/components/Projects/ListProjects.tsx +++ b/frontend/src/components/Projects/ListProjects.tsx @@ -2,183 +2,156 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - projects: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + projects: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListProjects = ({ projects, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROJECTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListProjects = ({ + projects, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROJECTS'); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && projects.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Name

    -

    { item.name }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + projects.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Name

    +

    {item.name}

    +
    - - -
    -

    Slug

    -

    { item.slug }

    -
    - +
    +

    Slug

    +

    {item.slug}

    +
    - - -
    -

    Description

    -

    { item.description }

    -
    - +
    +

    Description

    +

    {item.description}

    +
    - - -
    -

    Phase

    -

    { item.phase }

    -
    - +
    +

    Phase

    +

    {item.phase}

    +
    - - -
    -

    LogoURL

    -

    { item.logo_url }

    -
    - +
    +

    LogoURL

    +

    {item.logo_url}

    +
    - - -
    -

    FaviconURL

    -

    { item.favicon_url }

    -
    - +
    +

    FaviconURL

    +

    {item.favicon_url}

    +
    - - -
    -

    OGImageURL

    -

    { item.og_image_url }

    -
    - +
    +

    OGImageURL

    +

    {item.og_image_url}

    +
    - - -
    -

    ThemeconfigJSON

    -

    { item.theme_config_json }

    -
    - +
    +

    + ThemeconfigJSON +

    +

    {item.theme_config_json}

    +
    - - -
    -

    CustomCSSJSON

    -

    { item.custom_css_json }

    -
    - +
    +

    + CustomCSSJSON +

    +

    {item.custom_css_json}

    +
    - - -
    -

    CDNbaseURL

    -

    { item.cdn_base_url }

    -
    - +
    +

    CDNbaseURL

    +

    {item.cdn_base_url}

    +
    - - -
    -

    Entrypageslug

    -

    { item.entry_page_slug }

    -
    - +
    +

    + Entrypageslug +

    +

    {item.entry_page_slug}

    +
    - - -
    -

    Isdeleted

    -

    { dataFormatter.booleanFormatter(item.is_deleted) }

    -
    - +
    +

    Isdeleted

    +

    + {dataFormatter.booleanFormatter(item.is_deleted)} +

    +
    - - -
    -

    Deletedat

    -

    { dataFormatter.dateTimeFormatter(item.deleted_at_time) }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && projects.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    Deletedat

    +

    + {dataFormatter.dateTimeFormatter(item.deleted_at_time)} +

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && projects.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListProjects \ No newline at end of file +export default ListProjects; diff --git a/frontend/src/components/Projects/TableProjects.tsx b/frontend/src/components/Projects/TableProjects.tsx index 40dacf0..83f90dc 100644 --- a/frontend/src/components/Projects/TableProjects.tsx +++ b/frontend/src/components/Projects/TableProjects.tsx @@ -1,463 +1,48 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/projects/projectsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * Projects Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureProjectsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/projects/projectsSlice'; +import { loadColumns } from './configureProjectsCols'; +import type { Project } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSampleProjects = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { projects, loading, count, notify: projectsNotify, refetch } = useAppSelector((state) => state.projects) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (projectsNotify.showNotification) { - notify(projectsNotify.typeNotification, projectsNotify.textNotification); - } - }, [projectsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `projects`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={projects ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TableProjectsProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSampleProjects +const TableProjects: React.FC = ({ + filterItems, + setFilterItems, + filters, +}) => { + return ( + + entityName='projects' + sliceSelector={(state: RootState) => state.projects} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TableProjects; diff --git a/frontend/src/components/Projects/configureProjectsCols.tsx b/frontend/src/components/Projects/configureProjectsCols.tsx index 2f8415b..b7d5cc0 100644 --- a/frontend/src/components/Projects/configureProjectsCols.tsx +++ b/frontend/src/components/Projects/configureProjectsCols.tsx @@ -3,265 +3,222 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_PROJECTS') - - return [ - - { - field: 'name', - headerName: 'Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'slug', - headerName: 'Slug', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'description', - headerName: 'Description', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'phase', - headerName: 'Phase', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'logo_url', - headerName: 'LogoURL', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'favicon_url', - headerName: 'FaviconURL', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'og_image_url', - headerName: 'OGImageURL', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'theme_config_json', - headerName: 'ThemeconfigJSON', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'custom_css_json', - headerName: 'CustomCSSJSON', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'cdn_base_url', - headerName: 'CDNbaseURL', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'entry_page_slug', - headerName: 'Entrypageslug', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'is_deleted', - headerName: 'Isdeleted', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'deleted_at_time', - headerName: 'Deletedat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.deleted_at_time), - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_PROJECTS'); + + return [ + { + field: 'name', + headerName: 'Name', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'slug', + headerName: 'Slug', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'description', + headerName: 'Description', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'phase', + headerName: 'Phase', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'logo_url', + headerName: 'LogoURL', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'favicon_url', + headerName: 'FaviconURL', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'og_image_url', + headerName: 'OGImageURL', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'theme_config_json', + headerName: 'ThemeconfigJSON', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'custom_css_json', + headerName: 'CustomCSSJSON', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'cdn_base_url', + headerName: 'CDNbaseURL', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'entry_page_slug', + headerName: 'Entrypageslug', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'is_deleted', + headerName: 'Isdeleted', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'boolean', + }, + + { + field: 'deleted_at_time', + headerName: 'Deletedat', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.deleted_at_time), + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/Publish_events/CardPublish_events.tsx b/frontend/src/components/Publish_events/CardPublish_events.tsx index d63ad6a..c391d3a 100644 --- a/frontend/src/components/Publish_events/CardPublish_events.tsx +++ b/frontend/src/components/Publish_events/CardPublish_events.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { publish_events: any[]; @@ -28,17 +27,19 @@ const CardPublish_events = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PUBLISH_EVENTS') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_PUBLISH_EVENTS', + ); return (
    @@ -47,170 +48,156 @@ const CardPublish_events = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && publish_events.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.status} + }`} + > +
    + + {item.status} - -
    - +
    + +
    -
    -
    - - +
    -
    Project
    -
    -
    - { dataFormatter.projectsOneListFormatter(item.project) } -
    -
    +
    + Project +
    +
    +
    + {dataFormatter.projectsOneListFormatter(item.project)} +
    +
    - - -
    -
    User
    -
    -
    - { dataFormatter.usersOneListFormatter(item.user) } -
    -
    +
    User
    +
    +
    + {dataFormatter.usersOneListFormatter(item.user)} +
    +
    - - -
    -
    Fromenvironment
    -
    -
    - { item.from_environment } -
    -
    +
    + Fromenvironment +
    +
    +
    + {item.from_environment} +
    +
    - - -
    -
    Toenvironment
    -
    -
    - { item.to_environment } -
    -
    +
    + Toenvironment +
    +
    +
    + {item.to_environment} +
    +
    - - -
    -
    Startedat
    -
    -
    - { dataFormatter.dateTimeFormatter(item.started_at) } -
    -
    +
    + Startedat +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.started_at)} +
    +
    - - -
    -
    Finishedat
    -
    -
    - { dataFormatter.dateTimeFormatter(item.finished_at) } -
    -
    +
    + Finishedat +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.finished_at)} +
    +
    - - -
    -
    Status
    -
    -
    - { item.status } -
    -
    +
    + Status +
    +
    +
    + {item.status} +
    +
    - - -
    -
    Errormessage
    -
    -
    - { item.error_message } -
    -
    +
    + Errormessage +
    +
    +
    + {item.error_message} +
    +
    - - -
    -
    Pagescopied
    -
    -
    - { item.pages_copied } -
    -
    +
    + Pagescopied +
    +
    +
    + {item.pages_copied} +
    +
    - - -
    -
    Transitionscopied
    -
    -
    - { item.transitions_copied } -
    -
    +
    + Transitionscopied +
    +
    +
    + {item.transitions_copied} +
    +
    - - -
    -
    Audioscopied
    -
    -
    - { item.audios_copied } -
    -
    +
    + Audioscopied +
    +
    +
    + {item.audios_copied} +
    +
    - - - -
    -
  • - ))} + + + ))} {!loading && publish_events.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Publish_events/ListPublish_events.tsx b/frontend/src/components/Publish_events/ListPublish_events.tsx index b848e16..a47e42b 100644 --- a/frontend/src/components/Publish_events/ListPublish_events.tsx +++ b/frontend/src/components/Publish_events/ListPublish_events.tsx @@ -2,183 +2,165 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - publish_events: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + publish_events: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListPublish_events = ({ publish_events, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PUBLISH_EVENTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListPublish_events = ({ + publish_events, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission( + currentUser, + 'UPDATE_PUBLISH_EVENTS', + ); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && publish_events.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Project

    -

    { dataFormatter.projectsOneListFormatter(item.project) }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + publish_events.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Project

    +

    + {dataFormatter.projectsOneListFormatter(item.project)} +

    +
    - - -
    -

    User

    -

    { dataFormatter.usersOneListFormatter(item.user) }

    -
    - +
    +

    User

    +

    + {dataFormatter.usersOneListFormatter(item.user)} +

    +
    - - -
    -

    Fromenvironment

    -

    { item.from_environment }

    -
    - +
    +

    + Fromenvironment +

    +

    {item.from_environment}

    +
    - - -
    -

    Toenvironment

    -

    { item.to_environment }

    -
    - +
    +

    + Toenvironment +

    +

    {item.to_environment}

    +
    - - -
    -

    Startedat

    -

    { dataFormatter.dateTimeFormatter(item.started_at) }

    -
    - +
    +

    Startedat

    +

    + {dataFormatter.dateTimeFormatter(item.started_at)} +

    +
    - - -
    -

    Finishedat

    -

    { dataFormatter.dateTimeFormatter(item.finished_at) }

    -
    - +
    +

    Finishedat

    +

    + {dataFormatter.dateTimeFormatter(item.finished_at)} +

    +
    - - -
    -

    Status

    -

    { item.status }

    -
    - +
    +

    Status

    +

    {item.status}

    +
    - - -
    -

    Title

    -

    { item.title }

    -
    - +
    +

    Title

    +

    {item.title}

    +
    - - -
    -

    Description

    -

    { item.description }

    -
    - +
    +

    Description

    +

    {item.description}

    +
    - - -
    -

    Errormessage

    -

    { item.error_message }

    -
    - +
    +

    Errormessage

    +

    {item.error_message}

    +
    - - -
    -

    Pagescopied

    -

    { item.pages_copied }

    -
    - +
    +

    Pagescopied

    +

    {item.pages_copied}

    +
    - - -
    -

    Transitionscopied

    -

    { item.transitions_copied }

    -
    - +
    +

    + Transitionscopied +

    +

    + {item.transitions_copied} +

    +
    - - -
    -

    Audioscopied

    -

    { item.audios_copied }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && publish_events.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    Audioscopied

    +

    {item.audios_copied}

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && publish_events.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListPublish_events +export default ListPublish_events; diff --git a/frontend/src/components/Publish_events/TablePublish_events.tsx b/frontend/src/components/Publish_events/TablePublish_events.tsx index 7acbaca..c9f30cc 100644 --- a/frontend/src/components/Publish_events/TablePublish_events.tsx +++ b/frontend/src/components/Publish_events/TablePublish_events.tsx @@ -1,463 +1,48 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/publish_events/publish_eventsSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * Publish Events Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configurePublish_eventsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/publish_events/publish_eventsSlice'; +import { loadColumns } from './configurePublish_eventsCols'; +import type { PublishEvent } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSamplePublish_events = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { publish_events, loading, count, notify: publish_eventsNotify, refetch } = useAppSelector((state) => state.publish_events) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (publish_eventsNotify.showNotification) { - notify(publish_eventsNotify.typeNotification, publish_eventsNotify.textNotification); - } - }, [publish_eventsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `publish_events`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={publish_events ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TablePublish_eventsProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSamplePublish_events +const TablePublish_events: React.FC = ({ + filterItems, + setFilterItems, + filters, +}) => { + return ( + + entityName='publish_events' + sliceSelector={(state: RootState) => state.publish_events} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TablePublish_events; diff --git a/frontend/src/components/Publish_events/configurePublish_eventsCols.tsx b/frontend/src/components/Publish_events/configurePublish_eventsCols.tsx index b562886..b21f7b7 100644 --- a/frontend/src/components/Publish_events/configurePublish_eventsCols.tsx +++ b/frontend/src/components/Publish_events/configurePublish_eventsCols.tsx @@ -3,284 +3,246 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_PUBLISH_EVENTS') - - return [ - - { - field: 'project', - headerName: 'Project', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('projects'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'user', - headerName: 'User', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('users'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'from_environment', - headerName: 'Fromenvironment', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'to_environment', - headerName: 'Toenvironment', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'started_at', - headerName: 'Startedat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.started_at), - - }, - - { - field: 'finished_at', - headerName: 'Finishedat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.finished_at), - - }, - - { - field: 'title', - headerName: 'Title', - flex: 1, - minWidth: 160, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'description', - headerName: 'Description', - flex: 1, - minWidth: 220, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'error_message', - headerName: 'Errormessage', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'pages_copied', - headerName: 'Pagescopied', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'transitions_copied', - headerName: 'Transitionscopied', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'audios_copied', - headerName: 'Audioscopied', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_PUBLISH_EVENTS'); + + return [ + { + field: 'project', + headerName: 'Project', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('projects'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'user', + headerName: 'User', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('users'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'from_environment', + headerName: 'Fromenvironment', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'to_environment', + headerName: 'Toenvironment', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'started_at', + headerName: 'Startedat', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.started_at), + }, + + { + field: 'finished_at', + headerName: 'Finishedat', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.finished_at), + }, + + { + field: 'title', + headerName: 'Title', + flex: 1, + minWidth: 160, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'description', + headerName: 'Description', + flex: 1, + minWidth: 220, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'status', + headerName: 'Status', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'error_message', + headerName: 'Errormessage', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'pages_copied', + headerName: 'Pagescopied', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'transitions_copied', + headerName: 'Transitionscopied', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'audios_copied', + headerName: 'Audioscopied', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'number', + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/Pwa_caches/CardPwa_caches.tsx b/frontend/src/components/Pwa_caches/CardPwa_caches.tsx index 65c8506..70ecdef 100644 --- a/frontend/src/components/Pwa_caches/CardPwa_caches.tsx +++ b/frontend/src/components/Pwa_caches/CardPwa_caches.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { pwa_caches: any[]; @@ -28,17 +27,16 @@ const CardPwa_caches = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PWA_CACHES') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PWA_CACHES'); return (
    @@ -47,122 +45,114 @@ const CardPwa_caches = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && pwa_caches.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.cache_version} + }`} + > +
    + + {item.cache_version} - -
    - +
    + +
    -
    -
    - - +
    -
    Project
    -
    -
    - { dataFormatter.projectsOneListFormatter(item.project) } -
    -
    +
    + Project +
    +
    +
    + {dataFormatter.projectsOneListFormatter(item.project)} +
    +
    - - -
    -
    Environment
    -
    -
    - { item.environment } -
    -
    +
    + Environment +
    +
    +
    + {item.environment} +
    +
    - - -
    -
    Cacheversion
    -
    -
    - { item.cache_version } -
    -
    +
    + Cacheversion +
    +
    +
    + {item.cache_version} +
    +
    - - -
    -
    ManifestJSON
    -
    -
    - { item.manifest_json } -
    -
    +
    + ManifestJSON +
    +
    +
    + {item.manifest_json} +
    +
    - - -
    -
    AssetlistJSON
    -
    -
    - { item.asset_list_json } -
    -
    +
    + AssetlistJSON +
    +
    +
    + {item.asset_list_json} +
    +
    - - -
    -
    Generatedat
    -
    -
    - { dataFormatter.dateTimeFormatter(item.generated_at) } -
    -
    +
    + Generatedat +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.generated_at)} +
    +
    - - -
    -
    Isactive
    -
    -
    - { dataFormatter.booleanFormatter(item.is_active) } -
    -
    +
    + Isactive +
    +
    +
    + {dataFormatter.booleanFormatter(item.is_active)} +
    +
    - - - -
    -
  • - ))} + + + ))} {!loading && pwa_caches.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Pwa_caches/ListPwa_caches.tsx b/frontend/src/components/Pwa_caches/ListPwa_caches.tsx index e09d180..6f0bbfc 100644 --- a/frontend/src/components/Pwa_caches/ListPwa_caches.tsx +++ b/frontend/src/components/Pwa_caches/ListPwa_caches.tsx @@ -2,135 +2,124 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - pwa_caches: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + pwa_caches: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListPwa_caches = ({ pwa_caches, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PWA_CACHES') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListPwa_caches = ({ + pwa_caches, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PWA_CACHES'); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && pwa_caches.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Project

    -

    { dataFormatter.projectsOneListFormatter(item.project) }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + pwa_caches.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Project

    +

    + {dataFormatter.projectsOneListFormatter(item.project)} +

    +
    - - -
    -

    Environment

    -

    { item.environment }

    -
    - +
    +

    Environment

    +

    {item.environment}

    +
    - - -
    -

    Cacheversion

    -

    { item.cache_version }

    -
    - +
    +

    Cacheversion

    +

    {item.cache_version}

    +
    - - -
    -

    ManifestJSON

    -

    { item.manifest_json }

    -
    - +
    +

    ManifestJSON

    +

    {item.manifest_json}

    +
    - - -
    -

    AssetlistJSON

    -

    { item.asset_list_json }

    -
    - +
    +

    + AssetlistJSON +

    +

    {item.asset_list_json}

    +
    - - -
    -

    Generatedat

    -

    { dataFormatter.dateTimeFormatter(item.generated_at) }

    -
    - +
    +

    Generatedat

    +

    + {dataFormatter.dateTimeFormatter(item.generated_at)} +

    +
    - - -
    -

    Isactive

    -

    { dataFormatter.booleanFormatter(item.is_active) }

    -
    - - - - - -
    -
    -
    - ))} - {!loading && pwa_caches.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    Isactive

    +

    + {dataFormatter.booleanFormatter(item.is_active)} +

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && pwa_caches.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListPwa_caches \ No newline at end of file +export default ListPwa_caches; diff --git a/frontend/src/components/Pwa_caches/TablePwa_caches.tsx b/frontend/src/components/Pwa_caches/TablePwa_caches.tsx index 61cbf7c..c03fc20 100644 --- a/frontend/src/components/Pwa_caches/TablePwa_caches.tsx +++ b/frontend/src/components/Pwa_caches/TablePwa_caches.tsx @@ -1,463 +1,48 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/pwa_caches/pwa_cachesSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * PWA Caches Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configurePwa_cachesCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/pwa_caches/pwa_cachesSlice'; +import { loadColumns } from './configurePwa_cachesCols'; +import type { PwaCache } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSamplePwa_caches = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { pwa_caches, loading, count, notify: pwa_cachesNotify, refetch } = useAppSelector((state) => state.pwa_caches) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (pwa_cachesNotify.showNotification) { - notify(pwa_cachesNotify.typeNotification, pwa_cachesNotify.textNotification); - } - }, [pwa_cachesNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `pwa_caches`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={pwa_caches ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TablePwa_cachesProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSamplePwa_caches +const TablePwa_caches: React.FC = ({ + filterItems, + setFilterItems, + filters, +}) => { + return ( + + entityName='pwa_caches' + sliceSelector={(state: RootState) => state.pwa_caches} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TablePwa_caches; diff --git a/frontend/src/components/Pwa_caches/configurePwa_cachesCols.tsx b/frontend/src/components/Pwa_caches/configurePwa_cachesCols.tsx index 719a40b..e790929 100644 --- a/frontend/src/components/Pwa_caches/configurePwa_cachesCols.tsx +++ b/frontend/src/components/Pwa_caches/configurePwa_cachesCols.tsx @@ -3,182 +3,158 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_PWA_CACHES') - - return [ - - { - field: 'project', - headerName: 'Project', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('projects'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'environment', - headerName: 'Environment', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'cache_version', - headerName: 'Cacheversion', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'manifest_json', - headerName: 'ManifestJSON', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'asset_list_json', - headerName: 'AssetlistJSON', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'generated_at', - headerName: 'Generatedat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.generated_at), - - }, - - { - field: 'is_active', - headerName: 'Isactive', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_PWA_CACHES'); + + return [ + { + field: 'project', + headerName: 'Project', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('projects'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'environment', + headerName: 'Environment', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'cache_version', + headerName: 'Cacheversion', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'manifest_json', + headerName: 'ManifestJSON', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'asset_list_json', + headerName: 'AssetlistJSON', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'generated_at', + headerName: 'Generatedat', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.generated_at), + }, + + { + field: 'is_active', + headerName: 'Isactive', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'boolean', + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/RichTextField.tsx b/frontend/src/components/RichTextField.tsx index 97066b2..29b43a1 100644 --- a/frontend/src/components/RichTextField.tsx +++ b/frontend/src/components/RichTextField.tsx @@ -1,44 +1,41 @@ import React, { useEffect, useId, useState } from 'react'; -import {Editor} from "@tinymce/tinymce-react"; -import { tinyKey } from '../config' -import {useAppSelector} from "../stores/hooks"; +import { Editor } from '@tinymce/tinymce-react'; +import { tinyKey } from '../config'; +import { useAppSelector } from '../stores/hooks'; export const RichTextField = ({ options, field, form, itemRef, showField }) => { - const [value, setValue] = useState(null); - const darkMode = useAppSelector((state) => state.style.darkMode); + const [value, setValue] = useState(null); + const darkMode = useAppSelector((state) => state.style.darkMode); + useEffect(() => { + if (field.value) { + setValue(field.value); + } + }, [field.value]); - useEffect(() => { - if (field.value) { - setValue(field.value); - } - }, [field.value]); + const handleChange = (value) => { + form.setFieldValue(field.name, value); + setValue(value); + }; - const handleChange = (value) => { - form.setFieldValue(field.name, value); - setValue(value); - }; + return ( + - ); + content_style: `${darkMode ? 'body { color: #ffffff; }' : ''}`, + }} + /> + ); }; diff --git a/frontend/src/components/Roles/CardRoles.tsx b/frontend/src/components/Roles/CardRoles.tsx index 225c55c..7c24852 100644 --- a/frontend/src/components/Roles/CardRoles.tsx +++ b/frontend/src/components/Roles/CardRoles.tsx @@ -4,12 +4,11 @@ import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { roles: any[]; @@ -28,17 +27,16 @@ const CardRoles = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ROLES') - + const asideScrollbarsStyle = useAppSelector( + (state) => state.style.asideScrollbarsStyle, + ); + const bgColor = useAppSelector((state) => state.style.cardsColor); + const darkMode = useAppSelector((state) => state.style.darkMode); + const corners = useAppSelector((state) => state.style.corners); + const focusRing = useAppSelector((state) => state.style.focusRingColor); + + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ROLES'); return (
    @@ -47,62 +45,57 @@ const CardRoles = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && roles.map((item, index) => ( -
  • ( +
  • - -
    - - - {item.name} + }`} + > +
    + + {item.name} - -
    - +
    + +
    -
    -
    - - +
    -
    Name
    -
    -
    - { item.name } -
    -
    +
    Name
    +
    +
    {item.name}
    +
    - - -
    -
    Permissions
    -
    -
    - { dataFormatter.permissionsManyListFormatter(item.permissions).join(', ')} -
    -
    +
    + Permissions +
    +
    +
    + {dataFormatter + .permissionsManyListFormatter(item.permissions) + .join(', ')} +
    +
    - - - -
    -
  • - ))} + + + ))} {!loading && roles.length === 0 && (

    No data to display

    diff --git a/frontend/src/components/Roles/ListRoles.tsx b/frontend/src/components/Roles/ListRoles.tsx index 773bab0..e4299d9 100644 --- a/frontend/src/components/Roles/ListRoles.tsx +++ b/frontend/src/components/Roles/ListRoles.tsx @@ -2,95 +2,95 @@ import React from 'react'; import CardBox from '../CardBox'; import ImageField from '../ImageField'; import dataFormatter from '../../helpers/dataFormatter'; -import {saveFile} from "../../helpers/fileSaver"; -import ListActionsPopover from "../ListActionsPopover"; -import {useAppSelector} from "../../stores/hooks"; -import {Pagination} from "../Pagination"; -import LoadingSpinner from "../LoadingSpinner"; +import { saveFile } from '../../helpers/fileSaver'; +import ListActionsPopover from '../ListActionsPopover'; +import { useAppSelector } from '../../stores/hooks'; +import { Pagination } from '../Pagination'; +import LoadingSpinner from '../LoadingSpinner'; import Link from 'next/link'; -import {hasPermission} from "../../helpers/userPermissions"; - +import { hasPermission } from '../../helpers/userPermissions'; type Props = { - roles: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; + roles: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; }; -const ListRoles = ({ roles, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ROLES') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); +const ListRoles = ({ + roles, + loading, + onDelete, + currentPage, + numPages, + onPageChange, +}: Props) => { + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ROLES'); + const corners = useAppSelector((state) => state.style.corners); + const bgColor = useAppSelector((state) => state.style.cardsColor); - return ( - <> -
    - {loading && } - {!loading && roles.map((item) => ( -
    - -
    - - dark:divide-dark-700 overflow-x-auto' - } - > - - -
    -

    Name

    -

    { item.name }

    -
    - + return ( + <> +
    + {loading && } + {!loading && + roles.map((item) => ( +
    + +
    + dark:divide-dark-700 overflow-x-auto' + } + > +
    +

    Name

    +

    {item.name}

    +
    - - -
    -

    Permissions

    -

    { dataFormatter.permissionsManyListFormatter(item.permissions).join(', ')}

    -
    - - - - - -
    -
    -
    - ))} - {!loading && roles.length === 0 && ( -
    -

    No data to display

    -
    - )} +
    +

    Permissions

    +

    + {dataFormatter + .permissionsManyListFormatter(item.permissions) + .join(', ')} +

    +
    + + +
    +
    -
    - -
    - - ) + ))} + {!loading && roles.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ); }; -export default ListRoles \ No newline at end of file +export default ListRoles; diff --git a/frontend/src/components/Roles/TableRoles.tsx b/frontend/src/components/Roles/TableRoles.tsx index 69f45f5..16fe101 100644 --- a/frontend/src/components/Roles/TableRoles.tsx +++ b/frontend/src/components/Roles/TableRoles.tsx @@ -1,463 +1,48 @@ -import React, { useEffect, useState, useMemo } from 'react' -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton' -import CardBoxModal from '../CardBoxModal' -import CardBox from "../CardBox"; -import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/roles/rolesSlice' -import { useAppDispatch, useAppSelector } from '../../stores/hooks' -import { useRouter } from 'next/router' -import { Field, Form, Formik } from "formik"; +/** + * Roles Table Component + */ + +import React from 'react'; +import GenericTable from '../Generic/GenericTable'; import { - DataGrid, - GridColDef, -} from '@mui/x-data-grid'; -import {loadColumns} from "./configureRolesCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; + fetch, + update, + deleteItem, + setRefetch, + deleteItemsByIds, +} from '../../stores/roles/rolesSlice'; +import { loadColumns } from './configureRolesCols'; +import type { Role } from '../../types/entities'; +import type { RootState } from '../../stores/store'; +import type { Filter, FilterItem } from '../../types/filters'; - - -const perPage = 10 - -const TableSampleRoles = ({ filterItems, setFilterItems, filters, showGrid }) => { - const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { roles, loading, count, notify: rolesNotify, refetch } = useAppSelector((state) => state.roles) - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (rolesNotify.showNotification) { - notify(rolesNotify.typeNotification, rolesNotify.textNotification); - } - }, [rolesNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) - - const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } - - - - - - const handleDeleteModalAction = (id: string) => { - setId(id) - setIsModalTrashActive(true) - } - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } } - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `roles`, - currentUser, - ).then((newCols) => setColumns(newCols)); - }, [currentUser]); - - - - const handleTableSubmit = async (id: string, data) => { - - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - - const dataGrid = ( -
    - `datagrid--row`} - rows={roles ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids) - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
    - ) - - return ( - <> - {filterItems && Array.isArray( filterItems ) && filterItems.length ? - - null} - > -
    - <> - {filterItems && filterItems.map((filterItem) => { - return ( -
    -
    -
    Filter
    - - {filters.map((selectOption) => ( - - ))} - -
    - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.type === 'enum' ? ( -
    -
    - Value -
    - - - {filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.options?.map((option) => ( - - ))} - -
    - ) : filters.find((filter) => - filter.title === filterItem?.fields?.selectedField - )?.number ? ( -
    -
    -
    From
    - -
    -
    -
    To
    - -
    -
    - ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField - )?.date ? ( -
    -
    -
    - From -
    - -
    -
    -
    To
    - -
    -
    - ) : ( -
    -
    Contains
    - -
    - )} -
    -
    Action
    - { - deleteFilter(filterItem.id) - }} - /> -
    -
    - ) - })} -
    - - -
    - -
    -
    -
    : null - } - -

    Are you sure you want to delete this item?

    -
    - - - {dataGrid} - - - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) +interface TableRolesProps { + filterItems: FilterItem[]; + setFilterItems: (items: FilterItem[]) => void; + filters: Filter[]; + showGrid?: boolean; } -export default TableSampleRoles +const TableRoles: React.FC = ({ + filterItems, + setFilterItems, + filters, +}) => { + return ( + + entityName='roles' + sliceSelector={(state: RootState) => state.roles} + fetchAction={fetch} + updateAction={update} + deleteAction={deleteItem} + deleteByIdsAction={deleteItemsByIds} + setRefetchAction={setRefetch} + loadColumnsFunction={loadColumns} + filters={filters} + filterItems={filterItems} + setFilterItems={setFilterItems} + /> + ); +}; + +export default TableRoles; diff --git a/frontend/src/components/Roles/configureRolesCols.tsx b/frontend/src/components/Roles/configureRolesCols.tsx index 95d81ff..588ff92 100644 --- a/frontend/src/components/Roles/configureRolesCols.tsx +++ b/frontend/src/components/Roles/configureRolesCols.tsx @@ -3,101 +3,91 @@ import BaseIcon from '../BaseIcon'; import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; import axios from 'axios'; import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, + GridActionsCellItem, + GridRowParams, + GridValueGetterParams, } from '@mui/x-data-grid'; import ImageField from '../ImageField'; -import {saveFile} from "../../helpers/fileSaver"; -import dataFormatter from '../../helpers/dataFormatter' -import DataGridMultiSelect from "../DataGridMultiSelect"; +import { saveFile } from '../../helpers/fileSaver'; +import dataFormatter from '../../helpers/dataFormatter'; +import DataGridMultiSelect from '../DataGridMultiSelect'; import ListActionsPopover from '../ListActionsPopover'; -import {hasPermission} from "../../helpers/userPermissions"; +import { hasPermission } from '../../helpers/userPermissions'; type Params = (id: string) => void; export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user - + onDelete: Params, + entityName: string, + + user, ) => { - async function callOptionsApi(entityName: string) { - - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } + async function callOptionsApi(entityName: string) { + if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; + + try { + const data = await axios(`/${entityName}/autocomplete?limit=100`); + return data.data; + } catch (error) { + console.log(error); + return []; } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ROLES') - - return [ - - { - field: 'name', - headerName: 'Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'permissions', - headerName: 'Permissions', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - type: 'singleSelect', - valueFormatter: ({ value }) => - dataFormatter.permissionsManyListFormatter(value).join(', '), - renderEditCell: (params) => ( - - ), - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
    - -
    , - ] - }, - }, - ]; + } + + const hasUpdatePermission = hasPermission(user, 'UPDATE_ROLES'); + + return [ + { + field: 'name', + headerName: 'Name', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + }, + + { + field: 'permissions', + headerName: 'Permissions', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: false, + sortable: false, + type: 'singleSelect', + valueFormatter: ({ value }) => + dataFormatter.permissionsManyListFormatter(value).join(', '), + renderEditCell: (params) => ( + + ), + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ +
    + +
    , + ]; + }, + }, + ]; }; diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index b7beb98..4f6de00 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -31,7 +31,7 @@ const Search = () => { validateOnChange={false} > {({ errors, touched, values }) => ( -
    + { className={` ${corners} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-2 relative ml-2 w-full dark:placeholder-dark-600 ${focusRing} shadow-none`} /> {errors.search && touched.search && values.search.length < 2 ? ( -
    {errors.search}
    +
    + {errors.search} +
    ) : null} )} diff --git a/frontend/src/components/SectionFullScreen.tsx b/frontend/src/components/SectionFullScreen.tsx index a570d23..6548862 100644 --- a/frontend/src/components/SectionFullScreen.tsx +++ b/frontend/src/components/SectionFullScreen.tsx @@ -1,27 +1,32 @@ -import React, { ReactNode } from 'react' -import { BgKey } from '../interfaces' -import {gradientBgPurplePink, gradientBgDark, gradientBgPinkRed, gradientBgViolet} from '../colors' -import { useAppSelector } from '../stores/hooks' +import React, { ReactNode } from 'react'; +import { BgKey } from '../interfaces'; +import { + gradientBgPurplePink, + gradientBgDark, + gradientBgPinkRed, + gradientBgViolet, +} from '../colors'; +import { useAppSelector } from '../stores/hooks'; type Props = { - bg: BgKey - children?: ReactNode -} + bg: BgKey; + children?: ReactNode; +}; export default function SectionFullScreen({ bg, children }: Props) { - const darkMode = useAppSelector((state) => state.style.darkMode) + const darkMode = useAppSelector((state) => state.style.darkMode); - let componentClass = 'flex min-h-screen items-center justify-center ' + let componentClass = 'flex min-h-screen items-center justify-center '; if (darkMode) { - componentClass += gradientBgDark + componentClass += gradientBgDark; } else if (bg === 'violet') { - componentClass += gradientBgViolet + componentClass += gradientBgViolet; } else if (bg === 'purplePink') { - componentClass += gradientBgPurplePink + componentClass += gradientBgPurplePink; } else if (bg === 'pinkRed') { - componentClass += gradientBgPinkRed + componentClass += gradientBgPinkRed; } - return
    {children}
    + return
    {children}
    ; } diff --git a/frontend/src/components/SectionMain.tsx b/frontend/src/components/SectionMain.tsx index 2b18097..ba57321 100644 --- a/frontend/src/components/SectionMain.tsx +++ b/frontend/src/components/SectionMain.tsx @@ -1,10 +1,10 @@ -import React, { ReactNode } from 'react' -import { containerMaxW } from '../config' +import React, { ReactNode } from 'react'; +import { containerMaxW } from '../config'; type Props = { - children: ReactNode -} + children: ReactNode; +}; export default function SectionMain({ children }: Props) { - return
    {children}
    + return
    {children}
    ; } diff --git a/frontend/src/components/SectionTitle.tsx b/frontend/src/components/SectionTitle.tsx index c441e9e..c07d85b 100644 --- a/frontend/src/components/SectionTitle.tsx +++ b/frontend/src/components/SectionTitle.tsx @@ -1,27 +1,38 @@ -import React, { ReactNode } from 'react' +import React, { ReactNode } from 'react'; type Props = { - custom?: boolean - first?: boolean - last?: boolean - children: ReactNode -} + custom?: boolean; + first?: boolean; + last?: boolean; + children: ReactNode; +}; -const SectionTitle = ({ custom = false, first = false, last = false, children }: Props) => { - let classAddon = '-my-6' +const SectionTitle = ({ + custom = false, + first = false, + last = false, + children, +}: Props) => { + let classAddon = '-my-6'; if (first) { - classAddon = '-mb-6' + classAddon = '-mb-6'; } else if (last) { - classAddon = '-mt-6' + classAddon = '-mt-6'; } return ( -
    +
    {custom && children} - {!custom &&

    {children}

    } + {!custom && ( +

    + {children} +

    + )}
    - ) -} + ); +}; -export default SectionTitle +export default SectionTitle; diff --git a/frontend/src/components/SectionTitleLineWithButton.tsx b/frontend/src/components/SectionTitleLineWithButton.tsx index 3d3c5ef..8cfd157 100644 --- a/frontend/src/components/SectionTitleLineWithButton.tsx +++ b/frontend/src/components/SectionTitleLineWithButton.tsx @@ -1,29 +1,40 @@ -import { mdiCog } from '@mdi/js' -import React, { Children, ReactNode } from 'react' -import BaseButton from './BaseButton' -import BaseIcon from './BaseIcon' -import IconRounded from './IconRounded' +import { mdiCog } from '@mdi/js'; +import React, { Children, ReactNode } from 'react'; +import BaseButton from './BaseButton'; +import BaseIcon from './BaseIcon'; +import IconRounded from './IconRounded'; import { humanize } from '../helpers/humanize'; type Props = { - icon: string - title: string - main?: boolean - children?: ReactNode -} + icon: string; + title: string; + main?: boolean; + children?: ReactNode; +}; -export default function SectionTitleLineWithButton({ icon, title, main = false, children }: Props) { - const hasChildren = !!Children.count(children) +export default function SectionTitleLineWithButton({ + icon, + title, + main = false, + children, +}: Props) { + const hasChildren = !!Children.count(children); return ( -
    -
    - {icon && main && } - {icon && !main && } -

    {humanize(title)}

    +
    +
    + {icon && main && ( + + )} + {icon && !main && } +

    + {humanize(title)} +

    {children} - {!hasChildren && } + {!hasChildren && }
    - ) + ); } diff --git a/frontend/src/components/SelectField.tsx b/frontend/src/components/SelectField.tsx index cab0629..703d631 100644 --- a/frontend/src/components/SelectField.tsx +++ b/frontend/src/components/SelectField.tsx @@ -1,29 +1,35 @@ -import React, {useEffect, useId, useState} from 'react'; +import React, { useEffect, useId, useState } from 'react'; import { AsyncPaginate } from 'react-select-async-paginate'; -import axios from 'axios' +import axios from 'axios'; -export const SelectField = ({ options, field, form, itemRef, showField, disabled }) => { - - const [value, setValue] = useState(null) +export const SelectField = ({ + options, + field, + form, + itemRef, + showField, + disabled, +}) => { + const [value, setValue] = useState(null); const PAGE_SIZE = 100; useEffect(() => { - if(options?.id && field?.value?.id) { - setValue({value: field.value?.id, label: field.value[showField]}) + if (options?.id && field?.value?.id) { + setValue({ value: field.value?.id, label: field.value[showField] }); form.setFieldValue(field.name, field.value?.id); } else if (!field.value) { setValue(null); } - }, [options?.id, field?.value?.id, field?.value]) + }, [options?.id, field?.value?.id, field?.value]); const mapResponseToValuesAndLabels = (data) => ({ value: data.id, label: data.label, - }) + }); const handleChange = (option) => { - form.setFieldValue(field.name, option?.value || null) - setValue(option) - } + form.setFieldValue(field.name, option?.value || null); + setValue(option); + }; async function callApi(inputValue: string, loadedOptions: any[]) { const path = `/${itemRef}/autocomplete?limit=${PAGE_SIZE}&offset=${loadedOptions.length}${inputValue ? `&query=${inputValue}` : ''}`; @@ -31,23 +37,22 @@ export const SelectField = ({ options, field, form, itemRef, showField, disabled return { options: data.map(mapResponseToValuesAndLabels), hasMore: data.length === PAGE_SIZE, - } + }; } return ( - 'px-1 py-2', - }} - classNamePrefix='react-select' - instanceId={useId()} - value={value} - debounceTimeout={1000} - loadOptions={callApi} - onChange={handleChange} - defaultOptions - isDisabled={disabled} - isClearable - /> - ) -} - + 'px-1 py-2', + }} + classNamePrefix='react-select' + instanceId={useId()} + value={value} + debounceTimeout={1000} + loadOptions={callApi} + onChange={handleChange} + defaultOptions + isDisabled={disabled} + isClearable + /> + ); +}; diff --git a/frontend/src/components/SelectFieldMany.tsx b/frontend/src/components/SelectFieldMany.tsx index 34268ff..4229e9c 100644 --- a/frontend/src/components/SelectFieldMany.tsx +++ b/frontend/src/components/SelectFieldMany.tsx @@ -1,8 +1,14 @@ -import React, {useEffect, useId, useState} from 'react'; +import React, { useEffect, useId, useState } from 'react'; import { AsyncPaginate } from 'react-select-async-paginate'; import axios from 'axios'; -export const SelectFieldMany = ({ options, field, form, itemRef, showField }) => { +export const SelectFieldMany = ({ + options, + field, + form, + itemRef, + showField, +}) => { const [value, setValue] = useState([]); const PAGE_SIZE = 100; @@ -21,8 +27,8 @@ export const SelectFieldMany = ({ options, field, form, itemRef, showField }) => if (options) { setValue(options.map((el) => ({ value: el.id, label: el[showField] }))); form.setFieldValue( - field.name, - options.map((el) => ({ value: el.id, label: el[showField] })), + field.name, + options.map((el) => ({ value: el.id, label: el[showField] })), ); } }, [options]); @@ -33,10 +39,10 @@ export const SelectFieldMany = ({ options, field, form, itemRef, showField }) => }); const handleChange = (data: any) => { - setValue(data) + setValue(data); form.setFieldValue( - field.name, - data.map(el => (el?.value || null)), + field.name, + data.map((el) => el?.value || null), ); }; @@ -46,22 +52,22 @@ export const SelectFieldMany = ({ options, field, form, itemRef, showField }) => return { options: data.map(mapResponseToValuesAndLabels), hasMore: data.length === PAGE_SIZE, - } + }; } return ( - 'px-1 py-2', - }} - classNamePrefix='react-select' - instanceId={useId()} - value={value} - isMulti - debounceTimeout={1000} - loadOptions={callApi} - onChange={handleChange} - defaultOptions - isClearable - /> + 'px-1 py-2', + }} + classNamePrefix='react-select' + instanceId={useId()} + value={value} + isMulti + debounceTimeout={1000} + loadOptions={callApi} + onChange={handleChange} + defaultOptions + isClearable + /> ); }; diff --git a/frontend/src/components/SmartWidget/SmartWidget.tsx b/frontend/src/components/SmartWidget/SmartWidget.tsx index 6f6c0e0..ef76c98 100644 --- a/frontend/src/components/SmartWidget/SmartWidget.tsx +++ b/frontend/src/components/SmartWidget/SmartWidget.tsx @@ -5,17 +5,17 @@ import * as icons from '@mdi/js'; import { useAppDispatch, useAppSelector } from '../../stores/hooks'; import { fetchWidgets, removeWidget } from '../../stores/roles/rolesSlice'; -import { WidgetChartType, WidgetType } from "./models/widget.model"; -import { BarChart } from "./components/BarChart"; -import { PieChart } from "./components/PieChart"; -import { AreaChart } from "./components/AreaChart"; -import { LineChart } from "./components/LineChart"; +import { WidgetChartType, WidgetType } from './models/widget.model'; +import { BarChart } from './components/BarChart'; +import { PieChart } from './components/PieChart'; +import { AreaChart } from './components/AreaChart'; +import { LineChart } from './components/LineChart'; export const SmartWidget = ({ widget, userId, admin, roleId }) => { const dispatch = useAppDispatch(); const corners = useAppSelector((state) => state.style.corners); const cardsStyle = useAppSelector((state) => state.style.cardsStyle); - + const deleteWidget = async () => { await dispatch( removeWidget({ id: userId, widgetId: widget.widget_id, roleId }), @@ -40,57 +40,59 @@ export const SmartWidget = ({ widget, userId, admin, roleId }) => {
    {admin && ( - + )}
    -
    -
    +
    +
    {widget.value ? ( - widget.widget_type === WidgetType.chart ? ( - widget.chart_type === WidgetChartType.bar ? ( - - ) : widget.chart_type === WidgetChartType.line ? ( - - ) : widget.chart_type === WidgetChartType.pie ? ( - - ) : widget.chart_type === WidgetChartType.area ? ( - - ) : widget.chart_type === WidgetChartType.funnel ? ( - - ) : null - ) : ( -
    - {widget.value} -
    - ) - ) : ( -
    - Something went wrong, please try again or use a different query. + widget.widget_type === WidgetType.chart ? ( + widget.chart_type === WidgetChartType.bar ? ( + + ) : widget.chart_type === WidgetChartType.line ? ( + + ) : widget.chart_type === WidgetChartType.pie ? ( + + ) : widget.chart_type === WidgetChartType.area ? ( + + ) : widget.chart_type === WidgetChartType.funnel ? ( + + ) : null + ) : ( +
    + {widget.value}
    + ) + ) : ( +
    + Something went wrong, please try again or use a different query. +
    )}
    {widget.type === WidgetType.scalar && widget.mdiIcon && ( -
    - -
    +
    + +
    )}
    diff --git a/frontend/src/components/SmartWidget/components/BarChart/ChartJSBarChart.tsx b/frontend/src/components/SmartWidget/components/BarChart/ChartJSBarChart.tsx index d6b802c..eac4769 100644 --- a/frontend/src/components/SmartWidget/components/BarChart/ChartJSBarChart.tsx +++ b/frontend/src/components/SmartWidget/components/BarChart/ChartJSBarChart.tsx @@ -24,7 +24,7 @@ ChartJS.register( ); export const ChartJSBarChart = ({ widget }) => { - console.log(widget) + console.log(widget); const options = () => { return { responsive: true, diff --git a/frontend/src/components/SmartWidget/widgetHelpers.tsx b/frontend/src/components/SmartWidget/widgetHelpers.tsx index baf7969..73f6d91 100644 --- a/frontend/src/components/SmartWidget/widgetHelpers.tsx +++ b/frontend/src/components/SmartWidget/widgetHelpers.tsx @@ -4,7 +4,9 @@ interface DataObject { [key: string]: any; } -export const findFirstNumericKey = (obj: Record): string | undefined => { +export const findFirstNumericKey = ( + obj: Record, +): string | undefined => { for (const [key, value] of Object.entries(obj)) { if (typeof value === 'string') { const trimmedValue = value.trim(); @@ -26,11 +28,11 @@ export const findFirstNumericKey = (obj: Record): string | undefine }; export const collectOtherData = ( - obj: DataObject, - excludeKey: string, + obj: DataObject, + excludeKey: string, ): string => { return Object.entries(obj) - .filter(([key, _]) => key !== excludeKey) - .map(([_, value]) => humanize(value)) - .join(' / '); + .filter(([key, _]) => key !== excludeKey) + .map(([_, value]) => humanize(value)) + .join(' / '); }; diff --git a/frontend/src/components/SwitchField.tsx b/frontend/src/components/SwitchField.tsx index bdcaef3..324a287 100644 --- a/frontend/src/components/SwitchField.tsx +++ b/frontend/src/components/SwitchField.tsx @@ -1,22 +1,19 @@ import React, { useEffect, useId, useState } from 'react'; -import Switch from "react-switch"; +import Switch from 'react-switch'; +export const SwitchField = ({ field, form, disabled }) => { + const handleChange = (data: any) => { + form.setFieldValue(field.name, data); + }; - -export const SwitchField = ({ - field, - form, - disabled - }) => { - const handleChange = (data: any) => { - form.setFieldValue( - field.name, - data, - ); - }; - - - return ( - - ); + return ( + + ); }; diff --git a/frontend/src/components/TableSampleClients.tsx b/frontend/src/components/TableSampleClients.tsx index 3fb3411..5eaf9d1 100644 --- a/frontend/src/components/TableSampleClients.tsx +++ b/frontend/src/components/TableSampleClients.tsx @@ -1,43 +1,46 @@ -import { mdiEye, mdiTrashCan } from '@mdi/js' -import React, { useState } from 'react' -import { useSampleClients } from '../hooks/sampleData' -import { Client } from '../interfaces' -import BaseButton from './BaseButton' -import BaseButtons from './BaseButtons' -import CardBoxModal from './CardBoxModal' -import UserAvatar from './UserAvatar' +import { mdiEye, mdiTrashCan } from '@mdi/js'; +import React, { useState } from 'react'; +import { useSampleClients } from '../hooks/sampleData'; +import { Client } from '../interfaces'; +import BaseButton from './BaseButton'; +import BaseButtons from './BaseButtons'; +import CardBoxModal from './CardBoxModal'; +import UserAvatar from './UserAvatar'; const TableSampleClients = () => { - const { clients } = useSampleClients() + const { clients } = useSampleClients(); - const perPage = 5 + const perPage = 5; - const [currentPage, setCurrentPage] = useState(0) + const [currentPage, setCurrentPage] = useState(0); - const clientsPaginated = clients.slice(perPage * currentPage, perPage * (currentPage + 1)) + const clientsPaginated = clients.slice( + perPage * currentPage, + perPage * (currentPage + 1), + ); - const numPages = clients.length / perPage + const numPages = clients.length / perPage; - const pagesList = [] + const pagesList = []; for (let i = 0; i < numPages; i++) { - pagesList.push(i) + pagesList.push(i); } - const [isModalInfoActive, setIsModalInfoActive] = useState(false) - const [isModalTrashActive, setIsModalTrashActive] = useState(false) + const [isModalInfoActive, setIsModalInfoActive] = useState(false); + const [isModalTrashActive, setIsModalTrashActive] = useState(false); const handleModalAction = () => { - setIsModalInfoActive(false) - setIsModalTrashActive(false) - } + setIsModalInfoActive(false); + setIsModalTrashActive(false); + }; return ( <> { { {clientsPaginated.map((client: Client) => ( - - + + - {client.name} - {client.company} - {client.city} - + {client.name} + {client.company} + {client.city} + {client.progress} - - {client.created} + + + {client.created} + - - + + setIsModalInfoActive(true)} small /> setIsModalTrashActive(true)} small @@ -115,8 +123,8 @@ const TableSampleClients = () => { ))} -
    -
    +
    +
    {pagesList.map((page) => ( { /> ))} - + Page {currentPage + 1} of {numPages}
    - ) -} + ); +}; -export default TableSampleClients +export default TableSampleClients; diff --git a/frontend/src/components/TourFlowManager.tsx b/frontend/src/components/TourFlowManager.tsx index 2514005..737310f 100644 --- a/frontend/src/components/TourFlowManager.tsx +++ b/frontend/src/components/TourFlowManager.tsx @@ -9,6 +9,7 @@ import axios from 'axios'; import Head from 'next/head'; import { useRouter } from 'next/router'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { toast } from 'react-toastify'; import BaseButton from './BaseButton'; import CardBox from './CardBox'; import CardBoxModal from './CardBoxModal'; @@ -52,7 +53,8 @@ type ListEntry = { parentPageId: string; }; -const getRows = (response: any) => (Array.isArray(response?.data?.rows) ? response.data.rows : []); +const getRows = (response: any) => + Array.isArray(response?.data?.rows) ? response.data.rows : []; const sanitizeSlug = (value: string) => value @@ -89,9 +91,7 @@ const toRoutePath = (value?: string) => { }; const routeParts = (value?: string) => - toRoutePath(value) - .split('/') - .filter(Boolean); + toRoutePath(value).split('/').filter(Boolean); const compareRoutes = (a?: string, b?: string) => { const aPath = toRoutePath(a); @@ -116,7 +116,10 @@ const compareRoutes = (a?: string, b?: string) => { return aParts.length - bParts.length; }; -const getProjectId = (item: { projectId?: string; project?: { id?: string } | string }) => { +const getProjectId = (item: { + projectId?: string; + project?: { id?: string } | string; +}) => { if (item.projectId) return item.projectId; if (typeof item.project === 'string') return item.project; if (item.project?.id) return item.project.id; @@ -156,7 +159,19 @@ const TourFlowManager = () => { const projectOptions = Array.isArray(response?.data) ? response.data : []; setProjects(projectOptions); - if (routeProjectId && projectOptions.some((item: ProjectOption) => item.id === routeProjectId)) { + if (projectOptions.length === 0) { + toast('Please create a project first', { + type: 'info', + position: 'bottom-center', + }); + router.replace('/projects/projects-new'); + return ''; + } + + if ( + routeProjectId && + projectOptions.some((item: ProjectOption) => item.id === routeProjectId) + ) { setSelectedProjectId(routeProjectId); return routeProjectId; } @@ -168,7 +183,7 @@ const TourFlowManager = () => { setSelectedProjectId(''); return ''; - }, [routeProjectId]); + }, [routeProjectId, router]); const loadData = useCallback(async () => { try { @@ -184,14 +199,22 @@ const TourFlowManager = () => { } const [pagesResponse, transitionsResponse] = await Promise.all([ - axios.get(`/tour_pages?limit=500&sort=asc&field=sort_order&project=${projectId}`), - axios.get(`/transitions?limit=500&sort=desc&field=createdAt&project=${projectId}`), + axios.get( + `/tour_pages?limit=500&sort=asc&field=sort_order&project=${projectId}`, + ), + axios.get( + `/transitions?limit=500&sort=desc&field=createdAt&project=${projectId}`, + ), ]); setPages(getRows(pagesResponse)); setTransitions(getRows(transitionsResponse)); } catch (error: any) { - setErrorMessage(error?.response?.data?.message || error?.message || 'Failed to load pages and transitions.'); + setErrorMessage( + error?.response?.data?.message || + error?.message || + 'Failed to load pages and transitions.', + ); console.error('Failed to load merged pages/transitions list:', error); } finally { setIsLoading(false); @@ -235,11 +258,17 @@ const TourFlowManager = () => { const sortedTransitions = useMemo( () => [...transitions].sort((first, second) => { - const dateFirst = first.createdAt ? new Date(first.createdAt).getTime() : 0; - const dateSecond = second.createdAt ? new Date(second.createdAt).getTime() : 0; + const dateFirst = first.createdAt + ? new Date(first.createdAt).getTime() + : 0; + const dateSecond = second.createdAt + ? new Date(second.createdAt).getTime() + : 0; if (dateFirst !== dateSecond) return dateFirst - dateSecond; - return (first.name || first.slug || '').localeCompare(second.name || second.slug || ''); + return (first.name || first.slug || '').localeCompare( + second.name || second.slug || '', + ); }), [transitions], ); @@ -250,14 +279,17 @@ const TourFlowManager = () => { const fromPages = sortedPages.find((item) => getProjectId(item)); if (fromPages) return getProjectId(fromPages); - const fromTransitions = sortedTransitions.find((item) => getProjectId(item)); + const fromTransitions = sortedTransitions.find((item) => + getProjectId(item), + ); if (fromTransitions) return getProjectId(fromTransitions); return projects[0]?.id || ''; }, [projects, selectedProjectId, sortedPages, sortedTransitions]); const targetEnvironment = useMemo( - () => sortedPages[0]?.environment || sortedTransitions[0]?.environment || 'dev', + () => + sortedPages[0]?.environment || sortedTransitions[0]?.environment || 'dev', [sortedPages, sortedTransitions], ); @@ -275,8 +307,10 @@ const TourFlowManager = () => { const slugValidationError = useMemo(() => { const slug = newPageSlug.trim(); if (!slug) return 'Slug is required.'; - if (!slugPattern.test(slug)) return 'Use lowercase letters, numbers, and hyphens only.'; - if (pageSlugsInEnvironment.has(slug)) return 'This slug already exists in the selected environment.'; + if (!slugPattern.test(slug)) + return 'Use lowercase letters, numbers, and hyphens only.'; + if (pageSlugsInEnvironment.has(slug)) + return 'This slug already exists in the selected environment.'; return ''; }, [newPageSlug, pageSlugsInEnvironment]); @@ -313,7 +347,11 @@ const TourFlowManager = () => { return entries; }, [sortedPages, sortedTransitions]); - const openConstructor = (pageId: string, sourceId: string, sourceType: 'page' | 'transition') => { + const openConstructor = ( + pageId: string, + sourceId: string, + sourceType: 'page' | 'transition', + ) => { if (!activeProjectId) { router.push('/projects/projects-list'); return; @@ -336,7 +374,10 @@ const TourFlowManager = () => { return; } - const suggestedSlug = buildUniqueSlug(`page-${nextPageNumber}`, pageSlugsInEnvironment); + const suggestedSlug = buildUniqueSlug( + `page-${nextPageNumber}`, + pageSlugsInEnvironment, + ); setNewPageSlug(suggestedSlug); setNewPageSlugError(''); setIsCreatePageModalActive(true); @@ -376,7 +417,8 @@ const TourFlowManager = () => { source_key: '', name: `Page ${nextPageNumber}`, slug, - sort_order: Math.max(...pages.map((item) => Number(item.sort_order || 0)), 0) + 1, + sort_order: + Math.max(...pages.map((item) => Number(item.sort_order || 0)), 0) + 1, background_image_url: '', background_video_url: '', background_audio_url: '', @@ -390,7 +432,10 @@ const TourFlowManager = () => { setNewPageSlug(''); await loadData(); } catch (error: any) { - const message = error?.response?.data?.message || error?.message || 'Failed to create page.'; + const message = + error?.response?.data?.message || + error?.message || + 'Failed to create page.'; setErrorMessage(message); setNewPageSlugError(message); console.error('Failed to create page:', error); @@ -425,14 +470,22 @@ const TourFlowManager = () => { await axios.post('/transitions', { data: payload }); await loadData(); } catch (error: any) { - setErrorMessage(error?.response?.data?.message || error?.message || 'Failed to create transition.'); + setErrorMessage( + error?.response?.data?.message || + error?.message || + 'Failed to create transition.', + ); console.error('Failed to create transition:', error); } finally { setIsCreatingTransition(false); } }; - const handleDelete = async (event: React.MouseEvent, id: string, type: 'page' | 'transition') => { + const handleDelete = async ( + event: React.MouseEvent, + id: string, + type: 'page' | 'transition', + ) => { event.stopPropagation(); try { @@ -447,7 +500,11 @@ const TourFlowManager = () => { setTransitions((prev) => prev.filter((item) => item.id !== id)); } } catch (error: any) { - setErrorMessage(error?.response?.data?.message || error?.message || 'Failed to delete item.'); + setErrorMessage( + error?.response?.data?.message || + error?.message || + 'Failed to delete item.', + ); console.error('Failed to delete item:', error); } finally { setDeletingId(''); @@ -460,14 +517,19 @@ const TourFlowManager = () => { {getPageTitle('Pages & Transitions')} - + {''}

    {isLoading ? 'Loading project...' - : projects.find((project) => project.id === activeProjectId)?.label || 'No project selected'} + : projects.find((project) => project.id === activeProjectId) + ?.label || 'No project selected'}

    @@ -476,20 +538,38 @@ const TourFlowManager = () => { icon={mdiFileDocumentPlus} label='Create Page' onClick={openCreatePageModal} - disabled={!canCreatePage || isCreatingPage || isCreatingTransition || !activeProjectId} + disabled={ + !canCreatePage || + isCreatingPage || + isCreatingTransition || + !activeProjectId + } /> @@ -503,7 +583,10 @@ const TourFlowManager = () => { onCancel={isCreatingPage ? undefined : closeCreatePageModal} >
    -
    @@ -530,13 +617,18 @@ const TourFlowManager = () => { {isLoading ? ( -

    Loading pages and transitions...

    +

    + Loading pages and transitions... +

    ) : listEntries.length === 0 ? ( -

    No pages or transitions yet.

    +

    + No pages or transitions yet. +

    ) : (