From e4fb904fb68fd3c470884fdd7a2f0f169a082e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Zori=C4=87?= Date: Thu, 2 May 2024 12:43:06 +0200 Subject: [PATCH 01/11] refactor: rename locking mechanism into record locking (#4108) --- .github/workflows/pullRequests.yml | 2 +- .github/workflows/pushDev.yml | 2 +- .github/workflows/pushNext.yml | 2 +- apps/api/graphql/package.json | 2 +- apps/api/graphql/src/index.ts | 4 +- apps/api/graphql/tsconfig.json | 6 +- packages/api-locking-mechanism/README.md | 10 - .../src/abstractions/IGetLockRecordUseCase.ts | 11 - .../IKickOutCurrentUserUseCase.ts | 7 - .../src/abstractions/ILockEntryUseCase.ts | 14 - .../src/abstractions/IUnlockEntryUseCase.ts | 15 -- .../abstractions/IUpdateEntryLockUseCase.ts | 14 - packages/api-locking-mechanism/src/types.ts | 239 ------------------ .../.babelrc.js | 0 .../LICENSE | 0 packages/api-record-locking/README.md | 10 + .../__tests__/graphql/getLockRecord.test.ts | 2 +- .../graphql/getLockedEntryLockRecord.test.ts | 8 +- .../__tests__/graphql/isEntryLocked.test.ts | 2 +- .../__tests__/graphql/listLockRecords.test.ts | 10 +- .../__tests__/graphql/lockEntry.test.ts | 10 +- .../graphql/requestEntryUnlock.test.ts | 10 +- .../__tests__/graphql/unlockEntry.test.ts | 12 +- .../__tests__/graphql/updateEntryLock.test.ts | 14 +- .../helpers/graphql/recordLocking.ts} | 48 ++-- .../__tests__/helpers/identity.ts | 0 .../__tests__/helpers/locales.ts | 0 .../__tests__/helpers/permissions.ts | 0 .../__tests__/helpers/plugins.ts | 4 +- .../__tests__/helpers/tenancySecurity.ts | 0 .../__tests__/helpers/useGraphQLHandler.ts | 5 +- .../useCase/isEntryLockedUseCase.test.ts | 4 +- .../useCase/kickOutCurrentUser.test.ts | 4 +- .../useCase/lockEntryUseCase.test.ts | 6 +- .../useCase/unlockEntryRequestUseCase.test.ts | 8 +- .../useCase/unlockEntryUseCase.test.ts | 0 .../utils/convertWhereCondition.test.ts | 2 +- .../utils/validateSameIdentity.test.ts | 0 .../jest.setup.js | 0 .../package.json | 4 +- .../src/abstractions/IGetLockRecordUseCase.ts | 11 + .../IGetLockedEntryLockRecordUseCase.ts | 6 +- .../src/abstractions/IIsEntryLocked.ts | 4 +- .../IKickOutCurrentUserUseCase.ts | 7 + .../IListAllLockRecordsUseCase.ts | 8 +- .../abstractions/IListLockRecordsUseCase.ts | 4 +- .../src/abstractions/ILockEntryUseCase.ts | 14 + .../IUnlockEntryRequestUseCase.ts | 6 +- .../src/abstractions/IUnlockEntryUseCase.ts | 15 ++ .../abstractions/IUpdateEntryLockUseCase.ts | 14 + .../src/crud/crud.ts | 52 ++-- .../src/crud/model.ts | 0 .../src/graphql/schema.ts | 142 +++++------ .../src/index.ts | 8 +- packages/api-record-locking/src/types.ts | 237 +++++++++++++++++ .../GetLockRecord/GetLockRecordUseCase.ts | 6 +- .../GetLockedEntryLockRecordUseCase.ts | 4 +- .../IsEntryLocked/IsEntryLockedUseCase.ts | 0 .../KickOutCurrentUserUseCase.ts | 2 +- .../ListAllLockRecordsUseCase.ts | 6 +- .../ListLockRecordsUseCase.ts | 0 .../LockEntryUseCase/LockEntryUseCase.ts | 14 +- .../UnlockEntryUseCase/UnlockEntryUseCase.ts | 10 +- .../UnlockEntryRequestUseCase.ts | 14 +- .../UpdateEntryLock/UpdateEntryLockUseCase.ts | 8 +- .../src/useCases/index.ts | 4 +- .../src/utils/calculateExpiresOn.ts | 0 .../src/utils/checkPermissions.ts | 0 .../src/utils/convertEntryToLockRecord.ts | 62 ++--- .../src/utils/convertWhereCondition.ts | 4 +- .../src/utils/getTimeout.ts | 0 .../src/utils/isLockedFactory.ts | 4 +- .../src/utils/lockRecordDatabaseId.ts | 0 .../src/utils/resolve.ts | 4 +- .../src/utils/validateSameIdentity.ts | 6 +- .../tsconfig.build.json | 0 .../tsconfig.json | 0 .../webiny.config.js | 0 .../domain/LockingMechanismGetLockRecord.ts | 41 --- ...ockingMechanismGetLockedEntryLockRecord.ts | 41 --- .../domain/LockingMechanismIsEntryLocked.ts | 39 --- .../domain/LockingMechanismListLockRecords.ts | 48 ---- .../src/domain/LockingMechanismLockEntry.ts | 26 -- .../src/domain/LockingMechanismUnlockEntry.ts | 39 --- .../LockingMechanismUnlockEntryRequest.ts | 26 -- .../domain/LockingMechanismUpdateEntryLock.ts | 40 --- .../domain/abstractions/ILockingMechanism.ts | 62 ----- .../ILockingMechanismGetLockRecord.ts | 17 -- ...ockingMechanismGetLockedEntryLockRecord.ts | 17 -- .../ILockingMechanismIsEntryLocked.ts | 12 - .../ILockingMechanismListLockRecords.ts | 29 --- .../ILockingMechanismLockEntry.ts | 15 -- .../ILockingMechanismUnlockEntry.ts | 18 -- .../ILockingMechanismUnlockEntryRequest.ts | 17 -- .../ILockingMechanismUpdateEntryLock.ts | 16 -- .../src/domain/graphql/getLockRecord.ts | 30 --- .../graphql/getLockedEntryLockRecord.ts | 31 --- .../src/domain/graphql/isEntryLocked.ts | 28 -- .../src/domain/graphql/listLockRecords.ts | 48 ---- .../src/domain/graphql/lockEntry.ts | 32 --- .../src/domain/graphql/unlockEntry.ts | 30 --- .../src/domain/graphql/unlockEntryRequest.ts | 33 --- .../src/domain/graphql/updateEntryLock.ts | 31 --- .../utils/createLockingMechanismClient.ts | 12 - .../app-locking-mechanism/src/hooks/index.ts | 2 - .../src/hooks/useLockingMechanism.ts | 14 - packages/app-locking-mechanism/src/types.ts | 100 -------- .../.babelrc.js | 0 .../LICENSE | 0 .../README.md | 8 +- .../package.json | 4 +- .../components/HeadlessCmsActionsAcoCell.tsx | 10 +- .../ContentEntryGuard.tsx | 8 +- .../ContentEntryLocker.tsx | 18 +- .../HeadlessCmsContentEntry.tsx | 2 +- .../HeadlessCmsContentEntry/index.ts | 0 .../components/LockedRecord/LockedRecord.tsx | 8 +- .../LockedRecord/LockedRecordForceUnlock.tsx | 10 +- .../src/components/LockedRecord/index.ts | 0 .../src/components/RecordLockingProvider.tsx} | 54 ++-- .../src/components/assets/lock.svg | 0 .../UseContentEntriesListHookDecorator.ts | 12 +- .../UseSaveAndPublishHookDecorator.tsx | 6 +- .../decorators/UseSaveHookDecorator.tsx | 6 +- .../src/domain/RecordLocking.ts} | 155 ++++++------ .../src/domain/RecordLockingClient.ts} | 8 +- .../src/domain/RecordLockingGetLockRecord.ts | 41 +++ .../RecordLockingGetLockedEntryLockRecord.ts | 41 +++ .../src/domain/RecordLockingIsEntryLocked.ts | 39 +++ .../domain/RecordLockingListLockRecords.ts | 48 ++++ .../src/domain/RecordLockingLockEntry.ts | 26 ++ .../src/domain/RecordLockingUnlockEntry.ts | 39 +++ .../domain/RecordLockingUnlockEntryRequest.ts | 26 ++ .../domain/RecordLockingUpdateEntryLock.ts | 40 +++ .../src/domain/abstractions/IRecordLocking.ts | 60 +++++ .../abstractions/IRecordLockingClient.ts} | 2 +- .../IRecordLockingGetLockRecord.ts | 17 ++ .../IRecordLockingGetLockedEntryLockRecord.ts | 17 ++ .../IRecordLockingIsEntryLocked.ts | 10 + .../IRecordLockingListLockRecords.ts | 25 ++ .../abstractions/IRecordLockingLockEntry.ts | 15 ++ .../abstractions/IRecordLockingUnlockEntry.ts | 16 ++ .../IRecordLockingUnlockEntryRequest.ts | 17 ++ .../IRecordLockingUpdateEntryLock.ts | 16 ++ .../src/domain/graphql/fields.ts | 0 .../src/domain/graphql/getLockRecord.ts | 30 +++ .../graphql/getLockedEntryLockRecord.ts | 31 +++ .../src/domain/graphql/isEntryLocked.ts | 28 ++ .../src/domain/graphql/listLockRecords.ts | 44 ++++ .../src/domain/graphql/lockEntry.ts | 32 +++ .../src/domain/graphql/unlockEntry.ts | 30 +++ .../src/domain/graphql/unlockEntryRequest.ts | 32 +++ .../src/domain/graphql/updateEntryLock.ts | 30 +++ .../domain/utils/createRecordLockingClient.ts | 10 + .../domain/utils/createRecordLockingError.ts} | 8 +- .../app-record-locking/src/hooks/index.ts | 2 + .../src/hooks/usePermission.ts | 2 +- .../src/hooks/useRecordLocking.ts | 14 + .../src/index.tsx | 20 +- packages/app-record-locking/src/types.ts | 91 +++++++ .../src/utils/createCacheKey.ts | 0 .../tsconfig.build.json | 0 .../tsconfig.json | 0 .../webiny.config.js | 0 packages/app-serverless-cms/package.json | 2 +- packages/app-serverless-cms/src/Admin.tsx | 4 +- .../app-serverless-cms/tsconfig.build.json | 2 +- packages/app-serverless-cms/tsconfig.json | 6 +- .../ddb-es/apps/api/graphql/package.json | 2 +- .../ddb-es/apps/api/graphql/src/index.ts | 4 +- .../ddb-os/apps/api/graphql/package.json | 2 +- .../ddb-os/apps/api/graphql/src/index.ts | 4 +- .../ddb/apps/api/graphql/package.json | 2 +- .../ddb/apps/api/graphql/src/index.ts | 4 +- scripts/listPackagesWithTests.js | 6 + yarn.lock | 153 ++++++----- 176 files changed, 1708 insertions(+), 1752 deletions(-) delete mode 100644 packages/api-locking-mechanism/README.md delete mode 100644 packages/api-locking-mechanism/src/abstractions/IGetLockRecordUseCase.ts delete mode 100644 packages/api-locking-mechanism/src/abstractions/IKickOutCurrentUserUseCase.ts delete mode 100644 packages/api-locking-mechanism/src/abstractions/ILockEntryUseCase.ts delete mode 100644 packages/api-locking-mechanism/src/abstractions/IUnlockEntryUseCase.ts delete mode 100644 packages/api-locking-mechanism/src/abstractions/IUpdateEntryLockUseCase.ts delete mode 100644 packages/api-locking-mechanism/src/types.ts rename packages/{api-locking-mechanism => api-record-locking}/.babelrc.js (100%) rename packages/{api-locking-mechanism => api-record-locking}/LICENSE (100%) create mode 100644 packages/api-record-locking/README.md rename packages/{api-locking-mechanism => api-record-locking}/__tests__/graphql/getLockRecord.test.ts (95%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/graphql/getLockedEntryLockRecord.test.ts (94%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/graphql/isEntryLocked.test.ts (94%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/graphql/listLockRecords.test.ts (90%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/graphql/lockEntry.test.ts (96%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/graphql/requestEntryUnlock.test.ts (93%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/graphql/unlockEntry.test.ts (95%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/graphql/updateEntryLock.test.ts (88%) rename packages/{api-locking-mechanism/__tests__/helpers/graphql/lockingMechanism.ts => api-record-locking/__tests__/helpers/graphql/recordLocking.ts} (85%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/helpers/identity.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/helpers/locales.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/helpers/permissions.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/helpers/plugins.ts (98%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/helpers/tenancySecurity.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/helpers/useGraphQLHandler.ts (98%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/useCase/isEntryLockedUseCase.test.ts (94%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/useCase/kickOutCurrentUser.test.ts (93%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/useCase/lockEntryUseCase.test.ts (91%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/useCase/unlockEntryRequestUseCase.test.ts (92%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/useCase/unlockEntryUseCase.test.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/utils/convertWhereCondition.test.ts (97%) rename packages/{api-locking-mechanism => api-record-locking}/__tests__/utils/validateSameIdentity.test.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/jest.setup.js (100%) rename packages/{api-locking-mechanism => api-record-locking}/package.json (93%) create mode 100644 packages/api-record-locking/src/abstractions/IGetLockRecordUseCase.ts rename packages/{api-locking-mechanism => api-record-locking}/src/abstractions/IGetLockedEntryLockRecordUseCase.ts (50%) rename packages/{api-locking-mechanism => api-record-locking}/src/abstractions/IIsEntryLocked.ts (59%) create mode 100644 packages/api-record-locking/src/abstractions/IKickOutCurrentUserUseCase.ts rename packages/{api-locking-mechanism => api-record-locking}/src/abstractions/IListAllLockRecordsUseCase.ts (50%) rename packages/{api-locking-mechanism => api-record-locking}/src/abstractions/IListLockRecordsUseCase.ts (71%) create mode 100644 packages/api-record-locking/src/abstractions/ILockEntryUseCase.ts rename packages/{api-locking-mechanism => api-record-locking}/src/abstractions/IUnlockEntryRequestUseCase.ts (63%) create mode 100644 packages/api-record-locking/src/abstractions/IUnlockEntryUseCase.ts create mode 100644 packages/api-record-locking/src/abstractions/IUpdateEntryLockUseCase.ts rename packages/{api-locking-mechanism => api-record-locking}/src/crud/crud.ts (80%) rename packages/{api-locking-mechanism => api-record-locking}/src/crud/model.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/src/graphql/schema.ts (61%) rename packages/{api-locking-mechanism => api-record-locking}/src/index.ts (78%) create mode 100644 packages/api-record-locking/src/types.ts rename packages/{api-locking-mechanism => api-record-locking}/src/useCases/GetLockRecord/GetLockRecordUseCase.ts (86%) rename packages/{api-locking-mechanism => api-record-locking}/src/useCases/GetLockedEntryLockRecord/GetLockedEntryLockRecordUseCase.ts (91%) rename packages/{api-locking-mechanism => api-record-locking}/src/useCases/IsEntryLocked/IsEntryLockedUseCase.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/src/useCases/KickOutCurrentUser/KickOutCurrentUserUseCase.ts (95%) rename packages/{api-locking-mechanism => api-record-locking}/src/useCases/ListAllLockRecordsUseCase/ListAllLockRecordsUseCase.ts (86%) rename packages/{api-locking-mechanism => api-record-locking}/src/useCases/ListLockRecordsUseCase/ListLockRecordsUseCase.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/src/useCases/LockEntryUseCase/LockEntryUseCase.ts (83%) rename packages/{api-locking-mechanism => api-record-locking}/src/useCases/UnlockEntryUseCase/UnlockEntryUseCase.ts (92%) rename packages/{api-locking-mechanism => api-record-locking}/src/useCases/UnlockRequestUseCase/UnlockEntryRequestUseCase.ts (90%) rename packages/{api-locking-mechanism => api-record-locking}/src/useCases/UpdateEntryLock/UpdateEntryLockUseCase.ts (89%) rename packages/{api-locking-mechanism => api-record-locking}/src/useCases/index.ts (98%) rename packages/{api-locking-mechanism => api-record-locking}/src/utils/calculateExpiresOn.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/src/utils/checkPermissions.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/src/utils/convertEntryToLockRecord.ts (55%) rename packages/{api-locking-mechanism => api-record-locking}/src/utils/convertWhereCondition.ts (88%) rename packages/{api-locking-mechanism => api-record-locking}/src/utils/getTimeout.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/src/utils/isLockedFactory.ts (75%) rename packages/{api-locking-mechanism => api-record-locking}/src/utils/lockRecordDatabaseId.ts (100%) rename packages/{api-locking-mechanism => api-record-locking}/src/utils/resolve.ts (89%) rename packages/{api-locking-mechanism => api-record-locking}/src/utils/validateSameIdentity.ts (74%) rename packages/{api-locking-mechanism => api-record-locking}/tsconfig.build.json (100%) rename packages/{api-locking-mechanism => api-record-locking}/tsconfig.json (100%) rename packages/{api-locking-mechanism => api-record-locking}/webiny.config.js (100%) delete mode 100644 packages/app-locking-mechanism/src/domain/LockingMechanismGetLockRecord.ts delete mode 100644 packages/app-locking-mechanism/src/domain/LockingMechanismGetLockedEntryLockRecord.ts delete mode 100644 packages/app-locking-mechanism/src/domain/LockingMechanismIsEntryLocked.ts delete mode 100644 packages/app-locking-mechanism/src/domain/LockingMechanismListLockRecords.ts delete mode 100644 packages/app-locking-mechanism/src/domain/LockingMechanismLockEntry.ts delete mode 100644 packages/app-locking-mechanism/src/domain/LockingMechanismUnlockEntry.ts delete mode 100644 packages/app-locking-mechanism/src/domain/LockingMechanismUnlockEntryRequest.ts delete mode 100644 packages/app-locking-mechanism/src/domain/LockingMechanismUpdateEntryLock.ts delete mode 100644 packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanism.ts delete mode 100644 packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismGetLockRecord.ts delete mode 100644 packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismGetLockedEntryLockRecord.ts delete mode 100644 packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismIsEntryLocked.ts delete mode 100644 packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismListLockRecords.ts delete mode 100644 packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismLockEntry.ts delete mode 100644 packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUnlockEntry.ts delete mode 100644 packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUnlockEntryRequest.ts delete mode 100644 packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUpdateEntryLock.ts delete mode 100644 packages/app-locking-mechanism/src/domain/graphql/getLockRecord.ts delete mode 100644 packages/app-locking-mechanism/src/domain/graphql/getLockedEntryLockRecord.ts delete mode 100644 packages/app-locking-mechanism/src/domain/graphql/isEntryLocked.ts delete mode 100644 packages/app-locking-mechanism/src/domain/graphql/listLockRecords.ts delete mode 100644 packages/app-locking-mechanism/src/domain/graphql/lockEntry.ts delete mode 100644 packages/app-locking-mechanism/src/domain/graphql/unlockEntry.ts delete mode 100644 packages/app-locking-mechanism/src/domain/graphql/unlockEntryRequest.ts delete mode 100644 packages/app-locking-mechanism/src/domain/graphql/updateEntryLock.ts delete mode 100644 packages/app-locking-mechanism/src/domain/utils/createLockingMechanismClient.ts delete mode 100644 packages/app-locking-mechanism/src/hooks/index.ts delete mode 100644 packages/app-locking-mechanism/src/hooks/useLockingMechanism.ts delete mode 100644 packages/app-locking-mechanism/src/types.ts rename packages/{app-locking-mechanism => app-record-locking}/.babelrc.js (100%) rename packages/{app-locking-mechanism => app-record-locking}/LICENSE (100%) rename packages/{app-locking-mechanism => app-record-locking}/README.md (56%) rename packages/{app-locking-mechanism => app-record-locking}/package.json (94%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/HeadlessCmsActionsAcoCell.tsx (85%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/HeadlessCmsContentEntry/ContentEntryGuard.tsx (87%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/HeadlessCmsContentEntry/ContentEntryLocker.tsx (88%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.tsx (93%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/HeadlessCmsContentEntry/index.ts (100%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/LockedRecord/LockedRecord.tsx (94%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/LockedRecord/LockedRecordForceUnlock.tsx (89%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/LockedRecord/index.ts (100%) rename packages/{app-locking-mechanism/src/components/LockingMechanismProvider.tsx => app-record-locking/src/components/RecordLockingProvider.tsx} (63%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/assets/lock.svg (100%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/decorators/UseContentEntriesListHookDecorator.ts (53%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/decorators/UseSaveAndPublishHookDecorator.tsx (93%) rename packages/{app-locking-mechanism => app-record-locking}/src/components/decorators/UseSaveHookDecorator.tsx (93%) rename packages/{app-locking-mechanism/src/domain/LockingMechanism.ts => app-record-locking/src/domain/RecordLocking.ts} (67%) rename packages/{app-locking-mechanism/src/domain/LockingMechanismClient.ts => app-record-locking/src/domain/RecordLockingClient.ts} (71%) create mode 100644 packages/app-record-locking/src/domain/RecordLockingGetLockRecord.ts create mode 100644 packages/app-record-locking/src/domain/RecordLockingGetLockedEntryLockRecord.ts create mode 100644 packages/app-record-locking/src/domain/RecordLockingIsEntryLocked.ts create mode 100644 packages/app-record-locking/src/domain/RecordLockingListLockRecords.ts create mode 100644 packages/app-record-locking/src/domain/RecordLockingLockEntry.ts create mode 100644 packages/app-record-locking/src/domain/RecordLockingUnlockEntry.ts create mode 100644 packages/app-record-locking/src/domain/RecordLockingUnlockEntryRequest.ts create mode 100644 packages/app-record-locking/src/domain/RecordLockingUpdateEntryLock.ts create mode 100644 packages/app-record-locking/src/domain/abstractions/IRecordLocking.ts rename packages/{app-locking-mechanism/src/domain/abstractions/ILockingMechanismClient.ts => app-record-locking/src/domain/abstractions/IRecordLockingClient.ts} (86%) create mode 100644 packages/app-record-locking/src/domain/abstractions/IRecordLockingGetLockRecord.ts create mode 100644 packages/app-record-locking/src/domain/abstractions/IRecordLockingGetLockedEntryLockRecord.ts create mode 100644 packages/app-record-locking/src/domain/abstractions/IRecordLockingIsEntryLocked.ts create mode 100644 packages/app-record-locking/src/domain/abstractions/IRecordLockingListLockRecords.ts create mode 100644 packages/app-record-locking/src/domain/abstractions/IRecordLockingLockEntry.ts create mode 100644 packages/app-record-locking/src/domain/abstractions/IRecordLockingUnlockEntry.ts create mode 100644 packages/app-record-locking/src/domain/abstractions/IRecordLockingUnlockEntryRequest.ts create mode 100644 packages/app-record-locking/src/domain/abstractions/IRecordLockingUpdateEntryLock.ts rename packages/{app-locking-mechanism => app-record-locking}/src/domain/graphql/fields.ts (100%) create mode 100644 packages/app-record-locking/src/domain/graphql/getLockRecord.ts create mode 100644 packages/app-record-locking/src/domain/graphql/getLockedEntryLockRecord.ts create mode 100644 packages/app-record-locking/src/domain/graphql/isEntryLocked.ts create mode 100644 packages/app-record-locking/src/domain/graphql/listLockRecords.ts create mode 100644 packages/app-record-locking/src/domain/graphql/lockEntry.ts create mode 100644 packages/app-record-locking/src/domain/graphql/unlockEntry.ts create mode 100644 packages/app-record-locking/src/domain/graphql/unlockEntryRequest.ts create mode 100644 packages/app-record-locking/src/domain/graphql/updateEntryLock.ts create mode 100644 packages/app-record-locking/src/domain/utils/createRecordLockingClient.ts rename packages/{app-locking-mechanism/src/domain/utils/createLockingMechanismError.ts => app-record-locking/src/domain/utils/createRecordLockingError.ts} (68%) create mode 100644 packages/app-record-locking/src/hooks/index.ts rename packages/{app-locking-mechanism => app-record-locking}/src/hooks/usePermission.ts (84%) create mode 100644 packages/app-record-locking/src/hooks/useRecordLocking.ts rename packages/{app-locking-mechanism => app-record-locking}/src/index.tsx (53%) create mode 100644 packages/app-record-locking/src/types.ts rename packages/{app-locking-mechanism => app-record-locking}/src/utils/createCacheKey.ts (100%) rename packages/{app-locking-mechanism => app-record-locking}/tsconfig.build.json (100%) rename packages/{app-locking-mechanism => app-record-locking}/tsconfig.json (100%) rename packages/{app-locking-mechanism => app-record-locking}/webiny.config.js (100%) diff --git a/.github/workflows/pullRequests.yml b/.github/workflows/pullRequests.yml index d9fe4e5f955..bf0d09d0b5c 100644 --- a/.github/workflows/pullRequests.yml +++ b/.github/workflows/pullRequests.yml @@ -177,7 +177,7 @@ jobs: - 18 package: >- ${{ - fromJson('[{"cmd":"packages/api","id":"api"},{"cmd":"packages/api-admin-settings","id":"api-admin-settings"},{"cmd":"packages/api-authentication","id":"api-authentication"},{"cmd":"packages/api-authentication-cognito","id":"api-authentication-cognito"},{"cmd":"packages/api-dynamodb-to-elasticsearch","id":"api-dynamodb-to-elasticsearch"},{"cmd":"packages/api-headless-cms-ddb","id":"api-headless-cms-ddb"},{"cmd":"packages/api-locking-mechanism","id":"api-locking-mechanism"},{"cmd":"packages/api-wcp","id":"api-wcp"},{"cmd":"packages/api-websockets","id":"api-websockets"},{"cmd":"packages/app-aco","id":"app-aco"},{"cmd":"packages/app-admin","id":"app-admin"},{"cmd":"packages/cwp-template-aws","id":"cwp-template-aws"},{"cmd":"packages/data-migration","id":"data-migration"},{"cmd":"packages/db-dynamodb","id":"db-dynamodb"},{"cmd":"packages/form","id":"form"},{"cmd":"packages/handler","id":"handler"},{"cmd":"packages/handler-aws","id":"handler-aws"},{"cmd":"packages/handler-graphql","id":"handler-graphql"},{"cmd":"packages/handler-logs","id":"handler-logs"},{"cmd":"packages/ioc","id":"ioc"},{"cmd":"packages/lexical-converter","id":"lexical-converter"},{"cmd":"packages/plugins","id":"plugins"},{"cmd":"packages/pubsub","id":"pubsub"},{"cmd":"packages/react-composition","id":"react-composition"},{"cmd":"packages/react-properties","id":"react-properties"},{"cmd":"packages/react-rich-text-lexical-renderer","id":"react-rich-text-lexical-renderer"},{"cmd":"packages/utils","id":"utils"},{"cmd":"packages/validation","id":"validation"}]') + fromJson('[{"cmd":"packages/api","id":"api"},{"cmd":"packages/api-admin-settings","id":"api-admin-settings"},{"cmd":"packages/api-authentication","id":"api-authentication"},{"cmd":"packages/api-authentication-cognito","id":"api-authentication-cognito"},{"cmd":"packages/api-dynamodb-to-elasticsearch","id":"api-dynamodb-to-elasticsearch"},{"cmd":"packages/api-headless-cms-ddb","id":"api-headless-cms-ddb"},{"cmd":"packages/api-record-locking","id":"api-record-locking"},{"cmd":"packages/api-wcp","id":"api-wcp"},{"cmd":"packages/api-websockets","id":"api-websockets"},{"cmd":"packages/app-aco","id":"app-aco"},{"cmd":"packages/app-admin","id":"app-admin"},{"cmd":"packages/cwp-template-aws","id":"cwp-template-aws"},{"cmd":"packages/data-migration","id":"data-migration"},{"cmd":"packages/db-dynamodb","id":"db-dynamodb"},{"cmd":"packages/form","id":"form"},{"cmd":"packages/handler","id":"handler"},{"cmd":"packages/handler-aws","id":"handler-aws"},{"cmd":"packages/handler-graphql","id":"handler-graphql"},{"cmd":"packages/handler-logs","id":"handler-logs"},{"cmd":"packages/ioc","id":"ioc"},{"cmd":"packages/lexical-converter","id":"lexical-converter"},{"cmd":"packages/plugins","id":"plugins"},{"cmd":"packages/pubsub","id":"pubsub"},{"cmd":"packages/react-composition","id":"react-composition"},{"cmd":"packages/react-properties","id":"react-properties"},{"cmd":"packages/react-rich-text-lexical-renderer","id":"react-rich-text-lexical-renderer"},{"cmd":"packages/utils","id":"utils"},{"cmd":"packages/validation","id":"validation"}]') }} runs-on: ${{ matrix.os }} env: diff --git a/.github/workflows/pushDev.yml b/.github/workflows/pushDev.yml index 97093fc72df..fae212922d5 100644 --- a/.github/workflows/pushDev.yml +++ b/.github/workflows/pushDev.yml @@ -143,7 +143,7 @@ jobs: - 18 package: >- ${{ - fromJson('[{"cmd":"packages/api","id":"api"},{"cmd":"packages/api-admin-settings","id":"api-admin-settings"},{"cmd":"packages/api-authentication","id":"api-authentication"},{"cmd":"packages/api-authentication-cognito","id":"api-authentication-cognito"},{"cmd":"packages/api-dynamodb-to-elasticsearch","id":"api-dynamodb-to-elasticsearch"},{"cmd":"packages/api-headless-cms-ddb","id":"api-headless-cms-ddb"},{"cmd":"packages/api-locking-mechanism","id":"api-locking-mechanism"},{"cmd":"packages/api-wcp","id":"api-wcp"},{"cmd":"packages/api-websockets","id":"api-websockets"},{"cmd":"packages/app-aco","id":"app-aco"},{"cmd":"packages/app-admin","id":"app-admin"},{"cmd":"packages/cwp-template-aws","id":"cwp-template-aws"},{"cmd":"packages/data-migration","id":"data-migration"},{"cmd":"packages/db-dynamodb","id":"db-dynamodb"},{"cmd":"packages/form","id":"form"},{"cmd":"packages/handler","id":"handler"},{"cmd":"packages/handler-aws","id":"handler-aws"},{"cmd":"packages/handler-graphql","id":"handler-graphql"},{"cmd":"packages/handler-logs","id":"handler-logs"},{"cmd":"packages/ioc","id":"ioc"},{"cmd":"packages/lexical-converter","id":"lexical-converter"},{"cmd":"packages/plugins","id":"plugins"},{"cmd":"packages/pubsub","id":"pubsub"},{"cmd":"packages/react-composition","id":"react-composition"},{"cmd":"packages/react-properties","id":"react-properties"},{"cmd":"packages/react-rich-text-lexical-renderer","id":"react-rich-text-lexical-renderer"},{"cmd":"packages/utils","id":"utils"},{"cmd":"packages/validation","id":"validation"}]') + fromJson('[{"cmd":"packages/api","id":"api"},{"cmd":"packages/api-admin-settings","id":"api-admin-settings"},{"cmd":"packages/api-authentication","id":"api-authentication"},{"cmd":"packages/api-authentication-cognito","id":"api-authentication-cognito"},{"cmd":"packages/api-dynamodb-to-elasticsearch","id":"api-dynamodb-to-elasticsearch"},{"cmd":"packages/api-headless-cms-ddb","id":"api-headless-cms-ddb"},{"cmd":"packages/api-record-locking","id":"api-record-locking"},{"cmd":"packages/api-wcp","id":"api-wcp"},{"cmd":"packages/api-websockets","id":"api-websockets"},{"cmd":"packages/app-aco","id":"app-aco"},{"cmd":"packages/app-admin","id":"app-admin"},{"cmd":"packages/cwp-template-aws","id":"cwp-template-aws"},{"cmd":"packages/data-migration","id":"data-migration"},{"cmd":"packages/db-dynamodb","id":"db-dynamodb"},{"cmd":"packages/form","id":"form"},{"cmd":"packages/handler","id":"handler"},{"cmd":"packages/handler-aws","id":"handler-aws"},{"cmd":"packages/handler-graphql","id":"handler-graphql"},{"cmd":"packages/handler-logs","id":"handler-logs"},{"cmd":"packages/ioc","id":"ioc"},{"cmd":"packages/lexical-converter","id":"lexical-converter"},{"cmd":"packages/plugins","id":"plugins"},{"cmd":"packages/pubsub","id":"pubsub"},{"cmd":"packages/react-composition","id":"react-composition"},{"cmd":"packages/react-properties","id":"react-properties"},{"cmd":"packages/react-rich-text-lexical-renderer","id":"react-rich-text-lexical-renderer"},{"cmd":"packages/utils","id":"utils"},{"cmd":"packages/validation","id":"validation"}]') }} runs-on: ${{ matrix.os }} env: diff --git a/.github/workflows/pushNext.yml b/.github/workflows/pushNext.yml index bc81e311df8..3c46cd84448 100644 --- a/.github/workflows/pushNext.yml +++ b/.github/workflows/pushNext.yml @@ -143,7 +143,7 @@ jobs: - 18 package: >- ${{ - fromJson('[{"cmd":"packages/api","id":"api"},{"cmd":"packages/api-admin-settings","id":"api-admin-settings"},{"cmd":"packages/api-authentication","id":"api-authentication"},{"cmd":"packages/api-authentication-cognito","id":"api-authentication-cognito"},{"cmd":"packages/api-dynamodb-to-elasticsearch","id":"api-dynamodb-to-elasticsearch"},{"cmd":"packages/api-headless-cms-ddb","id":"api-headless-cms-ddb"},{"cmd":"packages/api-locking-mechanism","id":"api-locking-mechanism"},{"cmd":"packages/api-wcp","id":"api-wcp"},{"cmd":"packages/api-websockets","id":"api-websockets"},{"cmd":"packages/app-aco","id":"app-aco"},{"cmd":"packages/app-admin","id":"app-admin"},{"cmd":"packages/cwp-template-aws","id":"cwp-template-aws"},{"cmd":"packages/data-migration","id":"data-migration"},{"cmd":"packages/db-dynamodb","id":"db-dynamodb"},{"cmd":"packages/form","id":"form"},{"cmd":"packages/handler","id":"handler"},{"cmd":"packages/handler-aws","id":"handler-aws"},{"cmd":"packages/handler-graphql","id":"handler-graphql"},{"cmd":"packages/handler-logs","id":"handler-logs"},{"cmd":"packages/ioc","id":"ioc"},{"cmd":"packages/lexical-converter","id":"lexical-converter"},{"cmd":"packages/plugins","id":"plugins"},{"cmd":"packages/pubsub","id":"pubsub"},{"cmd":"packages/react-composition","id":"react-composition"},{"cmd":"packages/react-properties","id":"react-properties"},{"cmd":"packages/react-rich-text-lexical-renderer","id":"react-rich-text-lexical-renderer"},{"cmd":"packages/utils","id":"utils"},{"cmd":"packages/validation","id":"validation"}]') + fromJson('[{"cmd":"packages/api","id":"api"},{"cmd":"packages/api-admin-settings","id":"api-admin-settings"},{"cmd":"packages/api-authentication","id":"api-authentication"},{"cmd":"packages/api-authentication-cognito","id":"api-authentication-cognito"},{"cmd":"packages/api-dynamodb-to-elasticsearch","id":"api-dynamodb-to-elasticsearch"},{"cmd":"packages/api-headless-cms-ddb","id":"api-headless-cms-ddb"},{"cmd":"packages/api-record-locking","id":"api-record-locking"},{"cmd":"packages/api-wcp","id":"api-wcp"},{"cmd":"packages/api-websockets","id":"api-websockets"},{"cmd":"packages/app-aco","id":"app-aco"},{"cmd":"packages/app-admin","id":"app-admin"},{"cmd":"packages/cwp-template-aws","id":"cwp-template-aws"},{"cmd":"packages/data-migration","id":"data-migration"},{"cmd":"packages/db-dynamodb","id":"db-dynamodb"},{"cmd":"packages/form","id":"form"},{"cmd":"packages/handler","id":"handler"},{"cmd":"packages/handler-aws","id":"handler-aws"},{"cmd":"packages/handler-graphql","id":"handler-graphql"},{"cmd":"packages/handler-logs","id":"handler-logs"},{"cmd":"packages/ioc","id":"ioc"},{"cmd":"packages/lexical-converter","id":"lexical-converter"},{"cmd":"packages/plugins","id":"plugins"},{"cmd":"packages/pubsub","id":"pubsub"},{"cmd":"packages/react-composition","id":"react-composition"},{"cmd":"packages/react-properties","id":"react-properties"},{"cmd":"packages/react-rich-text-lexical-renderer","id":"react-rich-text-lexical-renderer"},{"cmd":"packages/utils","id":"utils"},{"cmd":"packages/validation","id":"validation"}]') }} runs-on: ${{ matrix.os }} env: diff --git a/apps/api/graphql/package.json b/apps/api/graphql/package.json index fe291afec65..2508c6af2ff 100644 --- a/apps/api/graphql/package.json +++ b/apps/api/graphql/package.json @@ -25,7 +25,6 @@ "@webiny/api-i18n": "0.0.0", "@webiny/api-i18n-content": "0.0.0", "@webiny/api-i18n-ddb": "0.0.0", - "@webiny/api-locking-mechanism": "0.0.0", "@webiny/api-page-builder": "0.0.0", "@webiny/api-page-builder-aco": "0.0.0", "@webiny/api-page-builder-import-export": "0.0.0", @@ -33,6 +32,7 @@ "@webiny/api-page-builder-so-ddb": "0.0.0", "@webiny/api-prerendering-service": "0.0.0", "@webiny/api-prerendering-service-aws": "0.0.0", + "@webiny/api-record-locking": "0.0.0", "@webiny/api-security": "0.0.0", "@webiny/api-security-cognito": "0.0.0", "@webiny/api-security-so-ddb": "0.0.0", diff --git a/apps/api/graphql/src/index.ts b/apps/api/graphql/src/index.ts index 6cfe84d46f3..5afef095ae9 100644 --- a/apps/api/graphql/src/index.ts +++ b/apps/api/graphql/src/index.ts @@ -43,7 +43,7 @@ import { createBenchmarkEnablePlugin } from "~/plugins/benchmarkEnable"; import { createCountDynamoDbTask } from "~/plugins/countDynamoDbTask"; import { createContinuingTask } from "~/plugins/continuingTask"; import { createWebsockets } from "@webiny/api-websockets"; -import { createLockingMechanism } from "@webiny/api-locking-mechanism"; +import { createRecordLocking } from "@webiny/api-record-locking"; const debug = process.env.DEBUG === "true"; const documentClient = getDocumentClient(); @@ -71,7 +71,7 @@ export const handler = createHandler({ }) }), createHeadlessCmsGraphQL(), - createLockingMechanism(), + createRecordLocking(), createBackgroundTasks(), createFileManagerContext({ storageOperations: createFileManagerStorageOperations({ diff --git a/apps/api/graphql/tsconfig.json b/apps/api/graphql/tsconfig.json index d7234fbb2da..422598f9954 100644 --- a/apps/api/graphql/tsconfig.json +++ b/apps/api/graphql/tsconfig.json @@ -18,7 +18,7 @@ "path": "../../../packages/api-audit-logs/tsconfig.build.json" }, { - "path": "../../../packages/api-locking-mechanism/tsconfig.build.json" + "path": "../../../packages/api-record-locking/tsconfig.build.json" }, { "path": "../../../packages/api-file-manager/tsconfig.build.json" @@ -156,8 +156,8 @@ "@webiny/api-headless-cms-aco": ["../../../packages/api-headless-cms-aco/src"], "@webiny/api-headless-cms-ddb/*": ["../../../packages/api-headless-cms-ddb/src/*"], "@webiny/api-headless-cms-ddb": ["../../../packages/api-headless-cms-ddb/src"], - "@webiny/api-locking-mechanism/*": ["../../../packages/api-locking-mechanism/src/*"], - "@webiny/api-locking-mechanism": ["../../../packages/api-locking-mechanism/src"], + "@webiny/api-record-locking/*": ["../../../packages/api-record-locking/src/*"], + "@webiny/api-record-locking": ["../../../packages/api-record-locking/src"], "@webiny/api-headless-cms-tasks/*": ["../../../packages/api-headless-cms-tasks/src/*"], "@webiny/api-headless-cms-tasks": ["../../../packages/api-headless-cms-tasks/src"], "@webiny/api-i18n/*": ["../../../packages/api-i18n/src/*"], diff --git a/packages/api-locking-mechanism/README.md b/packages/api-locking-mechanism/README.md deleted file mode 100644 index 9d9a47469bd..00000000000 --- a/packages/api-locking-mechanism/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# @webiny/api-locking-mechanism -[![](https://img.shields.io/npm/dw/@webiny/api-locking-mechanism.svg)](https://www.npmjs.com/package/@webiny/api-locking-mechanism) -[![](https://img.shields.io/npm/v/@webiny/api-locking-mechanism.svg)](https://www.npmjs.com/package/@webiny/api-locking-mechanism) -[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) - -## Install -``` -yarn add @webiny/api-locking-mechanism -``` diff --git a/packages/api-locking-mechanism/src/abstractions/IGetLockRecordUseCase.ts b/packages/api-locking-mechanism/src/abstractions/IGetLockRecordUseCase.ts deleted file mode 100644 index 678765cbbe7..00000000000 --- a/packages/api-locking-mechanism/src/abstractions/IGetLockRecordUseCase.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ILockingMechanismGetLockRecordParams, ILockingMechanismLockRecord } from "~/types"; - -export type IGetLockRecordUseCaseExecuteParams = ILockingMechanismGetLockRecordParams; - -export interface IGetLockRecordUseCaseExecute { - (params: IGetLockRecordUseCaseExecuteParams): Promise; -} - -export interface IGetLockRecordUseCase { - execute: IGetLockRecordUseCaseExecute; -} diff --git a/packages/api-locking-mechanism/src/abstractions/IKickOutCurrentUserUseCase.ts b/packages/api-locking-mechanism/src/abstractions/IKickOutCurrentUserUseCase.ts deleted file mode 100644 index 4aad3f9f7ef..00000000000 --- a/packages/api-locking-mechanism/src/abstractions/IKickOutCurrentUserUseCase.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ILockingMechanismLockRecord } from "~/types"; - -export type IKickOutCurrentUserUseCaseExecuteParams = ILockingMechanismLockRecord; - -export interface IKickOutCurrentUserUseCase { - execute(params: IKickOutCurrentUserUseCaseExecuteParams): Promise; -} diff --git a/packages/api-locking-mechanism/src/abstractions/ILockEntryUseCase.ts b/packages/api-locking-mechanism/src/abstractions/ILockEntryUseCase.ts deleted file mode 100644 index d0035f9e174..00000000000 --- a/packages/api-locking-mechanism/src/abstractions/ILockEntryUseCase.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ILockingMechanismLockRecord, ILockingMechanismLockRecordEntryType } from "~/types"; - -export interface ILockEntryUseCaseExecuteParams { - id: string; - type: ILockingMechanismLockRecordEntryType; -} - -export interface ILockEntryUseCaseExecute { - (params: ILockEntryUseCaseExecuteParams): Promise; -} - -export interface ILockEntryUseCase { - execute: ILockEntryUseCaseExecute; -} diff --git a/packages/api-locking-mechanism/src/abstractions/IUnlockEntryUseCase.ts b/packages/api-locking-mechanism/src/abstractions/IUnlockEntryUseCase.ts deleted file mode 100644 index 9d4511147af..00000000000 --- a/packages/api-locking-mechanism/src/abstractions/IUnlockEntryUseCase.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ILockingMechanismLockRecord, ILockingMechanismLockRecordEntryType } from "~/types"; - -export interface IUnlockEntryUseCaseExecuteParams { - id: string; - type: ILockingMechanismLockRecordEntryType; - force?: boolean; -} - -export interface IUnlockEntryUseCaseExecute { - (params: IUnlockEntryUseCaseExecuteParams): Promise; -} - -export interface IUnlockEntryUseCase { - execute: IUnlockEntryUseCaseExecute; -} diff --git a/packages/api-locking-mechanism/src/abstractions/IUpdateEntryLockUseCase.ts b/packages/api-locking-mechanism/src/abstractions/IUpdateEntryLockUseCase.ts deleted file mode 100644 index 3cea731f36b..00000000000 --- a/packages/api-locking-mechanism/src/abstractions/IUpdateEntryLockUseCase.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ILockingMechanismLockRecord, ILockingMechanismLockRecordEntryType } from "~/types"; - -export interface IUpdateEntryLockUseCaseExecuteParams { - id: string; - type: ILockingMechanismLockRecordEntryType; -} - -export interface IUpdateEntryLockUseCaseExecute { - (params: IUpdateEntryLockUseCaseExecuteParams): Promise; -} - -export interface IUpdateEntryLockUseCase { - execute: IUpdateEntryLockUseCaseExecute; -} diff --git a/packages/api-locking-mechanism/src/types.ts b/packages/api-locking-mechanism/src/types.ts deleted file mode 100644 index 6ff2171b83a..00000000000 --- a/packages/api-locking-mechanism/src/types.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { - CmsContext, - CmsEntry, - CmsEntryListParams, - CmsEntryMeta, - CmsError, - CmsIdentity, - CmsModel, - CmsModelManager -} from "@webiny/api-headless-cms/types"; -import { Topic } from "@webiny/pubsub/types"; -import { - Context as IWebsocketsContext, - IWebsocketsContextObject -} from "@webiny/api-websockets/types"; - -export { CmsError, CmsEntry }; - -export type ILockingMechanismIdentity = CmsIdentity; - -export type ILockingMechanismModelManager = CmsModelManager; - -export type ILockingMechanismMeta = CmsEntryMeta; - -export interface IHasFullAccessCallable { - (): Promise; -} - -export interface IGetWebsocketsContextCallable { - (): IWebsocketsContextObject; -} - -export interface IGetIdentity { - (): ILockingMechanismIdentity; -} - -export interface ILockingMechanismLockRecordValues { - targetId: string; - type: ILockingMechanismLockRecordEntryType; - actions?: ILockingMechanismLockRecordAction[]; -} -export enum ILockingMechanismLockRecordActionType { - requested = "requested", - approved = "approved", - denied = "denied" -} - -export interface ILockingMechanismLockRecordRequestedAction { - type: ILockingMechanismLockRecordActionType.requested; - message?: string; - createdOn: Date; - createdBy: ILockingMechanismIdentity; -} - -export interface ILockingMechanismLockRecordApprovedAction { - type: ILockingMechanismLockRecordActionType.approved; - message?: string; - createdOn: Date; - createdBy: ILockingMechanismIdentity; -} - -export interface ILockingMechanismLockRecordDeniedAction { - type: ILockingMechanismLockRecordActionType.denied; - message?: string; - createdOn: Date; - createdBy: ILockingMechanismIdentity; -} - -export type ILockingMechanismLockRecordAction = - | ILockingMechanismLockRecordRequestedAction - | ILockingMechanismLockRecordApprovedAction - | ILockingMechanismLockRecordDeniedAction; - -export interface ILockingMechanismLockRecordObject { - id: string; - targetId: string; - type: ILockingMechanismLockRecordEntryType; - lockedBy: ILockingMechanismIdentity; - lockedOn: Date; - updatedOn: Date; - expiresOn: Date; - actions?: ILockingMechanismLockRecordAction[]; -} - -export interface ILockingMechanismLockRecord extends ILockingMechanismLockRecordObject { - toObject(): ILockingMechanismLockRecordObject; - addAction(action: ILockingMechanismLockRecordAction): void; - getUnlockRequested(): ILockingMechanismLockRecordRequestedAction | undefined; - getUnlockApproved(): ILockingMechanismLockRecordApprovedAction | undefined; - getUnlockDenied(): ILockingMechanismLockRecordDeniedAction | undefined; -} - -/** - * Do not use any special chars other than #, as we use this to create lock record IDs. - */ -export type ILockingMechanismLockRecordEntryType = string; - -export type ILockingMechanismListAllLockRecordsParams = Pick< - CmsEntryListParams, - "where" | "limit" | "sort" | "after" ->; - -export type ILockingMechanismListLockRecordsParams = ILockingMechanismListAllLockRecordsParams; - -export interface ILockingMechanismListAllLockRecordsResponse { - items: ILockingMechanismLockRecord[]; - meta: ILockingMechanismMeta; -} - -export type ILockingMechanismListLockRecordsResponse = ILockingMechanismListAllLockRecordsResponse; - -export interface ILockingMechanismGetLockRecordParams { - id: string; - type: ILockingMechanismLockRecordEntryType; -} - -export interface ILockingMechanismIsLockedParams { - id: string; - type: ILockingMechanismLockRecordEntryType; -} - -export interface ILockingMechanismGetLockedEntryLockRecordParams { - id: string; - type: ILockingMechanismLockRecordEntryType; -} - -export interface ILockingMechanismLockEntryParams { - id: string; - type: ILockingMechanismLockRecordEntryType; -} - -export interface ILockingMechanismUpdateEntryLockParams { - id: string; - type: ILockingMechanismLockRecordEntryType; -} - -export interface ILockingMechanismUnlockEntryParams { - id: string; - type: ILockingMechanismLockRecordEntryType; - force?: boolean; -} - -export interface ILockingMechanismUnlockEntryRequestParams { - id: string; - type: ILockingMechanismLockRecordEntryType; -} - -export interface OnEntryBeforeLockTopicParams { - id: string; - type: ILockingMechanismLockRecordEntryType; -} - -export interface OnEntryAfterLockTopicParams { - id: string; - type: ILockingMechanismLockRecordEntryType; - record: ILockingMechanismLockRecord; -} - -export interface OnEntryLockErrorTopicParams { - id: string; - type: ILockingMechanismLockRecordEntryType; - error: CmsError; -} - -export interface OnEntryBeforeUnlockTopicParams { - id: string; - type: ILockingMechanismLockRecordEntryType; - getIdentity: IGetIdentity; -} - -export interface OnEntryAfterUnlockTopicParams { - id: string; - type: ILockingMechanismLockRecordEntryType; - record: ILockingMechanismLockRecord; -} - -export interface OnEntryUnlockErrorTopicParams { - id: string; - type: ILockingMechanismLockRecordEntryType; - error: CmsError; -} - -export interface OnEntryBeforeUnlockRequestTopicParams { - id: string; - type: ILockingMechanismLockRecordEntryType; -} - -export interface OnEntryAfterUnlockRequestTopicParams { - id: string; - type: ILockingMechanismLockRecordEntryType; - record: ILockingMechanismLockRecord; -} - -export interface OnEntryUnlockRequestErrorTopicParams { - id: string; - type: ILockingMechanismLockRecordEntryType; - error: CmsError; -} - -export interface ILockingMechanism { - onEntryBeforeLock: Topic; - onEntryAfterLock: Topic; - onEntryLockError: Topic; - onEntryBeforeUnlock: Topic; - onEntryAfterUnlock: Topic; - onEntryUnlockError: Topic; - onEntryBeforeUnlockRequest: Topic; - onEntryAfterUnlockRequest: Topic; - onEntryUnlockRequestError: Topic; - getModel(): Promise; - listAllLockRecords( - params?: ILockingMechanismListAllLockRecordsParams - ): Promise; - /** - * Same call as listAllLockRecords, except this one will filter out records with expired lock. - */ - listLockRecords( - params?: ILockingMechanismListLockRecordsParams - ): Promise; - getLockRecord( - params: ILockingMechanismGetLockRecordParams - ): Promise; - isEntryLocked(params: ILockingMechanismIsLockedParams): Promise; - getLockedEntryLockRecord( - params: ILockingMechanismGetLockedEntryLockRecordParams - ): Promise; - lockEntry(params: ILockingMechanismLockEntryParams): Promise; - updateEntryLock( - params: ILockingMechanismUpdateEntryLockParams - ): Promise; - unlockEntry(params: ILockingMechanismUnlockEntryParams): Promise; - unlockEntryRequest( - params: ILockingMechanismUnlockEntryRequestParams - ): Promise; -} - -export interface Context extends CmsContext, IWebsocketsContext { - lockingMechanism: ILockingMechanism; -} diff --git a/packages/api-locking-mechanism/.babelrc.js b/packages/api-record-locking/.babelrc.js similarity index 100% rename from packages/api-locking-mechanism/.babelrc.js rename to packages/api-record-locking/.babelrc.js diff --git a/packages/api-locking-mechanism/LICENSE b/packages/api-record-locking/LICENSE similarity index 100% rename from packages/api-locking-mechanism/LICENSE rename to packages/api-record-locking/LICENSE diff --git a/packages/api-record-locking/README.md b/packages/api-record-locking/README.md new file mode 100644 index 00000000000..547d8d47703 --- /dev/null +++ b/packages/api-record-locking/README.md @@ -0,0 +1,10 @@ +# @webiny/api-record-locking +[![](https://img.shields.io/npm/dw/@webiny/api-record-locking.svg)](https://www.npmjs.com/package/@webiny/api-record-locking) +[![](https://img.shields.io/npm/v/@webiny/api-record-locking.svg)](https://www.npmjs.com/package/@webiny/api-record-locking) +[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) + +## Install +``` +yarn add @webiny/api-record-locking +``` diff --git a/packages/api-locking-mechanism/__tests__/graphql/getLockRecord.test.ts b/packages/api-record-locking/__tests__/graphql/getLockRecord.test.ts similarity index 95% rename from packages/api-locking-mechanism/__tests__/graphql/getLockRecord.test.ts rename to packages/api-record-locking/__tests__/graphql/getLockRecord.test.ts index 1100705481a..66284c64ada 100644 --- a/packages/api-locking-mechanism/__tests__/graphql/getLockRecord.test.ts +++ b/packages/api-record-locking/__tests__/graphql/getLockRecord.test.ts @@ -11,7 +11,7 @@ describe("get lock record", () => { expect(response).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { getLockRecord: { data: null, error: { diff --git a/packages/api-locking-mechanism/__tests__/graphql/getLockedEntryLockRecord.test.ts b/packages/api-record-locking/__tests__/graphql/getLockedEntryLockRecord.test.ts similarity index 94% rename from packages/api-locking-mechanism/__tests__/graphql/getLockedEntryLockRecord.test.ts rename to packages/api-record-locking/__tests__/graphql/getLockedEntryLockRecord.test.ts index 358c5ca414f..7a8cab4d758 100644 --- a/packages/api-locking-mechanism/__tests__/graphql/getLockedEntryLockRecord.test.ts +++ b/packages/api-record-locking/__tests__/graphql/getLockedEntryLockRecord.test.ts @@ -23,7 +23,7 @@ describe("get locked entry lock record", () => { expect(response).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { getLockedEntryLockRecord: { data: null, error: null @@ -41,7 +41,7 @@ describe("get locked entry lock record", () => { expect(lockResult).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { lockEntry: { data: { id: "aTestId" @@ -58,7 +58,7 @@ describe("get locked entry lock record", () => { }); expect(shouldNotBeLockedResponse).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { getLockedEntryLockRecord: { data: null, error: null @@ -74,7 +74,7 @@ describe("get locked entry lock record", () => { expect(shouldBeLockedResponse).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { getLockedEntryLockRecord: { data: { id: "aTestId" diff --git a/packages/api-locking-mechanism/__tests__/graphql/isEntryLocked.test.ts b/packages/api-record-locking/__tests__/graphql/isEntryLocked.test.ts similarity index 94% rename from packages/api-locking-mechanism/__tests__/graphql/isEntryLocked.test.ts rename to packages/api-record-locking/__tests__/graphql/isEntryLocked.test.ts index f0c78ddfdd8..02e2b32a088 100644 --- a/packages/api-locking-mechanism/__tests__/graphql/isEntryLocked.test.ts +++ b/packages/api-record-locking/__tests__/graphql/isEntryLocked.test.ts @@ -11,7 +11,7 @@ describe("is entry locked", () => { expect(response).toEqual({ data: { - lockingMechanism: { + recordLocking: { isEntryLocked: { data: false, error: null diff --git a/packages/api-locking-mechanism/__tests__/graphql/listLockRecords.test.ts b/packages/api-record-locking/__tests__/graphql/listLockRecords.test.ts similarity index 90% rename from packages/api-locking-mechanism/__tests__/graphql/listLockRecords.test.ts rename to packages/api-record-locking/__tests__/graphql/listLockRecords.test.ts index 52c85ea803a..688ef84f93c 100644 --- a/packages/api-locking-mechanism/__tests__/graphql/listLockRecords.test.ts +++ b/packages/api-record-locking/__tests__/graphql/listLockRecords.test.ts @@ -17,7 +17,7 @@ describe("list lock records", () => { expect(result).toEqual({ data: { - lockingMechanism: { + recordLocking: { listLockRecords: { data: [], error: null @@ -40,10 +40,10 @@ describe("list lock records", () => { const [result] = await anotherUserGraphQL.listLockRecordsQuery(); - expect(result.data.lockingMechanism.listLockRecords.data).toHaveLength(2); + expect(result.data.recordLocking.listLockRecords.data).toHaveLength(2); expect(result).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { listLockRecords: { data: [ { @@ -78,10 +78,10 @@ describe("list lock records", () => { } }); - expect(resultIdIn.data.lockingMechanism.listLockRecords.data).toHaveLength(2); + expect(resultIdIn.data.recordLocking.listLockRecords.data).toHaveLength(2); expect(resultIdIn).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { listLockRecords: { data: [ { diff --git a/packages/api-locking-mechanism/__tests__/graphql/lockEntry.test.ts b/packages/api-record-locking/__tests__/graphql/lockEntry.test.ts similarity index 96% rename from packages/api-locking-mechanism/__tests__/graphql/lockEntry.test.ts rename to packages/api-record-locking/__tests__/graphql/lockEntry.test.ts index 8ecb2835643..c21be288272 100644 --- a/packages/api-locking-mechanism/__tests__/graphql/lockEntry.test.ts +++ b/packages/api-record-locking/__tests__/graphql/lockEntry.test.ts @@ -12,7 +12,7 @@ describe("lock entry", () => { expect(isEntryLockedResponse).toEqual({ data: { - lockingMechanism: { + recordLocking: { isEntryLocked: { data: false, error: null @@ -28,7 +28,7 @@ describe("lock entry", () => { expect(response).toEqual({ data: { - lockingMechanism: { + recordLocking: { lockEntry: { data: { id: "someId", @@ -56,7 +56,7 @@ describe("lock entry", () => { }); expect(getResponse).toEqual({ data: { - lockingMechanism: { + recordLocking: { getLockRecord: { data: { id: "someId", @@ -94,7 +94,7 @@ describe("lock entry", () => { expect(firstLockResponse).toEqual({ data: { - lockingMechanism: { + recordLocking: { lockEntry: { data: { id: "someId", @@ -122,7 +122,7 @@ describe("lock entry", () => { }); expect(response).toEqual({ data: { - lockingMechanism: { + recordLocking: { lockEntry: { data: null, error: { diff --git a/packages/api-locking-mechanism/__tests__/graphql/requestEntryUnlock.test.ts b/packages/api-record-locking/__tests__/graphql/requestEntryUnlock.test.ts similarity index 93% rename from packages/api-locking-mechanism/__tests__/graphql/requestEntryUnlock.test.ts rename to packages/api-record-locking/__tests__/graphql/requestEntryUnlock.test.ts index 332b96df282..eda2e1def03 100644 --- a/packages/api-locking-mechanism/__tests__/graphql/requestEntryUnlock.test.ts +++ b/packages/api-record-locking/__tests__/graphql/requestEntryUnlock.test.ts @@ -1,4 +1,4 @@ -import { ILockingMechanismLockRecordActionType } from "~/types"; +import { IRecordLockingLockRecordActionType } from "~/types"; import { useGraphQLHandler } from "~tests/helpers/useGraphQLHandler"; import { createIdentity } from "~tests/helpers/identity"; @@ -22,7 +22,7 @@ describe("request entry unlock", () => { }); expect(lockEntryResponse).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { lockEntry: { data: { id: "someId", @@ -51,7 +51,7 @@ describe("request entry unlock", () => { expect(unlockEntryRequestResponse).toEqual({ data: { - lockingMechanism: { + recordLocking: { unlockEntryRequest: { data: { id: "someId", @@ -67,7 +67,7 @@ describe("request entry unlock", () => { type: "cms#author", actions: [ { - type: ILockingMechanismLockRecordActionType.requested, + type: IRecordLockingLockRecordActionType.requested, message: null, createdBy: secondIdentity, createdOn: expect.toBeDateString() @@ -87,7 +87,7 @@ describe("request entry unlock", () => { expect(unlockEntryRequestErrorResponse).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { unlockEntryRequest: { data: null, error: { diff --git a/packages/api-locking-mechanism/__tests__/graphql/unlockEntry.test.ts b/packages/api-record-locking/__tests__/graphql/unlockEntry.test.ts similarity index 95% rename from packages/api-locking-mechanism/__tests__/graphql/unlockEntry.test.ts rename to packages/api-record-locking/__tests__/graphql/unlockEntry.test.ts index 7e1ed7863eb..34429c80ce1 100644 --- a/packages/api-locking-mechanism/__tests__/graphql/unlockEntry.test.ts +++ b/packages/api-record-locking/__tests__/graphql/unlockEntry.test.ts @@ -22,7 +22,7 @@ describe("unlock entry", () => { }); expect(unlockResponseNoEntry).toEqual({ data: { - lockingMechanism: { + recordLocking: { unlockEntry: { data: null, error: { @@ -44,7 +44,7 @@ describe("unlock entry", () => { }); expect(lockEntryResponse).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { lockEntry: { data: { id: "someId", @@ -71,7 +71,7 @@ describe("unlock entry", () => { }); expect(getResponse).toEqual({ data: { - lockingMechanism: { + recordLocking: { getLockRecord: { data: { id: "someId", @@ -95,7 +95,7 @@ describe("unlock entry", () => { }); expect(isEntryLockedResponse).toEqual({ data: { - lockingMechanism: { + recordLocking: { isEntryLocked: { data: true, error: null @@ -110,7 +110,7 @@ describe("unlock entry", () => { }); expect(unlockResponse).toEqual({ data: { - lockingMechanism: { + recordLocking: { unlockEntry: { data: { actions: [], @@ -134,7 +134,7 @@ describe("unlock entry", () => { }); expect(getResponseAfterUnlock).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { getLockRecord: { data: null, error: { diff --git a/packages/api-locking-mechanism/__tests__/graphql/updateEntryLock.test.ts b/packages/api-record-locking/__tests__/graphql/updateEntryLock.test.ts similarity index 88% rename from packages/api-locking-mechanism/__tests__/graphql/updateEntryLock.test.ts rename to packages/api-record-locking/__tests__/graphql/updateEntryLock.test.ts index c9d57338179..ceee4498fd9 100644 --- a/packages/api-locking-mechanism/__tests__/graphql/updateEntryLock.test.ts +++ b/packages/api-record-locking/__tests__/graphql/updateEntryLock.test.ts @@ -11,7 +11,7 @@ describe("update entry lock", () => { }); expect(lockResult).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { lockEntry: { data: { id: "aTestId", @@ -24,8 +24,8 @@ describe("update entry lock", () => { } }); - const initialLockedOn = lockResult.data.lockingMechanism.lockEntry.data!.lockedOn; - const initialUpdatedOn = lockResult.data.lockingMechanism.lockEntry.data!.updatedOn; + const initialLockedOn = lockResult.data.recordLocking.lockEntry.data!.lockedOn; + const initialUpdatedOn = lockResult.data.recordLocking.lockEntry.data!.updatedOn; expect(initialLockedOn).toEqual(initialUpdatedOn); @@ -35,7 +35,7 @@ describe("update entry lock", () => { }); expect(result).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { updateEntryLock: { data: { id: "aTestId", @@ -47,7 +47,7 @@ describe("update entry lock", () => { } } }); - const updatedOn = result.data.lockingMechanism.updateEntryLock.data!.updatedOn; + const updatedOn = result.data.recordLocking.updateEntryLock.data!.updatedOn; expect(new Date(updatedOn).getTime()).toBeGreaterThan(new Date(initialUpdatedOn).getTime()); }); @@ -71,7 +71,7 @@ describe("update entry lock", () => { }); expect(result).toEqual({ data: { - lockingMechanism: { + recordLocking: { updateEntryLock: { data: null, error: { @@ -93,7 +93,7 @@ describe("update entry lock", () => { expect(result).toMatchObject({ data: { - lockingMechanism: { + recordLocking: { updateEntryLock: { data: { id: "aTestId", diff --git a/packages/api-locking-mechanism/__tests__/helpers/graphql/lockingMechanism.ts b/packages/api-record-locking/__tests__/helpers/graphql/recordLocking.ts similarity index 85% rename from packages/api-locking-mechanism/__tests__/helpers/graphql/lockingMechanism.ts rename to packages/api-record-locking/__tests__/helpers/graphql/recordLocking.ts index 3508d8eaed7..62d8dd4cd8a 100644 --- a/packages/api-locking-mechanism/__tests__/helpers/graphql/lockingMechanism.ts +++ b/packages/api-record-locking/__tests__/helpers/graphql/recordLocking.ts @@ -1,4 +1,4 @@ -import { ILockingMechanismLockRecord } from "~/types"; +import { IRecordLockingLockRecord } from "~/types"; import { CmsError } from "@webiny/api-headless-cms/types"; export const LOCK_ERROR = /* GraphQL */ ` @@ -39,7 +39,7 @@ export interface IIsEntryLockedGraphQlVariables { } export interface IIsEntryLockedGraphQlResponse { - lockingMechanism: { + recordLocking: { isEntryLocked: { data?: boolean; error?: CmsError; @@ -49,7 +49,7 @@ export interface IIsEntryLockedGraphQlResponse { export const IS_ENTRY_LOCKED_QUERY = /* GraphQL */ ` query IsEntryLocked($id: ID!, $type: String!) { - lockingMechanism { + recordLocking { isEntryLocked(id: $id, type: $type) { data ${LOCK_ERROR} @@ -66,9 +66,9 @@ export interface IListLockRecordsGraphQlVariables { } export interface IListLockRecordsGraphQlResponse { - lockingMechanism: { + recordLocking: { listLockRecords: { - data?: ILockingMechanismLockRecord[]; + data?: IRecordLockingLockRecord[]; error?: CmsError; }; }; @@ -76,7 +76,7 @@ export interface IListLockRecordsGraphQlResponse { export const LIST_LOCK_RECORDS_QUERY = /* GraphQL */ ` query ListLockRecords { - lockingMechanism { + recordLocking { listLockRecords { data { ${LOCK_RECORD} @@ -93,9 +93,9 @@ export interface IGetLockRecordGraphQlVariables { } export interface IGetLockRecordGraphQlResponse { - lockingMechanism: { + recordLocking: { getLockRecord: { - data?: ILockingMechanismLockRecord; + data?: IRecordLockingLockRecord; error?: CmsError; }; }; @@ -103,7 +103,7 @@ export interface IGetLockRecordGraphQlResponse { export const GET_LOCK_RECORD_QUERY = /* GraphQL */ ` query GetLockRecord($id: ID!, $type: String!) { - lockingMechanism { + recordLocking { getLockRecord(id: $id, type: $type) { data { ${LOCK_RECORD} @@ -120,9 +120,9 @@ export interface IGetLockedEntryLockRecordGraphQlVariables { } export interface IGetLockedEntryLockRecordGraphQlResponse { - lockingMechanism: { + recordLocking: { getLockRecord: { - data?: ILockingMechanismLockRecord; + data?: IRecordLockingLockRecord; error?: CmsError; }; }; @@ -130,7 +130,7 @@ export interface IGetLockedEntryLockRecordGraphQlResponse { export const GET_LOCKED_ENTRY_LOCK_RECORD_QUERY = /* GraphQL */ ` query GetLockedEntryLockRecord($id: ID!, $type: String!) { - lockingMechanism { + recordLocking { getLockedEntryLockRecord(id: $id, type: $type) { data { ${LOCK_RECORD} @@ -147,9 +147,9 @@ export interface ILockEntryGraphQlVariables { } export interface ILockEntryGraphQlResponse { - lockingMechanism: { + recordLocking: { lockEntry: { - data?: ILockingMechanismLockRecord; + data?: IRecordLockingLockRecord; error?: CmsError; }; }; @@ -157,7 +157,7 @@ export interface ILockEntryGraphQlResponse { export const LOCK_ENTRY_MUTATION = /* GraphQL */ ` mutation LockEntry($id: ID!, $type: String!) { - lockingMechanism { + recordLocking { lockEntry(id: $id, type: $type) { data { ${LOCK_RECORD} @@ -174,9 +174,9 @@ export interface IUpdateEntryLockGraphQlVariables { } export interface IUpdateEntryLockGraphQlResponse { - lockingMechanism: { + recordLocking: { updateEntryLock: { - data?: ILockingMechanismLockRecord; + data?: IRecordLockingLockRecord; error?: CmsError; }; }; @@ -189,7 +189,7 @@ export interface IUnlockEntryGraphQlVariables { export const UPDATE_ENTRY_LOCK_MUTATION = /* GraphQL */ ` mutation UpdateEntryLock($id: ID!, $type: String!) { - lockingMechanism { + recordLocking { updateEntryLock(id: $id, type: $type) { data { ${LOCK_RECORD} @@ -201,9 +201,9 @@ export const UPDATE_ENTRY_LOCK_MUTATION = /* GraphQL */ ` `; export interface IUnlockEntryGraphQlResponse { - lockingMechanism: { + recordLocking: { unlockEntry: { - data?: ILockingMechanismLockRecord; + data?: IRecordLockingLockRecord; error?: CmsError; }; }; @@ -211,7 +211,7 @@ export interface IUnlockEntryGraphQlResponse { export const UNLOCK_ENTRY_MUTATION = /* GraphQL */ ` mutation UnlockEntry($id: ID!, $type: String!) { - lockingMechanism { + recordLocking { unlockEntry(id: $id, type: $type) { data { ${LOCK_RECORD} @@ -228,9 +228,9 @@ export interface IUnlockEntryRequestGraphQlVariables { } export interface IUnlockEntryRequestGraphQlResponse { - lockingMechanism: { + recordLocking: { unlockEntryRequest: { - data?: ILockingMechanismLockRecord; + data?: IRecordLockingLockRecord; error?: CmsError; }; }; @@ -238,7 +238,7 @@ export interface IUnlockEntryRequestGraphQlResponse { export const UNLOCK_ENTRY_REQUEST_MUTATION = /* GraphQL */ ` mutation UnlockEntryRequest($id: ID!, $type: String!) { - lockingMechanism { + recordLocking { unlockEntryRequest(id: $id, type: $type) { data { ${LOCK_RECORD} diff --git a/packages/api-locking-mechanism/__tests__/helpers/identity.ts b/packages/api-record-locking/__tests__/helpers/identity.ts similarity index 100% rename from packages/api-locking-mechanism/__tests__/helpers/identity.ts rename to packages/api-record-locking/__tests__/helpers/identity.ts diff --git a/packages/api-locking-mechanism/__tests__/helpers/locales.ts b/packages/api-record-locking/__tests__/helpers/locales.ts similarity index 100% rename from packages/api-locking-mechanism/__tests__/helpers/locales.ts rename to packages/api-record-locking/__tests__/helpers/locales.ts diff --git a/packages/api-locking-mechanism/__tests__/helpers/permissions.ts b/packages/api-record-locking/__tests__/helpers/permissions.ts similarity index 100% rename from packages/api-locking-mechanism/__tests__/helpers/permissions.ts rename to packages/api-record-locking/__tests__/helpers/permissions.ts diff --git a/packages/api-locking-mechanism/__tests__/helpers/plugins.ts b/packages/api-record-locking/__tests__/helpers/plugins.ts similarity index 98% rename from packages/api-locking-mechanism/__tests__/helpers/plugins.ts rename to packages/api-record-locking/__tests__/helpers/plugins.ts index fea3312aa8d..15acfb61a3b 100644 --- a/packages/api-locking-mechanism/__tests__/helpers/plugins.ts +++ b/packages/api-record-locking/__tests__/helpers/plugins.ts @@ -14,7 +14,7 @@ import { getStorageOps } from "@webiny/project-utils/testing/environment"; import { Context } from "~/types"; import { createPermissions, PermissionsArg } from "./permissions"; import { createDummyLocales } from "./locales"; -import { createLockingMechanism } from "~/index"; +import { createRecordLocking } from "~/index"; export interface CreateHandlerCoreParams { setupTenancyAndSecurityGraphQL?: boolean; @@ -107,7 +107,7 @@ export const createHandlerCore = (params: CreateHandlerCoreParams) => { storageOperations: cmsStorage.storageOperations }), createHeadlessCmsGraphQL(), - createLockingMechanism(), + createRecordLocking(), plugins, graphQLHandlerPlugins(), bottomPlugins diff --git a/packages/api-locking-mechanism/__tests__/helpers/tenancySecurity.ts b/packages/api-record-locking/__tests__/helpers/tenancySecurity.ts similarity index 100% rename from packages/api-locking-mechanism/__tests__/helpers/tenancySecurity.ts rename to packages/api-record-locking/__tests__/helpers/tenancySecurity.ts diff --git a/packages/api-locking-mechanism/__tests__/helpers/useGraphQLHandler.ts b/packages/api-record-locking/__tests__/helpers/useGraphQLHandler.ts similarity index 98% rename from packages/api-locking-mechanism/__tests__/helpers/useGraphQLHandler.ts rename to packages/api-record-locking/__tests__/helpers/useGraphQLHandler.ts index dd40b398abe..30570706d45 100644 --- a/packages/api-locking-mechanism/__tests__/helpers/useGraphQLHandler.ts +++ b/packages/api-record-locking/__tests__/helpers/useGraphQLHandler.ts @@ -29,7 +29,7 @@ import { UNLOCK_ENTRY_MUTATION, UNLOCK_ENTRY_REQUEST_MUTATION, UPDATE_ENTRY_LOCK_MUTATION -} from "./graphql/lockingMechanism"; +} from "./graphql/recordLocking"; export type GraphQLHandlerParams = CreateHandlerCoreParams; @@ -92,9 +92,6 @@ export const useGraphQLHandler = (params: GraphQLHandlerParams = {}) => { async introspect() { return invoke({ body: { query: getIntrospectionQuery() } }); }, - /** - * Locking Mechanism - */ async listLockRecordsQuery(variables?: IListLockRecordsGraphQlVariables) { return invoke({ body: { query: LIST_LOCK_RECORDS_QUERY, variables } diff --git a/packages/api-locking-mechanism/__tests__/useCase/isEntryLockedUseCase.test.ts b/packages/api-record-locking/__tests__/useCase/isEntryLockedUseCase.test.ts similarity index 94% rename from packages/api-locking-mechanism/__tests__/useCase/isEntryLockedUseCase.test.ts rename to packages/api-record-locking/__tests__/useCase/isEntryLockedUseCase.test.ts index fc4c84b516f..6636dfbbb7e 100644 --- a/packages/api-locking-mechanism/__tests__/useCase/isEntryLockedUseCase.test.ts +++ b/packages/api-record-locking/__tests__/useCase/isEntryLockedUseCase.test.ts @@ -1,6 +1,6 @@ import { IsEntryLockedUseCase } from "~/useCases/IsEntryLocked/IsEntryLockedUseCase"; import { WebinyError } from "@webiny/error"; -import { ILockingMechanismLockRecord } from "~/types"; +import { IRecordLockingLockRecord } from "~/types"; import { NotFoundError } from "@webiny/handler-graphql"; import { isLockedFactory } from "~/utils/isLockedFactory"; import { createIdentity } from "~tests/helpers/identity"; @@ -35,7 +35,7 @@ describe("is entry locked use case", () => { return { lockedOn: new Date("2020-01-01"), lockedBy: createIdentity() - } as unknown as ILockingMechanismLockRecord; + } as unknown as IRecordLockingLockRecord; } }, isLocked, diff --git a/packages/api-locking-mechanism/__tests__/useCase/kickOutCurrentUser.test.ts b/packages/api-record-locking/__tests__/useCase/kickOutCurrentUser.test.ts similarity index 93% rename from packages/api-locking-mechanism/__tests__/useCase/kickOutCurrentUser.test.ts rename to packages/api-record-locking/__tests__/useCase/kickOutCurrentUser.test.ts index 0b0d5690eae..c955e5bad69 100644 --- a/packages/api-locking-mechanism/__tests__/useCase/kickOutCurrentUser.test.ts +++ b/packages/api-record-locking/__tests__/useCase/kickOutCurrentUser.test.ts @@ -1,7 +1,7 @@ import { KickOutCurrentUserUseCase } from "~/useCases/KickOutCurrentUser/KickOutCurrentUserUseCase"; import { IWebsocketsContextObject } from "@webiny/api-websockets"; import { createIdentity } from "~tests/helpers/identity"; -import { ILockingMechanismLockRecord } from "~/types"; +import { IRecordLockingLockRecord } from "~/types"; describe("kick out current user", () => { it("should send message via websockets to kick out current user", async () => { @@ -35,7 +35,7 @@ describe("kick out current user", () => { lockedBy: createIdentity() }; } - } as unknown as ILockingMechanismLockRecord; + } as unknown as IRecordLockingLockRecord; let error: Error | null = null; try { diff --git a/packages/api-locking-mechanism/__tests__/useCase/lockEntryUseCase.test.ts b/packages/api-record-locking/__tests__/useCase/lockEntryUseCase.test.ts similarity index 91% rename from packages/api-locking-mechanism/__tests__/useCase/lockEntryUseCase.test.ts rename to packages/api-record-locking/__tests__/useCase/lockEntryUseCase.test.ts index e72eabce271..74754dafb5c 100644 --- a/packages/api-locking-mechanism/__tests__/useCase/lockEntryUseCase.test.ts +++ b/packages/api-record-locking/__tests__/useCase/lockEntryUseCase.test.ts @@ -1,7 +1,7 @@ import { LockEntryUseCase } from "~/useCases/LockEntryUseCase/LockEntryUseCase"; import { WebinyError } from "@webiny/error"; import { IIsEntryLockedUseCase } from "~/abstractions/IIsEntryLocked"; -import { ILockingMechanismModelManager } from "~/types"; +import { IRecordLockingModelManager } from "~/types"; import { NotFoundError } from "@webiny/handler-graphql"; describe("lock entry use case", () => { @@ -14,7 +14,7 @@ describe("lock entry use case", () => { } } as unknown as IIsEntryLockedUseCase, getManager: async () => { - return {} as unknown as ILockingMechanismModelManager; + return {} as unknown as IRecordLockingModelManager; } }); try { @@ -44,7 +44,7 @@ describe("lock entry use case", () => { {} ); } - } as unknown as ILockingMechanismModelManager; + } as unknown as IRecordLockingModelManager; } }); diff --git a/packages/api-locking-mechanism/__tests__/useCase/unlockEntryRequestUseCase.test.ts b/packages/api-record-locking/__tests__/useCase/unlockEntryRequestUseCase.test.ts similarity index 92% rename from packages/api-locking-mechanism/__tests__/useCase/unlockEntryRequestUseCase.test.ts rename to packages/api-record-locking/__tests__/useCase/unlockEntryRequestUseCase.test.ts index 94c4a59e6a1..0a78c0b458e 100644 --- a/packages/api-locking-mechanism/__tests__/useCase/unlockEntryRequestUseCase.test.ts +++ b/packages/api-record-locking/__tests__/useCase/unlockEntryRequestUseCase.test.ts @@ -1,7 +1,7 @@ import { UnlockEntryRequestUseCase } from "~/useCases/UnlockRequestUseCase/UnlockEntryRequestUseCase"; import { IGetLockRecordUseCase } from "~/abstractions/IGetLockRecordUseCase"; import { getSecurityIdentity } from "~tests/helpers/identity"; -import { ILockingMechanismModelManager } from "~/types"; +import { IRecordLockingModelManager } from "~/types"; import { WebinyError } from "@webiny/error"; describe("unlock entry request use case", () => { @@ -15,7 +15,7 @@ describe("unlock entry request use case", () => { } as unknown as IGetLockRecordUseCase, getIdentity: getSecurityIdentity, getManager: async () => { - return {} as unknown as ILockingMechanismModelManager; + return {} as unknown as IRecordLockingModelManager; } }); @@ -49,7 +49,7 @@ describe("unlock entry request use case", () => { } as unknown as IGetLockRecordUseCase, getIdentity: getSecurityIdentity, getManager: async () => { - return {} as unknown as ILockingMechanismModelManager; + return {} as unknown as IRecordLockingModelManager; } }); @@ -91,7 +91,7 @@ describe("unlock entry request use case", () => { } as unknown as IGetLockRecordUseCase, getIdentity: getSecurityIdentity, getManager: async () => { - return {} as unknown as ILockingMechanismModelManager; + return {} as unknown as IRecordLockingModelManager; } }); diff --git a/packages/api-locking-mechanism/__tests__/useCase/unlockEntryUseCase.test.ts b/packages/api-record-locking/__tests__/useCase/unlockEntryUseCase.test.ts similarity index 100% rename from packages/api-locking-mechanism/__tests__/useCase/unlockEntryUseCase.test.ts rename to packages/api-record-locking/__tests__/useCase/unlockEntryUseCase.test.ts diff --git a/packages/api-locking-mechanism/__tests__/utils/convertWhereCondition.test.ts b/packages/api-record-locking/__tests__/utils/convertWhereCondition.test.ts similarity index 97% rename from packages/api-locking-mechanism/__tests__/utils/convertWhereCondition.test.ts rename to packages/api-record-locking/__tests__/utils/convertWhereCondition.test.ts index 86888d12ed7..5d0888d1155 100644 --- a/packages/api-locking-mechanism/__tests__/utils/convertWhereCondition.test.ts +++ b/packages/api-record-locking/__tests__/utils/convertWhereCondition.test.ts @@ -1,7 +1,7 @@ import { convertWhereCondition } from "~/utils/convertWhereCondition"; import { createLockRecordDatabaseId } from "~/utils/lockRecordDatabaseId"; -describe("it should properly convert where condition from locking mechanism to standard cms where condition", () => { +describe("it should properly convert where condition from record locking to standard cms where condition", () => { it("should return undefined if no where condition is provided", () => { const result = convertWhereCondition(undefined); diff --git a/packages/api-locking-mechanism/__tests__/utils/validateSameIdentity.test.ts b/packages/api-record-locking/__tests__/utils/validateSameIdentity.test.ts similarity index 100% rename from packages/api-locking-mechanism/__tests__/utils/validateSameIdentity.test.ts rename to packages/api-record-locking/__tests__/utils/validateSameIdentity.test.ts diff --git a/packages/api-locking-mechanism/jest.setup.js b/packages/api-record-locking/jest.setup.js similarity index 100% rename from packages/api-locking-mechanism/jest.setup.js rename to packages/api-record-locking/jest.setup.js diff --git a/packages/api-locking-mechanism/package.json b/packages/api-record-locking/package.json similarity index 93% rename from packages/api-locking-mechanism/package.json rename to packages/api-record-locking/package.json index 4b5a83b87f7..3123619fee5 100644 --- a/packages/api-locking-mechanism/package.json +++ b/packages/api-record-locking/package.json @@ -1,12 +1,12 @@ { - "name": "@webiny/api-locking-mechanism", + "name": "@webiny/api-record-locking", "version": "0.0.0", "main": "index.js", "repository": { "type": "git", "url": "https://github.com/webiny/webiny-js.git" }, - "description": "Locking Mechanism built on top of the Headless CMS.", + "description": "Record Locking built on top of the Headless CMS.", "contributors": [ "Bruno Zorić " ], diff --git a/packages/api-record-locking/src/abstractions/IGetLockRecordUseCase.ts b/packages/api-record-locking/src/abstractions/IGetLockRecordUseCase.ts new file mode 100644 index 00000000000..681c9505bda --- /dev/null +++ b/packages/api-record-locking/src/abstractions/IGetLockRecordUseCase.ts @@ -0,0 +1,11 @@ +import { IRecordLockingGetLockRecordParams, IRecordLockingLockRecord } from "~/types"; + +export type IGetLockRecordUseCaseExecuteParams = IRecordLockingGetLockRecordParams; + +export interface IGetLockRecordUseCaseExecute { + (params: IGetLockRecordUseCaseExecuteParams): Promise; +} + +export interface IGetLockRecordUseCase { + execute: IGetLockRecordUseCaseExecute; +} diff --git a/packages/api-locking-mechanism/src/abstractions/IGetLockedEntryLockRecordUseCase.ts b/packages/api-record-locking/src/abstractions/IGetLockedEntryLockRecordUseCase.ts similarity index 50% rename from packages/api-locking-mechanism/src/abstractions/IGetLockedEntryLockRecordUseCase.ts rename to packages/api-record-locking/src/abstractions/IGetLockedEntryLockRecordUseCase.ts index 7b15d8e4540..d3b75ba7bac 100644 --- a/packages/api-locking-mechanism/src/abstractions/IGetLockedEntryLockRecordUseCase.ts +++ b/packages/api-record-locking/src/abstractions/IGetLockedEntryLockRecordUseCase.ts @@ -1,11 +1,11 @@ -import { ILockingMechanismIsLockedParams, ILockingMechanismLockRecord } from "~/types"; +import { IRecordLockingIsLockedParams, IRecordLockingLockRecord } from "~/types"; -export type IGetLockedEntryLockRecordUseCaseExecuteParams = ILockingMechanismIsLockedParams; +export type IGetLockedEntryLockRecordUseCaseExecuteParams = IRecordLockingIsLockedParams; export interface IGetLockedEntryLockRecordUseCaseExecute { ( params: IGetLockedEntryLockRecordUseCaseExecuteParams - ): Promise; + ): Promise; } export interface IGetLockedEntryLockRecordUseCase { diff --git a/packages/api-locking-mechanism/src/abstractions/IIsEntryLocked.ts b/packages/api-record-locking/src/abstractions/IIsEntryLocked.ts similarity index 59% rename from packages/api-locking-mechanism/src/abstractions/IIsEntryLocked.ts rename to packages/api-record-locking/src/abstractions/IIsEntryLocked.ts index 70626981c3b..0dc1c73198b 100644 --- a/packages/api-locking-mechanism/src/abstractions/IIsEntryLocked.ts +++ b/packages/api-record-locking/src/abstractions/IIsEntryLocked.ts @@ -1,6 +1,6 @@ -import { ILockingMechanismIsLockedParams } from "~/types"; +import { IRecordLockingIsLockedParams } from "~/types"; -export type IIsEntryLockedUseCaseExecuteParams = ILockingMechanismIsLockedParams; +export type IIsEntryLockedUseCaseExecuteParams = IRecordLockingIsLockedParams; export interface IIsEntryLockedUseCaseExecute { (params: IIsEntryLockedUseCaseExecuteParams): Promise; diff --git a/packages/api-record-locking/src/abstractions/IKickOutCurrentUserUseCase.ts b/packages/api-record-locking/src/abstractions/IKickOutCurrentUserUseCase.ts new file mode 100644 index 00000000000..2d52e0c352f --- /dev/null +++ b/packages/api-record-locking/src/abstractions/IKickOutCurrentUserUseCase.ts @@ -0,0 +1,7 @@ +import { IRecordLockingLockRecord } from "~/types"; + +export type IKickOutCurrentUserUseCaseExecuteParams = IRecordLockingLockRecord; + +export interface IKickOutCurrentUserUseCase { + execute(params: IKickOutCurrentUserUseCaseExecuteParams): Promise; +} diff --git a/packages/api-locking-mechanism/src/abstractions/IListAllLockRecordsUseCase.ts b/packages/api-record-locking/src/abstractions/IListAllLockRecordsUseCase.ts similarity index 50% rename from packages/api-locking-mechanism/src/abstractions/IListAllLockRecordsUseCase.ts rename to packages/api-record-locking/src/abstractions/IListAllLockRecordsUseCase.ts index 703f73c1745..8484534cc6a 100644 --- a/packages/api-locking-mechanism/src/abstractions/IListAllLockRecordsUseCase.ts +++ b/packages/api-record-locking/src/abstractions/IListAllLockRecordsUseCase.ts @@ -1,11 +1,11 @@ import { - ILockingMechanismListAllLockRecordsParams, - ILockingMechanismListAllLockRecordsResponse + IRecordLockingListAllLockRecordsParams, + IRecordLockingListAllLockRecordsResponse } from "~/types"; -export type IListAllLockRecordsUseCaseExecuteParams = ILockingMechanismListAllLockRecordsParams; +export type IListAllLockRecordsUseCaseExecuteParams = IRecordLockingListAllLockRecordsParams; -export type IListAllLockRecordsUseCaseExecuteResponse = ILockingMechanismListAllLockRecordsResponse; +export type IListAllLockRecordsUseCaseExecuteResponse = IRecordLockingListAllLockRecordsResponse; export interface IListAllLockRecordsUseCaseExecute { ( diff --git a/packages/api-locking-mechanism/src/abstractions/IListLockRecordsUseCase.ts b/packages/api-record-locking/src/abstractions/IListLockRecordsUseCase.ts similarity index 71% rename from packages/api-locking-mechanism/src/abstractions/IListLockRecordsUseCase.ts rename to packages/api-record-locking/src/abstractions/IListLockRecordsUseCase.ts index a0a23bb3047..f36ceeb6ca2 100644 --- a/packages/api-locking-mechanism/src/abstractions/IListLockRecordsUseCase.ts +++ b/packages/api-record-locking/src/abstractions/IListLockRecordsUseCase.ts @@ -1,9 +1,9 @@ import { IListAllLockRecordsUseCaseExecuteParams } from "./IListAllLockRecordsUseCase"; -import { ILockingMechanismListAllLockRecordsResponse } from "~/types"; +import { IRecordLockingListAllLockRecordsResponse } from "~/types"; export type IListLockRecordsUseCaseExecuteParams = IListAllLockRecordsUseCaseExecuteParams; -export type IListLockRecordsUseCaseExecuteResponse = ILockingMechanismListAllLockRecordsResponse; +export type IListLockRecordsUseCaseExecuteResponse = IRecordLockingListAllLockRecordsResponse; export interface IListLockRecordsUseCaseExecute { (params: IListLockRecordsUseCaseExecuteParams): Promise; diff --git a/packages/api-record-locking/src/abstractions/ILockEntryUseCase.ts b/packages/api-record-locking/src/abstractions/ILockEntryUseCase.ts new file mode 100644 index 00000000000..20fb23d4c9e --- /dev/null +++ b/packages/api-record-locking/src/abstractions/ILockEntryUseCase.ts @@ -0,0 +1,14 @@ +import { IRecordLockingLockRecord, IRecordLockingLockRecordEntryType } from "~/types"; + +export interface ILockEntryUseCaseExecuteParams { + id: string; + type: IRecordLockingLockRecordEntryType; +} + +export interface ILockEntryUseCaseExecute { + (params: ILockEntryUseCaseExecuteParams): Promise; +} + +export interface ILockEntryUseCase { + execute: ILockEntryUseCaseExecute; +} diff --git a/packages/api-locking-mechanism/src/abstractions/IUnlockEntryRequestUseCase.ts b/packages/api-record-locking/src/abstractions/IUnlockEntryRequestUseCase.ts similarity index 63% rename from packages/api-locking-mechanism/src/abstractions/IUnlockEntryRequestUseCase.ts rename to packages/api-record-locking/src/abstractions/IUnlockEntryRequestUseCase.ts index 983fc0b67d9..1f057e7363a 100644 --- a/packages/api-locking-mechanism/src/abstractions/IUnlockEntryRequestUseCase.ts +++ b/packages/api-record-locking/src/abstractions/IUnlockEntryRequestUseCase.ts @@ -1,12 +1,12 @@ -import { ILockingMechanismLockRecord, ILockingMechanismLockRecordEntryType } from "~/types"; +import { IRecordLockingLockRecord, IRecordLockingLockRecordEntryType } from "~/types"; export interface IUnlockEntryRequestUseCaseExecuteParams { id: string; - type: ILockingMechanismLockRecordEntryType; + type: IRecordLockingLockRecordEntryType; } export interface IUnlockEntryRequestUseCaseExecute { - (params: IUnlockEntryRequestUseCaseExecuteParams): Promise; + (params: IUnlockEntryRequestUseCaseExecuteParams): Promise; } export interface IUnlockEntryRequestUseCase { diff --git a/packages/api-record-locking/src/abstractions/IUnlockEntryUseCase.ts b/packages/api-record-locking/src/abstractions/IUnlockEntryUseCase.ts new file mode 100644 index 00000000000..599a711d92e --- /dev/null +++ b/packages/api-record-locking/src/abstractions/IUnlockEntryUseCase.ts @@ -0,0 +1,15 @@ +import { IRecordLockingLockRecord, IRecordLockingLockRecordEntryType } from "~/types"; + +export interface IUnlockEntryUseCaseExecuteParams { + id: string; + type: IRecordLockingLockRecordEntryType; + force?: boolean; +} + +export interface IUnlockEntryUseCaseExecute { + (params: IUnlockEntryUseCaseExecuteParams): Promise; +} + +export interface IUnlockEntryUseCase { + execute: IUnlockEntryUseCaseExecute; +} diff --git a/packages/api-record-locking/src/abstractions/IUpdateEntryLockUseCase.ts b/packages/api-record-locking/src/abstractions/IUpdateEntryLockUseCase.ts new file mode 100644 index 00000000000..778dab0df10 --- /dev/null +++ b/packages/api-record-locking/src/abstractions/IUpdateEntryLockUseCase.ts @@ -0,0 +1,14 @@ +import { IRecordLockingLockRecord, IRecordLockingLockRecordEntryType } from "~/types"; + +export interface IUpdateEntryLockUseCaseExecuteParams { + id: string; + type: IRecordLockingLockRecordEntryType; +} + +export interface IUpdateEntryLockUseCaseExecute { + (params: IUpdateEntryLockUseCaseExecuteParams): Promise; +} + +export interface IUpdateEntryLockUseCase { + execute: IUpdateEntryLockUseCaseExecute; +} diff --git a/packages/api-locking-mechanism/src/crud/crud.ts b/packages/api-record-locking/src/crud/crud.ts similarity index 80% rename from packages/api-locking-mechanism/src/crud/crud.ts rename to packages/api-record-locking/src/crud/crud.ts index 4e38f6c550d..e70598f4d36 100644 --- a/packages/api-locking-mechanism/src/crud/crud.ts +++ b/packages/api-record-locking/src/crud/crud.ts @@ -4,9 +4,9 @@ import { IGetIdentity, IGetWebsocketsContextCallable, IHasFullAccessCallable, - ILockingMechanism, - ILockingMechanismLockRecordValues, - ILockingMechanismModelManager, + IRecordLocking, + IRecordLockingLockRecordValues, + IRecordLockingModelManager, OnEntryAfterLockTopicParams, OnEntryAfterUnlockRequestTopicParams, OnEntryAfterUnlockTopicParams, @@ -34,21 +34,19 @@ interface Params { context: Pick; } -export const createLockingMechanismCrud = async ({ - context -}: Params): Promise => { +export const createRecordLockingCrud = async ({ context }: Params): Promise => { const getModel = async () => { const model = await context.cms.getModel(RECORD_LOCKING_MODEL_ID); if (model) { return model; } - throw new WebinyError("Locking Mechanism model not found", "MODEL_NOT_FOUND", { + throw new WebinyError("Record Locking model not found.", "MODEL_NOT_FOUND", { modelId: RECORD_LOCKING_MODEL_ID }); }; - const getManager = async (): Promise => { - return await context.cms.getEntryManager( + const getManager = async (): Promise => { + return await context.cms.getEntryManager( RECORD_LOCKING_MODEL_ID ); }; @@ -70,33 +68,33 @@ export const createLockingMechanismCrud = async ({ }; const onEntryBeforeLock = createTopic( - "cms.lockingMechanism.onEntryBeforeLock" + "cms.recordLocking.onEntryBeforeLock" ); const onEntryAfterLock = createTopic( - "cms.lockingMechanism.onEntryAfterLock" + "cms.recordLocking.onEntryAfterLock" ); const onEntryLockError = createTopic( - "cms.lockingMechanism.onEntryLockError" + "cms.recordLocking.onEntryLockError" ); const onEntryBeforeUnlock = createTopic( - "cms.lockingMechanism.onEntryBeforeUnlock" + "cms.recordLocking.onEntryBeforeUnlock" ); const onEntryAfterUnlock = createTopic( - "cms.lockingMechanism.onEntryAfterUnlock" + "cms.recordLocking.onEntryAfterUnlock" ); const onEntryUnlockError = createTopic( - "cms.lockingMechanism.onEntryUnlockError" + "cms.recordLocking.onEntryUnlockError" ); const onEntryBeforeUnlockRequest = createTopic( - "cms.lockingMechanism.onEntryBeforeUnlockRequest" + "cms.recordLocking.onEntryBeforeUnlockRequest" ); const onEntryAfterUnlockRequest = createTopic( - "cms.lockingMechanism.onEntryAfterUnlockRequest" + "cms.recordLocking.onEntryAfterUnlockRequest" ); const onEntryUnlockRequestError = createTopic( - "cms.lockingMechanism.onEntryUnlockRequestError" + "cms.recordLocking.onEntryUnlockRequestError" ); const getWebsockets: IGetWebsocketsContextCallable = () => { @@ -121,37 +119,37 @@ export const createLockingMechanismCrud = async ({ }); const listAllLockRecords: IListAllLockRecordsUseCaseExecute = async params => { - return context.benchmark.measure("lockingMechanism.listAllLockRecords", async () => { + return context.benchmark.measure("recordLocking.listAllLockRecords", async () => { return listAllLockRecordsUseCase.execute(params); }); }; const listLockRecords: IListLockRecordsUseCaseExecute = async params => { - return context.benchmark.measure("lockingMechanism.listLockRecords", async () => { + return context.benchmark.measure("recordLocking.listLockRecords", async () => { return listLockRecordsUseCase.execute(params); }); }; const getLockRecord: IGetLockRecordUseCaseExecute = async params => { - return context.benchmark.measure("lockingMechanism.getLockRecord", async () => { + return context.benchmark.measure("recordLocking.getLockRecord", async () => { return getLockRecordUseCase.execute(params); }); }; const isEntryLocked: IIsEntryLockedUseCaseExecute = async params => { - return context.benchmark.measure("lockingMechanism.isEntryLocked", async () => { + return context.benchmark.measure("recordLocking.isEntryLocked", async () => { return isEntryLockedUseCase.execute(params); }); }; const getLockedEntryLockRecord: IGetLockedEntryLockRecordUseCaseExecute = async params => { - return context.benchmark.measure("lockingMechanism.getLockedEntryLockRecord", async () => { + return context.benchmark.measure("recordLocking.getLockedEntryLockRecord", async () => { return getLockedEntryLockRecordUseCase.execute(params); }); }; const lockEntry: ILockEntryUseCaseExecute = async params => { - return context.benchmark.measure("lockingMechanism.lockEntry", async () => { + return context.benchmark.measure("recordLocking.lockEntry", async () => { try { await onEntryBeforeLock.publish(params); const record = await lockEntryUseCase.execute(params); @@ -171,13 +169,13 @@ export const createLockingMechanismCrud = async ({ }; const updateEntryLock: IUpdateEntryLockUseCaseExecute = async params => { - return context.benchmark.measure("lockingMechanism.updateEntryLock", async () => { + return context.benchmark.measure("recordLocking.updateEntryLock", async () => { return updateEntryLockUseCase.execute(params); }); }; const unlockEntry: IUnlockEntryUseCaseExecute = async params => { - return context.benchmark.measure("lockingMechanism.unlockEntry", async () => { + return context.benchmark.measure("recordLocking.unlockEntry", async () => { try { await onEntryBeforeUnlock.publish({ ...params, @@ -200,7 +198,7 @@ export const createLockingMechanismCrud = async ({ }; const unlockEntryRequest: IUnlockEntryRequestUseCaseExecute = async params => { - return context.benchmark.measure("lockingMechanism.unlockEntryRequest", async () => { + return context.benchmark.measure("recordLocking.unlockEntryRequest", async () => { try { await onEntryBeforeUnlockRequest.publish(params); const record = await unlockEntryRequestUseCase.execute(params); diff --git a/packages/api-locking-mechanism/src/crud/model.ts b/packages/api-record-locking/src/crud/model.ts similarity index 100% rename from packages/api-locking-mechanism/src/crud/model.ts rename to packages/api-record-locking/src/crud/model.ts diff --git a/packages/api-locking-mechanism/src/graphql/schema.ts b/packages/api-record-locking/src/graphql/schema.ts similarity index 61% rename from packages/api-locking-mechanism/src/graphql/schema.ts rename to packages/api-record-locking/src/graphql/schema.ts index 789dab4a50f..b02720390a9 100644 --- a/packages/api-locking-mechanism/src/graphql/schema.ts +++ b/packages/api-record-locking/src/graphql/schema.ts @@ -12,14 +12,14 @@ import { renderSortEnum } from "@webiny/api-headless-cms/utils/renderSortEnum"; import { checkPermissions } from "~/utils/checkPermissions"; interface Params { - context: Pick; + context: Pick; } export const createGraphQLSchema = async ( params: Params ): Promise> => { const context = params.context; - const model = await context.lockingMechanism.getModel(); + const model = await context.recordLocking.getModel(); const models = await context.security.withoutAuthorization(async () => { return (await context.cms.listModels()).filter(model => { @@ -34,7 +34,7 @@ export const createGraphQLSchema = async ( const fieldTypePlugins = createFieldTypePluginRecords(context.plugins); - const lockingMechanismFields = renderFields({ + const recordLockingFields = renderFields({ models, model, fields: model.fields, @@ -59,150 +59,150 @@ export const createGraphQLSchema = async ( const plugin = createGraphQLSchemaPlugin({ typeDefs: /* GraphQL */ ` - ${lockingMechanismFields.map(f => f.typeDefs).join("\n")} + ${recordLockingFields.map(f => f.typeDefs).join("\n")} - type LockingMechanismError { + type RecordLockingError { message: String code: String data: JSON stack: String } - enum LockingMechanismRecordActionType { + enum RecordLockingRecordActionType { requested approved denied } - type LockingMechanismIdentity { + type RecordLockingIdentity { id: String! displayName: String type: String } - type LockingMechanismRecordAction { + type RecordLockingRecordAction { id: ID! - type: LockingMechanismRecordActionType! + type: RecordLockingRecordActionType! message: String - createdBy: LockingMechanismIdentity! + createdBy: RecordLockingIdentity! createdOn: DateTime! } - type LockingMechanismRecord { + type RecordLockingRecord { id: ID! - lockedBy: LockingMechanismIdentity! + lockedBy: RecordLockingIdentity! lockedOn: DateTime! updatedOn: DateTime! expiresOn: DateTime! - ${lockingMechanismFields.map(f => f.fields).join("\n")} + ${recordLockingFields.map(f => f.fields).join("\n")} } - type LockingMechanismIsEntryLockedResponse { + type RecordLockingIsEntryLockedResponse { data: Boolean - error: LockingMechanismError + error: RecordLockingError } - type LockingMechanismGetLockRecordResponse { - data: LockingMechanismRecord - error: LockingMechanismError + type RecordLockingGetLockRecordResponse { + data: RecordLockingRecord + error: RecordLockingError } - type LockingMechanismGetLockedEntryLockRecordResponse { - data: LockingMechanismRecord - error: LockingMechanismError + type RecordLockingGetLockedEntryLockRecordResponse { + data: RecordLockingRecord + error: RecordLockingError } - type LockingMechanismListLockRecordsResponse { - data: [LockingMechanismRecord!] - error: LockingMechanismError + type RecordLockingListLockRecordsResponse { + data: [RecordLockingRecord!] + error: RecordLockingError } - type LockingMechanismLockEntryResponse { - data: LockingMechanismRecord - error: LockingMechanismError + type RecordLockingLockEntryResponse { + data: RecordLockingRecord + error: RecordLockingError } - type LockingMechanismUpdateLockResponse { - data: LockingMechanismRecord - error: LockingMechanismError + type RecordLockingUpdateLockResponse { + data: RecordLockingRecord + error: RecordLockingError } - type LockingMechanismUnlockEntryResponse { - data: LockingMechanismRecord - error: LockingMechanismError + type RecordLockingUnlockEntryResponse { + data: RecordLockingRecord + error: RecordLockingError } - type LockingMechanismUnlockEntryRequestResponse { - data: LockingMechanismRecord - error: LockingMechanismError + type RecordLockingUnlockEntryRequestResponse { + data: RecordLockingRecord + error: RecordLockingError } - input LockingMechanismListWhereInput { + input RecordLockingListWhereInput { ${listFilterFieldsRender} } - enum LockingMechanismListSorter { + enum RecordLockingListSorter { ${sortEnumRender} } - type LockingMechanismQuery { + type RecordLockingQuery { _empty: String } - type LockingMechanismMutation { + type RecordLockingMutation { _empty: String } - extend type LockingMechanismQuery { - isEntryLocked(id: ID!, type: String!): LockingMechanismIsEntryLockedResponse! - getLockRecord(id: ID!, type: String!): LockingMechanismGetLockRecordResponse! + extend type RecordLockingQuery { + isEntryLocked(id: ID!, type: String!): RecordLockingIsEntryLockedResponse! + getLockRecord(id: ID!, type: String!): RecordLockingGetLockRecordResponse! # Returns lock record or null - if entry is locked in context of the current user, does not throw an error like getLockRecord if no record in the DB - getLockedEntryLockRecord(id: ID!, type: String!): LockingMechanismGetLockedEntryLockRecordResponse! + getLockedEntryLockRecord(id: ID!, type: String!): RecordLockingGetLockedEntryLockRecordResponse! listAllLockRecords( - where: LockingMechanismListWhereInput - sort: [LockingMechanismListSorter!] + where: RecordLockingListWhereInput + sort: [RecordLockingListSorter!] limit: Int after: String - ): LockingMechanismListLockRecordsResponse! + ): RecordLockingListLockRecordsResponse! # Basically same as listAllLockRecords except this one will filter out records with expired lock. listLockRecords( - where: LockingMechanismListWhereInput - sort: [LockingMechanismListSorter!] + where: RecordLockingListWhereInput + sort: [RecordLockingListSorter!] limit: Int after: String - ): LockingMechanismListLockRecordsResponse! + ): RecordLockingListLockRecordsResponse! } - extend type LockingMechanismMutation { - lockEntry(id: ID!, type: String!): LockingMechanismLockEntryResponse! - updateEntryLock(id: ID!, type: String!): LockingMechanismUpdateLockResponse! - unlockEntry(id: ID!, type: String!, force: Boolean): LockingMechanismUnlockEntryResponse! + extend type RecordLockingMutation { + lockEntry(id: ID!, type: String!): RecordLockingLockEntryResponse! + updateEntryLock(id: ID!, type: String!): RecordLockingUpdateLockResponse! + unlockEntry(id: ID!, type: String!, force: Boolean): RecordLockingUnlockEntryResponse! unlockEntryRequest( id: ID! type: String! - ): LockingMechanismUnlockEntryRequestResponse! + ): RecordLockingUnlockEntryRequestResponse! } extend type Query { - lockingMechanism: LockingMechanismQuery + recordLocking: RecordLockingQuery } extend type Mutation { - lockingMechanism: LockingMechanismMutation + recordLocking: RecordLockingMutation } `, resolvers: { Query: { - lockingMechanism: async () => ({}) + recordLocking: async () => ({}) }, Mutation: { - lockingMechanism: async () => ({}) + recordLocking: async () => ({}) }, - LockingMechanismQuery: { + RecordLockingQuery: { async isEntryLocked(_, args, context) { return resolve(async () => { await checkPermissions(context); - return context.lockingMechanism.isEntryLocked({ + return context.recordLocking.isEntryLocked({ id: args.id, type: args.type }); @@ -211,7 +211,7 @@ export const createGraphQLSchema = async ( async getLockRecord(_, args, context) { return resolve(async () => { await checkPermissions(context); - const result = await context.lockingMechanism.getLockRecord({ + const result = await context.recordLocking.getLockRecord({ id: args.id, type: args.type }); @@ -224,7 +224,7 @@ export const createGraphQLSchema = async ( async getLockedEntryLockRecord(_, args, context) { return resolve(async () => { await checkPermissions(context); - return await context.lockingMechanism.getLockedEntryLockRecord({ + return await context.recordLocking.getLockedEntryLockRecord({ id: args.id, type: args.type }); @@ -234,21 +234,21 @@ export const createGraphQLSchema = async ( async listLockRecords(_, args, context) { return resolveList(async () => { await checkPermissions(context); - return await context.lockingMechanism.listLockRecords(args); + return await context.recordLocking.listLockRecords(args); }); }, listAllLockRecords(_, args, context) { return resolveList(async () => { await checkPermissions(context); - return await context.lockingMechanism.listAllLockRecords(args); + return await context.recordLocking.listAllLockRecords(args); }); } }, - LockingMechanismMutation: { + RecordLockingMutation: { async lockEntry(_, args, context) { return resolve(async () => { await checkPermissions(context); - return context.lockingMechanism.lockEntry({ + return context.recordLocking.lockEntry({ id: args.id, type: args.type }); @@ -257,7 +257,7 @@ export const createGraphQLSchema = async ( async updateEntryLock(_, args, context) { return resolve(async () => { await checkPermissions(context); - return context.lockingMechanism.updateEntryLock({ + return context.recordLocking.updateEntryLock({ id: args.id, type: args.type }); @@ -266,7 +266,7 @@ export const createGraphQLSchema = async ( async unlockEntry(_, args, context) { return resolve(async () => { await checkPermissions(context); - return await context.lockingMechanism.unlockEntry({ + return await context.recordLocking.unlockEntry({ id: args.id, type: args.type, force: args.force @@ -276,7 +276,7 @@ export const createGraphQLSchema = async ( async unlockEntryRequest(_, args, context) { return resolve(async () => { await checkPermissions(context); - return await context.lockingMechanism.unlockEntryRequest({ + return await context.recordLocking.unlockEntryRequest({ id: args.id, type: args.type }); @@ -286,7 +286,7 @@ export const createGraphQLSchema = async ( } }); - plugin.name = "lockingMechanism.graphql.schema.locking"; + plugin.name = "recordLocking.graphql.schema.locking"; return plugin; }; diff --git a/packages/api-locking-mechanism/src/index.ts b/packages/api-record-locking/src/index.ts similarity index 78% rename from packages/api-locking-mechanism/src/index.ts rename to packages/api-record-locking/src/index.ts index 5f631526547..7d94897c264 100644 --- a/packages/api-locking-mechanism/src/index.ts +++ b/packages/api-record-locking/src/index.ts @@ -1,7 +1,7 @@ import { createGraphQLSchema } from "~/graphql/schema"; import { ContextPlugin } from "@webiny/api"; import { Context } from "~/types"; -import { createLockingMechanismCrud } from "~/crud/crud"; +import { createRecordLockingCrud } from "~/crud/crud"; import { createLockingModel } from "~/crud/model"; import { isHeadlessCmsReady } from "@webiny/api-headless-cms"; @@ -17,18 +17,18 @@ const createContextPlugin = () => { } context.plugins.register(createLockingModel()); - context.lockingMechanism = await createLockingMechanismCrud({ + context.recordLocking = await createRecordLockingCrud({ context }); const graphQlPlugin = await createGraphQLSchema({ context }); context.plugins.register(graphQlPlugin); }); - plugin.name = "context.lockingMechanism"; + plugin.name = "context.recordLocking"; return plugin; }; -export const createLockingMechanism = () => { +export const createRecordLocking = () => { return [createContextPlugin()]; }; diff --git a/packages/api-record-locking/src/types.ts b/packages/api-record-locking/src/types.ts new file mode 100644 index 00000000000..99352f8a343 --- /dev/null +++ b/packages/api-record-locking/src/types.ts @@ -0,0 +1,237 @@ +import { + CmsContext, + CmsEntry, + CmsEntryListParams, + CmsEntryMeta, + CmsError, + CmsIdentity, + CmsModel, + CmsModelManager +} from "@webiny/api-headless-cms/types"; +import { Topic } from "@webiny/pubsub/types"; +import { + Context as IWebsocketsContext, + IWebsocketsContextObject +} from "@webiny/api-websockets/types"; + +export { CmsError, CmsEntry }; + +export type IRecordLockingIdentity = CmsIdentity; + +export type IRecordLockingModelManager = CmsModelManager; + +export type IRecordLockingMeta = CmsEntryMeta; + +export interface IHasFullAccessCallable { + (): Promise; +} + +export interface IGetWebsocketsContextCallable { + (): IWebsocketsContextObject; +} + +export interface IGetIdentity { + (): IRecordLockingIdentity; +} + +export interface IRecordLockingLockRecordValues { + targetId: string; + type: IRecordLockingLockRecordEntryType; + actions?: IRecordLockingLockRecordAction[]; +} +export enum IRecordLockingLockRecordActionType { + requested = "requested", + approved = "approved", + denied = "denied" +} + +export interface IRecordLockingLockRecordRequestedAction { + type: IRecordLockingLockRecordActionType.requested; + message?: string; + createdOn: Date; + createdBy: IRecordLockingIdentity; +} + +export interface IRecordLockingLockRecordApprovedAction { + type: IRecordLockingLockRecordActionType.approved; + message?: string; + createdOn: Date; + createdBy: IRecordLockingIdentity; +} + +export interface IRecordLockingLockRecordDeniedAction { + type: IRecordLockingLockRecordActionType.denied; + message?: string; + createdOn: Date; + createdBy: IRecordLockingIdentity; +} + +export type IRecordLockingLockRecordAction = + | IRecordLockingLockRecordRequestedAction + | IRecordLockingLockRecordApprovedAction + | IRecordLockingLockRecordDeniedAction; + +export interface IRecordLockingLockRecordObject { + id: string; + targetId: string; + type: IRecordLockingLockRecordEntryType; + lockedBy: IRecordLockingIdentity; + lockedOn: Date; + updatedOn: Date; + expiresOn: Date; + actions?: IRecordLockingLockRecordAction[]; +} + +export interface IRecordLockingLockRecord extends IRecordLockingLockRecordObject { + toObject(): IRecordLockingLockRecordObject; + addAction(action: IRecordLockingLockRecordAction): void; + getUnlockRequested(): IRecordLockingLockRecordRequestedAction | undefined; + getUnlockApproved(): IRecordLockingLockRecordApprovedAction | undefined; + getUnlockDenied(): IRecordLockingLockRecordDeniedAction | undefined; +} + +/** + * Do not use any special chars other than #, as we use this to create lock record IDs. + */ +export type IRecordLockingLockRecordEntryType = string; + +export type IRecordLockingListAllLockRecordsParams = Pick< + CmsEntryListParams, + "where" | "limit" | "sort" | "after" +>; + +export type IRecordLockingListLockRecordsParams = IRecordLockingListAllLockRecordsParams; + +export interface IRecordLockingListAllLockRecordsResponse { + items: IRecordLockingLockRecord[]; + meta: IRecordLockingMeta; +} + +export type IRecordLockingListLockRecordsResponse = IRecordLockingListAllLockRecordsResponse; + +export interface IRecordLockingGetLockRecordParams { + id: string; + type: IRecordLockingLockRecordEntryType; +} + +export interface IRecordLockingIsLockedParams { + id: string; + type: IRecordLockingLockRecordEntryType; +} + +export interface IRecordLockingGetLockedEntryLockRecordParams { + id: string; + type: IRecordLockingLockRecordEntryType; +} + +export interface IRecordLockingLockEntryParams { + id: string; + type: IRecordLockingLockRecordEntryType; +} + +export interface IRecordLockingUpdateEntryLockParams { + id: string; + type: IRecordLockingLockRecordEntryType; +} + +export interface IRecordLockingUnlockEntryParams { + id: string; + type: IRecordLockingLockRecordEntryType; + force?: boolean; +} + +export interface IRecordLockingUnlockEntryRequestParams { + id: string; + type: IRecordLockingLockRecordEntryType; +} + +export interface OnEntryBeforeLockTopicParams { + id: string; + type: IRecordLockingLockRecordEntryType; +} + +export interface OnEntryAfterLockTopicParams { + id: string; + type: IRecordLockingLockRecordEntryType; + record: IRecordLockingLockRecord; +} + +export interface OnEntryLockErrorTopicParams { + id: string; + type: IRecordLockingLockRecordEntryType; + error: CmsError; +} + +export interface OnEntryBeforeUnlockTopicParams { + id: string; + type: IRecordLockingLockRecordEntryType; + getIdentity: IGetIdentity; +} + +export interface OnEntryAfterUnlockTopicParams { + id: string; + type: IRecordLockingLockRecordEntryType; + record: IRecordLockingLockRecord; +} + +export interface OnEntryUnlockErrorTopicParams { + id: string; + type: IRecordLockingLockRecordEntryType; + error: CmsError; +} + +export interface OnEntryBeforeUnlockRequestTopicParams { + id: string; + type: IRecordLockingLockRecordEntryType; +} + +export interface OnEntryAfterUnlockRequestTopicParams { + id: string; + type: IRecordLockingLockRecordEntryType; + record: IRecordLockingLockRecord; +} + +export interface OnEntryUnlockRequestErrorTopicParams { + id: string; + type: IRecordLockingLockRecordEntryType; + error: CmsError; +} + +export interface IRecordLocking { + onEntryBeforeLock: Topic; + onEntryAfterLock: Topic; + onEntryLockError: Topic; + onEntryBeforeUnlock: Topic; + onEntryAfterUnlock: Topic; + onEntryUnlockError: Topic; + onEntryBeforeUnlockRequest: Topic; + onEntryAfterUnlockRequest: Topic; + onEntryUnlockRequestError: Topic; + getModel(): Promise; + listAllLockRecords( + params?: IRecordLockingListAllLockRecordsParams + ): Promise; + /** + * Same call as listAllLockRecords, except this one will filter out records with expired lock. + */ + listLockRecords( + params?: IRecordLockingListLockRecordsParams + ): Promise; + getLockRecord( + params: IRecordLockingGetLockRecordParams + ): Promise; + isEntryLocked(params: IRecordLockingIsLockedParams): Promise; + getLockedEntryLockRecord( + params: IRecordLockingGetLockedEntryLockRecordParams + ): Promise; + lockEntry(params: IRecordLockingLockEntryParams): Promise; + updateEntryLock(params: IRecordLockingUpdateEntryLockParams): Promise; + unlockEntry(params: IRecordLockingUnlockEntryParams): Promise; + unlockEntryRequest( + params: IRecordLockingUnlockEntryRequestParams + ): Promise; +} + +export interface Context extends CmsContext, IWebsocketsContext { + recordLocking: IRecordLocking; +} diff --git a/packages/api-locking-mechanism/src/useCases/GetLockRecord/GetLockRecordUseCase.ts b/packages/api-record-locking/src/useCases/GetLockRecord/GetLockRecordUseCase.ts similarity index 86% rename from packages/api-locking-mechanism/src/useCases/GetLockRecord/GetLockRecordUseCase.ts rename to packages/api-record-locking/src/useCases/GetLockRecord/GetLockRecordUseCase.ts index d73dda0e992..bf4a1fcc8b4 100644 --- a/packages/api-locking-mechanism/src/useCases/GetLockRecord/GetLockRecordUseCase.ts +++ b/packages/api-record-locking/src/useCases/GetLockRecord/GetLockRecordUseCase.ts @@ -2,14 +2,14 @@ import { IGetLockRecordUseCase, IGetLockRecordUseCaseExecuteParams } from "~/abstractions/IGetLockRecordUseCase"; -import { ILockingMechanismModelManager, ILockingMechanismLockRecord } from "~/types"; +import { IRecordLockingModelManager, IRecordLockingLockRecord } from "~/types"; import { NotFoundError } from "@webiny/handler-graphql"; import { convertEntryToLockRecord } from "~/utils/convertEntryToLockRecord"; import { createLockRecordDatabaseId } from "~/utils/lockRecordDatabaseId"; import { createIdentifier } from "@webiny/utils"; export interface IGetLockRecordUseCaseParams { - getManager(): Promise; + getManager(): Promise; } export class GetLockRecordUseCase implements IGetLockRecordUseCase { @@ -21,7 +21,7 @@ export class GetLockRecordUseCase implements IGetLockRecordUseCase { public async execute( input: IGetLockRecordUseCaseExecuteParams - ): Promise { + ): Promise { const recordId = createLockRecordDatabaseId(input.id); const id = createIdentifier({ id: recordId, diff --git a/packages/api-locking-mechanism/src/useCases/GetLockedEntryLockRecord/GetLockedEntryLockRecordUseCase.ts b/packages/api-record-locking/src/useCases/GetLockedEntryLockRecord/GetLockedEntryLockRecordUseCase.ts similarity index 91% rename from packages/api-locking-mechanism/src/useCases/GetLockedEntryLockRecord/GetLockedEntryLockRecordUseCase.ts rename to packages/api-record-locking/src/useCases/GetLockedEntryLockRecord/GetLockedEntryLockRecordUseCase.ts index 6f33de4db33..28943401a92 100644 --- a/packages/api-locking-mechanism/src/useCases/GetLockedEntryLockRecord/GetLockedEntryLockRecordUseCase.ts +++ b/packages/api-record-locking/src/useCases/GetLockedEntryLockRecord/GetLockedEntryLockRecordUseCase.ts @@ -1,5 +1,5 @@ import { IGetLockRecordUseCase } from "~/abstractions/IGetLockRecordUseCase"; -import { IGetIdentity, ILockingMechanismLockRecord } from "~/types"; +import { IGetIdentity, IRecordLockingLockRecord } from "~/types"; import { IGetLockedEntryLockRecordUseCase, IGetLockedEntryLockRecordUseCaseExecuteParams @@ -25,7 +25,7 @@ export class GetLockedEntryLockRecordUseCase implements IGetLockedEntryLockRecor public async execute( params: IGetLockedEntryLockRecordUseCaseExecuteParams - ): Promise { + ): Promise { const result = await this.getLockRecordUseCase.execute(params); if (!result?.lockedBy) { return null; diff --git a/packages/api-locking-mechanism/src/useCases/IsEntryLocked/IsEntryLockedUseCase.ts b/packages/api-record-locking/src/useCases/IsEntryLocked/IsEntryLockedUseCase.ts similarity index 100% rename from packages/api-locking-mechanism/src/useCases/IsEntryLocked/IsEntryLockedUseCase.ts rename to packages/api-record-locking/src/useCases/IsEntryLocked/IsEntryLockedUseCase.ts diff --git a/packages/api-locking-mechanism/src/useCases/KickOutCurrentUser/KickOutCurrentUserUseCase.ts b/packages/api-record-locking/src/useCases/KickOutCurrentUser/KickOutCurrentUserUseCase.ts similarity index 95% rename from packages/api-locking-mechanism/src/useCases/KickOutCurrentUser/KickOutCurrentUserUseCase.ts rename to packages/api-record-locking/src/useCases/KickOutCurrentUser/KickOutCurrentUserUseCase.ts index 3500bffd9e6..bd3f38b7f13 100644 --- a/packages/api-locking-mechanism/src/useCases/KickOutCurrentUser/KickOutCurrentUserUseCase.ts +++ b/packages/api-record-locking/src/useCases/KickOutCurrentUser/KickOutCurrentUserUseCase.ts @@ -36,7 +36,7 @@ export class KickOutCurrentUserUseCase implements IKickOutCurrentUserUseCase { await websockets.send( { id: lockedBy.id }, { - action: `lockingMechanism.entry.kickOut.${entryId}`, + action: `recordLocking.entry.kickOut.${entryId}`, data: { record: record.toObject(), user: identity diff --git a/packages/api-locking-mechanism/src/useCases/ListAllLockRecordsUseCase/ListAllLockRecordsUseCase.ts b/packages/api-record-locking/src/useCases/ListAllLockRecordsUseCase/ListAllLockRecordsUseCase.ts similarity index 86% rename from packages/api-locking-mechanism/src/useCases/ListAllLockRecordsUseCase/ListAllLockRecordsUseCase.ts rename to packages/api-record-locking/src/useCases/ListAllLockRecordsUseCase/ListAllLockRecordsUseCase.ts index c0566d4ade9..d85eee3b5d9 100644 --- a/packages/api-locking-mechanism/src/useCases/ListAllLockRecordsUseCase/ListAllLockRecordsUseCase.ts +++ b/packages/api-record-locking/src/useCases/ListAllLockRecordsUseCase/ListAllLockRecordsUseCase.ts @@ -3,16 +3,16 @@ import { IListAllLockRecordsUseCaseExecuteParams, IListAllLockRecordsUseCaseExecuteResponse } from "~/abstractions/IListAllLockRecordsUseCase"; -import { ILockingMechanismModelManager } from "~/types"; +import { IRecordLockingModelManager } from "~/types"; import { convertEntryToLockRecord } from "~/utils/convertEntryToLockRecord"; import { convertWhereCondition } from "~/utils/convertWhereCondition"; export interface IListAllLockRecordsUseCaseParams { - getManager(): Promise; + getManager(): Promise; } export class ListAllLockRecordsUseCase implements IListAllLockRecordsUseCase { - private readonly getManager: () => Promise; + private readonly getManager: () => Promise; public constructor(params: IListAllLockRecordsUseCaseParams) { this.getManager = params.getManager; } diff --git a/packages/api-locking-mechanism/src/useCases/ListLockRecordsUseCase/ListLockRecordsUseCase.ts b/packages/api-record-locking/src/useCases/ListLockRecordsUseCase/ListLockRecordsUseCase.ts similarity index 100% rename from packages/api-locking-mechanism/src/useCases/ListLockRecordsUseCase/ListLockRecordsUseCase.ts rename to packages/api-record-locking/src/useCases/ListLockRecordsUseCase/ListLockRecordsUseCase.ts diff --git a/packages/api-locking-mechanism/src/useCases/LockEntryUseCase/LockEntryUseCase.ts b/packages/api-record-locking/src/useCases/LockEntryUseCase/LockEntryUseCase.ts similarity index 83% rename from packages/api-locking-mechanism/src/useCases/LockEntryUseCase/LockEntryUseCase.ts rename to packages/api-record-locking/src/useCases/LockEntryUseCase/LockEntryUseCase.ts index 9e05b0f08e6..75f44685357 100644 --- a/packages/api-locking-mechanism/src/useCases/LockEntryUseCase/LockEntryUseCase.ts +++ b/packages/api-record-locking/src/useCases/LockEntryUseCase/LockEntryUseCase.ts @@ -4,9 +4,9 @@ import { ILockEntryUseCaseExecuteParams } from "~/abstractions/ILockEntryUseCase"; import { - ILockingMechanismLockRecord, - ILockingMechanismLockRecordValues, - ILockingMechanismModelManager + IRecordLockingLockRecord, + IRecordLockingLockRecordValues, + IRecordLockingModelManager } from "~/types"; import { IIsEntryLockedUseCase } from "~/abstractions/IIsEntryLocked"; import { convertEntryToLockRecord } from "~/utils/convertEntryToLockRecord"; @@ -15,12 +15,12 @@ import { NotFoundError } from "@webiny/handler-graphql"; export interface ILockEntryUseCaseParams { isEntryLockedUseCase: IIsEntryLockedUseCase; - getManager(): Promise; + getManager(): Promise; } export class LockEntryUseCase implements ILockEntryUseCase { private readonly isEntryLockedUseCase: IIsEntryLockedUseCase; - private readonly getManager: () => Promise; + private readonly getManager: () => Promise; public constructor(params: ILockEntryUseCaseParams) { this.isEntryLockedUseCase = params.isEntryLockedUseCase; @@ -29,7 +29,7 @@ export class LockEntryUseCase implements ILockEntryUseCase { public async execute( params: ILockEntryUseCaseExecuteParams - ): Promise { + ): Promise { let locked = false; try { locked = await this.isEntryLockedUseCase.execute(params); @@ -48,7 +48,7 @@ export class LockEntryUseCase implements ILockEntryUseCase { const manager = await this.getManager(); const id = createLockRecordDatabaseId(params.id); - const entry = await manager.create({ + const entry = await manager.create({ id, targetId: params.id, type: params.type, diff --git a/packages/api-locking-mechanism/src/useCases/UnlockEntryUseCase/UnlockEntryUseCase.ts b/packages/api-record-locking/src/useCases/UnlockEntryUseCase/UnlockEntryUseCase.ts similarity index 92% rename from packages/api-locking-mechanism/src/useCases/UnlockEntryUseCase/UnlockEntryUseCase.ts rename to packages/api-record-locking/src/useCases/UnlockEntryUseCase/UnlockEntryUseCase.ts index 3e87c86effa..8ea64617b37 100644 --- a/packages/api-locking-mechanism/src/useCases/UnlockEntryUseCase/UnlockEntryUseCase.ts +++ b/packages/api-record-locking/src/useCases/UnlockEntryUseCase/UnlockEntryUseCase.ts @@ -6,8 +6,8 @@ import { import { IGetIdentity, IHasFullAccessCallable, - ILockingMechanismLockRecord, - ILockingMechanismModelManager + IRecordLockingLockRecord, + IRecordLockingModelManager } from "~/types"; import { createLockRecordDatabaseId } from "~/utils/lockRecordDatabaseId"; import { IGetLockRecordUseCase } from "~/abstractions/IGetLockRecordUseCase"; @@ -18,7 +18,7 @@ import { IKickOutCurrentUserUseCase } from "~/abstractions/IKickOutCurrentUserUs export interface IUnlockEntryUseCaseParams { readonly getLockRecordUseCase: IGetLockRecordUseCase; readonly kickOutCurrentUserUseCase: IKickOutCurrentUserUseCase; - getManager(): Promise; + getManager(): Promise; getIdentity: IGetIdentity; hasFullAccess: IHasFullAccessCallable; } @@ -26,7 +26,7 @@ export interface IUnlockEntryUseCaseParams { export class UnlockEntryUseCase implements IUnlockEntryUseCase { private readonly getLockRecordUseCase: IGetLockRecordUseCase; private readonly kickOutCurrentUserUseCase: IKickOutCurrentUserUseCase; - private readonly getManager: () => Promise; + private readonly getManager: () => Promise; private readonly getIdentity: IGetIdentity; private readonly hasFullAccess: IHasFullAccessCallable; @@ -40,7 +40,7 @@ export class UnlockEntryUseCase implements IUnlockEntryUseCase { public async execute( params: IUnlockEntryUseCaseExecuteParams - ): Promise { + ): Promise { const record = await this.getLockRecordUseCase.execute(params); if (!record) { throw new WebinyError("Lock Record not found.", "LOCK_RECORD_NOT_FOUND", { diff --git a/packages/api-locking-mechanism/src/useCases/UnlockRequestUseCase/UnlockEntryRequestUseCase.ts b/packages/api-record-locking/src/useCases/UnlockRequestUseCase/UnlockEntryRequestUseCase.ts similarity index 90% rename from packages/api-locking-mechanism/src/useCases/UnlockRequestUseCase/UnlockEntryRequestUseCase.ts rename to packages/api-record-locking/src/useCases/UnlockRequestUseCase/UnlockEntryRequestUseCase.ts index c97419f6254..05044ba4a56 100644 --- a/packages/api-locking-mechanism/src/useCases/UnlockRequestUseCase/UnlockEntryRequestUseCase.ts +++ b/packages/api-record-locking/src/useCases/UnlockRequestUseCase/UnlockEntryRequestUseCase.ts @@ -5,9 +5,9 @@ import { } from "~/abstractions/IUnlockEntryRequestUseCase"; import { IGetIdentity, - ILockingMechanismLockRecord, - ILockingMechanismLockRecordActionType, - ILockingMechanismModelManager + IRecordLockingLockRecord, + IRecordLockingLockRecordActionType, + IRecordLockingModelManager } from "~/types"; import { IGetLockRecordUseCase } from "~/abstractions/IGetLockRecordUseCase"; import { createLockRecordDatabaseId } from "~/utils/lockRecordDatabaseId"; @@ -16,13 +16,13 @@ import { convertEntryToLockRecord } from "~/utils/convertEntryToLockRecord"; export interface IUnlockEntryRequestUseCaseParams { getLockRecordUseCase: IGetLockRecordUseCase; - getManager: () => Promise; + getManager: () => Promise; getIdentity: IGetIdentity; } export class UnlockEntryRequestUseCase implements IUnlockEntryRequestUseCase { private readonly getLockRecordUseCase: IGetLockRecordUseCase; - private readonly getManager: () => Promise; + private readonly getManager: () => Promise; private readonly getIdentity: IGetIdentity; public constructor(params: IUnlockEntryRequestUseCaseParams) { @@ -33,7 +33,7 @@ export class UnlockEntryRequestUseCase implements IUnlockEntryRequestUseCase { public async execute( params: IUnlockEntryRequestUseCaseExecuteParams - ): Promise { + ): Promise { const record = await this.getLockRecordUseCase.execute(params); if (!record) { throw new WebinyError("Entry is not locked.", "ENTRY_NOT_LOCKED", { @@ -68,7 +68,7 @@ export class UnlockEntryRequestUseCase implements IUnlockEntryRequestUseCase { } record.addAction({ - type: ILockingMechanismLockRecordActionType.requested, + type: IRecordLockingLockRecordActionType.requested, createdOn: new Date(), createdBy: this.getIdentity() }); diff --git a/packages/api-locking-mechanism/src/useCases/UpdateEntryLock/UpdateEntryLockUseCase.ts b/packages/api-record-locking/src/useCases/UpdateEntryLock/UpdateEntryLockUseCase.ts similarity index 89% rename from packages/api-locking-mechanism/src/useCases/UpdateEntryLock/UpdateEntryLockUseCase.ts rename to packages/api-record-locking/src/useCases/UpdateEntryLock/UpdateEntryLockUseCase.ts index 9f1fa4cdaa4..052f37b4b1a 100644 --- a/packages/api-locking-mechanism/src/useCases/UpdateEntryLock/UpdateEntryLockUseCase.ts +++ b/packages/api-record-locking/src/useCases/UpdateEntryLock/UpdateEntryLockUseCase.ts @@ -2,7 +2,7 @@ import { IUpdateEntryLockUseCase, IUpdateEntryLockUseCaseExecuteParams } from "~/abstractions/IUpdateEntryLockUseCase"; -import { IGetIdentity, ILockingMechanismLockRecord, ILockingMechanismModelManager } from "~/types"; +import { IGetIdentity, IRecordLockingLockRecord, IRecordLockingModelManager } from "~/types"; import { IGetLockRecordUseCase } from "~/abstractions/IGetLockRecordUseCase"; import { WebinyError } from "@webiny/error"; import { convertEntryToLockRecord } from "~/utils/convertEntryToLockRecord"; @@ -14,14 +14,14 @@ import { ILockEntryUseCase } from "~/abstractions/ILockEntryUseCase"; export interface IUpdateEntryLockUseCaseParams { readonly getLockRecordUseCase: IGetLockRecordUseCase; readonly lockEntryUseCase: ILockEntryUseCase; - getManager(): Promise; + getManager(): Promise; getIdentity: IGetIdentity; } export class UpdateEntryLockUseCase implements IUpdateEntryLockUseCase { private readonly getLockRecordUseCase: IGetLockRecordUseCase; private readonly lockEntryUseCase: ILockEntryUseCase; - private readonly getManager: () => Promise; + private readonly getManager: () => Promise; private readonly getIdentity: IGetIdentity; public constructor(params: IUpdateEntryLockUseCaseParams) { @@ -33,7 +33,7 @@ export class UpdateEntryLockUseCase implements IUpdateEntryLockUseCase { public async execute( params: IUpdateEntryLockUseCaseExecuteParams - ): Promise { + ): Promise { const record = await this.getLockRecordUseCase.execute(params); if (!record) { return this.lockEntryUseCase.execute(params); diff --git a/packages/api-locking-mechanism/src/useCases/index.ts b/packages/api-record-locking/src/useCases/index.ts similarity index 98% rename from packages/api-locking-mechanism/src/useCases/index.ts rename to packages/api-record-locking/src/useCases/index.ts index e76554b45dc..2198c85ca9e 100644 --- a/packages/api-locking-mechanism/src/useCases/index.ts +++ b/packages/api-record-locking/src/useCases/index.ts @@ -2,7 +2,7 @@ import { IGetIdentity, IGetWebsocketsContextCallable, IHasFullAccessCallable, - ILockingMechanismModelManager + IRecordLockingModelManager } from "~/types"; import { GetLockRecordUseCase } from "./GetLockRecord/GetLockRecordUseCase"; import { IsEntryLockedUseCase } from "./IsEntryLocked/IsEntryLockedUseCase"; @@ -28,7 +28,7 @@ import { IUnlockEntryRequestUseCase } from "~/abstractions/IUnlockEntryRequestUs export interface ICreateUseCasesParams { getIdentity: IGetIdentity; - getManager(): Promise; + getManager(): Promise; hasFullAccess: IHasFullAccessCallable; getWebsockets: IGetWebsocketsContextCallable; } diff --git a/packages/api-locking-mechanism/src/utils/calculateExpiresOn.ts b/packages/api-record-locking/src/utils/calculateExpiresOn.ts similarity index 100% rename from packages/api-locking-mechanism/src/utils/calculateExpiresOn.ts rename to packages/api-record-locking/src/utils/calculateExpiresOn.ts diff --git a/packages/api-locking-mechanism/src/utils/checkPermissions.ts b/packages/api-record-locking/src/utils/checkPermissions.ts similarity index 100% rename from packages/api-locking-mechanism/src/utils/checkPermissions.ts rename to packages/api-record-locking/src/utils/checkPermissions.ts diff --git a/packages/api-locking-mechanism/src/utils/convertEntryToLockRecord.ts b/packages/api-record-locking/src/utils/convertEntryToLockRecord.ts similarity index 55% rename from packages/api-locking-mechanism/src/utils/convertEntryToLockRecord.ts rename to packages/api-record-locking/src/utils/convertEntryToLockRecord.ts index fc6d4096348..d5cc5e746e3 100644 --- a/packages/api-locking-mechanism/src/utils/convertEntryToLockRecord.ts +++ b/packages/api-record-locking/src/utils/convertEntryToLockRecord.ts @@ -1,38 +1,38 @@ -import { CmsEntry, ILockingMechanismIdentity } from "~/types"; +import { CmsEntry, IRecordLockingIdentity } from "~/types"; import { - ILockingMechanismLockRecord, - ILockingMechanismLockRecordAction, - ILockingMechanismLockRecordActionType, - ILockingMechanismLockRecordApprovedAction, - ILockingMechanismLockRecordDeniedAction, - ILockingMechanismLockRecordEntryType, - ILockingMechanismLockRecordObject, - ILockingMechanismLockRecordRequestedAction, - ILockingMechanismLockRecordValues + IRecordLockingLockRecord, + IRecordLockingLockRecordAction, + IRecordLockingLockRecordActionType, + IRecordLockingLockRecordApprovedAction, + IRecordLockingLockRecordDeniedAction, + IRecordLockingLockRecordEntryType, + IRecordLockingLockRecordObject, + IRecordLockingLockRecordRequestedAction, + IRecordLockingLockRecordValues } from "~/types"; import { removeLockRecordDatabasePrefix } from "~/utils/lockRecordDatabaseId"; import { calculateExpiresOn } from "~/utils/calculateExpiresOn"; export const convertEntryToLockRecord = ( - entry: CmsEntry -): ILockingMechanismLockRecord => { + entry: CmsEntry +): IRecordLockingLockRecord => { return new HeadlessCmsLockRecord(entry); }; export type IHeadlessCmsLockRecordParams = Pick< - CmsEntry, + CmsEntry, "entryId" | "values" | "createdBy" | "createdOn" | "savedOn" >; -export class HeadlessCmsLockRecord implements ILockingMechanismLockRecord { +export class HeadlessCmsLockRecord implements IRecordLockingLockRecord { private readonly _id: string; private readonly _targetId: string; - private readonly _type: ILockingMechanismLockRecordEntryType; - private readonly _lockedBy: ILockingMechanismIdentity; + private readonly _type: IRecordLockingLockRecordEntryType; + private readonly _lockedBy: IRecordLockingIdentity; private readonly _lockedOn: Date; private readonly _updatedOn: Date; private readonly _expiresOn: Date; - private _actions?: ILockingMechanismLockRecordAction[]; + private _actions?: IRecordLockingLockRecordAction[]; public get id(): string { return this._id; @@ -42,11 +42,11 @@ export class HeadlessCmsLockRecord implements ILockingMechanismLockRecord { return this._targetId; } - public get type(): ILockingMechanismLockRecordEntryType { + public get type(): IRecordLockingLockRecordEntryType { return this._type; } - public get lockedBy(): ILockingMechanismIdentity { + public get lockedBy(): IRecordLockingIdentity { return this._lockedBy; } @@ -62,7 +62,7 @@ export class HeadlessCmsLockRecord implements ILockingMechanismLockRecord { return this._expiresOn; } - public get actions(): ILockingMechanismLockRecordAction[] | undefined { + public get actions(): IRecordLockingLockRecordAction[] | undefined { return this._actions; } @@ -77,7 +77,7 @@ export class HeadlessCmsLockRecord implements ILockingMechanismLockRecord { this._actions = input.values.actions; } - public toObject(): ILockingMechanismLockRecordObject { + public toObject(): IRecordLockingLockRecordObject { return { id: this._id, targetId: this._targetId, @@ -90,40 +90,40 @@ export class HeadlessCmsLockRecord implements ILockingMechanismLockRecord { }; } - public addAction(action: ILockingMechanismLockRecordAction) { + public addAction(action: IRecordLockingLockRecordAction) { if (!this._actions) { this._actions = []; } this._actions.push(action); } - public getUnlockRequested(): ILockingMechanismLockRecordRequestedAction | undefined { + public getUnlockRequested(): IRecordLockingLockRecordRequestedAction | undefined { if (!this._actions?.length) { return undefined; } return this._actions.find( - (action): action is ILockingMechanismLockRecordRequestedAction => - action.type === ILockingMechanismLockRecordActionType.requested + (action): action is IRecordLockingLockRecordRequestedAction => + action.type === IRecordLockingLockRecordActionType.requested ); } - public getUnlockApproved(): ILockingMechanismLockRecordApprovedAction | undefined { + public getUnlockApproved(): IRecordLockingLockRecordApprovedAction | undefined { if (!this._actions?.length) { return undefined; } return this._actions.find( - (action): action is ILockingMechanismLockRecordApprovedAction => - action.type === ILockingMechanismLockRecordActionType.approved + (action): action is IRecordLockingLockRecordApprovedAction => + action.type === IRecordLockingLockRecordActionType.approved ); } - public getUnlockDenied(): ILockingMechanismLockRecordDeniedAction | undefined { + public getUnlockDenied(): IRecordLockingLockRecordDeniedAction | undefined { if (!this._actions?.length) { return undefined; } return this._actions.find( - (action): action is ILockingMechanismLockRecordDeniedAction => - action.type === ILockingMechanismLockRecordActionType.denied + (action): action is IRecordLockingLockRecordDeniedAction => + action.type === IRecordLockingLockRecordActionType.denied ); } } diff --git a/packages/api-locking-mechanism/src/utils/convertWhereCondition.ts b/packages/api-record-locking/src/utils/convertWhereCondition.ts similarity index 88% rename from packages/api-locking-mechanism/src/utils/convertWhereCondition.ts rename to packages/api-record-locking/src/utils/convertWhereCondition.ts index 8dbefeb7ac5..618b2765ad7 100644 --- a/packages/api-locking-mechanism/src/utils/convertWhereCondition.ts +++ b/packages/api-record-locking/src/utils/convertWhereCondition.ts @@ -1,7 +1,7 @@ -import { ILockingMechanismListLockRecordsParams } from "~/types"; +import { IRecordLockingListLockRecordsParams } from "~/types"; import { createLockRecordDatabaseId } from "~/utils/lockRecordDatabaseId"; -type IWhere = ILockingMechanismListLockRecordsParams["where"] | undefined; +type IWhere = IRecordLockingListLockRecordsParams["where"] | undefined; const attachPrefix = (value: string | string[] | undefined) => { if (!value) { diff --git a/packages/api-locking-mechanism/src/utils/getTimeout.ts b/packages/api-record-locking/src/utils/getTimeout.ts similarity index 100% rename from packages/api-locking-mechanism/src/utils/getTimeout.ts rename to packages/api-record-locking/src/utils/getTimeout.ts diff --git a/packages/api-locking-mechanism/src/utils/isLockedFactory.ts b/packages/api-record-locking/src/utils/isLockedFactory.ts similarity index 75% rename from packages/api-locking-mechanism/src/utils/isLockedFactory.ts rename to packages/api-record-locking/src/utils/isLockedFactory.ts index 01e9fa45126..9d3cc7ddc4c 100644 --- a/packages/api-locking-mechanism/src/utils/isLockedFactory.ts +++ b/packages/api-record-locking/src/utils/isLockedFactory.ts @@ -1,7 +1,7 @@ -import { ILockingMechanismLockRecord } from "~/types"; +import { IRecordLockingLockRecord } from "~/types"; export interface IIsLocked { - (record?: Pick | null): boolean; + (record?: Pick | null): boolean; } export const isLockedFactory = (timeoutInput: number): IIsLocked => { diff --git a/packages/api-locking-mechanism/src/utils/lockRecordDatabaseId.ts b/packages/api-record-locking/src/utils/lockRecordDatabaseId.ts similarity index 100% rename from packages/api-locking-mechanism/src/utils/lockRecordDatabaseId.ts rename to packages/api-record-locking/src/utils/lockRecordDatabaseId.ts diff --git a/packages/api-locking-mechanism/src/utils/resolve.ts b/packages/api-record-locking/src/utils/resolve.ts similarity index 89% rename from packages/api-locking-mechanism/src/utils/resolve.ts rename to packages/api-record-locking/src/utils/resolve.ts index cf442720df0..61509a29c3b 100644 --- a/packages/api-locking-mechanism/src/utils/resolve.ts +++ b/packages/api-record-locking/src/utils/resolve.ts @@ -1,5 +1,5 @@ import { ErrorResponse, ListErrorResponse, ListResponse, Response } from "@webiny/handler-graphql"; -import { ILockingMechanismMeta } from "~/types"; +import { IRecordLockingMeta } from "~/types"; export const resolve = async (cb: () => Promise): Promise | ErrorResponse> => { try { @@ -12,7 +12,7 @@ export const resolve = async (cb: () => Promise): Promise | Er export interface IListResponse { items: T[]; - meta: ILockingMechanismMeta; + meta: IRecordLockingMeta; } export const resolveList = async ( diff --git a/packages/api-locking-mechanism/src/utils/validateSameIdentity.ts b/packages/api-record-locking/src/utils/validateSameIdentity.ts similarity index 74% rename from packages/api-locking-mechanism/src/utils/validateSameIdentity.ts rename to packages/api-record-locking/src/utils/validateSameIdentity.ts index 35177e07159..f596888e863 100644 --- a/packages/api-locking-mechanism/src/utils/validateSameIdentity.ts +++ b/packages/api-record-locking/src/utils/validateSameIdentity.ts @@ -1,9 +1,9 @@ import { NotAuthorizedError } from "@webiny/api-security"; -import { ILockingMechanismIdentity } from "~/types"; +import { IRecordLockingIdentity } from "~/types"; export interface IValidateSameIdentityParams { - getIdentity: () => Pick; - target: Pick; + getIdentity: () => Pick; + target: Pick; } export const validateSameIdentity = (params: IValidateSameIdentityParams): void => { diff --git a/packages/api-locking-mechanism/tsconfig.build.json b/packages/api-record-locking/tsconfig.build.json similarity index 100% rename from packages/api-locking-mechanism/tsconfig.build.json rename to packages/api-record-locking/tsconfig.build.json diff --git a/packages/api-locking-mechanism/tsconfig.json b/packages/api-record-locking/tsconfig.json similarity index 100% rename from packages/api-locking-mechanism/tsconfig.json rename to packages/api-record-locking/tsconfig.json diff --git a/packages/api-locking-mechanism/webiny.config.js b/packages/api-record-locking/webiny.config.js similarity index 100% rename from packages/api-locking-mechanism/webiny.config.js rename to packages/api-record-locking/webiny.config.js diff --git a/packages/app-locking-mechanism/src/domain/LockingMechanismGetLockRecord.ts b/packages/app-locking-mechanism/src/domain/LockingMechanismGetLockRecord.ts deleted file mode 100644 index 345cd0cc354..00000000000 --- a/packages/app-locking-mechanism/src/domain/LockingMechanismGetLockRecord.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { - ILockingMechanismGetLockRecord, - ILockingMechanismGetLockRecordExecuteParams, - ILockingMechanismGetLockRecordExecuteResult -} from "~/domain/abstractions/ILockingMechanismGetLockRecord"; -import { ILockingMechanismClient } from "~/domain/abstractions/ILockingMechanismClient"; -import { - GET_LOCK_RECORD_QUERY, - ILockingMechanismGetLockRecordResponse, - ILockingMechanismGetLockRecordVariables -} from "~/domain/graphql/getLockRecord"; -import { WebinyError } from "@webiny/error"; - -interface Params { - client: ILockingMechanismClient; -} - -export class LockingMechanismGetLockRecord implements ILockingMechanismGetLockRecord { - private readonly client: ILockingMechanismClient; - - public constructor(params: Params) { - this.client = params.client; - } - public async execute( - params: ILockingMechanismGetLockRecordExecuteParams - ): Promise { - const result = await this.client.query< - ILockingMechanismGetLockRecordResponse, - ILockingMechanismGetLockRecordVariables - >({ - query: GET_LOCK_RECORD_QUERY, - variables: params - }); - if (result.data.lockingMechanism.getLockRecord.error) { - throw new WebinyError(result.data.lockingMechanism.getLockRecord.error); - } else if (!result.data.lockingMechanism.getLockRecord.data) { - throw new WebinyError("No data returned from server."); - } - return result.data.lockingMechanism.getLockRecord; - } -} diff --git a/packages/app-locking-mechanism/src/domain/LockingMechanismGetLockedEntryLockRecord.ts b/packages/app-locking-mechanism/src/domain/LockingMechanismGetLockedEntryLockRecord.ts deleted file mode 100644 index d534fd2ea44..00000000000 --- a/packages/app-locking-mechanism/src/domain/LockingMechanismGetLockedEntryLockRecord.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ILockingMechanismClient } from "~/domain/abstractions/ILockingMechanismClient"; -import { - GET_LOCKED_ENTRY_LOCK_RECORD_QUERY, - ILockingMechanismGetLockedEntryLockRecordResponse, - ILockingMechanismGetLockedEntryLockRecordVariables -} from "~/domain/graphql/getLockedEntryLockRecord"; -import { WebinyError } from "@webiny/error"; -import { - ILockingMechanismGetLockedEntryLockRecord, - ILockingMechanismGetLockedEntryLockRecordExecuteParams, - ILockingMechanismGetLockedEntryLockRecordExecuteResult -} from "~/domain/abstractions/ILockingMechanismGetLockedEntryLockRecord"; - -interface Params { - client: ILockingMechanismClient; -} - -export class LockingMechanismGetLockedEntryLockRecord - implements ILockingMechanismGetLockedEntryLockRecord -{ - private readonly client: ILockingMechanismClient; - - public constructor(params: Params) { - this.client = params.client; - } - public async execute( - params: ILockingMechanismGetLockedEntryLockRecordExecuteParams - ): Promise { - const result = await this.client.query< - ILockingMechanismGetLockedEntryLockRecordResponse, - ILockingMechanismGetLockedEntryLockRecordVariables - >({ - query: GET_LOCKED_ENTRY_LOCK_RECORD_QUERY, - variables: params - }); - if (result.data.lockingMechanism.getLockedEntryLockRecord.error) { - throw new WebinyError(result.data.lockingMechanism.getLockedEntryLockRecord.error); - } - return result.data.lockingMechanism.getLockedEntryLockRecord; - } -} diff --git a/packages/app-locking-mechanism/src/domain/LockingMechanismIsEntryLocked.ts b/packages/app-locking-mechanism/src/domain/LockingMechanismIsEntryLocked.ts deleted file mode 100644 index bda3937999a..00000000000 --- a/packages/app-locking-mechanism/src/domain/LockingMechanismIsEntryLocked.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - ILockingMechanismIsEntryLocked, - ILockingMechanismIsEntryLockedParams, - ILockingMechanismIsEntryLockedResult -} from "~/domain/abstractions/ILockingMechanismIsEntryLocked"; -import { ILockingMechanismClient } from "./abstractions/ILockingMechanismClient"; -import { - ILockingMechanismIsEntryLockedResponse, - ILockingMechanismIsEntryLockedVariables, - IS_ENTRY_LOCKED_QUERY -} from "~/domain/graphql/isEntryLocked"; - -interface Params { - client: ILockingMechanismClient; -} - -export class LockingMechanismIsEntryLocked implements ILockingMechanismIsEntryLocked { - private readonly client: ILockingMechanismClient; - - public constructor(params: Params) { - this.client = params.client; - } - public async execute( - params: ILockingMechanismIsEntryLockedParams - ): Promise { - try { - const result = await this.client.query< - ILockingMechanismIsEntryLockedResponse, - ILockingMechanismIsEntryLockedVariables - >({ - query: IS_ENTRY_LOCKED_QUERY, - variables: params - }); - return !!result.data.lockingMechanism.isEntryLocked.data; - } catch { - return false; - } - } -} diff --git a/packages/app-locking-mechanism/src/domain/LockingMechanismListLockRecords.ts b/packages/app-locking-mechanism/src/domain/LockingMechanismListLockRecords.ts deleted file mode 100644 index b1f1dce601b..00000000000 --- a/packages/app-locking-mechanism/src/domain/LockingMechanismListLockRecords.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { WebinyError } from "@webiny/error"; -import { ApolloClient } from "apollo-client"; -import { - ILockingMechanismListLockRecords, - ILockingMechanismListLockRecordsParams, - ILockingMechanismListLockRecordsResult -} from "./abstractions/ILockingMechanismListLockRecords"; -import { ILockingMechanismClient } from "./abstractions/ILockingMechanismClient"; -import { createLockingMechanismClient } from "./utils/createLockingMechanismClient"; -import { - ILockingMechanismListLockedRecordsResponse, - ILockingMechanismListLockedRecordsVariables, - LIST_LOCK_RECORDS -} from "~/domain/graphql/listLockRecords"; - -interface Params { - client: ILockingMechanismClient | ApolloClient; -} - -export class LockingMechanismListLockRecords implements ILockingMechanismListLockRecords { - private readonly client: ILockingMechanismClient; - - public constructor(params: Params) { - this.client = createLockingMechanismClient(params.client); - } - public async execute( - params: ILockingMechanismListLockRecordsParams - ): Promise { - const { where, sort, limit, after } = params; - - const result = await this.client.query< - ILockingMechanismListLockedRecordsResponse, - ILockingMechanismListLockedRecordsVariables - >({ - query: LIST_LOCK_RECORDS, - variables: { - where, - sort, - limit, - after - } - }); - if (!result.data?.lockingMechanism?.listLockRecords) { - throw new WebinyError("No data returned from server."); - } - return result.data.lockingMechanism.listLockRecords; - } -} diff --git a/packages/app-locking-mechanism/src/domain/LockingMechanismLockEntry.ts b/packages/app-locking-mechanism/src/domain/LockingMechanismLockEntry.ts deleted file mode 100644 index 4244797e6b0..00000000000 --- a/packages/app-locking-mechanism/src/domain/LockingMechanismLockEntry.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { WebinyError } from "@webiny/error"; -import { - ILockingMechanismLockEntry, - ILockingMechanismLockEntryParams, - ILockingMechanismLockEntryResult -} from "~/domain/abstractions/ILockingMechanismLockEntry"; -import { ILockingMechanismClient } from "./abstractions/ILockingMechanismClient"; - -interface Params { - client: ILockingMechanismClient; -} - -export class LockingMechanismLockEntry implements ILockingMechanismLockEntry { - // eslint-disable-next-line - private readonly client: ILockingMechanismClient; - - public constructor(params: Params) { - this.client = params.client; - } - public async execute( - // eslint-disable-next-line - params: ILockingMechanismLockEntryParams - ): Promise { - throw new WebinyError("Method not implemented."); - } -} diff --git a/packages/app-locking-mechanism/src/domain/LockingMechanismUnlockEntry.ts b/packages/app-locking-mechanism/src/domain/LockingMechanismUnlockEntry.ts deleted file mode 100644 index 001ca485e01..00000000000 --- a/packages/app-locking-mechanism/src/domain/LockingMechanismUnlockEntry.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { WebinyError } from "@webiny/error"; -import { - ILockingMechanismUnlockEntry, - ILockingMechanismUnlockEntryParams, - ILockingMechanismUnlockEntryResult -} from "~/domain/abstractions/ILockingMechanismUnlockEntry"; -import { ILockingMechanismClient } from "./abstractions/ILockingMechanismClient"; -import { - ILockingMechanismUnlockEntryResponse, - ILockingMechanismUnlockEntryVariables, - UNLOCK_ENTRY_MUTATION -} from "~/domain/graphql/unlockEntry"; - -interface Params { - client: ILockingMechanismClient; -} - -export class LockingMechanismUnlockEntry implements ILockingMechanismUnlockEntry { - private readonly client: ILockingMechanismClient; - - public constructor(params: Params) { - this.client = params.client; - } - public async execute( - params: ILockingMechanismUnlockEntryParams - ): Promise { - const result = await this.client.mutation< - ILockingMechanismUnlockEntryResponse, - ILockingMechanismUnlockEntryVariables - >({ - mutation: UNLOCK_ENTRY_MUTATION, - variables: params - }); - if (!result.data?.lockingMechanism?.unlockEntry) { - throw new WebinyError("No data returned from server."); - } - return result.data.lockingMechanism.unlockEntry; - } -} diff --git a/packages/app-locking-mechanism/src/domain/LockingMechanismUnlockEntryRequest.ts b/packages/app-locking-mechanism/src/domain/LockingMechanismUnlockEntryRequest.ts deleted file mode 100644 index 212e0814862..00000000000 --- a/packages/app-locking-mechanism/src/domain/LockingMechanismUnlockEntryRequest.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { WebinyError } from "@webiny/error"; -import { - ILockingMechanismUnlockEntryRequest, - ILockingMechanismUnlockEntryRequestParams, - ILockingMechanismUnlockEntryRequestResult -} from "~/domain/abstractions/ILockingMechanismUnlockEntryRequest"; -import { ILockingMechanismClient } from "./abstractions/ILockingMechanismClient"; - -interface Params { - client: ILockingMechanismClient; -} - -export class LockingMechanismUnlockEntryRequest implements ILockingMechanismUnlockEntryRequest { - // @eslint-disable-next-line - private readonly client: ILockingMechanismClient; - - public constructor(params: Params) { - this.client = params.client; - } - public async execute( - // eslint-disable-next-line - params: ILockingMechanismUnlockEntryRequestParams - ): Promise { - throw new WebinyError("Method not implemented."); - } -} diff --git a/packages/app-locking-mechanism/src/domain/LockingMechanismUpdateEntryLock.ts b/packages/app-locking-mechanism/src/domain/LockingMechanismUpdateEntryLock.ts deleted file mode 100644 index 4f20aa56b70..00000000000 --- a/packages/app-locking-mechanism/src/domain/LockingMechanismUpdateEntryLock.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { WebinyError } from "@webiny/error"; -import { - ILockingMechanismUpdateEntryLock, - ILockingMechanismUpdateEntryLockExecuteParams, - ILockingMechanismUpdateEntryLockExecuteResult -} from "~/domain/abstractions/ILockingMechanismUpdateEntryLock"; -import { ILockingMechanismClient } from "~/domain/abstractions/ILockingMechanismClient"; -import { - ILockingMechanismUpdateEntryLockResponse, - ILockingMechanismUpdateEntryLockVariables, - UPDATE_ENTRY_LOCK -} from "~/domain/graphql/updateEntryLock"; - -interface Params { - client: ILockingMechanismClient; -} - -export class LockingMechanismUpdateEntryLock implements ILockingMechanismUpdateEntryLock { - private readonly client: ILockingMechanismClient; - - public constructor(params: Params) { - this.client = params.client; - } - - public async execute( - params: ILockingMechanismUpdateEntryLockExecuteParams - ): Promise { - const result = await this.client.mutation< - ILockingMechanismUpdateEntryLockResponse, - ILockingMechanismUpdateEntryLockVariables - >({ - mutation: UPDATE_ENTRY_LOCK, - variables: params - }); - if (!result.data?.lockingMechanism?.updateEntryLock) { - throw new WebinyError("No data returned from server."); - } - return result.data.lockingMechanism.updateEntryLock; - } -} diff --git a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanism.ts b/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanism.ts deleted file mode 100644 index fc9afc8d99a..00000000000 --- a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanism.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - IIsRecordLockedParams, - IUpdateEntryLockParams, - ILockingMechanismRecord, - IPossiblyLockingMechanismRecord, - ILockingMechanismError, - ILockingMechanismLockRecord, - IUnlockEntryParams, - IFetchLockRecordParams, - IFetchLockRecordResult, - IFetchLockedEntryLockRecordParams -} from "~/types"; -import { ILockingMechanismUnlockEntryResult } from "./ILockingMechanismUnlockEntry"; - -export interface ILockingMechanismUpdateEntryLockResult { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; -} - -export interface ILockingMechanism< - T extends IPossiblyLockingMechanismRecord = IPossiblyLockingMechanismRecord -> { - loading: boolean; - records: ILockingMechanismRecord[]; - setRecords( - folderId: string, - type: string, - records: T[] - ): Promise; - isLockExpired(input: Date | string): boolean; - isRecordLocked(record: IIsRecordLockedParams): boolean; - getLockRecordEntry(id: string): ILockingMechanismRecord | undefined; - fetchLockRecord(params: IFetchLockRecordParams): Promise; - fetchLockedEntryLockRecord( - params: IFetchLockedEntryLockRecordParams - ): Promise; - updateEntryLock( - params: IUpdateEntryLockParams - ): Promise; - removeEntryLock(params: IUnlockEntryParams): void; - unlockEntry( - params: IUnlockEntryParams, - force?: boolean - ): Promise; - // lockEntry(params: ILockingMechanismLockEntryParams): Promise; - // unlockEntryRequest( - // params: ILockingMechanismUnlockEntryRequestParams - // ): Promise; - // isEntryLocked( - // params: ILockingMechanismIsEntryLockedParams - // ): Promise; - // getLockRecord( - // params: ILockingMechanismGetLockRecordParams - // ): Promise; - // listLockRecords( - // params: ILockingMechanismListLockRecordsParams - // ): Promise; - - // onRequestAccess(): Promise; - // acceptAccessRequest(): Promise; - // rejectAccessRequest(): Promise; -} diff --git a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismGetLockRecord.ts b/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismGetLockRecord.ts deleted file mode 100644 index 00696741623..00000000000 --- a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismGetLockRecord.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; - -export interface ILockingMechanismGetLockRecordExecuteParams { - id: string; - type: string; -} - -export interface ILockingMechanismGetLockRecordExecuteResult { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; -} - -export interface ILockingMechanismGetLockRecord { - execute( - params: ILockingMechanismGetLockRecordExecuteParams - ): Promise; -} diff --git a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismGetLockedEntryLockRecord.ts b/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismGetLockedEntryLockRecord.ts deleted file mode 100644 index b000503bfb7..00000000000 --- a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismGetLockedEntryLockRecord.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; - -export interface ILockingMechanismGetLockedEntryLockRecordExecuteParams { - id: string; - type: string; -} - -export interface ILockingMechanismGetLockedEntryLockRecordExecuteResult { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; -} - -export interface ILockingMechanismGetLockedEntryLockRecord { - execute( - params: ILockingMechanismGetLockedEntryLockRecordExecuteParams - ): Promise; -} diff --git a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismIsEntryLocked.ts b/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismIsEntryLocked.ts deleted file mode 100644 index 98acea42caf..00000000000 --- a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismIsEntryLocked.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface ILockingMechanismIsEntryLockedParams { - id: string; - type: string; -} - -export type ILockingMechanismIsEntryLockedResult = boolean; - -export interface ILockingMechanismIsEntryLocked { - execute( - params: ILockingMechanismIsEntryLockedParams - ): Promise; -} diff --git a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismListLockRecords.ts b/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismListLockRecords.ts deleted file mode 100644 index a5a8d03d386..00000000000 --- a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismListLockRecords.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { - ILockingMechanismError, - ILockingMechanismLockRecord, - ILockingMechanismMeta -} from "~/types"; - -export interface ILockingMechanismListLockRecordsParamsWhere { - id_in?: string[]; - type?: string; -} - -export interface ILockingMechanismListLockRecordsParams { - where?: ILockingMechanismListLockRecordsParamsWhere; - sort?: string[]; - limit?: number; - after?: string; -} - -export interface ILockingMechanismListLockRecordsResult { - data: ILockingMechanismLockRecord[] | null; - error: ILockingMechanismError | null; - meta: ILockingMechanismMeta | null; -} - -export interface ILockingMechanismListLockRecords { - execute( - params: ILockingMechanismListLockRecordsParams - ): Promise; -} diff --git a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismLockEntry.ts b/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismLockEntry.ts deleted file mode 100644 index bc0de457f20..00000000000 --- a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismLockEntry.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; - -export interface ILockingMechanismLockEntryParams { - id: string; - type: string; -} - -export interface ILockingMechanismLockEntryResult { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; -} - -export interface ILockingMechanismLockEntry { - execute(params: ILockingMechanismLockEntryParams): Promise; -} diff --git a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUnlockEntry.ts b/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUnlockEntry.ts deleted file mode 100644 index 9cff3c9f38c..00000000000 --- a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUnlockEntry.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; - -export interface ILockingMechanismUnlockEntryParams { - id: string; - type: string; - force?: boolean; -} - -export interface ILockingMechanismUnlockEntryResult { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; -} - -export interface ILockingMechanismUnlockEntry { - execute( - params: ILockingMechanismUnlockEntryParams - ): Promise; -} diff --git a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUnlockEntryRequest.ts b/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUnlockEntryRequest.ts deleted file mode 100644 index 139757e1c57..00000000000 --- a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUnlockEntryRequest.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; - -export interface ILockingMechanismUnlockEntryRequestParams { - id: string; - type: string; -} - -export interface ILockingMechanismUnlockEntryRequestResult { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; -} - -export interface ILockingMechanismUnlockEntryRequest { - execute( - params: ILockingMechanismUnlockEntryRequestParams - ): Promise; -} diff --git a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUpdateEntryLock.ts b/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUpdateEntryLock.ts deleted file mode 100644 index b25c60df962..00000000000 --- a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismUpdateEntryLock.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; - -export interface ILockingMechanismUpdateEntryLockExecuteParams { - id: string; - type: string; -} -export interface ILockingMechanismUpdateEntryLockExecuteResult { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; -} - -export interface ILockingMechanismUpdateEntryLock { - execute( - params: ILockingMechanismUpdateEntryLockExecuteParams - ): Promise; -} diff --git a/packages/app-locking-mechanism/src/domain/graphql/getLockRecord.ts b/packages/app-locking-mechanism/src/domain/graphql/getLockRecord.ts deleted file mode 100644 index e665e66df52..00000000000 --- a/packages/app-locking-mechanism/src/domain/graphql/getLockRecord.ts +++ /dev/null @@ -1,30 +0,0 @@ -import gql from "graphql-tag"; -import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; -import { ILockingMechanismGetLockRecordExecuteParams } from "~/domain/abstractions/ILockingMechanismGetLockRecord"; - -export type ILockingMechanismGetLockRecordVariables = ILockingMechanismGetLockRecordExecuteParams; - -export interface ILockingMechanismGetLockRecordResponse { - lockingMechanism: { - getLockRecord: { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; - }; - }; -} - -export const GET_LOCK_RECORD_QUERY = gql` - query LockingMechanismGetLockRecord($id: ID!) { - lockingMechanism { - getLockRecord(id: $id) { - data { - ${LOCK_RECORD_FIELDS} - } - error { - ${ERROR_FIELDS} - } - } - } - } -`; diff --git a/packages/app-locking-mechanism/src/domain/graphql/getLockedEntryLockRecord.ts b/packages/app-locking-mechanism/src/domain/graphql/getLockedEntryLockRecord.ts deleted file mode 100644 index bd63be8dbea..00000000000 --- a/packages/app-locking-mechanism/src/domain/graphql/getLockedEntryLockRecord.ts +++ /dev/null @@ -1,31 +0,0 @@ -import gql from "graphql-tag"; -import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; -import { ILockingMechanismGetLockedEntryLockRecordExecuteParams } from "~/domain/abstractions/ILockingMechanismGetLockedEntryLockRecord"; - -export type ILockingMechanismGetLockedEntryLockRecordVariables = - ILockingMechanismGetLockedEntryLockRecordExecuteParams; - -export interface ILockingMechanismGetLockedEntryLockRecordResponse { - lockingMechanism: { - getLockedEntryLockRecord: { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; - }; - }; -} - -export const GET_LOCKED_ENTRY_LOCK_RECORD_QUERY = gql` - query LockingMechanismGetLockedEntryLockRecord($id: ID!, $type: String!) { - lockingMechanism { - getLockedEntryLockRecord(id: $id, type: $type) { - data { - ${LOCK_RECORD_FIELDS} - } - error { - ${ERROR_FIELDS} - } - } - } - } -`; diff --git a/packages/app-locking-mechanism/src/domain/graphql/isEntryLocked.ts b/packages/app-locking-mechanism/src/domain/graphql/isEntryLocked.ts deleted file mode 100644 index e8a805ceef9..00000000000 --- a/packages/app-locking-mechanism/src/domain/graphql/isEntryLocked.ts +++ /dev/null @@ -1,28 +0,0 @@ -import gql from "graphql-tag"; -import { ERROR_FIELDS } from "~/domain/graphql/fields"; -import { ILockingMechanismError } from "~/types"; -import { ILockingMechanismIsEntryLockedParams } from "../abstractions/ILockingMechanismIsEntryLocked"; - -export type ILockingMechanismIsEntryLockedVariables = ILockingMechanismIsEntryLockedParams; - -export interface ILockingMechanismIsEntryLockedResponse { - lockingMechanism: { - isEntryLocked: { - data: boolean | null; - error: ILockingMechanismError | null; - }; - }; -} - -export const IS_ENTRY_LOCKED_QUERY = gql` - query LockingMechanismIsEntryLocked($id: ID!, $type: String!) { - lockingMechanism { - isEntryLocked(id: $id, type: $type) { - data - error { - ${ERROR_FIELDS} - } - } - } - } -`; diff --git a/packages/app-locking-mechanism/src/domain/graphql/listLockRecords.ts b/packages/app-locking-mechanism/src/domain/graphql/listLockRecords.ts deleted file mode 100644 index f88c1b74f07..00000000000 --- a/packages/app-locking-mechanism/src/domain/graphql/listLockRecords.ts +++ /dev/null @@ -1,48 +0,0 @@ -import gql from "graphql-tag"; -import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "~/domain/graphql/fields"; -import { - ILockingMechanismError, - ILockingMechanismLockRecord, - ILockingMechanismMeta -} from "~/types"; -import { ILockingMechanismListLockRecordsParams } from "~/domain/abstractions/ILockingMechanismListLockRecords"; - -export interface ILockingMechanismListLockedRecordsVariablesWhere { - id_in?: string[]; -} - -export type ILockingMechanismListLockedRecordsVariables = ILockingMechanismListLockRecordsParams; - -export interface ILockingMechanismListLockedRecordsResponse { - lockingMechanism: { - listLockRecords: { - data: ILockingMechanismLockRecord[] | null; - error: ILockingMechanismError | null; - meta: ILockingMechanismMeta | null; - }; - }; -} - -export const createListLockRecords = () => { - return gql` - query LockingMechanismListLockedRecords( - $where: LockingMechanismListWhereInput - $sort: [LockingMechanismListSorter!] - $limit: Int - $after: String - ) { - lockingMechanism { - listLockRecords(where: $where, sort: $sort, limit: $limit, after: $after) { - data { - ${LOCK_RECORD_FIELDS} - } - error { - ${ERROR_FIELDS} - } - } - } - } - `; -}; - -export const LIST_LOCK_RECORDS = createListLockRecords(); diff --git a/packages/app-locking-mechanism/src/domain/graphql/lockEntry.ts b/packages/app-locking-mechanism/src/domain/graphql/lockEntry.ts deleted file mode 100644 index 225c5fceba9..00000000000 --- a/packages/app-locking-mechanism/src/domain/graphql/lockEntry.ts +++ /dev/null @@ -1,32 +0,0 @@ -import gql from "graphql-tag"; -import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; -import { ILockingMechanismLockEntryParams } from "~/domain/abstractions/ILockingMechanismLockEntry"; - -export type ILockingMechanismLockEntryVariables = ILockingMechanismLockEntryParams; - -export interface ILockingMechanismLockEntryResponse { - lockingMechanism: { - lockEntry: { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; - }; - }; -} - -export const createLockGraphQL = () => { - return gql` - mutation LockingMechanismLockEntry($id: ID!, $type: String!) { - lockingMechanism { - lockEntry(id: $id, type: $type) { - data { - ${LOCK_RECORD_FIELDS} - } - error { - ${ERROR_FIELDS} - } - } - } - } - `; -}; diff --git a/packages/app-locking-mechanism/src/domain/graphql/unlockEntry.ts b/packages/app-locking-mechanism/src/domain/graphql/unlockEntry.ts deleted file mode 100644 index 0871c67e903..00000000000 --- a/packages/app-locking-mechanism/src/domain/graphql/unlockEntry.ts +++ /dev/null @@ -1,30 +0,0 @@ -import gql from "graphql-tag"; -import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; -import { ILockingMechanismUnlockEntryParams } from "../abstractions/ILockingMechanismUnlockEntry"; - -export type ILockingMechanismUnlockEntryVariables = ILockingMechanismUnlockEntryParams; - -export interface ILockingMechanismUnlockEntryResponse { - lockingMechanism: { - unlockEntry: { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; - }; - }; -} - -export const UNLOCK_ENTRY_MUTATION = gql` - mutation LockingMechanismUnlockEntry($id: ID!, $type: String!, $force: Boolean) { - lockingMechanism { - unlockEntry(id: $id, type: $type, force: $force) { - data { - ${LOCK_RECORD_FIELDS} - } - error { - ${ERROR_FIELDS} - } - } - } - } -`; diff --git a/packages/app-locking-mechanism/src/domain/graphql/unlockEntryRequest.ts b/packages/app-locking-mechanism/src/domain/graphql/unlockEntryRequest.ts deleted file mode 100644 index 70fde29a546..00000000000 --- a/packages/app-locking-mechanism/src/domain/graphql/unlockEntryRequest.ts +++ /dev/null @@ -1,33 +0,0 @@ -import gql from "graphql-tag"; -import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; -import { ILockingMechanismUnlockEntryRequestParams } from "../abstractions/ILockingMechanismUnlockEntryRequest"; - -export type ILockingMechanismUnlockEntryRequestVariables = - ILockingMechanismUnlockEntryRequestParams; - -export interface ILockingMechanismUnlockEntryRequestResponse { - lockingMechanism: { - unlockEntryRequest: { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; - }; - }; -} - -export const createUnlockEntryRequestGraphQL = () => { - return gql` - mutation LockingMechanismUnlockEntryRequest($id: ID!, $type: String!) { - lockingMechanism { - unlockEntryRequest(id: $id, type: $type) { - data { - ${LOCK_RECORD_FIELDS} - } - error { - ${ERROR_FIELDS} - } - } - } - } - `; -}; diff --git a/packages/app-locking-mechanism/src/domain/graphql/updateEntryLock.ts b/packages/app-locking-mechanism/src/domain/graphql/updateEntryLock.ts deleted file mode 100644 index 75c3f93c28f..00000000000 --- a/packages/app-locking-mechanism/src/domain/graphql/updateEntryLock.ts +++ /dev/null @@ -1,31 +0,0 @@ -import gql from "graphql-tag"; -import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; -import { ILockingMechanismError, ILockingMechanismLockRecord } from "~/types"; -import { ILockingMechanismUpdateEntryLockExecuteParams } from "~/domain/abstractions/ILockingMechanismUpdateEntryLock"; - -export type ILockingMechanismUpdateEntryLockVariables = - ILockingMechanismUpdateEntryLockExecuteParams; - -export interface ILockingMechanismUpdateEntryLockResponse { - lockingMechanism: { - updateEntryLock: { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; - }; - }; -} - -export const UPDATE_ENTRY_LOCK = gql` - mutation LockingMechanismUpdateEntryLock($id: ID!, $type: String!) { - lockingMechanism { - updateEntryLock(id: $id, type: $type) { - data { - ${LOCK_RECORD_FIELDS} - } - error { - ${ERROR_FIELDS} - } - } - } - } -`; diff --git a/packages/app-locking-mechanism/src/domain/utils/createLockingMechanismClient.ts b/packages/app-locking-mechanism/src/domain/utils/createLockingMechanismClient.ts deleted file mode 100644 index 8f0f1b12c2c..00000000000 --- a/packages/app-locking-mechanism/src/domain/utils/createLockingMechanismClient.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ILockingMechanismClient } from "~/domain/abstractions/ILockingMechanismClient"; -import { LockingMechanismClient } from "~/domain/LockingMechanismClient"; -import { ApolloClient } from "apollo-client"; - -export const createLockingMechanismClient = ( - client: ILockingMechanismClient | ApolloClient -) => { - if (client instanceof ApolloClient) { - return new LockingMechanismClient({ client }); - } - return client; -}; diff --git a/packages/app-locking-mechanism/src/hooks/index.ts b/packages/app-locking-mechanism/src/hooks/index.ts deleted file mode 100644 index a0a8d7b8055..00000000000 --- a/packages/app-locking-mechanism/src/hooks/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./useLockingMechanism"; -export * from "./usePermission"; diff --git a/packages/app-locking-mechanism/src/hooks/useLockingMechanism.ts b/packages/app-locking-mechanism/src/hooks/useLockingMechanism.ts deleted file mode 100644 index b33cb202e84..00000000000 --- a/packages/app-locking-mechanism/src/hooks/useLockingMechanism.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { WebinyError } from "@webiny/error"; -import { useContext } from "react"; -import { LockingMechanismContext } from "~/components/LockingMechanismProvider"; -import { ILockingMechanismContext, IPossiblyLockingMechanismRecord } from "~/types"; - -export const useLockingMechanism = < - T extends IPossiblyLockingMechanismRecord = IPossiblyLockingMechanismRecord ->() => { - const context = useContext(LockingMechanismContext); - if (!context) { - throw new WebinyError("useLockingMechanism must be used within a LockingMechanismProvider"); - } - return context as ILockingMechanismContext; -}; diff --git a/packages/app-locking-mechanism/src/types.ts b/packages/app-locking-mechanism/src/types.ts deleted file mode 100644 index 5aa8b94f480..00000000000 --- a/packages/app-locking-mechanism/src/types.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { EntryTableItem } from "@webiny/app-headless-cms/types"; -import { GenericRecord } from "@webiny/app/types"; -import { ILockingMechanismUnlockEntryResult } from "~/domain/abstractions/ILockingMechanismUnlockEntry"; - -// export interface ILockingMechanismContextRecord { -// id: string; -// type: string; -// locked?: boolean; -// } - -export interface ILockingMechanismIdentity { - id: string; - displayName: string; - type: string; -} - -export interface ILockingMechanismRecordLocked { - lockedBy: ILockingMechanismIdentity; - lockedOn: string; - expiresOn: string; - actions: ILockingMechanismLockRecordAction[]; -} - -export interface IPossiblyLockingMechanismRecord extends EntryTableItem { - $lockingType?: string; - entryId: string; - $locked?: ILockingMechanismRecordLocked | null; -} - -export interface ILockingMechanismRecord extends IPossiblyLockingMechanismRecord { - entryId: string; - $lockingType: string; -} - -export type IIsRecordLockedParams = Pick; - -export type IUpdateEntryLockParams = Pick; - -export type IUnlockEntryParams = Pick; - -export type IFetchLockRecordParams = Pick; - -export type IFetchLockedEntryLockRecordParams = Pick< - ILockingMechanismRecord, - "id" | "$lockingType" ->; - -export interface IFetchLockRecordResult { - data: ILockingMechanismLockRecord | null; - error: ILockingMechanismError | null; -} - -export interface ILockingMechanismContext< - T extends IPossiblyLockingMechanismRecord = IPossiblyLockingMechanismRecord -> { - readonly loading: boolean; - readonly records: IPossiblyLockingMechanismRecord[]; - readonly error?: ILockingMechanismError | null; - setRecords(folderId: string, type: string, records: T[]): Promise; - updateEntryLock(params: IUpdateEntryLockParams): Promise; - isRecordLocked(params?: IIsRecordLockedParams): boolean; - getLockRecordEntry(id: string): ILockingMechanismRecord | undefined; - fetchLockRecord(params: IFetchLockRecordParams): Promise; - fetchLockedEntryLockRecord( - params: IFetchLockedEntryLockRecordParams - ): Promise; - unlockEntry(params: IUnlockEntryParams): Promise; - removeEntryLock(params: IUnlockEntryParams): void; - unlockEntryForce(params: IUnlockEntryParams): Promise; - isLockExpired(input: Date | string): boolean; -} - -export interface ILockingMechanismLockRecordAction { - type: string; - message: string; - createdBy: ILockingMechanismIdentity; - createdOn: string; -} - -export interface ILockingMechanismLockRecord { - id: string; - lockedOn: string; - expiresOn: string; - lockedBy: ILockingMechanismIdentity; - targetId: string; - type: string; - actions: ILockingMechanismLockRecordAction[]; -} - -export interface ILockingMechanismMeta { - totalCount: number; - cursor: string | null; - hasMoreItems: boolean; -} - -export interface ILockingMechanismError { - message: string; - code: string; - data?: T; -} diff --git a/packages/app-locking-mechanism/.babelrc.js b/packages/app-record-locking/.babelrc.js similarity index 100% rename from packages/app-locking-mechanism/.babelrc.js rename to packages/app-record-locking/.babelrc.js diff --git a/packages/app-locking-mechanism/LICENSE b/packages/app-record-locking/LICENSE similarity index 100% rename from packages/app-locking-mechanism/LICENSE rename to packages/app-record-locking/LICENSE diff --git a/packages/app-locking-mechanism/README.md b/packages/app-record-locking/README.md similarity index 56% rename from packages/app-locking-mechanism/README.md rename to packages/app-record-locking/README.md index cbb628109b3..359bd6dabe5 100644 --- a/packages/app-locking-mechanism/README.md +++ b/packages/app-record-locking/README.md @@ -1,6 +1,6 @@ -# @webiny/app-locking-mechanism -[![](https://img.shields.io/npm/dw/@webiny/app-locking-mechanism.svg)](https://www.npmjs.com/package/@webiny/app-locking-mechanism) -[![](https://img.shields.io/npm/v/@webiny/app-locking-mechanism.svg)](https://www.npmjs.com/package/@webiny/app-locking-mechanism) +# @webiny/app-record-locking +[![](https://img.shields.io/npm/dw/@webiny/app-record-locking.svg)](https://www.npmjs.com/package/@webiny/app-record-locking) +[![](https://img.shields.io/npm/v/@webiny/app-record-locking.svg)](https://www.npmjs.com/package/@webiny/app-record-locking) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) @@ -8,5 +8,5 @@ Exposes a simple `SocketsProvider` React provider component and enables you to q ## Install ``` -yarn add @webiny/app-locking-mechanism +yarn add @webiny/app-record-locking ``` diff --git a/packages/app-locking-mechanism/package.json b/packages/app-record-locking/package.json similarity index 94% rename from packages/app-locking-mechanism/package.json rename to packages/app-record-locking/package.json index fde34bb190b..fd1e61c01fe 100644 --- a/packages/app-locking-mechanism/package.json +++ b/packages/app-record-locking/package.json @@ -1,5 +1,5 @@ { - "name": "@webiny/app-locking-mechanism", + "name": "@webiny/app-record-locking", "version": "0.0.0", "main": "index.js", "repository": { @@ -15,7 +15,7 @@ "dependencies": { "@apollo/react-hooks": "^3.1.5", "@emotion/styled": "^11.10.6", - "@material-design-icons/svg": "^0.14.13", + "@material-design-icons/svg": "^0.14.2", "@webiny/app": "0.0.0", "@webiny/app-admin": "0.0.0", "@webiny/app-headless-cms": "0.0.0", diff --git a/packages/app-locking-mechanism/src/components/HeadlessCmsActionsAcoCell.tsx b/packages/app-record-locking/src/components/HeadlessCmsActionsAcoCell.tsx similarity index 85% rename from packages/app-locking-mechanism/src/components/HeadlessCmsActionsAcoCell.tsx rename to packages/app-record-locking/src/components/HeadlessCmsActionsAcoCell.tsx index 33b515d6cce..09a1c1a1eba 100644 --- a/packages/app-locking-mechanism/src/components/HeadlessCmsActionsAcoCell.tsx +++ b/packages/app-record-locking/src/components/HeadlessCmsActionsAcoCell.tsx @@ -2,7 +2,7 @@ import React from "react"; import { ContentEntryListConfig } from "@webiny/app-headless-cms"; import { ReactComponent as LockedIcon } from "./assets/lock.svg"; import { Tooltip } from "@webiny/ui/Tooltip"; -import { useLockingMechanism } from "~/hooks"; +import { useRecordLocking } from "~/hooks"; import { UseContentEntriesListHookDecorator } from "./decorators/UseContentEntriesListHookDecorator"; import { CellActions } from "@webiny/app-headless-cms/admin/components/ContentEntries/Table/Cells"; import styled from "@emotion/styled"; @@ -15,9 +15,9 @@ const CenterAlignment = styled("div")({ width: "28px" }); -const LockingMechanismCellActions = CellActions.createDecorator(Original => { - return function LockingMechanismCellActions(props) { - const { getLockRecordEntry, isRecordLocked } = useLockingMechanism(); +const RecordLockingCellActions = CellActions.createDecorator(Original => { + return function RecordLockingCellActions(props) { + const { getLockRecordEntry, isRecordLocked } = useRecordLocking(); const { useTableRow, isFolderRow } = ContentEntryListConfig.Browser.Table.Column; const { row } = useTableRow(); @@ -50,7 +50,7 @@ export const HeadlessCmsActionsAcoCell = () => { - + ); }; diff --git a/packages/app-locking-mechanism/src/components/HeadlessCmsContentEntry/ContentEntryGuard.tsx b/packages/app-record-locking/src/components/HeadlessCmsContentEntry/ContentEntryGuard.tsx similarity index 87% rename from packages/app-locking-mechanism/src/components/HeadlessCmsContentEntry/ContentEntryGuard.tsx rename to packages/app-record-locking/src/components/HeadlessCmsContentEntry/ContentEntryGuard.tsx index f0ea9fca05a..62bbb7f0f86 100644 --- a/packages/app-locking-mechanism/src/components/HeadlessCmsContentEntry/ContentEntryGuard.tsx +++ b/packages/app-record-locking/src/components/HeadlessCmsContentEntry/ContentEntryGuard.tsx @@ -1,12 +1,12 @@ import { useContentEntry } from "@webiny/app-headless-cms"; -import { useLockingMechanism } from "~/hooks"; +import { useRecordLocking } from "~/hooks"; import { Elevation } from "@webiny/ui/Elevation"; import { CircularProgress } from "@webiny/ui/Progress"; import { css } from "emotion"; import styled from "@emotion/styled"; import React, { useEffect, useState } from "react"; import { LockedRecord } from "../LockedRecord"; -import { ILockingMechanismLockRecord } from "~/types"; +import { IRecordLockingLockRecord } from "~/types"; const DetailsContainer = styled("div")({ height: "calc(100% - 10px)", @@ -37,9 +37,9 @@ export interface IContentEntryGuardProps { export const ContentEntryGuard = (props: IContentEntryGuardProps) => { const { loading, entry, contentModel: model } = useContentEntry(); const { children } = props; - const { fetchLockedEntryLockRecord } = useLockingMechanism(); + const { fetchLockedEntryLockRecord } = useRecordLocking(); - const [locked, setLocked] = useState(undefined); + const [locked, setLocked] = useState(undefined); useEffect(() => { if (!entry.id || loading || locked !== undefined) { diff --git a/packages/app-locking-mechanism/src/components/HeadlessCmsContentEntry/ContentEntryLocker.tsx b/packages/app-record-locking/src/components/HeadlessCmsContentEntry/ContentEntryLocker.tsx similarity index 88% rename from packages/app-locking-mechanism/src/components/HeadlessCmsContentEntry/ContentEntryLocker.tsx rename to packages/app-record-locking/src/components/HeadlessCmsContentEntry/ContentEntryLocker.tsx index cca3c26476a..b5c9e6afd04 100644 --- a/packages/app-locking-mechanism/src/components/HeadlessCmsContentEntry/ContentEntryLocker.tsx +++ b/packages/app-record-locking/src/components/HeadlessCmsContentEntry/ContentEntryLocker.tsx @@ -1,11 +1,7 @@ import { useContentEntriesList, useContentEntry } from "@webiny/app-headless-cms"; import React, { useEffect, useRef } from "react"; -import { useLockingMechanism } from "~/hooks"; -import { - IIsRecordLockedParams, - ILockingMechanismIdentity, - ILockingMechanismLockRecord -} from "~/types"; +import { useRecordLocking } from "~/hooks"; +import { IIsRecordLockedParams, IRecordLockingIdentity, IRecordLockingLockRecord } from "~/types"; import { IncomingGenericData, IWebsocketsSubscription, @@ -20,12 +16,12 @@ export interface IContentEntryLockerProps { export interface IKickOutWebsocketsMessage extends IncomingGenericData { data: { - record: ILockingMechanismLockRecord; - user: ILockingMechanismIdentity; + record: IRecordLockingLockRecord; + user: IRecordLockingIdentity; }; } interface IForceUnlockedProps { - user: ILockingMechanismIdentity; + user: IRecordLockingIdentity; } const ForceUnlocked = ({ user }: IForceUnlockedProps) => { return ( @@ -40,7 +36,7 @@ const ForceUnlocked = ({ user }: IForceUnlockedProps) => { export const ContentEntryLocker = ({ children }: IContentEntryLockerProps) => { const { entry, contentModel: model } = useContentEntry(); const { updateEntryLock, unlockEntry, fetchLockedEntryLockRecord, removeEntryLock } = - useLockingMechanism(); + useRecordLocking(); const { navigateTo } = useContentEntriesList(); @@ -59,7 +55,7 @@ export const ContentEntryLocker = ({ children }: IContentEntryLockerProps) => { const { id: entryId } = parseIdentifier(entry.id); subscription.current = websockets.onMessage( - `lockingMechanism.entry.kickOut.${entryId}`, + `recordLocking.entry.kickOut.${entryId}`, async incoming => { const { user } = incoming.data; const record: IIsRecordLockedParams = { diff --git a/packages/app-locking-mechanism/src/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.tsx b/packages/app-record-locking/src/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.tsx similarity index 93% rename from packages/app-locking-mechanism/src/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.tsx rename to packages/app-record-locking/src/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.tsx index 75537f4dce0..4ea075e5b6b 100644 --- a/packages/app-locking-mechanism/src/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.tsx +++ b/packages/app-record-locking/src/components/HeadlessCmsContentEntry/HeadlessCmsContentEntry.tsx @@ -5,7 +5,7 @@ import { ContentEntryLocker } from "./ContentEntryLocker"; import { useContentEntry } from "@webiny/app-headless-cms"; export const HeadlessCmsContentEntry = ContentEntry.createDecorator(Original => { - return function LockingMechanismContentEntry(props) { + return function RecordLockingContentEntry(props) { const { entry } = useContentEntry(); /** * New entry does not have ID yet. diff --git a/packages/app-locking-mechanism/src/components/HeadlessCmsContentEntry/index.ts b/packages/app-record-locking/src/components/HeadlessCmsContentEntry/index.ts similarity index 100% rename from packages/app-locking-mechanism/src/components/HeadlessCmsContentEntry/index.ts rename to packages/app-record-locking/src/components/HeadlessCmsContentEntry/index.ts diff --git a/packages/app-locking-mechanism/src/components/LockedRecord/LockedRecord.tsx b/packages/app-record-locking/src/components/LockedRecord/LockedRecord.tsx similarity index 94% rename from packages/app-locking-mechanism/src/components/LockedRecord/LockedRecord.tsx rename to packages/app-record-locking/src/components/LockedRecord/LockedRecord.tsx index d59641f08b1..d40d72e915c 100644 --- a/packages/app-locking-mechanism/src/components/LockedRecord/LockedRecord.tsx +++ b/packages/app-record-locking/src/components/LockedRecord/LockedRecord.tsx @@ -1,11 +1,11 @@ import React from "react"; import styled from "@emotion/styled"; import { Elevation as BaseElevation } from "@webiny/ui/Elevation"; -import { useLockingMechanism } from "~/hooks"; +import { useRecordLocking } from "~/hooks"; import { useContentEntry } from "@webiny/app-headless-cms"; import { LockedRecordForceUnlock } from "./LockedRecordForceUnlock"; import { ReactComponent as LockIcon } from "@material-design-icons/svg/outlined/lock.svg"; -import { ILockingMechanismLockRecord } from "~/types"; +import { IRecordLockingLockRecord } from "~/types"; const StyledWrapper = styled("div")({ width: "50%", @@ -89,11 +89,11 @@ const Elevation = styled(BaseElevation)({ }); export interface ILockedRecordProps { - record: ILockingMechanismLockRecord; + record: IRecordLockingLockRecord; } export const LockedRecord = ({ record: lockRecordEntry }: ILockedRecordProps) => { - const { getLockRecordEntry } = useLockingMechanism(); + const { getLockRecordEntry } = useRecordLocking(); const record = getLockRecordEntry(lockRecordEntry.id); diff --git a/packages/app-locking-mechanism/src/components/LockedRecord/LockedRecordForceUnlock.tsx b/packages/app-record-locking/src/components/LockedRecord/LockedRecordForceUnlock.tsx similarity index 89% rename from packages/app-locking-mechanism/src/components/LockedRecord/LockedRecordForceUnlock.tsx rename to packages/app-record-locking/src/components/LockedRecord/LockedRecordForceUnlock.tsx index 14f9efd6f90..f90784ba836 100644 --- a/packages/app-locking-mechanism/src/components/LockedRecord/LockedRecordForceUnlock.tsx +++ b/packages/app-record-locking/src/components/LockedRecord/LockedRecordForceUnlock.tsx @@ -1,8 +1,8 @@ import React, { useCallback, useEffect, useState } from "react"; import styled from "@emotion/styled"; -import { ILockingMechanismError, ILockingMechanismIdentity } from "~/types"; +import { IRecordLockingError, IRecordLockingIdentity } from "~/types"; import { useConfirmationDialog, useSnackbar } from "@webiny/app-admin"; -import { useLockingMechanism, usePermission } from "~/hooks"; +import { useRecordLocking, usePermission } from "~/hooks"; import { useRouter } from "@webiny/react-router"; import { useContentEntriesList } from "@webiny/app-headless-cms"; import { Alert } from "@webiny/ui/Alert"; @@ -25,7 +25,7 @@ export interface ILockedRecordForceUnlockProps { id: string; type: string; title: string; - lockedBy?: ILockingMechanismIdentity; + lockedBy?: IRecordLockingIdentity; } const ErrorMessage = (props: ILockedRecordForceUnlockProps) => { @@ -47,7 +47,7 @@ const ErrorMessage = (props: ILockedRecordForceUnlockProps) => { }; export const LockedRecordForceUnlock = (props: ILockedRecordForceUnlockProps) => { - const { unlockEntryForce } = useLockingMechanism(); + const { unlockEntryForce } = useRecordLocking(); const { navigateTo } = useContentEntriesList(); const { showConfirmation: showForceUnlockConfirmation } = useConfirmationDialog({ @@ -58,7 +58,7 @@ export const LockedRecordForceUnlock = (props: ILockedRecordForceUnlockProps) => const { history } = useRouter(); - const [error, setError] = useState(); + const [error, setError] = useState(); useEffect(() => { if (!error?.message) { diff --git a/packages/app-locking-mechanism/src/components/LockedRecord/index.ts b/packages/app-record-locking/src/components/LockedRecord/index.ts similarity index 100% rename from packages/app-locking-mechanism/src/components/LockedRecord/index.ts rename to packages/app-record-locking/src/components/LockedRecord/index.ts diff --git a/packages/app-locking-mechanism/src/components/LockingMechanismProvider.tsx b/packages/app-record-locking/src/components/RecordLockingProvider.tsx similarity index 63% rename from packages/app-locking-mechanism/src/components/LockingMechanismProvider.tsx rename to packages/app-record-locking/src/components/RecordLockingProvider.tsx index 04b4686fe42..f4215cb5e54 100644 --- a/packages/app-locking-mechanism/src/components/LockingMechanismProvider.tsx +++ b/packages/app-record-locking/src/components/RecordLockingProvider.tsx @@ -1,28 +1,26 @@ import React, { useCallback, useMemo, useState } from "react"; import { useApolloClient } from "@apollo/react-hooks"; -import { createLockingMechanism } from "~/domain/LockingMechanism"; +import { createRecordLocking } from "~/domain/RecordLocking"; import { IFetchLockedEntryLockRecordParams, IFetchLockRecordParams, - ILockingMechanismContext, - ILockingMechanismError, - IPossiblyLockingMechanismRecord, + IRecordLockingContext, + IRecordLockingError, + IPossiblyRecordLockingRecord, IUnlockEntryParams, IUpdateEntryLockParams } from "~/types"; import { useStateIfMounted } from "@webiny/app-admin"; -export interface ILockingMechanismProviderProps { +export interface IRecordLockingProviderProps { children: React.ReactNode; } -export const LockingMechanismContext = React.createContext( - {} as unknown as ILockingMechanismContext -); +export const RecordLockingContext = React.createContext({} as unknown as IRecordLockingContext); const isSameArray = ( - existingRecords: Pick[], - newRecords: Pick[] + existingRecords: Pick[], + newRecords: Pick[] ): boolean => { if (existingRecords.length !== newRecords.length) { return false; @@ -32,24 +30,24 @@ const isSameArray = ( }); }; -export const LockingMechanismProvider = (props: ILockingMechanismProviderProps) => { +export const RecordLockingProvider = (props: IRecordLockingProviderProps) => { const client = useApolloClient(); const [loading, setLoading] = useState(false); - const lockingMechanism = useMemo(() => { - return createLockingMechanism({ + const recordLocking = useMemo(() => { + return createRecordLocking({ client, setLoading }); }, []); - const [error, setError] = useStateIfMounted(null); + const [error, setError] = useStateIfMounted(null); - const [records, setRecords] = useStateIfMounted([]); + const [records, setRecords] = useStateIfMounted([]); const setRecordsIfNeeded = useCallback( - (newRecords: IPossiblyLockingMechanismRecord[]) => { + (newRecords: IPossiblyRecordLockingRecord[]) => { const sameArray = isSameArray(records, newRecords); if (sameArray) { return; @@ -59,9 +57,9 @@ export const LockingMechanismProvider = (props: ILockingMechanismProviderProps) [records] ); - const value: ILockingMechanismContext = { + const value: IRecordLockingContext = { async updateEntryLock(params: IUpdateEntryLockParams) { - const result = await lockingMechanism.updateEntryLock(params); + const result = await recordLocking.updateEntryLock(params); if (result.error) { setError(result.error); return; @@ -88,29 +86,29 @@ export const LockingMechanismProvider = (props: ILockingMechanismProviderProps) }); }, async unlockEntry(params: IUnlockEntryParams) { - return await lockingMechanism.unlockEntry(params); + return await recordLocking.unlockEntry(params); }, async unlockEntryForce(params: IUnlockEntryParams) { - return await lockingMechanism.unlockEntry(params, true); + return await recordLocking.unlockEntry(params, true); }, isLockExpired(input: Date | string): boolean { - return lockingMechanism.isLockExpired(input); + return recordLocking.isLockExpired(input); }, isRecordLocked(record) { if (!record) { return false; } - return lockingMechanism.isRecordLocked(record); + return recordLocking.isRecordLocked(record); }, getLockRecordEntry(id: string) { - return lockingMechanism.getLockRecordEntry(id); + return recordLocking.getLockRecordEntry(id); }, removeEntryLock(params: IUnlockEntryParams) { - return lockingMechanism.removeEntryLock(params); + return recordLocking.removeEntryLock(params); }, async fetchLockRecord(params: IFetchLockRecordParams) { try { - return await lockingMechanism.fetchLockRecord(params); + return await recordLocking.fetchLockRecord(params); } catch (ex) { return { data: null, @@ -119,12 +117,12 @@ export const LockingMechanismProvider = (props: ILockingMechanismProviderProps) } }, async fetchLockedEntryLockRecord(params: IFetchLockedEntryLockRecordParams) { - return lockingMechanism.fetchLockedEntryLockRecord(params); + return recordLocking.fetchLockedEntryLockRecord(params); }, async setRecords(folderId, type, newRecords) { setRecordsIfNeeded(newRecords); - const result = await lockingMechanism.setRecords(folderId, type, newRecords); + const result = await recordLocking.setRecords(folderId, type, newRecords); if (!result) { return; } @@ -135,5 +133,5 @@ export const LockingMechanismProvider = (props: ILockingMechanismProviderProps) loading }; - return ; + return ; }; diff --git a/packages/app-locking-mechanism/src/components/assets/lock.svg b/packages/app-record-locking/src/components/assets/lock.svg similarity index 100% rename from packages/app-locking-mechanism/src/components/assets/lock.svg rename to packages/app-record-locking/src/components/assets/lock.svg diff --git a/packages/app-locking-mechanism/src/components/decorators/UseContentEntriesListHookDecorator.ts b/packages/app-record-locking/src/components/decorators/UseContentEntriesListHookDecorator.ts similarity index 53% rename from packages/app-locking-mechanism/src/components/decorators/UseContentEntriesListHookDecorator.ts rename to packages/app-record-locking/src/components/decorators/UseContentEntriesListHookDecorator.ts index 24281550ee3..d8ff7dcea6b 100644 --- a/packages/app-locking-mechanism/src/components/decorators/UseContentEntriesListHookDecorator.ts +++ b/packages/app-record-locking/src/components/decorators/UseContentEntriesListHookDecorator.ts @@ -1,24 +1,24 @@ import { useEffect } from "react"; import { useContentEntriesList } from "@webiny/app-headless-cms"; -import { useLockingMechanism } from "~/hooks"; +import { useRecordLocking } from "~/hooks"; export const UseContentEntriesListHookDecorator = useContentEntriesList.createDecorator( originalHook => { - return function LockingMechanismUseContentEntriesList() { + return function RecordLockingUseContentEntriesList() { const value = originalHook(); - const lockingMechanism = useLockingMechanism(); + const recordLocking = useRecordLocking(); useEffect(() => { if (!value.records) { return; } - lockingMechanism.setRecords(value.folderId, value.modelId, value.records); - }, [value.folderId, value.modelId, value.records, lockingMechanism]); + recordLocking.setRecords(value.folderId, value.modelId, value.records); + }, [value.folderId, value.modelId, value.records, recordLocking]); return { ...value, - records: lockingMechanism.records + records: recordLocking.records }; }; } diff --git a/packages/app-locking-mechanism/src/components/decorators/UseSaveAndPublishHookDecorator.tsx b/packages/app-record-locking/src/components/decorators/UseSaveAndPublishHookDecorator.tsx similarity index 93% rename from packages/app-locking-mechanism/src/components/decorators/UseSaveAndPublishHookDecorator.tsx rename to packages/app-record-locking/src/components/decorators/UseSaveAndPublishHookDecorator.tsx index 28656e96165..edbb91fb281 100644 --- a/packages/app-locking-mechanism/src/components/decorators/UseSaveAndPublishHookDecorator.tsx +++ b/packages/app-record-locking/src/components/decorators/UseSaveAndPublishHookDecorator.tsx @@ -3,15 +3,15 @@ import { ShowConfirmationDialogParams, useSaveAndPublish } from "@webiny/app-headless-cms/admin/components/ContentEntryForm/Header/SaveAndPublishContent/useSaveAndPublish"; -import { useLockingMechanism } from "~/hooks"; +import { useRecordLocking } from "~/hooks"; import { useContentEntry } from "@webiny/app-headless-cms"; import { useSnackbar } from "@webiny/app-admin"; export const UseSaveAndPublishHookDecorator = useSaveAndPublish.createDecorator(originalHook => { - return function useLockingMechanismUseSaveAndPublish() { + return function useRecordLockingUseSaveAndPublish() { const values = originalHook(); const { entry, contentModel: model } = useContentEntry(); - const { fetchLockedEntryLockRecord, updateEntryLock } = useLockingMechanism(); + const { fetchLockedEntryLockRecord, updateEntryLock } = useRecordLocking(); const { showSnackbar } = useSnackbar(); const showConfirmationDialog = useCallback( diff --git a/packages/app-locking-mechanism/src/components/decorators/UseSaveHookDecorator.tsx b/packages/app-record-locking/src/components/decorators/UseSaveHookDecorator.tsx similarity index 93% rename from packages/app-locking-mechanism/src/components/decorators/UseSaveHookDecorator.tsx rename to packages/app-record-locking/src/components/decorators/UseSaveHookDecorator.tsx index c2cd7ac63bc..15103f62fc2 100644 --- a/packages/app-locking-mechanism/src/components/decorators/UseSaveHookDecorator.tsx +++ b/packages/app-record-locking/src/components/decorators/UseSaveHookDecorator.tsx @@ -1,14 +1,14 @@ import { useCallback } from "react"; -import { useLockingMechanism } from "~/hooks"; +import { useRecordLocking } from "~/hooks"; import { useContentEntry } from "@webiny/app-headless-cms"; import { useSnackbar } from "@webiny/app-admin"; import { useSave } from "@webiny/app-headless-cms/admin/components/ContentEntryForm/Header/SaveContent/useSave"; export const UseSaveHookDecorator = useSave.createDecorator(originalHook => { - return function useLockingMechanismUseSave() { + return function useRecordLockingUseSave() { const values = originalHook(); const { entry, contentModel: model } = useContentEntry(); - const { fetchLockedEntryLockRecord, updateEntryLock } = useLockingMechanism(); + const { fetchLockedEntryLockRecord, updateEntryLock } = useRecordLocking(); const { showSnackbar } = useSnackbar(); const saveEntry = useCallback( diff --git a/packages/app-locking-mechanism/src/domain/LockingMechanism.ts b/packages/app-record-locking/src/domain/RecordLocking.ts similarity index 67% rename from packages/app-locking-mechanism/src/domain/LockingMechanism.ts rename to packages/app-record-locking/src/domain/RecordLocking.ts index 310fe5d8e43..9375fd3f09e 100644 --- a/packages/app-locking-mechanism/src/domain/LockingMechanism.ts +++ b/packages/app-record-locking/src/domain/RecordLocking.ts @@ -1,93 +1,90 @@ -import { - ILockingMechanism, - ILockingMechanismUpdateEntryLockResult -} from "./abstractions/ILockingMechanism"; +import { IRecordLocking, IRecordLockingUpdateEntryLockResult } from "./abstractions/IRecordLocking"; import { ApolloClient } from "apollo-client"; -import { LockingMechanismGetLockRecord } from "./LockingMechanismGetLockRecord"; -import { LockingMechanismIsEntryLocked } from "./LockingMechanismIsEntryLocked"; -import { LockingMechanismListLockRecords } from "./LockingMechanismListLockRecords"; -import { LockingMechanismLockEntry } from "./LockingMechanismLockEntry"; -import { LockingMechanismUnlockEntry } from "./LockingMechanismUnlockEntry"; -import { LockingMechanismUnlockEntryRequest } from "./LockingMechanismUnlockEntryRequest"; -import { LockingMechanismClient } from "./LockingMechanismClient"; -import { ILockingMechanismGetLockRecord } from "./abstractions/ILockingMechanismGetLockRecord"; -import { ILockingMechanismIsEntryLocked } from "./abstractions/ILockingMechanismIsEntryLocked"; +import { RecordLockingGetLockRecord } from "./RecordLockingGetLockRecord"; +import { RecordLockingIsEntryLocked } from "./RecordLockingIsEntryLocked"; +import { RecordLockingListLockRecords } from "./RecordLockingListLockRecords"; +import { RecordLockingLockEntry } from "./RecordLockingLockEntry"; +import { RecordLockingUnlockEntry } from "./RecordLockingUnlockEntry"; +import { RecordLockingUnlockEntryRequest } from "./RecordLockingUnlockEntryRequest"; +import { RecordLockingClient } from "./RecordLockingClient"; +import { IRecordLockingGetLockRecord } from "./abstractions/IRecordLockingGetLockRecord"; +import { IRecordLockingIsEntryLocked } from "./abstractions/IRecordLockingIsEntryLocked"; import { - ILockingMechanismListLockRecords, - ILockingMechanismListLockRecordsResult -} from "./abstractions/ILockingMechanismListLockRecords"; -import { ILockingMechanismLockEntry } from "./abstractions/ILockingMechanismLockEntry"; + IRecordLockingListLockRecords, + IRecordLockingListLockRecordsResult +} from "./abstractions/IRecordLockingListLockRecords"; +import { IRecordLockingLockEntry } from "./abstractions/IRecordLockingLockEntry"; import { - ILockingMechanismUnlockEntry, - ILockingMechanismUnlockEntryResult -} from "./abstractions/ILockingMechanismUnlockEntry"; -import { ILockingMechanismUnlockEntryRequest } from "./abstractions/ILockingMechanismUnlockEntryRequest"; + IRecordLockingUnlockEntry, + IRecordLockingUnlockEntryResult +} from "./abstractions/IRecordLockingUnlockEntry"; +import { IRecordLockingUnlockEntryRequest } from "./abstractions/IRecordLockingUnlockEntryRequest"; import { IFetchLockedEntryLockRecordParams, IFetchLockRecordParams, IFetchLockRecordResult, IIsRecordLockedParams, - ILockingMechanismError, - ILockingMechanismLockRecord, - ILockingMechanismRecord, - IPossiblyLockingMechanismRecord, + IRecordLockingError, + IRecordLockingLockRecord, + IRecordLockingRecord, + IPossiblyRecordLockingRecord, IUnlockEntryParams, IUpdateEntryLockParams } from "~/types"; -import { ILockingMechanismClient } from "./abstractions/ILockingMechanismClient"; -import { createLockingMechanismError } from "./utils/createLockingMechanismError"; +import { IRecordLockingClient } from "./abstractions/IRecordLockingClient"; +import { createRecordLockingError } from "./utils/createRecordLockingError"; import { parseIdentifier } from "@webiny/utils/parseIdentifier"; import { createCacheKey } from "~/utils/createCacheKey"; -import { LockingMechanismUpdateEntryLock } from "~/domain/LockingMechanismUpdateEntryLock"; -import { ILockingMechanismUpdateEntryLock } from "~/domain/abstractions/ILockingMechanismUpdateEntryLock"; -import { LockingMechanismGetLockedEntryLockRecord } from "~/domain/LockingMechanismGetLockedEntryLockRecord"; -import { ILockingMechanismGetLockedEntryLockRecord } from "./abstractions/ILockingMechanismGetLockedEntryLockRecord"; +import { RecordLockingUpdateEntryLock } from "~/domain/RecordLockingUpdateEntryLock"; +import { IRecordLockingUpdateEntryLock } from "~/domain/abstractions/IRecordLockingUpdateEntryLock"; +import { RecordLockingGetLockedEntryLockRecord } from "~/domain/RecordLockingGetLockedEntryLockRecord"; +import { IRecordLockingGetLockedEntryLockRecord } from "./abstractions/IRecordLockingGetLockedEntryLockRecord"; -export interface ICreateLockingMechanismParams { +export interface ICreateRecordLockingParams { client: ApolloClient; setLoading: (loading: boolean) => void; } -export interface ILockingMechanismParams { - client: ILockingMechanismClient; +export interface IRecordLockingParams { + client: IRecordLockingClient; setLoading: (loading: boolean) => void; - getLockRecord: ILockingMechanismGetLockRecord; - getLockedEntryLockRecord: ILockingMechanismGetLockedEntryLockRecord; - isEntryLocked: ILockingMechanismIsEntryLocked; - listLockRecords: ILockingMechanismListLockRecords; - lockEntry: ILockingMechanismLockEntry; - unlockEntry: ILockingMechanismUnlockEntry; - unlockEntryRequest: ILockingMechanismUnlockEntryRequest; - updateEntryLock: ILockingMechanismUpdateEntryLock; + getLockRecord: IRecordLockingGetLockRecord; + getLockedEntryLockRecord: IRecordLockingGetLockedEntryLockRecord; + isEntryLocked: IRecordLockingIsEntryLocked; + listLockRecords: IRecordLockingListLockRecords; + lockEntry: IRecordLockingLockEntry; + unlockEntry: IRecordLockingUnlockEntry; + unlockEntryRequest: IRecordLockingUnlockEntryRequest; + updateEntryLock: IRecordLockingUpdateEntryLock; } export interface IOnErrorCb { - (error: ILockingMechanismError): void; + (error: IRecordLockingError): void; } -class LockingMechanism - implements ILockingMechanism +class RecordLocking + implements IRecordLocking { private currentRecordType?: string; private currentFolderId?: string; private currentRecordsCacheKey?: string; private readonly _setLoading: (loading: boolean) => void; public loading = false; - public records: ILockingMechanismRecord[] = []; - - private readonly client: ILockingMechanismClient; - private readonly _getLockRecord: ILockingMechanismGetLockRecord; - private readonly _isEntryLocked: ILockingMechanismIsEntryLocked; - private readonly _getLockedEntryLockRecord: ILockingMechanismGetLockedEntryLockRecord; - private readonly _listLockRecords: ILockingMechanismListLockRecords; - private readonly _lockEntry: ILockingMechanismLockEntry; - private readonly _unlockEntry: ILockingMechanismUnlockEntry; - private readonly _unlockEntryRequest: ILockingMechanismUnlockEntryRequest; - private readonly _updateEntryLock: ILockingMechanismUpdateEntryLock; + public records: IRecordLockingRecord[] = []; + + private readonly client: IRecordLockingClient; + private readonly _getLockRecord: IRecordLockingGetLockRecord; + private readonly _isEntryLocked: IRecordLockingIsEntryLocked; + private readonly _getLockedEntryLockRecord: IRecordLockingGetLockedEntryLockRecord; + private readonly _listLockRecords: IRecordLockingListLockRecords; + private readonly _lockEntry: IRecordLockingLockEntry; + private readonly _unlockEntry: IRecordLockingUnlockEntry; + private readonly _unlockEntryRequest: IRecordLockingUnlockEntryRequest; + private readonly _updateEntryLock: IRecordLockingUpdateEntryLock; private onErrorCb: IOnErrorCb | null = null; - public constructor(params: ILockingMechanismParams) { + public constructor(params: IRecordLockingParams) { this.client = params.client; this._setLoading = params.setLoading; this._getLockRecord = params.getLockRecord; @@ -104,7 +101,7 @@ class LockingMechanism { + ): Promise { const result = await this.fetchAndAssignRecords(folderId, type, records); if (!result) { return undefined; @@ -147,7 +144,7 @@ class LockingMechanism { + ): Promise { const { id, $lockingType } = params; const { id: entryId } = parseIdentifier(id); @@ -158,7 +155,7 @@ class LockingMechanism { const { id: entryId } = parseIdentifier(id); return record.entryId === entryId; @@ -180,7 +177,7 @@ class LockingMechanism { + ): Promise { try { return await this._updateEntryLock.execute({ id: params.id, @@ -212,7 +209,7 @@ class LockingMechanism { + ): Promise { try { const result = await this._unlockEntry.execute({ id: params.id, @@ -249,7 +246,7 @@ class LockingMechanism { + ): Promise { if (records.length === 0) { return; } else if (this.loading) { @@ -282,7 +279,7 @@ class LockingMechanism( - config: ICreateLockingMechanismParams -): ILockingMechanism => { - const client = new LockingMechanismClient({ +export const createRecordLocking = ( + config: ICreateRecordLockingParams +): IRecordLocking => { + const client = new RecordLockingClient({ client: config.client }); - const getLockRecord = new LockingMechanismGetLockRecord({ + const getLockRecord = new RecordLockingGetLockRecord({ client }); - const getLockedEntryLockRecord = new LockingMechanismGetLockedEntryLockRecord({ + const getLockedEntryLockRecord = new RecordLockingGetLockedEntryLockRecord({ client }); - const isEntryLocked = new LockingMechanismIsEntryLocked({ + const isEntryLocked = new RecordLockingIsEntryLocked({ client }); - const listLockRecords = new LockingMechanismListLockRecords({ + const listLockRecords = new RecordLockingListLockRecords({ client }); - const lockEntry = new LockingMechanismLockEntry({ + const lockEntry = new RecordLockingLockEntry({ client }); - const unlockEntry = new LockingMechanismUnlockEntry({ + const unlockEntry = new RecordLockingUnlockEntry({ client }); - const unlockEntryRequest = new LockingMechanismUnlockEntryRequest({ + const unlockEntryRequest = new RecordLockingUnlockEntryRequest({ client }); - const updateEntryLock = new LockingMechanismUpdateEntryLock({ + const updateEntryLock = new RecordLockingUpdateEntryLock({ client }); - return new LockingMechanism({ + return new RecordLocking({ client, setLoading: config.setLoading, getLockRecord, diff --git a/packages/app-locking-mechanism/src/domain/LockingMechanismClient.ts b/packages/app-record-locking/src/domain/RecordLockingClient.ts similarity index 71% rename from packages/app-locking-mechanism/src/domain/LockingMechanismClient.ts rename to packages/app-record-locking/src/domain/RecordLockingClient.ts index 2304a511481..6856f43585c 100644 --- a/packages/app-locking-mechanism/src/domain/LockingMechanismClient.ts +++ b/packages/app-record-locking/src/domain/RecordLockingClient.ts @@ -1,15 +1,15 @@ import { ApolloClient, ApolloQueryResult, MutationOptions, QueryOptions } from "apollo-client"; import { FetchResult } from "apollo-link"; -import { ILockingMechanismClient } from "~/domain/abstractions/ILockingMechanismClient"; +import { IRecordLockingClient } from "~/domain/abstractions/IRecordLockingClient"; -export interface ILockingMechanismClientParams { +export interface IRecordLockingClientParams { client: ApolloClient; } -export class LockingMechanismClient implements ILockingMechanismClient { +export class RecordLockingClient implements IRecordLockingClient { private readonly client: ApolloClient; - public constructor(params: ILockingMechanismClientParams) { + public constructor(params: IRecordLockingClientParams) { this.client = params.client; } diff --git a/packages/app-record-locking/src/domain/RecordLockingGetLockRecord.ts b/packages/app-record-locking/src/domain/RecordLockingGetLockRecord.ts new file mode 100644 index 00000000000..0d15994616a --- /dev/null +++ b/packages/app-record-locking/src/domain/RecordLockingGetLockRecord.ts @@ -0,0 +1,41 @@ +import { + IRecordLockingGetLockRecord, + IRecordLockingGetLockRecordExecuteParams, + IRecordLockingGetLockRecordExecuteResult +} from "~/domain/abstractions/IRecordLockingGetLockRecord"; +import { IRecordLockingClient } from "~/domain/abstractions/IRecordLockingClient"; +import { + GET_LOCK_RECORD_QUERY, + IRecordLockingGetLockRecordResponse, + IRecordLockingGetLockRecordVariables +} from "~/domain/graphql/getLockRecord"; +import { WebinyError } from "@webiny/error"; + +interface Params { + client: IRecordLockingClient; +} + +export class RecordLockingGetLockRecord implements IRecordLockingGetLockRecord { + private readonly client: IRecordLockingClient; + + public constructor(params: Params) { + this.client = params.client; + } + public async execute( + params: IRecordLockingGetLockRecordExecuteParams + ): Promise { + const result = await this.client.query< + IRecordLockingGetLockRecordResponse, + IRecordLockingGetLockRecordVariables + >({ + query: GET_LOCK_RECORD_QUERY, + variables: params + }); + if (result.data.recordLocking.getLockRecord.error) { + throw new WebinyError(result.data.recordLocking.getLockRecord.error); + } else if (!result.data.recordLocking.getLockRecord.data) { + throw new WebinyError("No data returned from server."); + } + return result.data.recordLocking.getLockRecord; + } +} diff --git a/packages/app-record-locking/src/domain/RecordLockingGetLockedEntryLockRecord.ts b/packages/app-record-locking/src/domain/RecordLockingGetLockedEntryLockRecord.ts new file mode 100644 index 00000000000..b851d40eeae --- /dev/null +++ b/packages/app-record-locking/src/domain/RecordLockingGetLockedEntryLockRecord.ts @@ -0,0 +1,41 @@ +import { IRecordLockingClient } from "~/domain/abstractions/IRecordLockingClient"; +import { + GET_LOCKED_ENTRY_LOCK_RECORD_QUERY, + IRecordLockingGetLockedEntryLockRecordResponse, + IRecordLockingGetLockedEntryLockRecordVariables +} from "~/domain/graphql/getLockedEntryLockRecord"; +import { WebinyError } from "@webiny/error"; +import { + IRecordLockingGetLockedEntryLockRecord, + IRecordLockingGetLockedEntryLockRecordExecuteParams, + IRecordLockingGetLockedEntryLockRecordExecuteResult +} from "~/domain/abstractions/IRecordLockingGetLockedEntryLockRecord"; + +interface Params { + client: IRecordLockingClient; +} + +export class RecordLockingGetLockedEntryLockRecord + implements IRecordLockingGetLockedEntryLockRecord +{ + private readonly client: IRecordLockingClient; + + public constructor(params: Params) { + this.client = params.client; + } + public async execute( + params: IRecordLockingGetLockedEntryLockRecordExecuteParams + ): Promise { + const result = await this.client.query< + IRecordLockingGetLockedEntryLockRecordResponse, + IRecordLockingGetLockedEntryLockRecordVariables + >({ + query: GET_LOCKED_ENTRY_LOCK_RECORD_QUERY, + variables: params + }); + if (result.data.recordLocking.getLockedEntryLockRecord.error) { + throw new WebinyError(result.data.recordLocking.getLockedEntryLockRecord.error); + } + return result.data.recordLocking.getLockedEntryLockRecord; + } +} diff --git a/packages/app-record-locking/src/domain/RecordLockingIsEntryLocked.ts b/packages/app-record-locking/src/domain/RecordLockingIsEntryLocked.ts new file mode 100644 index 00000000000..ee3b803e662 --- /dev/null +++ b/packages/app-record-locking/src/domain/RecordLockingIsEntryLocked.ts @@ -0,0 +1,39 @@ +import { + IRecordLockingIsEntryLocked, + IRecordLockingIsEntryLockedParams, + IRecordLockingIsEntryLockedResult +} from "~/domain/abstractions/IRecordLockingIsEntryLocked"; +import { IRecordLockingClient } from "./abstractions/IRecordLockingClient"; +import { + IRecordLockingIsEntryLockedResponse, + IRecordLockingIsEntryLockedVariables, + IS_ENTRY_LOCKED_QUERY +} from "~/domain/graphql/isEntryLocked"; + +interface Params { + client: IRecordLockingClient; +} + +export class RecordLockingIsEntryLocked implements IRecordLockingIsEntryLocked { + private readonly client: IRecordLockingClient; + + public constructor(params: Params) { + this.client = params.client; + } + public async execute( + params: IRecordLockingIsEntryLockedParams + ): Promise { + try { + const result = await this.client.query< + IRecordLockingIsEntryLockedResponse, + IRecordLockingIsEntryLockedVariables + >({ + query: IS_ENTRY_LOCKED_QUERY, + variables: params + }); + return !!result.data.recordLocking.isEntryLocked.data; + } catch { + return false; + } + } +} diff --git a/packages/app-record-locking/src/domain/RecordLockingListLockRecords.ts b/packages/app-record-locking/src/domain/RecordLockingListLockRecords.ts new file mode 100644 index 00000000000..054bc2bcdd6 --- /dev/null +++ b/packages/app-record-locking/src/domain/RecordLockingListLockRecords.ts @@ -0,0 +1,48 @@ +import { WebinyError } from "@webiny/error"; +import { ApolloClient } from "apollo-client"; +import { + IRecordLockingListLockRecords, + IRecordLockingListLockRecordsParams, + IRecordLockingListLockRecordsResult +} from "./abstractions/IRecordLockingListLockRecords"; +import { IRecordLockingClient } from "./abstractions/IRecordLockingClient"; +import { createRecordLockingClient } from "./utils/createRecordLockingClient"; +import { + IRecordLockingListLockedRecordsResponse, + IRecordLockingListLockedRecordsVariables, + LIST_LOCK_RECORDS +} from "~/domain/graphql/listLockRecords"; + +interface Params { + client: IRecordLockingClient | ApolloClient; +} + +export class RecordLockingListLockRecords implements IRecordLockingListLockRecords { + private readonly client: IRecordLockingClient; + + public constructor(params: Params) { + this.client = createRecordLockingClient(params.client); + } + public async execute( + params: IRecordLockingListLockRecordsParams + ): Promise { + const { where, sort, limit, after } = params; + + const result = await this.client.query< + IRecordLockingListLockedRecordsResponse, + IRecordLockingListLockedRecordsVariables + >({ + query: LIST_LOCK_RECORDS, + variables: { + where, + sort, + limit, + after + } + }); + if (!result.data?.recordLocking?.listLockRecords) { + throw new WebinyError("No data returned from server."); + } + return result.data.recordLocking.listLockRecords; + } +} diff --git a/packages/app-record-locking/src/domain/RecordLockingLockEntry.ts b/packages/app-record-locking/src/domain/RecordLockingLockEntry.ts new file mode 100644 index 00000000000..b06f0731cee --- /dev/null +++ b/packages/app-record-locking/src/domain/RecordLockingLockEntry.ts @@ -0,0 +1,26 @@ +import { WebinyError } from "@webiny/error"; +import { + IRecordLockingLockEntry, + IRecordLockingLockEntryParams, + IRecordLockingLockEntryResult +} from "~/domain/abstractions/IRecordLockingLockEntry"; +import { IRecordLockingClient } from "./abstractions/IRecordLockingClient"; + +interface Params { + client: IRecordLockingClient; +} + +export class RecordLockingLockEntry implements IRecordLockingLockEntry { + // eslint-disable-next-line + private readonly client: IRecordLockingClient; + + public constructor(params: Params) { + this.client = params.client; + } + public async execute( + // eslint-disable-next-line + params: IRecordLockingLockEntryParams + ): Promise { + throw new WebinyError("Method not implemented."); + } +} diff --git a/packages/app-record-locking/src/domain/RecordLockingUnlockEntry.ts b/packages/app-record-locking/src/domain/RecordLockingUnlockEntry.ts new file mode 100644 index 00000000000..74720a1d3e9 --- /dev/null +++ b/packages/app-record-locking/src/domain/RecordLockingUnlockEntry.ts @@ -0,0 +1,39 @@ +import { WebinyError } from "@webiny/error"; +import { + IRecordLockingUnlockEntry, + IRecordLockingUnlockEntryParams, + IRecordLockingUnlockEntryResult +} from "~/domain/abstractions/IRecordLockingUnlockEntry"; +import { IRecordLockingClient } from "./abstractions/IRecordLockingClient"; +import { + RecordLockingUnlockEntryResponse, + IRecordLockingUnlockEntryVariables, + UNLOCK_ENTRY_MUTATION +} from "~/domain/graphql/unlockEntry"; + +interface Params { + client: IRecordLockingClient; +} + +export class RecordLockingUnlockEntry implements IRecordLockingUnlockEntry { + private readonly client: IRecordLockingClient; + + public constructor(params: Params) { + this.client = params.client; + } + public async execute( + params: IRecordLockingUnlockEntryParams + ): Promise { + const result = await this.client.mutation< + RecordLockingUnlockEntryResponse, + IRecordLockingUnlockEntryVariables + >({ + mutation: UNLOCK_ENTRY_MUTATION, + variables: params + }); + if (!result.data?.recordLocking?.unlockEntry) { + throw new WebinyError("No data returned from server."); + } + return result.data.recordLocking.unlockEntry; + } +} diff --git a/packages/app-record-locking/src/domain/RecordLockingUnlockEntryRequest.ts b/packages/app-record-locking/src/domain/RecordLockingUnlockEntryRequest.ts new file mode 100644 index 00000000000..bea3e15637d --- /dev/null +++ b/packages/app-record-locking/src/domain/RecordLockingUnlockEntryRequest.ts @@ -0,0 +1,26 @@ +import { WebinyError } from "@webiny/error"; +import { + IRecordLockingUnlockEntryRequest, + IRecordLockingUnlockEntryRequestParams, + IRecordLockingUnlockEntryRequestResult +} from "~/domain/abstractions/IRecordLockingUnlockEntryRequest"; +import { IRecordLockingClient } from "./abstractions/IRecordLockingClient"; + +interface Params { + client: IRecordLockingClient; +} + +export class RecordLockingUnlockEntryRequest implements IRecordLockingUnlockEntryRequest { + // @eslint-disable-next-line + private readonly client: IRecordLockingClient; + + public constructor(params: Params) { + this.client = params.client; + } + public async execute( + // eslint-disable-next-line + params: IRecordLockingUnlockEntryRequestParams + ): Promise { + throw new WebinyError("Method not implemented."); + } +} diff --git a/packages/app-record-locking/src/domain/RecordLockingUpdateEntryLock.ts b/packages/app-record-locking/src/domain/RecordLockingUpdateEntryLock.ts new file mode 100644 index 00000000000..aad72a37647 --- /dev/null +++ b/packages/app-record-locking/src/domain/RecordLockingUpdateEntryLock.ts @@ -0,0 +1,40 @@ +import { WebinyError } from "@webiny/error"; +import { + IRecordLockingUpdateEntryLock, + IRecordLockingUpdateEntryLockExecuteParams, + IRecordLockingUpdateEntryLockExecuteResult +} from "~/domain/abstractions/IRecordLockingUpdateEntryLock"; +import { IRecordLockingClient } from "~/domain/abstractions/IRecordLockingClient"; +import { + IRecordLockingUpdateEntryLockResponse, + IRecordLockingUpdateEntryLockVariables, + UPDATE_ENTRY_LOCK +} from "~/domain/graphql/updateEntryLock"; + +interface Params { + client: IRecordLockingClient; +} + +export class RecordLockingUpdateEntryLock implements IRecordLockingUpdateEntryLock { + private readonly client: IRecordLockingClient; + + public constructor(params: Params) { + this.client = params.client; + } + + public async execute( + params: IRecordLockingUpdateEntryLockExecuteParams + ): Promise { + const result = await this.client.mutation< + IRecordLockingUpdateEntryLockResponse, + IRecordLockingUpdateEntryLockVariables + >({ + mutation: UPDATE_ENTRY_LOCK, + variables: params + }); + if (!result.data?.recordLocking?.updateEntryLock) { + throw new WebinyError("No data returned from server."); + } + return result.data.recordLocking.updateEntryLock; + } +} diff --git a/packages/app-record-locking/src/domain/abstractions/IRecordLocking.ts b/packages/app-record-locking/src/domain/abstractions/IRecordLocking.ts new file mode 100644 index 00000000000..7310c16dfce --- /dev/null +++ b/packages/app-record-locking/src/domain/abstractions/IRecordLocking.ts @@ -0,0 +1,60 @@ +import { + IIsRecordLockedParams, + IUpdateEntryLockParams, + IRecordLockingRecord, + IPossiblyRecordLockingRecord, + IRecordLockingError, + IRecordLockingLockRecord, + IUnlockEntryParams, + IFetchLockRecordParams, + IFetchLockRecordResult, + IFetchLockedEntryLockRecordParams +} from "~/types"; +import { IRecordLockingUnlockEntryResult } from "./IRecordLockingUnlockEntry"; + +export interface IRecordLockingUpdateEntryLockResult { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; +} + +export interface IRecordLocking< + T extends IPossiblyRecordLockingRecord = IPossiblyRecordLockingRecord +> { + loading: boolean; + records: IRecordLockingRecord[]; + setRecords( + folderId: string, + type: string, + records: T[] + ): Promise; + isLockExpired(input: Date | string): boolean; + isRecordLocked(record: IIsRecordLockedParams): boolean; + getLockRecordEntry(id: string): IRecordLockingRecord | undefined; + fetchLockRecord(params: IFetchLockRecordParams): Promise; + fetchLockedEntryLockRecord( + params: IFetchLockedEntryLockRecordParams + ): Promise; + updateEntryLock(params: IUpdateEntryLockParams): Promise; + removeEntryLock(params: IUnlockEntryParams): void; + unlockEntry( + params: IUnlockEntryParams, + force?: boolean + ): Promise; + // lockEntry(params: IRecordLockingLockEntryParams): Promise; + // unlockEntryRequest( + // params: IRecordLockingUnlockEntryRequestParams + // ): Promise; + // isEntryLocked( + // params: IRecordLockingIsEntryLockedParams + // ): Promise; + // getLockRecord( + // params: IRecordLockingGetLockRecordParams + // ): Promise; + // listLockRecords( + // params: IRecordLockingListLockRecordsParams + // ): Promise; + + // onRequestAccess(): Promise; + // acceptAccessRequest(): Promise; + // rejectAccessRequest(): Promise; +} diff --git a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismClient.ts b/packages/app-record-locking/src/domain/abstractions/IRecordLockingClient.ts similarity index 86% rename from packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismClient.ts rename to packages/app-record-locking/src/domain/abstractions/IRecordLockingClient.ts index 6dbefbfa229..a9d9f424112 100644 --- a/packages/app-locking-mechanism/src/domain/abstractions/ILockingMechanismClient.ts +++ b/packages/app-record-locking/src/domain/abstractions/IRecordLockingClient.ts @@ -1,7 +1,7 @@ import { ApolloQueryResult, QueryOptions, MutationOptions } from "apollo-client"; import { FetchResult } from "apollo-link"; -export interface ILockingMechanismClient { +export interface IRecordLockingClient { query(params: QueryOptions): Promise>; mutation(options: MutationOptions): Promise>; } diff --git a/packages/app-record-locking/src/domain/abstractions/IRecordLockingGetLockRecord.ts b/packages/app-record-locking/src/domain/abstractions/IRecordLockingGetLockRecord.ts new file mode 100644 index 00000000000..eee52633f01 --- /dev/null +++ b/packages/app-record-locking/src/domain/abstractions/IRecordLockingGetLockRecord.ts @@ -0,0 +1,17 @@ +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; + +export interface IRecordLockingGetLockRecordExecuteParams { + id: string; + type: string; +} + +export interface IRecordLockingGetLockRecordExecuteResult { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; +} + +export interface IRecordLockingGetLockRecord { + execute( + params: IRecordLockingGetLockRecordExecuteParams + ): Promise; +} diff --git a/packages/app-record-locking/src/domain/abstractions/IRecordLockingGetLockedEntryLockRecord.ts b/packages/app-record-locking/src/domain/abstractions/IRecordLockingGetLockedEntryLockRecord.ts new file mode 100644 index 00000000000..04cf47a00d8 --- /dev/null +++ b/packages/app-record-locking/src/domain/abstractions/IRecordLockingGetLockedEntryLockRecord.ts @@ -0,0 +1,17 @@ +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; + +export interface IRecordLockingGetLockedEntryLockRecordExecuteParams { + id: string; + type: string; +} + +export interface IRecordLockingGetLockedEntryLockRecordExecuteResult { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; +} + +export interface IRecordLockingGetLockedEntryLockRecord { + execute( + params: IRecordLockingGetLockedEntryLockRecordExecuteParams + ): Promise; +} diff --git a/packages/app-record-locking/src/domain/abstractions/IRecordLockingIsEntryLocked.ts b/packages/app-record-locking/src/domain/abstractions/IRecordLockingIsEntryLocked.ts new file mode 100644 index 00000000000..fc05ec4a1a5 --- /dev/null +++ b/packages/app-record-locking/src/domain/abstractions/IRecordLockingIsEntryLocked.ts @@ -0,0 +1,10 @@ +export interface IRecordLockingIsEntryLockedParams { + id: string; + type: string; +} + +export type IRecordLockingIsEntryLockedResult = boolean; + +export interface IRecordLockingIsEntryLocked { + execute(params: IRecordLockingIsEntryLockedParams): Promise; +} diff --git a/packages/app-record-locking/src/domain/abstractions/IRecordLockingListLockRecords.ts b/packages/app-record-locking/src/domain/abstractions/IRecordLockingListLockRecords.ts new file mode 100644 index 00000000000..dc1e522d7a2 --- /dev/null +++ b/packages/app-record-locking/src/domain/abstractions/IRecordLockingListLockRecords.ts @@ -0,0 +1,25 @@ +import { IRecordLockingError, IRecordLockingLockRecord, IRecordLockingMeta } from "~/types"; + +export interface IRecordLockingListLockRecordsParamsWhere { + id_in?: string[]; + type?: string; +} + +export interface IRecordLockingListLockRecordsParams { + where?: IRecordLockingListLockRecordsParamsWhere; + sort?: string[]; + limit?: number; + after?: string; +} + +export interface IRecordLockingListLockRecordsResult { + data: IRecordLockingLockRecord[] | null; + error: IRecordLockingError | null; + meta: IRecordLockingMeta | null; +} + +export interface IRecordLockingListLockRecords { + execute( + params: IRecordLockingListLockRecordsParams + ): Promise; +} diff --git a/packages/app-record-locking/src/domain/abstractions/IRecordLockingLockEntry.ts b/packages/app-record-locking/src/domain/abstractions/IRecordLockingLockEntry.ts new file mode 100644 index 00000000000..b015c50622f --- /dev/null +++ b/packages/app-record-locking/src/domain/abstractions/IRecordLockingLockEntry.ts @@ -0,0 +1,15 @@ +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; + +export interface IRecordLockingLockEntryParams { + id: string; + type: string; +} + +export interface IRecordLockingLockEntryResult { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; +} + +export interface IRecordLockingLockEntry { + execute(params: IRecordLockingLockEntryParams): Promise; +} diff --git a/packages/app-record-locking/src/domain/abstractions/IRecordLockingUnlockEntry.ts b/packages/app-record-locking/src/domain/abstractions/IRecordLockingUnlockEntry.ts new file mode 100644 index 00000000000..35c8ada671a --- /dev/null +++ b/packages/app-record-locking/src/domain/abstractions/IRecordLockingUnlockEntry.ts @@ -0,0 +1,16 @@ +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; + +export interface IRecordLockingUnlockEntryParams { + id: string; + type: string; + force?: boolean; +} + +export interface IRecordLockingUnlockEntryResult { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; +} + +export interface IRecordLockingUnlockEntry { + execute(params: IRecordLockingUnlockEntryParams): Promise; +} diff --git a/packages/app-record-locking/src/domain/abstractions/IRecordLockingUnlockEntryRequest.ts b/packages/app-record-locking/src/domain/abstractions/IRecordLockingUnlockEntryRequest.ts new file mode 100644 index 00000000000..3e3fdb13abc --- /dev/null +++ b/packages/app-record-locking/src/domain/abstractions/IRecordLockingUnlockEntryRequest.ts @@ -0,0 +1,17 @@ +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; + +export interface IRecordLockingUnlockEntryRequestParams { + id: string; + type: string; +} + +export interface IRecordLockingUnlockEntryRequestResult { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; +} + +export interface IRecordLockingUnlockEntryRequest { + execute( + params: IRecordLockingUnlockEntryRequestParams + ): Promise; +} diff --git a/packages/app-record-locking/src/domain/abstractions/IRecordLockingUpdateEntryLock.ts b/packages/app-record-locking/src/domain/abstractions/IRecordLockingUpdateEntryLock.ts new file mode 100644 index 00000000000..de4ac7d71be --- /dev/null +++ b/packages/app-record-locking/src/domain/abstractions/IRecordLockingUpdateEntryLock.ts @@ -0,0 +1,16 @@ +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; + +export interface IRecordLockingUpdateEntryLockExecuteParams { + id: string; + type: string; +} +export interface IRecordLockingUpdateEntryLockExecuteResult { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; +} + +export interface IRecordLockingUpdateEntryLock { + execute( + params: IRecordLockingUpdateEntryLockExecuteParams + ): Promise; +} diff --git a/packages/app-locking-mechanism/src/domain/graphql/fields.ts b/packages/app-record-locking/src/domain/graphql/fields.ts similarity index 100% rename from packages/app-locking-mechanism/src/domain/graphql/fields.ts rename to packages/app-record-locking/src/domain/graphql/fields.ts diff --git a/packages/app-record-locking/src/domain/graphql/getLockRecord.ts b/packages/app-record-locking/src/domain/graphql/getLockRecord.ts new file mode 100644 index 00000000000..361df958ded --- /dev/null +++ b/packages/app-record-locking/src/domain/graphql/getLockRecord.ts @@ -0,0 +1,30 @@ +import gql from "graphql-tag"; +import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; +import { IRecordLockingGetLockRecordExecuteParams } from "~/domain/abstractions/IRecordLockingGetLockRecord"; + +export type IRecordLockingGetLockRecordVariables = IRecordLockingGetLockRecordExecuteParams; + +export interface IRecordLockingGetLockRecordResponse { + recordLocking: { + getLockRecord: { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; + }; + }; +} + +export const GET_LOCK_RECORD_QUERY = gql` + query RecordLockingGetLockRecord($id: ID!) { + recordLocking { + getLockRecord(id: $id) { + data { + ${LOCK_RECORD_FIELDS} + } + error { + ${ERROR_FIELDS} + } + } + } + } +`; diff --git a/packages/app-record-locking/src/domain/graphql/getLockedEntryLockRecord.ts b/packages/app-record-locking/src/domain/graphql/getLockedEntryLockRecord.ts new file mode 100644 index 00000000000..1607e438ab8 --- /dev/null +++ b/packages/app-record-locking/src/domain/graphql/getLockedEntryLockRecord.ts @@ -0,0 +1,31 @@ +import gql from "graphql-tag"; +import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; +import { IRecordLockingGetLockedEntryLockRecordExecuteParams } from "~/domain/abstractions/IRecordLockingGetLockedEntryLockRecord"; + +export type IRecordLockingGetLockedEntryLockRecordVariables = + IRecordLockingGetLockedEntryLockRecordExecuteParams; + +export interface IRecordLockingGetLockedEntryLockRecordResponse { + recordLocking: { + getLockedEntryLockRecord: { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; + }; + }; +} + +export const GET_LOCKED_ENTRY_LOCK_RECORD_QUERY = gql` + query RecordLockingGetLockedEntryLockRecord($id: ID!, $type: String!) { + recordLocking { + getLockedEntryLockRecord(id: $id, type: $type) { + data { + ${LOCK_RECORD_FIELDS} + } + error { + ${ERROR_FIELDS} + } + } + } + } +`; diff --git a/packages/app-record-locking/src/domain/graphql/isEntryLocked.ts b/packages/app-record-locking/src/domain/graphql/isEntryLocked.ts new file mode 100644 index 00000000000..13d6609665e --- /dev/null +++ b/packages/app-record-locking/src/domain/graphql/isEntryLocked.ts @@ -0,0 +1,28 @@ +import gql from "graphql-tag"; +import { ERROR_FIELDS } from "~/domain/graphql/fields"; +import { IRecordLockingError } from "~/types"; +import { IRecordLockingIsEntryLockedParams } from "../abstractions/IRecordLockingIsEntryLocked"; + +export type IRecordLockingIsEntryLockedVariables = IRecordLockingIsEntryLockedParams; + +export interface IRecordLockingIsEntryLockedResponse { + recordLocking: { + isEntryLocked: { + data: boolean | null; + error: IRecordLockingError | null; + }; + }; +} + +export const IS_ENTRY_LOCKED_QUERY = gql` + query RecordLockingIsEntryLocked($id: ID!, $type: String!) { + recordLocking { + isEntryLocked(id: $id, type: $type) { + data + error { + ${ERROR_FIELDS} + } + } + } + } +`; diff --git a/packages/app-record-locking/src/domain/graphql/listLockRecords.ts b/packages/app-record-locking/src/domain/graphql/listLockRecords.ts new file mode 100644 index 00000000000..b32e9987c9e --- /dev/null +++ b/packages/app-record-locking/src/domain/graphql/listLockRecords.ts @@ -0,0 +1,44 @@ +import gql from "graphql-tag"; +import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "~/domain/graphql/fields"; +import { IRecordLockingError, IRecordLockingLockRecord, IRecordLockingMeta } from "~/types"; +import { IRecordLockingListLockRecordsParams } from "~/domain/abstractions/IRecordLockingListLockRecords"; + +export interface IRecordLockingListLockedRecordsVariablesWhere { + id_in?: string[]; +} + +export type IRecordLockingListLockedRecordsVariables = IRecordLockingListLockRecordsParams; + +export interface IRecordLockingListLockedRecordsResponse { + recordLocking: { + listLockRecords: { + data: IRecordLockingLockRecord[] | null; + error: IRecordLockingError | null; + meta: IRecordLockingMeta | null; + }; + }; +} + +export const createListLockRecords = () => { + return gql` + query RecordLockingListLockedRecords( + $where: RecordLockingListWhereInput + $sort: [RecordLockingListSorter!] + $limit: Int + $after: String + ) { + recordLocking { + listLockRecords(where: $where, sort: $sort, limit: $limit, after: $after) { + data { + ${LOCK_RECORD_FIELDS} + } + error { + ${ERROR_FIELDS} + } + } + } + } + `; +}; + +export const LIST_LOCK_RECORDS = createListLockRecords(); diff --git a/packages/app-record-locking/src/domain/graphql/lockEntry.ts b/packages/app-record-locking/src/domain/graphql/lockEntry.ts new file mode 100644 index 00000000000..b9e57806778 --- /dev/null +++ b/packages/app-record-locking/src/domain/graphql/lockEntry.ts @@ -0,0 +1,32 @@ +import gql from "graphql-tag"; +import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; +import { IRecordLockingLockEntryParams } from "~/domain/abstractions/IRecordLockingLockEntry"; + +export type IRecordLockingLockEntryVariables = IRecordLockingLockEntryParams; + +export interface IRecordLockingLockEntryResponse { + recordLocking: { + lockEntry: { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; + }; + }; +} + +export const createLockGraphQL = () => { + return gql` + mutation RecordLockingLockEntry($id: ID!, $type: String!) { + recordLocking { + lockEntry(id: $id, type: $type) { + data { + ${LOCK_RECORD_FIELDS} + } + error { + ${ERROR_FIELDS} + } + } + } + } + `; +}; diff --git a/packages/app-record-locking/src/domain/graphql/unlockEntry.ts b/packages/app-record-locking/src/domain/graphql/unlockEntry.ts new file mode 100644 index 00000000000..64e2bf04bfe --- /dev/null +++ b/packages/app-record-locking/src/domain/graphql/unlockEntry.ts @@ -0,0 +1,30 @@ +import gql from "graphql-tag"; +import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; +import { IRecordLockingUnlockEntryParams } from "../abstractions/IRecordLockingUnlockEntry"; + +export type IRecordLockingUnlockEntryVariables = IRecordLockingUnlockEntryParams; + +export interface RecordLockingUnlockEntryResponse { + recordLocking: { + unlockEntry: { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; + }; + }; +} + +export const UNLOCK_ENTRY_MUTATION = gql` + mutation RecordLockingUnlockEntry($id: ID!, $type: String!, $force: Boolean) { + recordLocking { + unlockEntry(id: $id, type: $type, force: $force) { + data { + ${LOCK_RECORD_FIELDS} + } + error { + ${ERROR_FIELDS} + } + } + } + } +`; diff --git a/packages/app-record-locking/src/domain/graphql/unlockEntryRequest.ts b/packages/app-record-locking/src/domain/graphql/unlockEntryRequest.ts new file mode 100644 index 00000000000..fedc359c686 --- /dev/null +++ b/packages/app-record-locking/src/domain/graphql/unlockEntryRequest.ts @@ -0,0 +1,32 @@ +import gql from "graphql-tag"; +import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; +import { IRecordLockingUnlockEntryRequestParams } from "../abstractions/IRecordLockingUnlockEntryRequest"; + +export type IRecordLockingUnlockEntryRequestVariables = IRecordLockingUnlockEntryRequestParams; + +export interface IRecordLockingUnlockEntryRequestResponse { + recordLocking: { + unlockEntryRequest: { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; + }; + }; +} + +export const createUnlockEntryRequestGraphQL = () => { + return gql` + mutation RecordLockingUnlockEntryRequest($id: ID!, $type: String!) { + recordLocking { + unlockEntryRequest(id: $id, type: $type) { + data { + ${LOCK_RECORD_FIELDS} + } + error { + ${ERROR_FIELDS} + } + } + } + } + `; +}; diff --git a/packages/app-record-locking/src/domain/graphql/updateEntryLock.ts b/packages/app-record-locking/src/domain/graphql/updateEntryLock.ts new file mode 100644 index 00000000000..b2d3d424ef2 --- /dev/null +++ b/packages/app-record-locking/src/domain/graphql/updateEntryLock.ts @@ -0,0 +1,30 @@ +import gql from "graphql-tag"; +import { ERROR_FIELDS, LOCK_RECORD_FIELDS } from "./fields"; +import { IRecordLockingError, IRecordLockingLockRecord } from "~/types"; +import { IRecordLockingUpdateEntryLockExecuteParams } from "~/domain/abstractions/IRecordLockingUpdateEntryLock"; + +export type IRecordLockingUpdateEntryLockVariables = IRecordLockingUpdateEntryLockExecuteParams; + +export interface IRecordLockingUpdateEntryLockResponse { + recordLocking: { + updateEntryLock: { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; + }; + }; +} + +export const UPDATE_ENTRY_LOCK = gql` + mutation RecordLockingUpdateEntryLock($id: ID!, $type: String!) { + recordLocking { + updateEntryLock(id: $id, type: $type) { + data { + ${LOCK_RECORD_FIELDS} + } + error { + ${ERROR_FIELDS} + } + } + } + } +`; diff --git a/packages/app-record-locking/src/domain/utils/createRecordLockingClient.ts b/packages/app-record-locking/src/domain/utils/createRecordLockingClient.ts new file mode 100644 index 00000000000..d4b3164cee5 --- /dev/null +++ b/packages/app-record-locking/src/domain/utils/createRecordLockingClient.ts @@ -0,0 +1,10 @@ +import { IRecordLockingClient } from "~/domain/abstractions/IRecordLockingClient"; +import { RecordLockingClient } from "~/domain/RecordLockingClient"; +import { ApolloClient } from "apollo-client"; + +export const createRecordLockingClient = (client: IRecordLockingClient | ApolloClient) => { + if (client instanceof ApolloClient) { + return new RecordLockingClient({ client }); + } + return client; +}; diff --git a/packages/app-locking-mechanism/src/domain/utils/createLockingMechanismError.ts b/packages/app-record-locking/src/domain/utils/createRecordLockingError.ts similarity index 68% rename from packages/app-locking-mechanism/src/domain/utils/createLockingMechanismError.ts rename to packages/app-record-locking/src/domain/utils/createRecordLockingError.ts index 4f12c556d18..68a27ed9206 100644 --- a/packages/app-locking-mechanism/src/domain/utils/createLockingMechanismError.ts +++ b/packages/app-record-locking/src/domain/utils/createRecordLockingError.ts @@ -1,13 +1,13 @@ -import { ILockingMechanismError } from "~/types"; +import { IRecordLockingError } from "~/types"; export interface IError extends Error { code?: string; data?: any; } -export const createLockingMechanismError = ( - error: IError | ILockingMechanismError -): ILockingMechanismError => { +export const createRecordLockingError = ( + error: IError | IRecordLockingError +): IRecordLockingError => { if (error instanceof Error) { return { message: error.message, diff --git a/packages/app-record-locking/src/hooks/index.ts b/packages/app-record-locking/src/hooks/index.ts new file mode 100644 index 00000000000..70f37a9b983 --- /dev/null +++ b/packages/app-record-locking/src/hooks/index.ts @@ -0,0 +1,2 @@ +export * from "./useRecordLocking"; +export * from "./usePermission"; diff --git a/packages/app-locking-mechanism/src/hooks/usePermission.ts b/packages/app-record-locking/src/hooks/usePermission.ts similarity index 84% rename from packages/app-locking-mechanism/src/hooks/usePermission.ts rename to packages/app-record-locking/src/hooks/usePermission.ts index af5dec8d332..af78d623cb3 100644 --- a/packages/app-locking-mechanism/src/hooks/usePermission.ts +++ b/packages/app-record-locking/src/hooks/usePermission.ts @@ -5,7 +5,7 @@ export const usePermission = () => { const { identity, getPermission } = useSecurity(); const hasFullAccess = useMemo(() => { - return !!getPermission("lockingMechanism.*"); + return !!getPermission("recordLocking.*"); }, [identity]); return { diff --git a/packages/app-record-locking/src/hooks/useRecordLocking.ts b/packages/app-record-locking/src/hooks/useRecordLocking.ts new file mode 100644 index 00000000000..05b1a8058d4 --- /dev/null +++ b/packages/app-record-locking/src/hooks/useRecordLocking.ts @@ -0,0 +1,14 @@ +import { WebinyError } from "@webiny/error"; +import { useContext } from "react"; +import { RecordLockingContext } from "~/components/RecordLockingProvider"; +import { IRecordLockingContext, IPossiblyRecordLockingRecord } from "~/types"; + +export const useRecordLocking = < + T extends IPossiblyRecordLockingRecord = IPossiblyRecordLockingRecord +>() => { + const context = useContext(RecordLockingContext); + if (!context) { + throw new WebinyError("useRecordLocking must be used within a RecordLockingProvider."); + } + return context as IRecordLockingContext; +}; diff --git a/packages/app-locking-mechanism/src/index.tsx b/packages/app-record-locking/src/index.tsx similarity index 53% rename from packages/app-locking-mechanism/src/index.tsx rename to packages/app-record-locking/src/index.tsx index 849de02d8a1..765e630957b 100644 --- a/packages/app-locking-mechanism/src/index.tsx +++ b/packages/app-record-locking/src/index.tsx @@ -1,41 +1,41 @@ import React from "react"; import { Provider } from "@webiny/app"; -import { LockingMechanismProvider as LockingMechanismProviderComponent } from "~/components/LockingMechanismProvider"; +import { RecordLockingProvider as RecordLockingProviderComponent } from "~/components/RecordLockingProvider"; import { HeadlessCmsActionsAcoCell } from "~/components/HeadlessCmsActionsAcoCell"; import { HeadlessCmsContentEntry } from "~/components/HeadlessCmsContentEntry"; import { useWcp } from "@webiny/app-wcp"; -export * from "~/components/LockingMechanismProvider"; +export * from "~/components/RecordLockingProvider"; export * from "~/hooks"; -export interface LockingMechanismProviderProps { +export interface RecordLockingProviderProps { children: React.ReactNode; } -const LockingMechanismHoc = (Component: React.ComponentType) => { - return function LockingMechanismProvider({ children }: LockingMechanismProviderProps) { +const RecordLockingHoc = (Component: React.ComponentType) => { + return function RecordLockingProvider({ children }: RecordLockingProviderProps) { const { canUseRecordLocking } = useWcp(); if (!canUseRecordLocking()) { return {children}; } return ( - + {children} - + ); }; }; -const LockingMechanismExtension = () => { +const RecordLockingExtension = () => { return ( <> - + ); }; -export const LockingMechanism = React.memo(LockingMechanismExtension); +export const RecordLocking = React.memo(RecordLockingExtension); diff --git a/packages/app-record-locking/src/types.ts b/packages/app-record-locking/src/types.ts new file mode 100644 index 00000000000..9a4d679ebf9 --- /dev/null +++ b/packages/app-record-locking/src/types.ts @@ -0,0 +1,91 @@ +import { EntryTableItem } from "@webiny/app-headless-cms/types"; +import { GenericRecord } from "@webiny/app/types"; +import { IRecordLockingUnlockEntryResult } from "~/domain/abstractions/IRecordLockingUnlockEntry"; + +export interface IRecordLockingIdentity { + id: string; + displayName: string; + type: string; +} + +export interface IRecordLockingRecordLocked { + lockedBy: IRecordLockingIdentity; + lockedOn: string; + expiresOn: string; + actions: IRecordLockingLockRecordAction[]; +} + +export interface IPossiblyRecordLockingRecord extends EntryTableItem { + $lockingType?: string; + entryId: string; + $locked?: IRecordLockingRecordLocked | null; +} + +export interface IRecordLockingRecord extends IPossiblyRecordLockingRecord { + entryId: string; + $lockingType: string; +} + +export type IIsRecordLockedParams = Pick; + +export type IUpdateEntryLockParams = Pick; + +export type IUnlockEntryParams = Pick; + +export type IFetchLockRecordParams = Pick; + +export type IFetchLockedEntryLockRecordParams = Pick; + +export interface IFetchLockRecordResult { + data: IRecordLockingLockRecord | null; + error: IRecordLockingError | null; +} + +export interface IRecordLockingContext< + T extends IPossiblyRecordLockingRecord = IPossiblyRecordLockingRecord +> { + readonly loading: boolean; + readonly records: IPossiblyRecordLockingRecord[]; + readonly error?: IRecordLockingError | null; + setRecords(folderId: string, type: string, records: T[]): Promise; + updateEntryLock(params: IUpdateEntryLockParams): Promise; + isRecordLocked(params?: IIsRecordLockedParams): boolean; + getLockRecordEntry(id: string): IRecordLockingRecord | undefined; + fetchLockRecord(params: IFetchLockRecordParams): Promise; + fetchLockedEntryLockRecord( + params: IFetchLockedEntryLockRecordParams + ): Promise; + unlockEntry(params: IUnlockEntryParams): Promise; + removeEntryLock(params: IUnlockEntryParams): void; + unlockEntryForce(params: IUnlockEntryParams): Promise; + isLockExpired(input: Date | string): boolean; +} + +export interface IRecordLockingLockRecordAction { + type: string; + message: string; + createdBy: IRecordLockingIdentity; + createdOn: string; +} + +export interface IRecordLockingLockRecord { + id: string; + lockedOn: string; + expiresOn: string; + lockedBy: IRecordLockingIdentity; + targetId: string; + type: string; + actions: IRecordLockingLockRecordAction[]; +} + +export interface IRecordLockingMeta { + totalCount: number; + cursor: string | null; + hasMoreItems: boolean; +} + +export interface IRecordLockingError { + message: string; + code: string; + data?: T; +} diff --git a/packages/app-locking-mechanism/src/utils/createCacheKey.ts b/packages/app-record-locking/src/utils/createCacheKey.ts similarity index 100% rename from packages/app-locking-mechanism/src/utils/createCacheKey.ts rename to packages/app-record-locking/src/utils/createCacheKey.ts diff --git a/packages/app-locking-mechanism/tsconfig.build.json b/packages/app-record-locking/tsconfig.build.json similarity index 100% rename from packages/app-locking-mechanism/tsconfig.build.json rename to packages/app-record-locking/tsconfig.build.json diff --git a/packages/app-locking-mechanism/tsconfig.json b/packages/app-record-locking/tsconfig.json similarity index 100% rename from packages/app-locking-mechanism/tsconfig.json rename to packages/app-record-locking/tsconfig.json diff --git a/packages/app-locking-mechanism/webiny.config.js b/packages/app-record-locking/webiny.config.js similarity index 100% rename from packages/app-locking-mechanism/webiny.config.js rename to packages/app-record-locking/webiny.config.js diff --git a/packages/app-serverless-cms/package.json b/packages/app-serverless-cms/package.json index e58af42ed96..a49546674dd 100644 --- a/packages/app-serverless-cms/package.json +++ b/packages/app-serverless-cms/package.json @@ -24,9 +24,9 @@ "@webiny/app-headless-cms": "0.0.0", "@webiny/app-i18n": "0.0.0", "@webiny/app-i18n-content": "0.0.0", - "@webiny/app-locking-mechanism": "0.0.0", "@webiny/app-mailer": "0.0.0", "@webiny/app-page-builder": "0.0.0", + "@webiny/app-record-locking": "0.0.0", "@webiny/app-security": "0.0.0", "@webiny/app-security-access-management": "0.0.0", "@webiny/app-tenancy": "0.0.0", diff --git a/packages/app-serverless-cms/src/Admin.tsx b/packages/app-serverless-cms/src/Admin.tsx index 7609b75d93d..10c6a9d8244 100644 --- a/packages/app-serverless-cms/src/Admin.tsx +++ b/packages/app-serverless-cms/src/Admin.tsx @@ -30,7 +30,7 @@ import { LexicalEditorActions } from "@webiny/lexical-editor-actions"; import { Module as MailerSettings } from "@webiny/app-mailer"; import { Folders } from "@webiny/app-aco"; import { Websockets } from "@webiny/app-websockets"; -import { LockingMechanism } from "@webiny/app-locking-mechanism"; +import { RecordLocking } from "@webiny/app-record-locking"; export interface AdminProps extends Omit { createApolloClient?: BaseAdminProps["createApolloClient"]; @@ -57,7 +57,7 @@ const App = (props: AdminProps) => { - + diff --git a/packages/app-serverless-cms/tsconfig.build.json b/packages/app-serverless-cms/tsconfig.build.json index 71d0eaea0f6..75c2b18e2c4 100644 --- a/packages/app-serverless-cms/tsconfig.build.json +++ b/packages/app-serverless-cms/tsconfig.build.json @@ -15,7 +15,7 @@ { "path": "../app-headless-cms/tsconfig.build.json" }, { "path": "../app-i18n/tsconfig.build.json" }, { "path": "../app-i18n-content/tsconfig.build.json" }, - { "path": "../app-locking-mechanism/tsconfig.build.json" }, + { "path": "../app-record-locking/tsconfig.build.json" }, { "path": "../app-mailer/tsconfig.build.json" }, { "path": "../app-page-builder/tsconfig.build.json" }, { "path": "../app-security/tsconfig.build.json" }, diff --git a/packages/app-serverless-cms/tsconfig.json b/packages/app-serverless-cms/tsconfig.json index 3bd99dee6f1..ae8e16405ec 100644 --- a/packages/app-serverless-cms/tsconfig.json +++ b/packages/app-serverless-cms/tsconfig.json @@ -15,7 +15,7 @@ { "path": "../app-headless-cms" }, { "path": "../app-i18n" }, { "path": "../app-i18n-content" }, - { "path": "../app-locking-mechanism" }, + { "path": "../app-record-locking" }, { "path": "../app-mailer" }, { "path": "../app-page-builder" }, { "path": "../app-security" }, @@ -60,8 +60,8 @@ "@webiny/app-i18n": ["../app-i18n/src"], "@webiny/app-i18n-content/*": ["../app-i18n-content/src/*"], "@webiny/app-i18n-content": ["../app-i18n-content/src"], - "@webiny/app-locking-mechanism/*": ["../app-locking-mechanism/src/*"], - "@webiny/app-locking-mechanism": ["../app-locking-mechanism/src"], + "@webiny/app-record-locking/*": ["../app-record-locking/src/*"], + "@webiny/app-record-locking": ["../app-record-locking/src"], "@webiny/app-mailer/*": ["../app-mailer/src/*"], "@webiny/app-mailer": ["../app-mailer/src"], "@webiny/app-page-builder/*": ["../app-page-builder/src/*"], diff --git a/packages/cwp-template-aws/template/ddb-es/apps/api/graphql/package.json b/packages/cwp-template-aws/template/ddb-es/apps/api/graphql/package.json index d7743622de4..2618162f417 100644 --- a/packages/cwp-template-aws/template/ddb-es/apps/api/graphql/package.json +++ b/packages/cwp-template-aws/template/ddb-es/apps/api/graphql/package.json @@ -23,7 +23,7 @@ "@webiny/api-headless-cms-aco": "latest", "@webiny/api-headless-cms-ddb-es": "latest", "@webiny/api-headless-cms-tasks": "latest", - "@webiny/api-locking-mechanism": "latest", + "@webiny/api-record-locking": "latest", "@webiny/api-page-builder": "latest", "@webiny/api-page-builder-aco": "latest", "@webiny/api-page-builder-so-ddb-es": "latest", diff --git a/packages/cwp-template-aws/template/ddb-es/apps/api/graphql/src/index.ts b/packages/cwp-template-aws/template/ddb-es/apps/api/graphql/src/index.ts index 5015bf90806..690bc8e2b1a 100644 --- a/packages/cwp-template-aws/template/ddb-es/apps/api/graphql/src/index.ts +++ b/packages/cwp-template-aws/template/ddb-es/apps/api/graphql/src/index.ts @@ -39,7 +39,7 @@ import { createBackgroundTasks } from "@webiny/api-background-tasks-es"; import { createApwGraphQL, createApwPageBuilderContext } from "@webiny/api-apw"; import { createStorageOperations as createApwSaStorageOperations } from "@webiny/api-apw-scheduler-so-ddb"; import { createWebsockets } from "@webiny/api-websockets"; -import { createLockingMechanism } from "@webiny/api-locking-mechanism"; +import { createRecordLocking } from "@webiny/api-record-locking"; // Imports plugins created via scaffolding utilities. import scaffoldsPlugins from "./plugins/scaffolds"; @@ -77,7 +77,7 @@ export const handler = createHandler({ }) }), createHeadlessCmsGraphQL(), - createLockingMechanism(), + createRecordLocking(), createBackgroundTasks(), createFileManagerContext({ storageOperations: createFileManagerStorageOperations({ diff --git a/packages/cwp-template-aws/template/ddb-os/apps/api/graphql/package.json b/packages/cwp-template-aws/template/ddb-os/apps/api/graphql/package.json index 41dfdba71b3..4981a0191f7 100644 --- a/packages/cwp-template-aws/template/ddb-os/apps/api/graphql/package.json +++ b/packages/cwp-template-aws/template/ddb-os/apps/api/graphql/package.json @@ -23,7 +23,7 @@ "@webiny/api-headless-cms-aco": "latest", "@webiny/api-headless-cms-ddb-es": "latest", "@webiny/api-headless-cms-tasks": "latest", - "@webiny/api-locking-mechanism": "latest", + "@webiny/api-record-locking": "latest", "@webiny/api-page-builder": "latest", "@webiny/api-page-builder-aco": "latest", "@webiny/api-page-builder-so-ddb-es": "latest", diff --git a/packages/cwp-template-aws/template/ddb-os/apps/api/graphql/src/index.ts b/packages/cwp-template-aws/template/ddb-os/apps/api/graphql/src/index.ts index 97fe678c147..1ae3afb7698 100644 --- a/packages/cwp-template-aws/template/ddb-os/apps/api/graphql/src/index.ts +++ b/packages/cwp-template-aws/template/ddb-os/apps/api/graphql/src/index.ts @@ -39,7 +39,7 @@ import { createBackgroundTasks } from "@webiny/api-background-tasks-os"; import { createApwGraphQL, createApwPageBuilderContext } from "@webiny/api-apw"; import { createStorageOperations as createApwSaStorageOperations } from "@webiny/api-apw-scheduler-so-ddb"; import { createWebsockets } from "@webiny/api-websockets"; -import { createLockingMechanism } from "@webiny/api-locking-mechanism"; +import { createRecordLocking } from "@webiny/api-record-locking"; // Imports plugins created via scaffolding utilities. import scaffoldsPlugins from "./plugins/scaffolds"; @@ -77,7 +77,7 @@ export const handler = createHandler({ }) }), createHeadlessCmsGraphQL(), - createLockingMechanism(), + createRecordLocking(), createBackgroundTasks(), createFileManagerContext({ storageOperations: createFileManagerStorageOperations({ diff --git a/packages/cwp-template-aws/template/ddb/apps/api/graphql/package.json b/packages/cwp-template-aws/template/ddb/apps/api/graphql/package.json index 431da51d52b..c906b6ef7e7 100644 --- a/packages/cwp-template-aws/template/ddb/apps/api/graphql/package.json +++ b/packages/cwp-template-aws/template/ddb/apps/api/graphql/package.json @@ -23,7 +23,7 @@ "@webiny/api-headless-cms-aco": "latest", "@webiny/api-headless-cms-ddb": "latest", "@webiny/api-headless-cms-tasks": "latest", - "@webiny/api-locking-mechanism": "latest", + "@webiny/api-record-locking": "latest", "@webiny/api-page-builder": "latest", "@webiny/api-page-builder-aco": "latest", "@webiny/api-page-builder-so-ddb": "latest", diff --git a/packages/cwp-template-aws/template/ddb/apps/api/graphql/src/index.ts b/packages/cwp-template-aws/template/ddb/apps/api/graphql/src/index.ts index 842c962b53a..a146c148387 100644 --- a/packages/cwp-template-aws/template/ddb/apps/api/graphql/src/index.ts +++ b/packages/cwp-template-aws/template/ddb/apps/api/graphql/src/index.ts @@ -35,7 +35,7 @@ import { createBackgroundTasks } from "@webiny/api-background-tasks-ddb"; import { createApwGraphQL, createApwPageBuilderContext } from "@webiny/api-apw"; import { createStorageOperations as createApwSaStorageOperations } from "@webiny/api-apw-scheduler-so-ddb"; import { createWebsockets } from "@webiny/api-websockets"; -import { createLockingMechanism } from "@webiny/api-locking-mechanism"; +import { createRecordLocking } from "@webiny/api-record-locking"; // Imports plugins created via scaffolding utilities. import scaffoldsPlugins from "./plugins/scaffolds"; @@ -66,7 +66,7 @@ export const handler = createHandler({ }) }), createHeadlessCmsGraphQL(), - createLockingMechanism(), + createRecordLocking(), createBackgroundTasks(), createFileManagerContext({ storageOperations: createFileManagerStorageOperations({ diff --git a/scripts/listPackagesWithTests.js b/scripts/listPackagesWithTests.js index 371cc1c3b36..544dc6c49e6 100644 --- a/scripts/listPackagesWithTests.js +++ b/scripts/listPackagesWithTests.js @@ -138,6 +138,12 @@ const CUSTOM_HANDLERS = { }, "api-elasticsearch-tasks": () => { return ["packages/api-elasticsearch-tasks --storage=ddb-es,ddb"]; + }, + "api-record-locking": () => { + return [ + "packages/api-record-locking --storage=ddb", + "packages/api-record-locking --storage=ddb-es,ddb" + ]; } }; diff --git a/yarn.lock b/yarn.lock index 7789aa2ae13..4338e960221 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7643,13 +7643,6 @@ __metadata: languageName: node linkType: hard -"@material-design-icons/svg@npm:^0.14.13": - version: 0.14.13 - resolution: "@material-design-icons/svg@npm:0.14.13" - checksum: 80815e94a7b4a9775a4afe2af1b24c3b55270e09ad7cbebf669d7cea2d8c31050c371e435257bfb6289c782f672b82c73873c5d4d5bd15b12ec7d9b8b58909d4 - languageName: node - linkType: hard - "@material-design-icons/svg@npm:^0.14.2": version: 0.14.2 resolution: "@material-design-icons/svg@npm:0.14.2" @@ -13706,40 +13699,6 @@ __metadata: languageName: unknown linkType: soft -"@webiny/api-locking-mechanism@0.0.0, @webiny/api-locking-mechanism@workspace:packages/api-locking-mechanism": - version: 0.0.0-use.local - resolution: "@webiny/api-locking-mechanism@workspace:packages/api-locking-mechanism" - dependencies: - "@babel/cli": ^7.23.9 - "@babel/core": ^7.24.0 - "@babel/preset-env": ^7.24.0 - "@babel/preset-typescript": ^7.23.3 - "@babel/runtime": ^7.24.0 - "@types/aws-lambda": ^8.10.131 - "@webiny/api": 0.0.0 - "@webiny/api-headless-cms": 0.0.0 - "@webiny/api-i18n": 0.0.0 - "@webiny/api-security": 0.0.0 - "@webiny/api-tenancy": 0.0.0 - "@webiny/api-wcp": 0.0.0 - "@webiny/api-websockets": 0.0.0 - "@webiny/cli": 0.0.0 - "@webiny/error": 0.0.0 - "@webiny/handler": 0.0.0 - "@webiny/handler-aws": 0.0.0 - "@webiny/handler-graphql": 0.0.0 - "@webiny/plugins": 0.0.0 - "@webiny/project-utils": 0.0.0 - "@webiny/pubsub": 0.0.0 - "@webiny/utils": 0.0.0 - graphql: ^15.8.0 - rimraf: ^5.0.5 - ttypescript: ^1.5.13 - type-fest: ^2.19.0 - typescript: 4.7.4 - languageName: unknown - linkType: soft - "@webiny/api-mailer@0.0.0, @webiny/api-mailer@workspace:packages/api-mailer": version: 0.0.0-use.local resolution: "@webiny/api-mailer@workspace:packages/api-mailer" @@ -14117,6 +14076,40 @@ __metadata: languageName: unknown linkType: soft +"@webiny/api-record-locking@0.0.0, @webiny/api-record-locking@workspace:packages/api-record-locking": + version: 0.0.0-use.local + resolution: "@webiny/api-record-locking@workspace:packages/api-record-locking" + dependencies: + "@babel/cli": ^7.23.9 + "@babel/core": ^7.24.0 + "@babel/preset-env": ^7.24.0 + "@babel/preset-typescript": ^7.23.3 + "@babel/runtime": ^7.24.0 + "@types/aws-lambda": ^8.10.131 + "@webiny/api": 0.0.0 + "@webiny/api-headless-cms": 0.0.0 + "@webiny/api-i18n": 0.0.0 + "@webiny/api-security": 0.0.0 + "@webiny/api-tenancy": 0.0.0 + "@webiny/api-wcp": 0.0.0 + "@webiny/api-websockets": 0.0.0 + "@webiny/cli": 0.0.0 + "@webiny/error": 0.0.0 + "@webiny/handler": 0.0.0 + "@webiny/handler-aws": 0.0.0 + "@webiny/handler-graphql": 0.0.0 + "@webiny/plugins": 0.0.0 + "@webiny/project-utils": 0.0.0 + "@webiny/pubsub": 0.0.0 + "@webiny/utils": 0.0.0 + graphql: ^15.8.0 + rimraf: ^5.0.5 + ttypescript: ^1.5.13 + type-fest: ^2.19.0 + typescript: 4.7.4 + languageName: unknown + linkType: soft + "@webiny/api-security-auth0@workspace:packages/api-security-auth0": version: 0.0.0-use.local resolution: "@webiny/api-security-auth0@workspace:packages/api-security-auth0" @@ -15222,43 +15215,6 @@ __metadata: languageName: unknown linkType: soft -"@webiny/app-locking-mechanism@0.0.0, @webiny/app-locking-mechanism@workspace:packages/app-locking-mechanism": - version: 0.0.0-use.local - resolution: "@webiny/app-locking-mechanism@workspace:packages/app-locking-mechanism" - dependencies: - "@apollo/react-hooks": ^3.1.5 - "@babel/cli": ^7.23.9 - "@babel/core": ^7.24.0 - "@babel/preset-env": ^7.24.0 - "@babel/preset-react": ^7.23.3 - "@babel/preset-typescript": ^7.23.3 - "@emotion/styled": ^11.10.6 - "@material-design-icons/svg": ^0.14.13 - "@webiny/app": 0.0.0 - "@webiny/app-admin": 0.0.0 - "@webiny/app-headless-cms": 0.0.0 - "@webiny/app-security": 0.0.0 - "@webiny/app-wcp": 0.0.0 - "@webiny/app-websockets": 0.0.0 - "@webiny/cli": 0.0.0 - "@webiny/error": 0.0.0 - "@webiny/project-utils": 0.0.0 - "@webiny/react-router": 0.0.0 - "@webiny/ui": 0.0.0 - "@webiny/utils": 0.0.0 - apollo-client: ^2.6.10 - apollo-link: ^1.2.14 - crypto-hash: ^3.0.0 - emotion: ^10.0.27 - graphql-tag: ^2.12.6 - react: 17.0.2 - react-dom: 17.0.2 - rimraf: ^5.0.5 - ttypescript: ^1.5.12 - typescript: 4.7.4 - languageName: unknown - linkType: soft - "@webiny/app-mailer@0.0.0, @webiny/app-mailer@workspace:packages/app-mailer": version: 0.0.0-use.local resolution: "@webiny/app-mailer@workspace:packages/app-mailer" @@ -15509,6 +15465,43 @@ __metadata: languageName: unknown linkType: soft +"@webiny/app-record-locking@0.0.0, @webiny/app-record-locking@workspace:packages/app-record-locking": + version: 0.0.0-use.local + resolution: "@webiny/app-record-locking@workspace:packages/app-record-locking" + dependencies: + "@apollo/react-hooks": ^3.1.5 + "@babel/cli": ^7.23.9 + "@babel/core": ^7.24.0 + "@babel/preset-env": ^7.24.0 + "@babel/preset-react": ^7.23.3 + "@babel/preset-typescript": ^7.23.3 + "@emotion/styled": ^11.10.6 + "@material-design-icons/svg": ^0.14.2 + "@webiny/app": 0.0.0 + "@webiny/app-admin": 0.0.0 + "@webiny/app-headless-cms": 0.0.0 + "@webiny/app-security": 0.0.0 + "@webiny/app-wcp": 0.0.0 + "@webiny/app-websockets": 0.0.0 + "@webiny/cli": 0.0.0 + "@webiny/error": 0.0.0 + "@webiny/project-utils": 0.0.0 + "@webiny/react-router": 0.0.0 + "@webiny/ui": 0.0.0 + "@webiny/utils": 0.0.0 + apollo-client: ^2.6.10 + apollo-link: ^1.2.14 + crypto-hash: ^3.0.0 + emotion: ^10.0.27 + graphql-tag: ^2.12.6 + react: 17.0.2 + react-dom: 17.0.2 + rimraf: ^5.0.5 + ttypescript: ^1.5.12 + typescript: 4.7.4 + languageName: unknown + linkType: soft + "@webiny/app-security-access-management@0.0.0, @webiny/app-security-access-management@workspace:packages/app-security-access-management": version: 0.0.0-use.local resolution: "@webiny/app-security-access-management@workspace:packages/app-security-access-management" @@ -15596,9 +15589,9 @@ __metadata: "@webiny/app-headless-cms": 0.0.0 "@webiny/app-i18n": 0.0.0 "@webiny/app-i18n-content": 0.0.0 - "@webiny/app-locking-mechanism": 0.0.0 "@webiny/app-mailer": 0.0.0 "@webiny/app-page-builder": 0.0.0 + "@webiny/app-record-locking": 0.0.0 "@webiny/app-security": 0.0.0 "@webiny/app-security-access-management": 0.0.0 "@webiny/app-tenancy": 0.0.0 @@ -18116,7 +18109,6 @@ __metadata: "@webiny/api-i18n": 0.0.0 "@webiny/api-i18n-content": 0.0.0 "@webiny/api-i18n-ddb": 0.0.0 - "@webiny/api-locking-mechanism": 0.0.0 "@webiny/api-page-builder": 0.0.0 "@webiny/api-page-builder-aco": 0.0.0 "@webiny/api-page-builder-import-export": 0.0.0 @@ -18124,6 +18116,7 @@ __metadata: "@webiny/api-page-builder-so-ddb": 0.0.0 "@webiny/api-prerendering-service": 0.0.0 "@webiny/api-prerendering-service-aws": 0.0.0 + "@webiny/api-record-locking": 0.0.0 "@webiny/api-security": 0.0.0 "@webiny/api-security-cognito": 0.0.0 "@webiny/api-security-so-ddb": 0.0.0 From ecfeaed7d018135e28c98b5b09b960dbbd004ea5 Mon Sep 17 00:00:00 2001 From: Pavel Denisjuk Date: Thu, 2 May 2024 15:50:05 +0200 Subject: [PATCH 02/11] wip: refactor CMS content entry hooks and contexts --- packages/app-aco/src/contexts/records.tsx | 4 +- .../FileManagerViewContext.tsx | 6 +- .../FileManagerViewProvider/useListFiles.ts | 10 +- packages/app-headless-cms/src/HeadlessCMS.tsx | 12 +- .../BulkActions/ActionDelete.tsx | 22 +- .../Table/Actions/DeleteEntry.tsx | 10 +- .../ContentEntryForm/ContentEntryForm.tsx | 213 +++------ .../ContentEntryFormPreview.tsx | 87 +--- .../ContentEntryFormProvider.tsx | 201 +++++++++ .../ContentEntryForm/CustomLayout.tsx | 46 ++ .../ContentEntryForm/DefaultLayout.tsx | 24 ++ .../Header/DeleteEntry/DeleteEntry.tsx | 30 +- .../DeleteEntry/ShowConfirmationOnDelete.tsx | 84 ++++ .../Header/DeleteEntry/useDeleteEntry.tsx | 65 --- .../SaveAndPublishContent.tsx | 26 +- .../ShowConfirmationOnPublish.tsx | 29 ++ .../useSaveAndPublish.tsx | 47 -- .../Header/SaveContent/SaveContent.tsx | 12 +- .../Header/SaveContent/useSave.tsx | 29 -- .../ContentEntryForm/useContentEntryForm.ts | 367 +--------------- .../ContentEntryForm/useDefaultValues.ts | 73 ++++ .../ContentEntryForm/useFormRenderer.ts | 8 + .../ContentEntryForm/useGoToRevision.ts | 21 + .../ContentModelEditor/ContentModelEditor.tsx | 14 +- .../src/admin/contexts/Cms/index.tsx | 407 ++++++++++++------ .../app-headless-cms/src/admin/hooks/index.ts | 1 - .../src/admin/hooks/useDeleteEntry.tsx | 63 --- .../src/admin/menus/NothingToShowElement.tsx | 1 - .../plugins/entry/DefaultOnEntryDelete.tsx | 117 ----- .../plugins/entry/DefaultOnEntryUnpublish.tsx | 96 ----- .../components/ContentEntriesAutocomplete.tsx | 1 - .../components/NewReferencedEntryDialog.tsx | 43 +- .../contentEntries/ContentEntriesContext.tsx | 8 +- .../contentEntries/ContentEntriesModule.tsx | 6 +- .../views/contentEntries/ContentEntry.tsx | 10 +- .../ContentEntry/ContentEntryContext.tsx | 216 ++++++---- .../PublishEntryRevisionListItem.tsx | 0 .../{ => RevisionsList}/RevisionListItem.tsx | 0 .../{ => RevisionsList}/RevisionsList.tsx | 4 +- .../{ => RevisionsList}/useRevision.tsx | 112 ++--- .../ContentEntry/useMockRecords.ts | 5 + .../contentEntries/hooks/useContentEntries.ts | 4 +- .../contentEntries/hooks/useContentEntry.ts | 9 +- packages/app-headless-cms/src/components.ts | 6 +- packages/app-serverless-cms/src/Admin.tsx | 8 +- 45 files changed, 1151 insertions(+), 1406 deletions(-) create mode 100644 packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryFormProvider.tsx create mode 100644 packages/app-headless-cms/src/admin/components/ContentEntryForm/CustomLayout.tsx create mode 100644 packages/app-headless-cms/src/admin/components/ContentEntryForm/DefaultLayout.tsx create mode 100644 packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/ShowConfirmationOnDelete.tsx delete mode 100644 packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/useDeleteEntry.tsx create mode 100644 packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/ShowConfirmationOnPublish.tsx delete mode 100644 packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/useSaveAndPublish.tsx delete mode 100644 packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveContent/useSave.tsx create mode 100644 packages/app-headless-cms/src/admin/components/ContentEntryForm/useDefaultValues.ts create mode 100644 packages/app-headless-cms/src/admin/components/ContentEntryForm/useFormRenderer.ts create mode 100644 packages/app-headless-cms/src/admin/components/ContentEntryForm/useGoToRevision.ts delete mode 100644 packages/app-headless-cms/src/admin/hooks/useDeleteEntry.tsx delete mode 100644 packages/app-headless-cms/src/admin/plugins/entry/DefaultOnEntryDelete.tsx delete mode 100644 packages/app-headless-cms/src/admin/plugins/entry/DefaultOnEntryUnpublish.tsx rename packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/{ => RevisionsList}/PublishEntryRevisionListItem.tsx (100%) rename packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/{ => RevisionsList}/RevisionListItem.tsx (100%) rename packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/{ => RevisionsList}/RevisionsList.tsx (96%) rename packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/{ => RevisionsList}/useRevision.tsx (52%) create mode 100644 packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/useMockRecords.ts diff --git a/packages/app-aco/src/contexts/records.tsx b/packages/app-aco/src/contexts/records.tsx index 6b3f3b3384d..d285004e902 100644 --- a/packages/app-aco/src/contexts/records.tsx +++ b/packages/app-aco/src/contexts/records.tsx @@ -167,7 +167,9 @@ export const SearchRecordsProvider = ({ children }: Props) => { }, removeRecordFromCache: (id: string) => { setRecords(prev => { - return prev.filter(record => record.id !== id); + return prev.filter( + record => record.id !== id && !record.id.startsWith(`${id}#`) + ); }); }, diff --git a/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/FileManagerViewContext.tsx b/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/FileManagerViewContext.tsx index 1d5a9fb0fe9..dc034cefd22 100644 --- a/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/FileManagerViewContext.tsx +++ b/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/FileManagerViewContext.tsx @@ -1,6 +1,6 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useMemo } from "react"; import omit from "lodash/omit"; -import { useShiftKey } from "@webiny/app-admin"; +import { useShiftKey, useStateIfMounted } from "@webiny/app-admin"; import { FileItem } from "@webiny/app-admin/types"; import { FileTag } from "~/types"; import { useFileManagerApi } from "~/index"; @@ -109,7 +109,7 @@ export const FileManagerViewProvider = ({ children, ...props }: FileManagerViewP const { folders: originalFolders, loading: foldersLoading } = useFolders(); const { currentFolderId = ROOT_FOLDER, navigateToFolder } = useNavigateFolder(); const tags = useTags(modifiers); - const [state, setState] = useState(initializeState()); + const [state, setState] = useStateIfMounted(initializeState()); const { loading, files, meta, listFiles, setFiles, getListVariables } = useListFiles({ folderId: currentFolderId, diff --git a/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/useListFiles.ts b/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/useListFiles.ts index 34e9a85f472..2a8843727e9 100644 --- a/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/useListFiles.ts +++ b/packages/app-file-manager/src/modules/FileManagerRenderer/FileManagerViewProvider/useListFiles.ts @@ -1,10 +1,10 @@ -import { useState } from "react"; import isEqual from "lodash/isEqual"; import { validateOrGetDefaultDbSort } from "@webiny/app-aco/sorting"; import { useFolders } from "@webiny/app-aco"; import { ListMeta } from "@webiny/app-aco/types"; import { useSecurity } from "@webiny/app-security"; import { FileItem } from "@webiny/app-admin/types"; +import { useStateIfMounted } from "@webiny/app-admin"; import { Loading, LoadingActions } from "~/modules/FileManagerRenderer/FileManagerViewProvider"; import { ListFilesQueryVariables, @@ -38,10 +38,10 @@ export function useListFiles({ modifiers, folderId, state }: UseListFilesParams) const { identity } = useSecurity(); const fileManager = useFileManagerApi(); const { getDescendantFolders } = useFolders(); - const [meta, setMeta] = useState(undefined); - const [files, setFiles] = useState([]); - const [loading, setLoading] = useState>({}); - const [lastSort, setLastSort] = useState(undefined); + const [meta, setMeta] = useStateIfMounted(undefined); + const [files, setFiles] = useStateIfMounted([]); + const [loading, setLoading] = useStateIfMounted>({}); + const [lastSort, setLastSort] = useStateIfMounted(undefined); const listFiles = async (params: ListFilesQueryVariables) => { const { after, limit, sort: sorting, search, where } = params; diff --git a/packages/app-headless-cms/src/HeadlessCMS.tsx b/packages/app-headless-cms/src/HeadlessCMS.tsx index 6d2d03facd0..a95c7fb9dd5 100644 --- a/packages/app-headless-cms/src/HeadlessCMS.tsx +++ b/packages/app-headless-cms/src/HeadlessCMS.tsx @@ -1,15 +1,13 @@ import React, { Fragment, memo } from "react"; +import { ApolloClient } from "apollo-client"; import { plugins } from "@webiny/plugins"; import { Plugins, Provider } from "@webiny/app-admin"; import { ApolloCacheObjectIdPlugin } from "@webiny/app"; -import { ApolloClient } from "apollo-client"; -import { CmsProvider } from "./admin/contexts/Cms"; +import { CmsProvider } from "~/admin/contexts/Cms"; import { CmsMenuLoader } from "~/admin/menus/CmsMenuLoader"; -import apiInformation from "./admin/plugins/apiInformation"; +import apiInformation from "~/admin/plugins/apiInformation"; import { ContentEntriesModule } from "~/admin/views/contentEntries/ContentEntriesModule"; -import { DefaultOnEntryDelete } from "./admin/plugins/entry/DefaultOnEntryDelete"; -import { DefaultOnEntryUnpublish } from "~/admin/plugins/entry/DefaultOnEntryUnpublish"; -import allPlugins from "./allPlugins"; +import allPlugins from "~/allPlugins"; import { LexicalEditorCmsPlugin } from "~/admin/components/LexicalCmsEditor/LexicalEditorCmsPlugin"; interface HeadlessCMSProvider { @@ -67,8 +65,6 @@ const HeadlessCMSExtension = ({ createApolloClient }: HeadlessCMSProps) => { - - diff --git a/packages/app-headless-cms/src/admin/components/ContentEntries/BulkActions/ActionDelete.tsx b/packages/app-headless-cms/src/admin/components/ContentEntries/BulkActions/ActionDelete.tsx index 208b5d9fddf..fd032b2812c 100644 --- a/packages/app-headless-cms/src/admin/components/ContentEntries/BulkActions/ActionDelete.tsx +++ b/packages/app-headless-cms/src/admin/components/ContentEntries/BulkActions/ActionDelete.tsx @@ -1,16 +1,13 @@ import React, { useMemo } from "react"; import { ReactComponent as DeleteIcon } from "@material-design-icons/svg/outlined/delete.svg"; -import { useRecords } from "@webiny/app-aco"; import { observer } from "mobx-react-lite"; import { ContentEntryListConfig } from "~/admin/config/contentEntries"; -import { useCms, useContentEntry } from "~/admin/hooks"; +import { useContentEntry } from "~/admin/hooks"; import { getEntriesLabel } from "~/admin/components/ContentEntries/BulkActions/BulkActions"; import { parseIdentifier } from "@webiny/utils/parseIdentifier"; export const ActionDelete = observer(() => { - const { deleteEntry } = useCms(); - const { contentModel } = useContentEntry(); - const { removeRecordFromCache } = useRecords(); + const { deleteEntry } = useContentEntry(); const { useWorker, useButtons, useDialog } = ContentEntryListConfig.Browser.BulkAction; const { IconButton } = useButtons(); @@ -34,22 +31,15 @@ export const ActionDelete = observer(() => { * By sending an entryId (id without #version), we are telling to the API to delete all revisions. */ const { id } = parseIdentifier(item.id); - const response = await deleteEntry({ - model: contentModel, - entry: item, - id - }); - - const { error } = response; + const response = await deleteEntry({ id }); - if (error) { + if (typeof response !== "boolean") { throw new Error( - error.message || "Unknown error while moving the entry to trash." + response.error.message || + "Unknown error while moving the entry to trash." ); } - removeRecordFromCache(item.id); - report.success({ title: `${item.meta.title}`, message: "Entry successfully moved to trash." diff --git a/packages/app-headless-cms/src/admin/components/ContentEntries/Table/Actions/DeleteEntry.tsx b/packages/app-headless-cms/src/admin/components/ContentEntries/Table/Actions/DeleteEntry.tsx index 19de4039527..76dff2b8a72 100644 --- a/packages/app-headless-cms/src/admin/components/ContentEntries/Table/Actions/DeleteEntry.tsx +++ b/packages/app-headless-cms/src/admin/components/ContentEntries/Table/Actions/DeleteEntry.tsx @@ -1,14 +1,18 @@ import React from "react"; import { ReactComponent as Delete } from "@material-design-icons/svg/outlined/delete.svg"; import { ContentEntryListConfig } from "~/admin/config/contentEntries"; -import { useDeleteEntry, useEntry, usePermission } from "~/admin/hooks"; +import { useContentEntry, useEntry, usePermission } from "~/admin/hooks"; export const DeleteEntry = () => { const { entry } = useEntry(); + const contentEntry = useContentEntry(); const { canDelete } = usePermission(); - const { openDialogDeleteEntry } = useDeleteEntry({ entry }); const { OptionsMenuItem } = ContentEntryListConfig.Browser.EntryAction; + const deleteEntry = async () => { + await contentEntry.deleteEntry({ id: entry.entryId }); + }; + if (!canDelete(entry, "cms.contentEntry")) { return null; } @@ -17,7 +21,7 @@ export const DeleteEntry = () => { } label={"Trash"} - onAction={openDialogDeleteEntry} + onAction={deleteEntry} data-testid={"aco.actions.entry.delete"} /> ); diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryForm.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryForm.tsx index bcca9e2bf80..d1278357f01 100644 --- a/packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryForm.tsx +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryForm.tsx @@ -1,77 +1,69 @@ -import React, { useCallback, useEffect, useRef } from "react"; -import { FieldElement } from "./FieldElement"; +import React, { useEffect, useRef } from "react"; import styled from "@emotion/styled"; -import { Form } from "@webiny/form"; -import { FormAPI, FormRenderPropParams } from "@webiny/form/types"; -import { plugins } from "@webiny/plugins"; -import { CircularProgress } from "@webiny/ui/Progress"; -import { CmsContentEntry, CmsContentFormRendererPlugin } from "~/types"; -import { useContentEntryForm, UseContentEntryFormParams } from "./useContentEntryForm"; -import { Fields } from "./Fields"; -import { Prompt } from "@webiny/react-router"; -import { makeDecoratable, useSnackbar } from "@webiny/app-admin"; +import { CmsContentEntry } from "~/types"; +import { makeDecoratable } from "@webiny/app-admin"; import { ModelProvider, useModel } from "~/admin/components/ModelProvider"; import { Header } from "~/admin/components/ContentEntryForm/Header"; +import { useDefaultValues } from "~/admin/components/ContentEntryForm/useDefaultValues"; +import { useFormRenderer } from "~/admin/components/ContentEntryForm/useFormRenderer"; +import { ContentEntryFormContext, ContentEntryFormProvider } from "./ContentEntryFormProvider"; +import { CustomLayout } from "./CustomLayout"; +import { DefaultLayout } from "./DefaultLayout"; +import { useGoToRevision } from "~/admin/components/ContentEntryForm/useGoToRevision"; const FormWrapper = styled("div")({ height: "calc(100vh - 260px)", overflow: "auto" }); -export interface ContentEntryFormProps extends UseContentEntryFormParams { - onForm?: (form: FormAPI) => void; +export interface ContentEntryFormProps { + entry: Partial; + /** + * This callback is executed when an entry, or a revision, are created. + * @param entry + */ + onAfterCreate?: (entry: CmsContentEntry) => void; + header?: boolean; + /** + * This prop is used to get a reference to `saveEntry` callback, so it can be triggered by components + * outside the ContentEntryForm context. + * TODO: introduce a `layout` prop to be able to mount arbitrary components around the entry form, within the context. + */ + setSaveEntry?: (cb: ContentEntryFormContext["saveEntry"]) => void; + /** + * This flag exists for a lack of better Apollo cache control, at the moment. + * We use this flag when we need to tell the system to add new entries to apollo cache. + * Why would you want to NOT add entries to cache? When using a `ref` field, which usually points to + * a different model than the main entry you're working on. Example: Book -> Author, you don't want + * an Author created via a `ref` field dialog to be added to the list of Books. + * TODO: revisit this, and look for a better solution. + */ + addEntryToListCache?: boolean; } -function omitTypename(key: string, value: string): string | undefined { - return key === "__typename" ? undefined : value; -} - -const stringify = (value: any): string => { - return JSON.stringify(value || {}, omitTypename); -}; - -const isDifferent = (value: any, compare: any): boolean => { - if (!value && !compare) { - return false; - } - return stringify(value) !== stringify(compare); -}; - export const ContentEntryForm = makeDecoratable( "ContentEntryForm", - ({ onForm, ...props }: ContentEntryFormProps) => { + ({ + entry, + onAfterCreate, + addEntryToListCache, + setSaveEntry, + header = true + }: ContentEntryFormProps) => { const formElementRef = useRef(null); const { model } = useModel(); - const { - loading, - data: initialData, - onChange, - onSubmit, - invalidFields - } = useContentEntryForm(props); - - const [isDirty, setIsDirty] = React.useState(false); - /** - * Reset isDirty when the loaded data changes. - */ - useEffect(() => { - if (!isDirty) { - return; - } - setIsDirty(false); - }, [initialData]); + const { goToRevision } = useGoToRevision(); + const defaultValues = useDefaultValues(model); + const formRenderer = useFormRenderer(model); - const { showSnackbar } = useSnackbar(); + const defaultOnAfterCreate = (entry: CmsContentEntry) => { + goToRevision(entry.id); + }; - const ref = useRef(null); - - useEffect(() => { - if (typeof onForm !== "function" || !ref.current) { - return; - } - onForm(ref.current); - }, []); + // Determine initial entry. + const initialData = entry && entry.id ? entry : defaultValues; + // When entry changes, scroll to the top of the form. useEffect(() => { if (!formElementRef.current) { return; @@ -80,100 +72,25 @@ export const ContentEntryForm = makeDecoratable( formElementRef.current.scrollTo(0, 0); }, [initialData.id, formElementRef.current]); - const formRenderer = plugins - .byType("cms-content-form-renderer") - .find(pl => pl.modelId === model.modelId); - - const renderCustomLayout = useCallback( - (formRenderProps: FormRenderPropParams) => { - const fields = model.fields.reduce((acc, field) => { - acc[field.fieldId] = ( - - ); - - return acc; - }, {} as Record); - if (!formRenderer) { - return <>{`Missing form renderer for modelId "${model.modelId}".`}; - } - return formRenderer.render({ - ...formRenderProps, - contentModel: model, - fields, - /** - * TODO @ts-refactor - * Figure out type for Bind. - */ - // @ts-expect-error - Bind: formRenderProps.Bind - }); - }, - [formRenderer] - ); - return ( - - onChange={(data, form) => { - const different = isDifferent(data, initialData); - if (isDirty !== different) { - setIsDirty(different); - } - return onChange(data, form); - }} - onSubmit={(data, form) => { - setIsDirty(false); - return onSubmit(data, form); - }} - data={initialData} - ref={ref} - invalidFields={invalidFields} - onInvalid={() => { - setIsDirty(true); - showSnackbar("Some fields did not pass the validation. Please check the form."); - }} + - {formProps => { - return ( - - -
- - {loading && } - {formRenderer ? ( - renderCustomLayout(formProps) - ) : ( - - )} - - - ); - }} - + + {header ?
: null} + + {formRenderer ? ( + + ) : ( + + )} + + + ); } ); diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryFormPreview.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryFormPreview.tsx index bf9a0760474..5b860792c60 100644 --- a/packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryFormPreview.tsx +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryFormPreview.tsx @@ -1,12 +1,12 @@ -import React, { useCallback } from "react"; +import React from "react"; import styled from "@emotion/styled"; -import { Form, FormRenderPropParams } from "@webiny/form"; -import { plugins } from "@webiny/plugins"; import { makeDecoratable } from "@webiny/app-admin"; -import { FieldElement } from "./FieldElement"; -import { CmsContentFormRendererPlugin, CmsEditorContentModel } from "~/types"; -import { Fields } from "~/admin/components/ContentEntryForm/Fields"; +import { CmsEditorContentModel } from "~/types"; import { ModelProvider } from "~/admin/components/ModelProvider"; +import { useFormRenderer } from "~/admin/components/ContentEntryForm/useFormRenderer"; +import { CustomLayout } from "~/admin/components/ContentEntryForm/CustomLayout"; +import { DefaultLayout } from "~/admin/components/ContentEntryForm/DefaultLayout"; +import { ContentEntryFormProvider } from "./ContentEntryFormProvider"; const FormWrapper = styled("div")({ height: "calc(100vh - 260px)", @@ -22,71 +22,20 @@ export const ContentEntryFormPreview = makeDecoratable( (props: ContentEntryFormPreviewProps) => { const { contentModel } = props; - const formRenderer = plugins - .byType("cms-content-form-renderer") - .find(pl => pl.modelId === contentModel.modelId); - - const renderCustomLayout = useCallback( - (formRenderProps: FormRenderPropParams) => { - const fields = contentModel.fields.reduce((acc, field) => { - acc[field.fieldId] = ( - - ); - - return acc; - }, {} as Record); - if (!formRenderer) { - return <>{`Missing form renderer for modelId "${contentModel.modelId}".`}; - } - return formRenderer.render({ - ...formRenderProps, - /** - * TODO @ts-refactor - * Figure out type for Bind. - */ - // @ts-expect-error - Bind: formRenderProps.Bind, - contentModel, - fields - }); - }, - [formRenderer, contentModel.fields] - ); + const formRenderer = useFormRenderer(contentModel); return ( -
- {formProps => ( - - - {formRenderer ? ( - renderCustomLayout(formProps) - ) : ( - - )} - - - )} -
+ + + + {formRenderer ? ( + + ) : ( + + )} + + + ); } ); diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryFormProvider.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryFormProvider.tsx new file mode 100644 index 00000000000..47cb68f4921 --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/ContentEntryFormProvider.tsx @@ -0,0 +1,201 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import pick from "lodash/pick"; +import debounce from "lodash/debounce"; +import { Prompt } from "@webiny/react-router"; +import { Form, FormAPI, FormOnSubmit } from "@webiny/form"; +import { CmsContentEntry, CmsModel } from "@webiny/app-headless-cms-common/types"; +import { useSnackbar } from "@webiny/app-admin"; +import { prepareFormData } from "@webiny/app-headless-cms-common"; +import { useContentEntry } from "~/index"; +import { PartialCmsContentEntryWithId } from "~/admin/contexts/Cms"; + +function omitTypename(key: string, value: string): string | undefined { + return key === "__typename" ? undefined : value; +} + +const stringify = (value: any): string => { + return JSON.stringify(value || {}, omitTypename); +}; + +const isDifferent = (value: any, compare: any): boolean => { + if (!value && !compare) { + return false; + } + return stringify(value) !== stringify(compare); +}; + +interface SaveEntryOptions { + skipValidators?: string[]; +} + +export interface ContentEntryFormContext { + entry: Partial; + isDirty: boolean; + saveEntry: (options?: SaveEntryOptions) => Promise; + invalidFields: Record; +} + +export const ContentEntryFormContext = React.createContext( + undefined +); + +interface InvalidFieldError { + fieldId: string; + error: string; +} + +interface PersistEntryParams { + entry: PartialCmsContentEntryWithId; + isLocked: boolean; +} + +export interface SetSaveEntry { + (cb: ContentEntryFormContext["saveEntry"]): void; +} + +interface ContentEntryFormProviderProps { + entry: Partial; + model: CmsModel; + onAfterCreate?: (entry: CmsContentEntry) => void; + setSaveEntry?: SetSaveEntry; + addItemToListCache?: boolean; + children: React.ReactNode; +} + +export const ContentEntryFormProvider = ({ + model, + entry, + children, + onAfterCreate, + setSaveEntry, + addItemToListCache +}: ContentEntryFormProviderProps) => { + const ref = useRef | null>(null); + const [invalidFields, setInvalidFields] = useState({}); + const { showSnackbar } = useSnackbar(); + const contentEntry = useContentEntry(); + const saveOptionsRef = useRef({ skipValidators: undefined }); + const [isDirty, setIsDirty] = React.useState(false); + + // Reset `isDirty` when data changes from outside. + useEffect(() => { + if (!isDirty) { + return; + } + setIsDirty(false); + }, [entry]); + + const saveEntry = useCallback(async (options: SaveEntryOptions = {}) => { + saveOptionsRef.current.skipValidators = options.skipValidators; + + return ref.current!.submit(undefined, { + skipValidators: options.skipValidators + }) as unknown as Promise; + }, []); + + const persistEntry = ({ entry, isLocked }: PersistEntryParams) => { + const options = { + skipValidators: saveOptionsRef.current.skipValidators, + addItemToListCache + }; + + if (!entry.id) { + return contentEntry.createEntry({ entry, options }); + } + + if (!isLocked) { + return contentEntry.updateEntryRevision({ + entry, + options: { skipValidators: options?.skipValidators } + }); + } + + const { id, ...input } = entry; + + return contentEntry.createEntryRevisionFrom({ + id, + input, + options: { skipValidators: options?.skipValidators } + }); + }; + + const onChange = useMemo(() => { + return debounce(data => { + const different = isDifferent(data, entry); + if (isDirty !== different) { + setIsDirty(different); + } + }, 500); + }, [isDirty, entry]); + + const onFormSubmit: FormOnSubmit = async data => { + const fieldsIds = model.fields.map(item => item.fieldId); + const formData = pick(data, [...fieldsIds]); + + const gqlData = prepareFormData(formData, model.fields) as Partial; + const isNewEntry = data.id === undefined; + + const { entry, error } = await persistEntry({ + entry: { id: data.id, ...gqlData }, + isLocked: data.meta?.locked === true + }); + + if (error) { + showSnackbar(error.message); + setInvalidFields(error.data as InvalidFieldError[]); + return; + } + + setInvalidFields({}); + + const isNewRevision = !isNewEntry && data.id !== entry.id; + + if ((isNewEntry || isNewRevision) && onAfterCreate) { + onAfterCreate(entry); + } + + setIsDirty(false); + + return entry; + }; + + useEffect(() => { + if (typeof setSaveEntry === "function") { + setSaveEntry(saveEntry); + } + }, [setSaveEntry]); + + return ( + + onChange={onChange} + onSubmit={onFormSubmit} + data={entry} + ref={ref} + invalidFields={invalidFields} + onInvalid={() => { + setIsDirty(true); + showSnackbar("Some fields did not pass the validation. Please check the form."); + }} + > + {formProps => { + const context: ContentEntryFormContext = { + entry: formProps.data, + saveEntry, + isDirty, + invalidFields + }; + return ( + + + {children} + + ); + }} + + ); +}; diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/CustomLayout.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/CustomLayout.tsx new file mode 100644 index 00000000000..d22767c7941 --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/CustomLayout.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { Bind, useForm } from "@webiny/form"; +import { CmsContentFormRendererPlugin, CmsModel } from "@webiny/app-headless-cms-common/types"; +import { FieldElement } from "~/admin/components/ContentEntryForm/FieldElement"; + +interface CustomLayoutProps { + model: CmsModel; + formRenderer: CmsContentFormRendererPlugin; +} + +export const CustomLayout = ({ model, formRenderer }: CustomLayoutProps) => { + const { data } = useForm(); + + const fields = model.fields.reduce((acc, field) => { + acc[field.fieldId] = ( + + ); + + return acc; + }, {} as Record); + + return ( + <> + {formRenderer.render({ + data, + contentModel: model, + fields, + /** + * TODO @ts-refactor + * Figure out type for Bind. + */ + // @ts-expect-error + Bind + })} + + ); +}; diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/DefaultLayout.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/DefaultLayout.tsx new file mode 100644 index 00000000000..106e1ed9fb4 --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/DefaultLayout.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { Bind } from "@webiny/form"; +import { CmsModel } from "@webiny/app-headless-cms-common/types"; +import { Fields } from "~/admin/components/ContentEntryForm/Fields"; + +interface DefaultLayoutProps { + model: CmsModel; +} + +export const DefaultLayout = ({ model }: DefaultLayoutProps) => { + return ( + + ); +}; diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/DeleteEntry.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/DeleteEntry.tsx index 7cb368da142..4262a28ba51 100644 --- a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/DeleteEntry.tsx +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/DeleteEntry.tsx @@ -1,24 +1,24 @@ -import React, { useCallback } from "react"; +import React from "react"; import { ReactComponent as DeleteIcon } from "@material-design-icons/svg/outlined/delete.svg"; -import { useRouter } from "@webiny/react-router"; - -import { usePermission } from "~/admin/hooks/usePermission"; +import { useNavigateFolder } from "@webiny/app-aco"; +import { usePermission } from "~/admin/hooks"; import { ContentEntryEditorConfig } from "~/admin/config/contentEntries"; import { useContentEntry } from "~/admin/views/contentEntries/hooks/useContentEntry"; -import { useDeleteEntry } from "./useDeleteEntry"; export const DeleteEntry = () => { - const { history } = useRouter(); - const { entry, contentModel, loading } = useContentEntry(); + const { navigateToLatestFolder } = useNavigateFolder(); + const { entry, contentModel, loading, ...contentEntry } = useContentEntry(); const { canDelete } = usePermission(); - const { showConfirmationDialog } = useDeleteEntry(); + const { OptionsMenuItem } = ContentEntryEditorConfig.Actions.MenuItemAction.useOptionsMenuItem(); - const navigateBacktoList = useCallback( - () => history.push("/cms/content-entries/" + contentModel.modelId), - [] - ); + const deleteEntry = async () => { + const response = await contentEntry.deleteEntry({ id: entry.entryId }); + if (typeof response === "boolean") { + navigateToLatestFolder(); + } + }; if (!canDelete(entry, "cms.contentEntry")) { return null; @@ -28,11 +28,7 @@ export const DeleteEntry = () => { } label={"Trash"} - onAction={() => - showConfirmationDialog({ - onAccept: navigateBacktoList - }) - } + onAction={deleteEntry} disabled={!entry.id || loading} data-testid={"cms.content-form.header.delete"} /> diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/ShowConfirmationOnDelete.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/ShowConfirmationOnDelete.tsx new file mode 100644 index 00000000000..bfc0dc79c18 --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/ShowConfirmationOnDelete.tsx @@ -0,0 +1,84 @@ +import React, { useEffect } from "react"; +import styled from "@emotion/styled"; +import { useBind } from "@webiny/form"; +import { useDialogs, useSnackbar } from "@webiny/app-admin"; +import { useContentEntry } from "~/admin/views/contentEntries/hooks"; +import { CircularProgress } from "@webiny/ui/Progress"; + +type GetEntry = ReturnType["getEntry"]; +type DeleteEntry = ReturnType["deleteEntry"]; +type DeleteEntryParams = Parameters[0]; +type DeleteEntryResponse = Awaited>; + +const Title = styled.span` + font-weight: bold; +`; + +const EntryMessage = ({ id, getEntry }: { id: string; getEntry: GetEntry }) => { + const entryBind = useBind({ + name: "entry" + }); + + useEffect(() => { + getEntry({ id }).then(response => { + entryBind.onChange(response.entry); + }); + }, []); + + if (!entryBind.value) { + return ; + } + + return ( +

+ Are you sure you want to move {entryBind.value.meta.title} to trash? +
+ This action will include all of the revisions. +

+ ); +}; + +export const ShowConfirmationOnDelete = useContentEntry.createDecorator(baseHook => { + return () => { + const hook = baseHook(); + const dialogs = useDialogs(); + const { showSnackbar } = useSnackbar(); + + const showConfirmation = (params: DeleteEntryParams) => { + return new Promise(resolve => { + dialogs.showDialog({ + title: "Trash entry", + content: , + acceptLabel: "Confirm", + cancelLabel: "Cancel", + loadingLabel: "Moving entry to trash...", + onAccept: async ({ entry }) => { + const response = await hook.deleteEntry(params); + + if (typeof response !== "boolean") { + showSnackbar( + <> + Could not move {entry.meta.title} to trash! ( + {response.error.message}) + + ); + + return resolve(response); + } + + showSnackbar(`${entry.meta.title} has been moved to trash successfully!`); + resolve(response); + }, + onClose: () => resolve({ error: { message: "Cancelled" } }) + }); + }); + }; + + return { + ...hook, + deleteEntry: params => { + return showConfirmation(params); + } + }; + }; +}); diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/useDeleteEntry.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/useDeleteEntry.tsx deleted file mode 100644 index 1e2cc2c26fb..00000000000 --- a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/DeleteEntry/useDeleteEntry.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useCallback } from "react"; -import get from "lodash/get"; -import { useConfirmationDialog, useDialog, useSnackbar } from "@webiny/app-admin"; -import { parseIdentifier } from "@webiny/utils"; -import { useCms, useContentEntry } from "~/admin/hooks"; -import { useNavigateFolder, useRecords } from "@webiny/app-aco"; - -interface ShowConfirmationDialogParams { - onAccept?: () => void; - onCancel?: () => void; -} - -interface UseDeleteEntryDialogResponse { - showConfirmationDialog: (params: ShowConfirmationDialogParams) => void; -} - -export const useDeleteEntry = (): UseDeleteEntryDialogResponse => { - const { deleteEntry } = useCms(); - const { contentModel, entry } = useContentEntry(); - const { showSnackbar } = useSnackbar(); - const { showDialog } = useDialog(); - const { navigateToLatestFolder } = useNavigateFolder(); - const { removeRecordFromCache } = useRecords(); - - const title = get(entry, "meta.title"); - const { showConfirmation } = useConfirmationDialog({ - title: "Trash entry", - message: ( -

- Are you sure you want to move {title} to trash? -
- This action will include all of its revisions? -

- ), - dataTestId: "cms.content-form.header.delete-dialog" - }); - - const showConfirmationDialog = useCallback( - ({ onAccept, onCancel }) => - showConfirmation(async () => { - const { id: entryId } = parseIdentifier(entry.id); - const { error } = await deleteEntry({ - model: contentModel, - entry, - id: entryId - }); - - if (error) { - showDialog(error.message, { title: `Could not move ${title} to trash!` }); - return; - } - - showSnackbar(`${title} has been moved to trash successfully!`); - removeRecordFromCache(entry.id); - navigateToLatestFolder(); - - if (typeof onAccept === "function") { - await onAccept(); - } - }, onCancel), - [entry] - ); - - return { showConfirmationDialog }; -}; diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/SaveAndPublishContent.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/SaveAndPublishContent.tsx index 1cd88a1295d..e9e1a63d0d7 100644 --- a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/SaveAndPublishContent.tsx +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/SaveAndPublishContent.tsx @@ -1,16 +1,23 @@ import React from "react"; -import { makeDecoratable } from "@webiny/react-composition"; import { ContentEntryEditorConfig } from "~/admin/config/contentEntries"; import { useContentEntry } from "~/admin/views/contentEntries/hooks/useContentEntry"; import usePermission from "~/admin/hooks/usePermission"; -import { useSaveAndPublish } from "./useSaveAndPublish"; +import { useContentEntryForm } from "~/admin/components/ContentEntryForm/useContentEntryForm"; const SaveAndPublishButtonComponent = () => { - const { loading, entry } = useContentEntry(); - const { showConfirmationDialog } = useSaveAndPublish(); + const { loading, entry, publishEntryRevision } = useContentEntry(); + const { saveEntry } = useContentEntryForm(); const { ButtonPrimary } = ContentEntryEditorConfig.Actions.ButtonAction.useButtons(); + const saveAndPublish = async () => { + const entry = await saveEntry(); + if (!entry || !entry.id) { + return; + } + await publishEntryRevision({ id: entry.id }); + }; + const { canEdit, canPublish } = usePermission(); if (!canEdit(entry, "cms.contentEntry") || !canPublish("cms.contentEntry")) { @@ -19,7 +26,7 @@ const SaveAndPublishButtonComponent = () => { return ( @@ -28,7 +35,8 @@ const SaveAndPublishButtonComponent = () => { ); }; -export const SaveAndPublishButton = makeDecoratable( - "SaveAndPublishButton", - SaveAndPublishButtonComponent -); +const { Actions } = ContentEntryEditorConfig; + +export const SaveAndPublishButton = () => { + return } />; +}; diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/ShowConfirmationOnPublish.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/ShowConfirmationOnPublish.tsx new file mode 100644 index 00000000000..37f02e77aa3 --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/ShowConfirmationOnPublish.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { useConfirmationDialog } from "@webiny/app-admin"; +import { useContentEntry } from "~/admin/views/contentEntries/hooks"; + +export const ShowConfirmationOnPublish = useContentEntry.createDecorator(baseHook => { + return () => { + const hook = baseHook(); + + const { showConfirmation } = useConfirmationDialog({ + title: "Publish content", + message: ( +

You are about to publish a new revision. Are you sure you want to continue?

+ ), + dataTestId: "cms-confirm-save-and-publish" + }); + + return { + ...hook, + publishEntryRevision: (...params) => { + return new Promise(resolve => { + showConfirmation( + () => resolve(hook.publishEntryRevision(...params)), + () => resolve({ error: { message: "Cancelled" } }) + ); + }); + } + }; + }; +}); diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/useSaveAndPublish.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/useSaveAndPublish.tsx deleted file mode 100644 index f492d44f209..00000000000 --- a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveAndPublishContent/useSaveAndPublish.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { useCallback } from "react"; -import { makeDecoratableHook, useConfirmationDialog } from "@webiny/app-admin"; -import { useRevision } from "~/admin/views/contentEntries/ContentEntry/useRevision"; -import { useContentEntry } from "~/admin/views/contentEntries/hooks"; - -export interface ShowConfirmationDialogParams { - ev: React.MouseEvent; - onAccept?: () => void; - onCancel?: () => void; -} - -interface UseSaveAndPublishResponse { - showConfirmationDialog: (params: ShowConfirmationDialogParams) => void; -} - -const useSaveAndPublishDefault = (): UseSaveAndPublishResponse => { - const { form, entry } = useContentEntry(); - const { publishRevision } = useRevision({ revision: entry }); - - const { showConfirmation } = useConfirmationDialog({ - title: "Publish content", - message:

You are about to publish a new revision. Are you sure you want to continue?

, - dataTestId: "cms-confirm-save-and-publish" - }); - - const showConfirmationDialog = useCallback( - async ({ ev, onAccept, onCancel }: ShowConfirmationDialogParams) => { - const entry = await form.current.submit(ev); - if (!entry || !entry.id) { - return; - } - - showConfirmation(async () => { - await publishRevision(entry.id); - - if (typeof onAccept === "function") { - await onAccept(); - } - }, onCancel); - }, - [showConfirmation] - ); - - return { showConfirmationDialog }; -}; - -export const useSaveAndPublish = makeDecoratableHook(useSaveAndPublishDefault); diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveContent/SaveContent.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveContent/SaveContent.tsx index a7d1b7ed0ae..18356b27367 100644 --- a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveContent/SaveContent.tsx +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveContent/SaveContent.tsx @@ -1,23 +1,23 @@ import React from "react"; import { ContentEntryEditorConfig } from "~/admin/config/contentEntries"; import { usePermission } from "~/admin/hooks/usePermission"; -import { useContentEntry } from "~/admin/views/contentEntries/hooks/useContentEntry"; -import { useSave } from "./useSave"; +import { useContentEntryForm } from "~/admin/components/ContentEntryForm/useContentEntryForm"; export const SaveContentButton = () => { - const { entry } = useContentEntry(); const { canEdit } = usePermission(); const { useButtons } = ContentEntryEditorConfig.Actions.ButtonAction; const { ButtonSecondary } = useButtons(); - - const { saveEntry } = useSave(); + const { entry, saveEntry } = useContentEntryForm(); if (!canEdit(entry, "cms.contentEntry")) { return null; } return ( - + saveEntry({ skipValidators: ["required"] })} + > {"Save"} ); diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveContent/useSave.tsx b/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveContent/useSave.tsx deleted file mode 100644 index 3798fc90ce1..00000000000 --- a/packages/app-headless-cms/src/admin/components/ContentEntryForm/Header/SaveContent/useSave.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { useCallback } from "react"; -import { useContentEntry } from "~/admin/views/contentEntries/hooks"; -import { makeDecoratableHook } from "@webiny/react-composition"; - -interface UseSaveResponse { - saveEntry: (ev: React.SyntheticEvent) => Promise; -} - -const useSaveDefault = (): UseSaveResponse => { - const { form, entry } = useContentEntry(); - - const saveEntry = useCallback( - async (ev: React.SyntheticEvent) => { - await form.current.submit(ev, { - /** - * We are skipping the required validator on purpose, because we want to allow partial saving of the entry. - */ - skipValidators: ["required"] - }); - }, - [form, entry] - ); - - return { - saveEntry - }; -}; - -export const useSave = makeDecoratableHook(useSaveDefault); diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/useContentEntryForm.ts b/packages/app-headless-cms/src/admin/components/ContentEntryForm/useContentEntryForm.ts index 4f158017b4d..36d59033d68 100644 --- a/packages/app-headless-cms/src/admin/components/ContentEntryForm/useContentEntryForm.ts +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/useContentEntryForm.ts @@ -1,359 +1,12 @@ -import { Dispatch, SetStateAction, useCallback, useMemo, useState } from "react"; -import pick from "lodash/pick"; -import { useRouter } from "@webiny/react-router"; -import { makeDecoratable, useSnackbar } from "@webiny/app-admin"; -import { FormOnSubmit } from "@webiny/form"; -import { - CmsEntryCreateFromMutationResponse, - CmsEntryCreateFromMutationVariables, - CmsEntryCreateMutationResponse, - CmsEntryCreateMutationVariables, - CmsEntryUpdateMutationResponse, - CmsEntryUpdateMutationVariables, - createCreateFromMutation, - createCreateMutation, - createUpdateMutation, - prepareFormData -} from "@webiny/app-headless-cms-common"; -import { useModel, useMutation } from "~/admin/hooks"; -import { CmsContentEntry, CmsModelField, CmsModelFieldRendererPlugin } from "~/types"; -import { plugins } from "@webiny/plugins"; -import { getFetchPolicy } from "~/utils/getFetchPolicy"; -import { useNavigateFolder, useRecords } from "@webiny/app-aco"; -import { ROOT_FOLDER } from "~/admin/constants"; - -/** - * Used for some fields to convert their values. - */ -const convertDefaultValue = (field: CmsModelField, value: any): string | number | boolean => { - switch (field.type) { - case "boolean": - return Boolean(value); - case "number": - return Number(value); - default: - return value; +import React from "react"; +import { makeDecoratable } from "@webiny/react-composition"; +import { ContentEntryFormContext } from "./ContentEntryFormProvider"; + +export const useContentEntryForm = makeDecoratable(() => { + const context = React.useContext(ContentEntryFormContext); + if (!context) { + throw new Error("ContentEntryFormProvider is missing in the component hierarchy!"); } -}; - -interface InvalidFieldError { - fieldId: string; - error: string; -} - -export interface UseContentEntryForm { - data: Record; - loading: boolean; - setLoading: Dispatch>; - onChange: FormOnSubmit; - onSubmit: FormOnSubmit; - invalidFields: Record; - renderPlugins: CmsModelFieldRendererPlugin[]; -} - -export interface UseContentEntryFormParams { - entry: Partial; - onChange?: FormOnSubmit; - onSubmit?: FormOnSubmit; - addEntryToListCache: boolean; -} - -export const useContentEntryForm = makeDecoratable( - (params: UseContentEntryFormParams): UseContentEntryForm => { - const { model } = useModel(); - const { history, search: routerSearch } = useRouter(); - const [query] = routerSearch; - const { currentFolderId } = useNavigateFolder(); - const { showSnackbar } = useSnackbar(); - const [invalidFields, setInvalidFields] = useState>({}); - const [loading, setLoading] = useState(false); - const entry = params.entry; - - const { addRecordToCache, updateRecordInCache } = useRecords(); - - const renderPlugins = useMemo( - () => plugins.byType("cms-editor-field-renderer"), - [] - ); - - const goToRevision = useCallback( - id => { - const fId = query.get("folderId"); - const folderId = fId ? `&folderId=${encodeURIComponent(fId)}` : ""; - history.push( - `/cms/content-entries/${model.modelId}?id=${encodeURIComponent(id)}${folderId}` - ); - }, - [query] - ); - - const { CREATE_CONTENT, UPDATE_CONTENT, CREATE_CONTENT_FROM } = useMemo(() => { - return { - CREATE_CONTENT: createCreateMutation(model), - UPDATE_CONTENT: createUpdateMutation(model), - CREATE_CONTENT_FROM: createCreateFromMutation(model) - }; - }, [model.modelId]); - - const [createMutation] = useMutation< - CmsEntryCreateMutationResponse, - CmsEntryCreateMutationVariables - >(CREATE_CONTENT); - const [updateMutation] = useMutation< - CmsEntryUpdateMutationResponse, - CmsEntryUpdateMutationVariables - >(UPDATE_CONTENT); - const [createFromMutation] = useMutation< - CmsEntryCreateFromMutationResponse, - CmsEntryCreateFromMutationVariables - >(CREATE_CONTENT_FROM); - - /** - * Note that when passing `error.data` variable we cast as InvalidFieldError[] because we know it is so. - */ - const setInvalidFieldValues = (errors?: InvalidFieldError[]): void => { - if (Array.isArray(errors) === false || !errors) { - return; - } - const values = (errors || []).reduce((acc, er) => { - acc[er.fieldId] = er.error; - return acc; - }, {} as Record); - setInvalidFields(() => values); - }; - - const resetInvalidFieldValues = (): void => { - setInvalidFields(() => ({})); - }; - - const createContent: FormOnSubmit = useCallback( - async (formData, form) => { - setLoading(true); - const response = await createMutation({ - variables: { - data: { - ...formData, - /** - * Added for the ACO to work. - * TODO: introduce hook like onEntryPublish, or similar. - */ - wbyAco_location: { - folderId: currentFolderId || ROOT_FOLDER - } - }, - options: { - skipValidators: form.options.skipValidators - } - }, - fetchPolicy: getFetchPolicy(model) - }); - - setLoading(false); - - /** - * Finalize "create" process - */ - if (!response.data) { - showSnackbar("Missing response data in Create Entry."); - return; - } - const { data: entry, error } = response.data.content || {}; - if (error) { - showSnackbar(error.message); - setInvalidFieldValues(error.data as InvalidFieldError[]); - return; - } else if (!entry) { - showSnackbar("Missing entry data in Create Entry Response."); - return; - } - resetInvalidFieldValues(); - - if (params.addEntryToListCache) { - addRecordToCache(entry); - } - - showSnackbar(`${model.name} entry created successfully!`); - if (typeof params.onSubmit === "function") { - params.onSubmit(entry, form); - return entry; - } - goToRevision(entry.id); - return entry; - }, - [model.modelId, params.onSubmit, params.addEntryToListCache, query, currentFolderId] - ); - - const updateContent = useCallback( - async (revision, data, form) => { - setLoading(true); - const response = await updateMutation({ - variables: { - revision, - data, - options: { - skipValidators: form.options.skipValidators - } - }, - fetchPolicy: getFetchPolicy(model) - }); - setLoading(false); - if (!response.data) { - showSnackbar("Missing response data on Update Entry Response."); - return; - } - - const { error } = response.data.content; - if (error) { - showSnackbar(error.message); - setInvalidFieldValues(error.data as InvalidFieldError[]); - return null; - } - resetInvalidFieldValues(); - showSnackbar("Content saved successfully."); - const { data: entry } = response.data.content; - - updateRecordInCache(entry); - - return entry; - }, - [model.modelId] - ); - - const createContentFrom = useCallback( - async (revision: string, formData: Record, form) => { - setLoading(true); - const response = await createFromMutation({ - variables: { - revision, - data: formData, - options: { - skipValidators: form.options.skipValidators - } - }, - fetchPolicy: getFetchPolicy(model) - }); - - if (!response.data) { - showSnackbar("Missing data in update callback on Create From Entry Response."); - return; - } - const { data: newRevision, error } = response.data.content; - if (error) { - showSnackbar(error.message); - setInvalidFieldValues(error.data as InvalidFieldError[]); - return; - } else if (!newRevision) { - showSnackbar("Missing entry data in update callback on Create From Entry."); - return; - } - resetInvalidFieldValues(); - - updateRecordInCache(newRevision); - - showSnackbar("A new revision was created!"); - goToRevision(newRevision.id); - - setLoading(false); - - return newRevision; - }, - [model.modelId] - ); - - const onChange: FormOnSubmit = (data, form) => { - if (!params.onChange) { - return; - } - return params.onChange(data, form); - }; - - const onSubmit: FormOnSubmit = async (data, form) => { - const fieldsIds = model.fields.map(item => item.fieldId); - const formData = pick(data, [...fieldsIds]); - - const gqlData = prepareFormData(formData, model.fields); - - if (!entry.id) { - return createContent(gqlData as CmsContentEntry, form); - } - - const { meta } = entry; - const { locked: isLocked } = meta || {}; - - if (!isLocked) { - return updateContent(entry.id, gqlData, form); - } - - return createContentFrom(entry.id, gqlData, form); - }; - - const defaultValues = useMemo((): Record => { - const values: Record = {}; - /** - * Assign the default values: - * * check the settings.defaultValue - * * check the predefinedValues for selected value - */ - for (const field of model.fields) { - /** - * When checking if defaultValue is set in settings, we do the undefined check because it can be null, 0, empty string, false, etc... - */ - const { settings, multipleValues = false } = field; - if (settings && settings.defaultValue !== undefined) { - /** - * Special type of field is the boolean one. - * We MUST set true/false for default value. - */ - values[field.fieldId] = convertDefaultValue(field, settings.defaultValue); - continue; - } - /** - * No point in going further if predefined values are not enabled. - */ - const { predefinedValues } = field; - if ( - !predefinedValues || - !predefinedValues.enabled || - Array.isArray(predefinedValues.values) === false - ) { - continue; - } - /** - * When field is not a multiple values one, we find the first possible default selected value and set it as field value. - */ - if (!multipleValues) { - const selectedValue = predefinedValues.values.find(({ selected }) => { - return !!selected; - }); - if (selectedValue) { - values[field.fieldId] = convertDefaultValue(field, selectedValue.value); - } - continue; - } - /** - * - */ - values[field.fieldId] = predefinedValues.values - .filter(({ selected }) => !!selected) - .map(({ value }) => { - return convertDefaultValue(field, value); - }); - } - return values; - }, [model.modelId]); - - return { - /** - * If entry is not set or `entry.id` does not exist, it means it's a new entry, so fetch default values. - */ - data: entry && entry.id ? entry : defaultValues, - loading, - setLoading, - onChange, - onSubmit, - invalidFields, - renderPlugins - }; - } -); + return context; +}); diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/useDefaultValues.ts b/packages/app-headless-cms/src/admin/components/ContentEntryForm/useDefaultValues.ts new file mode 100644 index 00000000000..39b8e3e6ff7 --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/useDefaultValues.ts @@ -0,0 +1,73 @@ +import { useMemo } from "react"; +import { CmsContentEntry, CmsModel, CmsModelField } from "@webiny/app-headless-cms-common/types"; + +/** + * Used for some fields to convert their values. + */ +const convertDefaultValue = (field: CmsModelField, value: any): string | number | boolean => { + switch (field.type) { + case "boolean": + return Boolean(value); + case "number": + return Number(value); + default: + return value; + } +}; + +export const useDefaultValues = (model: CmsModel) => { + return useMemo(() => { + const values: Partial = {}; + /** + * Assign the default values: + * * check the settings.defaultValue + * * check the predefinedValues for selected value + */ + for (const field of model.fields) { + /** + * When checking if defaultValue is set in settings, we do the undefined check because it can be null, 0, empty string, false, etc... + */ + const { settings, multipleValues = false } = field; + if (settings && settings.defaultValue !== undefined) { + /** + * Special type of field is the boolean one. + * We MUST set true/false for default value. + */ + values[field.fieldId] = convertDefaultValue(field, settings.defaultValue); + continue; + } + /** + * No point in going further if predefined values are not enabled. + */ + const { predefinedValues } = field; + if ( + !predefinedValues || + !predefinedValues.enabled || + Array.isArray(predefinedValues.values) === false + ) { + continue; + } + /** + * When field is not a multiple values one, we find the first possible default selected value and set it as field value. + */ + if (!multipleValues) { + const selectedValue = predefinedValues.values.find(({ selected }) => { + return !!selected; + }); + if (selectedValue) { + values[field.fieldId] = convertDefaultValue(field, selectedValue.value); + } + continue; + } + /** + * + */ + values[field.fieldId] = predefinedValues.values + .filter(({ selected }) => !!selected) + .map(({ value }) => { + return convertDefaultValue(field, value); + }); + } + return values; + }, [model.modelId]); +}; diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/useFormRenderer.ts b/packages/app-headless-cms/src/admin/components/ContentEntryForm/useFormRenderer.ts new file mode 100644 index 00000000000..9208a0b325d --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/useFormRenderer.ts @@ -0,0 +1,8 @@ +import { plugins } from "@webiny/plugins"; +import { CmsContentFormRendererPlugin, CmsModel } from "@webiny/app-headless-cms-common/types"; + +export const useFormRenderer = (model: CmsModel) => { + return plugins + .byType("cms-content-form-renderer") + .find(pl => pl.modelId === model.modelId); +}; diff --git a/packages/app-headless-cms/src/admin/components/ContentEntryForm/useGoToRevision.ts b/packages/app-headless-cms/src/admin/components/ContentEntryForm/useGoToRevision.ts new file mode 100644 index 00000000000..da582b4a713 --- /dev/null +++ b/packages/app-headless-cms/src/admin/components/ContentEntryForm/useGoToRevision.ts @@ -0,0 +1,21 @@ +import { useRouter } from "@webiny/react-router"; +import { useCallback } from "react"; +import { useModel } from "~/admin/hooks"; + +export const useGoToRevision = () => { + const { model } = useModel(); + const { history, search } = useRouter(); + + const goToRevision = useCallback( + id => { + const fId = search[0].get("folderId"); + const folderId = fId ? `&folderId=${encodeURIComponent(fId)}` : ""; + history.push( + `/cms/content-entries/${model.modelId}?id=${encodeURIComponent(id)}${folderId}` + ); + }, + [search[0]] + ); + + return { goToRevision }; +}; diff --git a/packages/app-headless-cms/src/admin/components/ContentModelEditor/ContentModelEditor.tsx b/packages/app-headless-cms/src/admin/components/ContentModelEditor/ContentModelEditor.tsx index fe3e271d3dd..c2439c4200e 100644 --- a/packages/app-headless-cms/src/admin/components/ContentModelEditor/ContentModelEditor.tsx +++ b/packages/app-headless-cms/src/admin/components/ContentModelEditor/ContentModelEditor.tsx @@ -18,6 +18,8 @@ import DragPreview from "../DragPreview"; import { useModelEditor } from "./useModelEditor"; import { CmsModelField, CmsEditorFieldsLayout } from "~/types"; import { ContentEntryEditorWithConfig } from "~/admin/config/contentEntries"; +import { ContentEntryProvider } from "~/admin/views/contentEntries/ContentEntry/ContentEntryContext"; +import { ContentEntriesProvider } from "~/admin/views/contentEntries/ContentEntriesContext"; const t = i18n.ns("app-headless-cms/admin/editor"); @@ -113,11 +115,13 @@ export const ContentModelEditor = makeDecoratable("ContentModelEditor", () => { - - - - - + + + + + + + diff --git a/packages/app-headless-cms/src/admin/contexts/Cms/index.tsx b/packages/app-headless-cms/src/admin/contexts/Cms/index.tsx index 11c771aec20..322610e7f88 100644 --- a/packages/app-headless-cms/src/admin/contexts/Cms/index.tsx +++ b/packages/app-headless-cms/src/admin/contexts/Cms/index.tsx @@ -1,24 +1,33 @@ -import React, { useCallback, useRef } from "react"; +import React from "react"; import ApolloClient from "apollo-client"; import { useI18N } from "@webiny/app-i18n/hooks/useI18N"; import { CircularProgress } from "@webiny/ui/Progress"; import { config as appConfig } from "@webiny/app/config"; import { CmsContentEntry, CmsErrorResponse, CmsModel } from "~/types"; -import { MutationHookOptions } from "@apollo/react-hooks"; -import { AsyncProcessor, composeAsync } from "@webiny/utils"; -import { DocumentNode } from "graphql"; import { CmsEntryPublishMutationResponse, CmsEntryPublishMutationVariables, - createPublishMutation + createReadQuery, + createCreateMutation, + createCreateFromMutation, + createUpdateMutation, + createDeleteMutation, + createPublishMutation, + createUnpublishMutation, + CmsEntryCreateMutationResponse, + CmsEntryCreateMutationVariables, + CmsEntryUpdateMutationResponse, + CmsEntryUpdateMutationVariables, + CmsEntryDeleteMutationResponse, + CmsEntryDeleteMutationVariables, + CmsEntryUnpublishMutationResponse, + CmsEntryUnpublishMutationVariables, + CmsEntryCreateFromMutationResponse, + CmsEntryCreateFromMutationVariables, + CmsEntryGetQueryResponse, + CmsEntryGetQueryVariables } from "@webiny/app-headless-cms-common"; - -interface MutationEntryOptions { - mutationOptions?: MutationHookOptions; -} - -type UnpublishEntryOptions = MutationEntryOptions; -type DeleteEntryOptions = MutationEntryOptions; +import { getFetchPolicy } from "~/utils/getFetchPolicy"; interface EntryError { message: string; @@ -26,115 +35,96 @@ interface EntryError { data?: Record; } -export type OnEntryPublishResponse = - | { - entry: CmsContentEntry; - error?: never; - } - | { - entry?: never; - error: EntryError; - }; - -export interface OnEntryDeleteRequest { - model: CmsModel; - entry: Pick; - id: string; - options: DeleteEntryOptions; - // TODO: Maybe a different input and output type for compose. - error?: EntryError | null; - locale: string; - client: ApolloClient; +interface OperationSuccess { + entry: CmsContentEntry; + error?: never; } -export interface OnEntryDeleteResponse extends Omit { - entry: Pick | undefined; +interface OperationError { + entry?: never; + error: EntryError; } -export interface OnEntryUnpublishRequest { +export type PartialCmsContentEntryWithId = Partial & { id: string }; +export type GetEntryResponse = OperationSuccess | OperationError; +export type CreateEntryResponse = OperationSuccess | OperationError; +export type CreateEntryRevisionFromResponse = OperationSuccess | OperationError; +export type UpdateEntryRevisionResponse = OperationSuccess | OperationError; +export type DeleteEntryResponse = boolean | OperationError; +export type PublishEntryRevisionResponse = OperationSuccess | OperationError; +export type UnpublishEntryRevisionResponse = OperationSuccess | OperationError; + +export interface CreateEntryParams { model: CmsModel; - entry: Pick; - id: string; - options: UnpublishEntryOptions; - // TODO: Maybe a different input and output type for compose. - error?: EntryError | null; - locale: string; - client: ApolloClient; + entry: PartialCmsContentEntryWithId; + options?: { + skipValidators?: string[]; + }; } -export interface OnEntryUnpublishResponse extends Omit { - entry: CmsContentEntry | undefined; +export interface CreateEntryRevisionFromParams { + model: CmsModel; + id: string; + input?: Record; + options?: { + skipValidators?: string[]; + }; } -type OnEntryDeleteSubscriber = AsyncProcessor; -type OnEntryRevisionUnpublishSubscriber = AsyncProcessor< - OnEntryUnpublishRequest, - OnEntryUnpublishResponse ->; +export interface UpdateEntryRevisionParams { + model: CmsModel; + entry: PartialCmsContentEntryWithId; + options?: { + skipValidators?: string[]; + }; +} -interface PublishEntryRevisionParams { +export interface PublishEntryRevisionParams { model: CmsModel; - entry: CmsContentEntry; id: string; } -interface DeleteEntryParams { +export interface DeleteEntryParams { model: CmsModel; - entry: Pick; id: string; - options?: DeleteEntryOptions; } -interface UnpublishEntryRevisionParams { +export interface UnpublishEntryRevisionParams { + model: CmsModel; + id: string; +} + +export interface GetEntryParams { model: CmsModel; - entry: Pick; id: string; - options?: UnpublishEntryOptions; } export interface CmsContext { getApolloClient(locale: string): ApolloClient; createApolloClient: CmsProviderProps["createApolloClient"]; apolloClient: ApolloClient; - publishEntryRevision: (params: PublishEntryRevisionParams) => Promise; + getEntry: (params: GetEntryParams) => Promise; + createEntry: (params: CreateEntryParams) => Promise; + createEntryRevisionFrom: ( + params: CreateEntryRevisionFromParams + ) => Promise; + updateEntryRevision: ( + params: UpdateEntryRevisionParams + ) => Promise; + publishEntryRevision: ( + params: PublishEntryRevisionParams + ) => Promise; unpublishEntryRevision: ( params: UnpublishEntryRevisionParams - ) => Promise; - onEntryRevisionUnpublish: (fn: OnEntryRevisionUnpublishSubscriber) => () => void; - deleteEntry: (params: DeleteEntryParams) => Promise; - onEntryDelete: (fn: OnEntryDeleteSubscriber) => () => void; + ) => Promise; + deleteEntry: (params: DeleteEntryParams) => Promise; } -export const CmsContext = React.createContext({ - getApolloClient: () => { - return null; - }, - createApolloClient: () => { - return null; - }, - apolloClient: null - /** - * Safe to cast. - */ -} as unknown as CmsContext); +export const CmsContext = React.createContext(undefined); interface ApolloClientsCache { [locale: string]: ApolloClient; } -interface Mutations { - [key: string]: DocumentNode; -} - -interface CreateMutationKeyParams { - model: CmsModel; - locale: string; -} - -const createMutationKey = (params: CreateMutationKeyParams): string => { - const { model, locale } = params; - return `${model.modelId}_${locale}_${model.savedOn}`; -}; - const apolloClientsCache: ApolloClientsCache = {}; export interface CmsProviderProps { @@ -145,20 +135,6 @@ export interface CmsProviderProps { export const CmsProvider = (props: CmsProviderProps) => { const apiUrl = appConfig.getKey("API_URL", process.env.REACT_APP_API_URL); const { getCurrentLocale } = useI18N(); - const mutations = useRef({}); - const onEntryRevisionUnpublish = useRef([]); - const onEntryDelete = useRef([]); - - const getMutation = useCallback( - (model: CmsModel, locale: string): DocumentNode => { - const key = createMutationKey({ model, locale }); - if (!mutations.current[key]) { - mutations.current[key] = createPublishMutation(model); - } - return mutations.current[key]; - }, - [mutations.current] - ); const currentLocale = getCurrentLocale("content"); @@ -185,21 +161,159 @@ export const CmsProvider = (props: CmsProviderProps) => { getApolloClient, createApolloClient: props.createApolloClient, apolloClient: getApolloClient(currentLocale), - publishEntryRevision: async params => { - const mutation = getMutation(params.model, currentLocale); + getEntry: async ({ model, id }) => { + const query = createReadQuery(model); + const isRevisionId = id.includes("#"); + + const response = await value.apolloClient.query< + CmsEntryGetQueryResponse, + CmsEntryGetQueryVariables + >({ + query, + variables: isRevisionId ? { revision: id } : { entryId: id }, + fetchPolicy: getFetchPolicy(model) + }); + + if (!response.data) { + return { + error: { + message: "Missing response data on Get Entry query.", + code: "MISSING_RESPONSE_DATA", + data: {} + } + }; + } + + const { data, error } = response.data.content; + + if (error) { + return { error }; + } + + return { + entry: data as CmsContentEntry + }; + }, + createEntry: async ({ model, entry, options }) => { + const mutation = createCreateMutation(model); + const response = await value.apolloClient.mutate< + CmsEntryCreateMutationResponse, + CmsEntryCreateMutationVariables + >({ + mutation, + variables: { + data: entry, + options + }, + fetchPolicy: getFetchPolicy(model) + }); + + if (!response.data) { + return { + error: { + message: "Missing response data on Create Entry mutation.", + code: "MISSING_RESPONSE_DATA", + data: {} + } + }; + } + + const { data, error } = response.data.content; + + if (error) { + return { error }; + } + + return { + entry: data as CmsContentEntry + }; + }, + createEntryRevisionFrom: async ({ model, id, input, options }) => { + const mutation = createCreateFromMutation(model); + const response = await value.apolloClient.mutate< + CmsEntryCreateFromMutationResponse, + CmsEntryCreateFromMutationVariables + >({ + mutation, + variables: { + revision: id, + data: input, + options + }, + fetchPolicy: getFetchPolicy(model) + }); + + if (!response.data) { + return { + error: { + message: "Missing response data on Create Entry mutation.", + code: "MISSING_RESPONSE_DATA", + data: {} + } + }; + } + + const { data, error } = response.data.content; + + if (error) { + return { error }; + } + + return { + entry: data as CmsContentEntry + }; + }, + updateEntryRevision: async ({ model, entry, options }) => { + const mutation = createUpdateMutation(model); + const { id, ...input } = entry; + const response = await value.apolloClient.mutate< + CmsEntryUpdateMutationResponse, + CmsEntryUpdateMutationVariables + >({ + mutation, + variables: { + revision: id, + data: input, + options + }, + fetchPolicy: getFetchPolicy(model) + }); + + if (!response.data) { + return { + error: { + message: "Missing response data on Update Entry mutation.", + code: "MISSING_RESPONSE_DATA", + data: {} + } + }; + } + + const { data, error } = response.data.content; + + if (error) { + return { error }; + } + + return { + entry: data as CmsContentEntry + }; + }, + publishEntryRevision: async ({ model, id }) => { + const mutation = createPublishMutation(model); const response = await value.apolloClient.mutate< CmsEntryPublishMutationResponse, CmsEntryPublishMutationVariables >({ mutation, variables: { - revision: params.id + revision: id } }); if (!response.data) { const error: CmsErrorResponse = { - message: "Missing response data on Publish Entry Mutation.", + message: "Missing response data on Publish Entry mutation.", code: "MISSING_RESPONSE_DATA", data: {} }; @@ -216,35 +330,68 @@ export const CmsProvider = (props: CmsProviderProps) => { entry: data as CmsContentEntry }; }, - unpublishEntryRevision: async params => { - return await composeAsync([...onEntryRevisionUnpublish.current].reverse())({ - locale: currentLocale, - ...params, - client: getApolloClient(currentLocale), - options: params.options || {} + unpublishEntryRevision: async ({ model, id }) => { + const mutation = createUnpublishMutation(model); + + const response = await value.apolloClient.mutate< + CmsEntryUnpublishMutationResponse, + CmsEntryUnpublishMutationVariables + >({ + mutation, + variables: { + revision: id + } }); - }, - onEntryRevisionUnpublish: fn => { - onEntryRevisionUnpublish.current.push(fn); - return () => { - const index = onEntryRevisionUnpublish.current.length; - onEntryRevisionUnpublish.current.splice(index, 1); + + if (!response.data) { + return { + error: { + message: "Missing response data on Unpublish Entry mutation.", + code: "MISSING_RESPONSE_DATA", + data: {} + } + }; + } + const { data, error } = response.data.content; + if (error) { + return { + error + }; + } + + return { + entry: data as CmsContentEntry }; }, - deleteEntry: async params => { - return await composeAsync([...onEntryDelete.current].reverse())({ - locale: currentLocale, - ...params, - client: getApolloClient(currentLocale), - options: params.options || {} + deleteEntry: async ({ model, id }) => { + const mutation = createDeleteMutation(model); + const response = await value.apolloClient.mutate< + CmsEntryDeleteMutationResponse, + CmsEntryDeleteMutationVariables + >({ + mutation, + variables: { + revision: id, + permanently: false + } }); - }, - onEntryDelete: fn => { - onEntryDelete.current.push(fn); - return () => { - const index = onEntryDelete.current.length; - onEntryDelete.current.splice(index, 1); - }; + + if (!response.data) { + const error: CmsErrorResponse = { + message: "Missing response data on Delete Entry mutation.", + code: "MISSING_RESPONSE_DATA", + data: {} + }; + return { error }; + } + + const { error } = response.data.content; + + if (error) { + return { error }; + } + + return true; } }; diff --git a/packages/app-headless-cms/src/admin/hooks/index.ts b/packages/app-headless-cms/src/admin/hooks/index.ts index 3c09b3ac606..2d9cfb9d3b4 100644 --- a/packages/app-headless-cms/src/admin/hooks/index.ts +++ b/packages/app-headless-cms/src/admin/hooks/index.ts @@ -11,7 +11,6 @@ export { useParentField, ParentFieldProvider } from "../components/ContentEntryF export { useModelField } from "../components/ModelFieldProvider"; export { useModelFieldEditor } from "../components/FieldEditor"; export { useChangeEntryStatus } from "~/admin/hooks/useChangeEntryStatus"; -export { useDeleteEntry } from "~/admin/hooks/useDeleteEntry"; export { useEntry } from "~/admin/hooks/useEntry"; export * from "./useContentModels"; export * from "~/admin/views/contentEntries/hooks"; diff --git a/packages/app-headless-cms/src/admin/hooks/useDeleteEntry.tsx b/packages/app-headless-cms/src/admin/hooks/useDeleteEntry.tsx deleted file mode 100644 index b60a6578201..00000000000 --- a/packages/app-headless-cms/src/admin/hooks/useDeleteEntry.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useCallback } from "react"; -import get from "lodash/get"; -import { useConfirmationDialog, useDialog, useSnackbar } from "@webiny/app-admin"; -import { parseIdentifier } from "@webiny/utils"; -import { useCms, useModel } from "~/admin/hooks"; -import { useNavigateFolder, useRecords } from "@webiny/app-aco"; -import { CmsContentEntry } from "@webiny/app-headless-cms-common/types"; - -interface UseDeleteEntryParams { - entry: CmsContentEntry; - onAccept?: () => void; - onCancel?: () => void; -} - -export const useDeleteEntry = ({ entry, onAccept, onCancel }: UseDeleteEntryParams) => { - const { deleteEntry } = useCms(); - const { model } = useModel(); - const { showSnackbar } = useSnackbar(); - const { showDialog } = useDialog(); - const { navigateToLatestFolder } = useNavigateFolder(); - const { removeRecordFromCache } = useRecords(); - - const title = get(entry, "meta.title"); - const { showConfirmation } = useConfirmationDialog({ - title: "Trash entry", - message: ( -

- Are you sure you want to move {title} to trash? -
- This action will include all of its revisions? -

- ), - dataTestId: "cms.content-form.header.delete-dialog" - }); - - const openDialogDeleteEntry = useCallback( - () => - showConfirmation(async () => { - const { id: entryId } = parseIdentifier(entry.id); - const { error } = await deleteEntry({ - model, - entry, - id: entryId - }); - - if (error) { - showDialog(error.message, { title: `Could not move ${title} to trash!` }); - return; - } - - showSnackbar(`${title} has been moved to trash successfully!`); - removeRecordFromCache(entry.id); - navigateToLatestFolder(); - - if (typeof onAccept === "function") { - await onAccept(); - } - }, onCancel), - [entry] - ); - - return { openDialogDeleteEntry }; -}; diff --git a/packages/app-headless-cms/src/admin/menus/NothingToShowElement.tsx b/packages/app-headless-cms/src/admin/menus/NothingToShowElement.tsx index f64c648ba1d..ac5aa24965a 100644 --- a/packages/app-headless-cms/src/admin/menus/NothingToShowElement.tsx +++ b/packages/app-headless-cms/src/admin/menus/NothingToShowElement.tsx @@ -1,4 +1,3 @@ -// TODO: implement this without relying on the `ui` package import React from "react"; import { css } from "emotion"; import { List, ListItem } from "@webiny/ui/List"; diff --git a/packages/app-headless-cms/src/admin/plugins/entry/DefaultOnEntryDelete.tsx b/packages/app-headless-cms/src/admin/plugins/entry/DefaultOnEntryDelete.tsx deleted file mode 100644 index 0fc69fafa41..00000000000 --- a/packages/app-headless-cms/src/admin/plugins/entry/DefaultOnEntryDelete.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React, { useEffect, useRef } from "react"; -import { useCms } from "~/admin/hooks"; -import { CmsErrorResponse, CmsModel } from "~/types"; -import { - CmsEntryDeleteMutationResponse, - CmsEntryDeleteMutationVariables, - createDeleteMutation -} from "@webiny/app-headless-cms-common"; -import { DocumentNode } from "graphql"; -import { OnEntryDeleteRequest } from "~/admin/contexts/Cms"; -import { parseIdentifier } from "@webiny/utils"; - -interface Mutations { - [key: string]: DocumentNode; -} - -interface CreateMutationKeyParams { - model: CmsModel; - locale: string; -} - -const createMutationKey = (params: CreateMutationKeyParams): string => { - const { model, locale } = params; - return `${model.modelId}_${locale}_${model.savedOn}`; -}; - -const OnEntryDelete = () => { - const { onEntryDelete } = useCms(); - - const mutations = useRef({}); - - const getMutation = (model: CmsModel, locale: string) => { - const key = createMutationKey({ model, locale }); - if (!mutations.current[key]) { - mutations.current[key] = createDeleteMutation(model); - } - return mutations.current[key]; - }; - - const handleOnDelete = async (params: OnEntryDeleteRequest) => { - const { entry, model, id, client, locale } = params; - const mutation = getMutation(model, locale); - - const response = await client.mutate< - CmsEntryDeleteMutationResponse, - CmsEntryDeleteMutationVariables - >({ - mutation, - variables: { - revision: id, - permanently: false - } - }); - - if (!response.data) { - const error: CmsErrorResponse = { - message: "Missing response data on Delete Entry Mutation.", - code: "MISSING_RESPONSE_DATA", - data: {} - }; - return { - error - }; - } - const { error } = response.data.content; - if (error) { - return { - error - }; - } - /** - * TODO figure out how to do this in a smart way. - * If there is no version in the ID, we are deleting whole entry. - */ - const { version } = parseIdentifier(id); - if (version === null) { - return { - data: true, - error: null - }; - } - - if (entry.id !== id) { - return { - data: true, - error: null - }; - } - return { - entry, - data: true, - error: null - }; - }; - - useEffect(() => { - return onEntryDelete(next => async params => { - const result = await next(params); - - if (result.error) { - return result; - } - - const response = await handleOnDelete({ - ...params - }); - return { - ...result, - ...response - }; - }); - }, []); - - return null; -}; - -export const DefaultOnEntryDelete = React.memo(OnEntryDelete); diff --git a/packages/app-headless-cms/src/admin/plugins/entry/DefaultOnEntryUnpublish.tsx b/packages/app-headless-cms/src/admin/plugins/entry/DefaultOnEntryUnpublish.tsx deleted file mode 100644 index a1e6aa79961..00000000000 --- a/packages/app-headless-cms/src/admin/plugins/entry/DefaultOnEntryUnpublish.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useEffect, useRef } from "react"; -import { useCms } from "~/admin/hooks"; -import { CmsErrorResponse, CmsModel } from "~/types"; -import { - CmsEntryUnpublishMutationResponse, - CmsEntryUnpublishMutationVariables, - createUnpublishMutation -} from "@webiny/app-headless-cms-common"; -import { DocumentNode } from "graphql"; -import { OnEntryUnpublishRequest } from "~/admin/contexts/Cms"; - -interface Mutations { - [key: string]: DocumentNode; -} - -interface CreateMutationKeyParams { - model: CmsModel; - locale: string; -} - -const createMutationKey = (params: CreateMutationKeyParams): string => { - const { model, locale } = params; - return `${model.modelId}_${locale}_${model.savedOn}`; -}; - -const OnEntryUnpublish = () => { - const { onEntryRevisionUnpublish } = useCms(); - - const mutations = useRef({}); - - const getMutation = (model: CmsModel, locale: string) => { - const key = createMutationKey({ model, locale }); - if (!mutations.current[key]) { - mutations.current[key] = createUnpublishMutation(model); - } - return mutations.current[key]; - }; - - const handleOnUnpublish = async ({ model, id, client, locale }: OnEntryUnpublishRequest) => { - const mutation = getMutation(model, locale); - - const response = await client.mutate< - CmsEntryUnpublishMutationResponse, - CmsEntryUnpublishMutationVariables - >({ - mutation, - variables: { - revision: id - } - }); - - if (!response.data) { - const error: CmsErrorResponse = { - message: "Missing response data on Publish Entry Mutation.", - code: "MISSING_RESPONSE_DATA", - data: {} - }; - return { - error - }; - } - const { data, error } = response.data.content; - if (error) { - return { - error - }; - } - - return { - entry: data, - error: null - }; - }; - - useEffect(() => { - return onEntryRevisionUnpublish(next => async params => { - const result = await next(params); - - if (result.error) { - return result; - } - - const response = await handleOnUnpublish({ - ...params - }); - return { - ...result, - ...response - }; - }); - }, []); - - return null; -}; - -export const DefaultOnEntryUnpublish = React.memo(OnEntryUnpublish); diff --git a/packages/app-headless-cms/src/admin/plugins/fieldRenderers/ref/components/ContentEntriesAutocomplete.tsx b/packages/app-headless-cms/src/admin/plugins/fieldRenderers/ref/components/ContentEntriesAutocomplete.tsx index e86632f779a..da6720a7cfe 100644 --- a/packages/app-headless-cms/src/admin/plugins/fieldRenderers/ref/components/ContentEntriesAutocomplete.tsx +++ b/packages/app-headless-cms/src/admin/plugins/fieldRenderers/ref/components/ContentEntriesAutocomplete.tsx @@ -67,7 +67,6 @@ const ContentEntriesAutocomplete = ({ bind, field }: ContentEntriesAutocompleteP onClose={() => setShowNewEntryModal(false)} model={model} onChange={entry => { - /* TODO: The `any` argument is wrong, and needs revision. */ return onChange(entry, entry); }} /> diff --git a/packages/app-headless-cms/src/admin/plugins/fieldRenderers/ref/components/NewReferencedEntryDialog.tsx b/packages/app-headless-cms/src/admin/plugins/fieldRenderers/ref/components/NewReferencedEntryDialog.tsx index 58d400e39d9..9b4bf784c91 100644 --- a/packages/app-headless-cms/src/admin/plugins/fieldRenderers/ref/components/NewReferencedEntryDialog.tsx +++ b/packages/app-headless-cms/src/admin/plugins/fieldRenderers/ref/components/NewReferencedEntryDialog.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import { ContentEntryProvider } from "~/admin/views/contentEntries/ContentEntry/ContentEntryContext"; import { FoldersProvider } from "@webiny/app-aco/contexts/folders"; import { DialogActions, DialogCancel, DialogContent, DialogTitle } from "@webiny/ui/Dialog"; @@ -21,6 +21,7 @@ import { FolderTree, useNavigateFolder } from "@webiny/app-aco"; import styled from "@emotion/styled"; import { Elevation } from "@webiny/ui/Elevation"; import { SplitView, LeftPanel, RightPanel } from "@webiny/app-admin/components/SplitView"; +import { CircularProgress } from "@webiny/ui/Progress"; const t = i18n.ns("app-headless-cms/admin/fields/ref"); @@ -65,12 +66,17 @@ const ModalFullWidthDialog = styled(FullWidthDialog)` } `; +interface SaveEntry { + (): void; +} + interface EntryFormProps { onCreate: (entry: CmsContentEntry) => void; + setSaveEntry: (cb: SaveEntry) => void; } -const EntryForm = ({ onCreate }: EntryFormProps) => { - const { setFormRef, contentModel } = useContentEntry(); +const EntryForm = ({ onCreate, setSaveEntry }: EntryFormProps) => { + const { contentModel, loading } = useContentEntry(); const { currentFolderId, navigateToFolder } = useNavigateFolder(); return ( @@ -87,11 +93,13 @@ const EntryForm = ({ onCreate }: EntryFormProps) => { + {loading ? : null} onCreate(data)} - onForm={form => setFormRef(form)} + header={false} + onAfterCreate={entry => onCreate(entry)} entry={{}} addEntryToListCache={false} + setSaveEntry={setSaveEntry} /> @@ -101,21 +109,6 @@ const EntryForm = ({ onCreate }: EntryFormProps) => { ); }; -const DialogSaveButton = () => { - const { form } = useContentEntry(); - - const onClick = useCallback( - (ev: React.MouseEvent) => { - (async () => { - await form.current.submit(ev); - })(); - }, - [form.current] - ); - - return {t`Create Entry`}; -}; - interface NewReferencedEntryDialogProps { model: Pick; onClose: () => void; @@ -129,6 +122,7 @@ export const NewReferencedEntryDialog = ({ }: NewReferencedEntryDialogProps) => { const { apolloClient } = useCms(); const [model, setModel] = useState(undefined); + const saveEntryRef = useRef(); useEffect(() => { (async () => { @@ -178,11 +172,16 @@ export const NewReferencedEntryDialog = ({ {t`New {modelName} Entry`({ modelName: model.name })} - + (saveEntryRef.current = cb)} + /> {t`Cancel`} - + saveEntryRef.current && saveEntryRef.current()} + >{t`Create Entry`} diff --git a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntriesContext.tsx b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntriesContext.tsx index ab582f29636..7c2269ec472 100644 --- a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntriesContext.tsx +++ b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntriesContext.tsx @@ -8,7 +8,9 @@ export interface ContentEntriesContext { insideDialog?: boolean; } -export const Context = React.createContext(undefined); +export const ContentEntriesContext = React.createContext( + undefined +); export interface ContentEntriesContextProviderProps { contentModel: CmsModel; @@ -42,7 +44,9 @@ export const ContentEntriesProvider = ({ canCreate }; - return {children}; + return ( + {children} + ); }; ContentEntriesProvider.displayName = "ContentEntriesProvider"; diff --git a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntriesModule.tsx b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntriesModule.tsx index 60f1dd67746..ec389d5b5b9 100644 --- a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntriesModule.tsx +++ b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntriesModule.tsx @@ -29,6 +29,8 @@ import { CellStatus } from "~/admin/components/ContentEntries/Table/Cells"; import { Ref } from "~/admin/components/ContentEntries/Filters/RefFieldRenderer"; +import { ShowConfirmationOnDelete } from "~/admin/components/ContentEntryForm/Header/DeleteEntry/ShowConfirmationOnDelete"; +import { ShowConfirmationOnPublish } from "~/admin/components/ContentEntryForm/Header/SaveAndPublishContent/ShowConfirmationOnPublish"; const { Browser } = ContentEntryListConfig; const { Actions } = ContentEntryEditorConfig; @@ -101,9 +103,11 @@ export const ContentEntriesModule = () => { } /> - } /> + } /> + + ); }; diff --git a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry.tsx b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry.tsx index e4a74107865..d8f513fff6f 100644 --- a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry.tsx +++ b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry.tsx @@ -4,7 +4,7 @@ import styled from "@emotion/styled"; import { Tab, Tabs } from "@webiny/ui/Tabs"; import { Elevation } from "@webiny/ui/Elevation"; import { CircularProgress } from "@webiny/ui/Progress"; -import RevisionsList from "./ContentEntry/RevisionsList"; +import { RevisionsList } from "./ContentEntry/RevisionsList/RevisionsList"; import { useContentEntry } from "./hooks/useContentEntry"; import { ContentEntryForm } from "~/admin/components/ContentEntryForm/ContentEntryForm"; import { makeDecoratable } from "@webiny/app"; @@ -43,7 +43,7 @@ declare global { } const DefaultContentEntry = () => { - const { loading, entry, activeTab, setActiveTab, setFormRef } = useContentEntry(); + const { loading, entry, activeTab, setActiveTab } = useContentEntry(); return ( @@ -57,11 +57,7 @@ const DefaultContentEntry = () => { {loading && } - setFormRef(form)} - addEntryToListCache={true} - /> + diff --git a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/ContentEntryContext.tsx b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/ContentEntryContext.tsx index 4add26842cd..98402c53661 100644 --- a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/ContentEntryContext.tsx +++ b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/ContentEntryContext.tsx @@ -1,17 +1,8 @@ -import React, { - Dispatch, - MutableRefObject, - SetStateAction, - useCallback, - useEffect, - useMemo, - useRef, - useState -} from "react"; +import React, { useEffect, useMemo, useState } from "react"; import get from "lodash/get"; import { useRouter } from "@webiny/react-router"; -import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; -import { useQuery } from "~/admin/hooks"; +import { useSnackbar, useIsMounted } from "@webiny/app-admin"; +import { useCms, useQuery } from "~/admin/hooks"; import { ContentEntriesContext } from "~/admin/views/contentEntries/ContentEntriesContext"; import { useContentEntries } from "~/admin/views/contentEntries/hooks/useContentEntries"; import { CmsContentEntry, CmsContentEntryRevision } from "~/types"; @@ -25,23 +16,45 @@ import { createRevisionsQuery } from "@webiny/app-headless-cms-common"; import { getFetchPolicy } from "~/utils/getFetchPolicy"; -import { FormAPI, FormSubmitOptions } from "@webiny/form"; +import { useRecords } from "@webiny/app-aco"; +import * as Cms from "~/admin/contexts/Cms"; +import { useMockRecords } from "./useMockRecords"; -interface ContentEntryContextForm { - submit: ( - ev: React.SyntheticEvent, - options?: FormSubmitOptions - ) => Promise; +interface UpdateListCacheOptions { + options?: { + addItemToListCache?: boolean; + }; } -type ContentEntryContextFormRef = MutableRefObject; -export interface ContentEntryContext extends ContentEntriesContext { - createEntry: () => void; + +export type GetEntryParams = Omit; +export type CreateEntryParams = Omit & UpdateListCacheOptions; +export type CreateEntryRevisionFromParams = Omit; +export type UpdateEntryRevisionParams = Omit; +export type PublishEntryRevisionParams = Omit; +export type UnpublishEntryRevisionParams = Omit; +export type DeleteEntryParams = Omit; + +export interface ContentEntryCrud { + getEntry: (params: GetEntryParams) => Promise; + createEntry: (params: CreateEntryParams) => Promise; + createEntryRevisionFrom: ( + params: CreateEntryRevisionFromParams + ) => Promise; + updateEntryRevision: ( + params: UpdateEntryRevisionParams + ) => Promise; + publishEntryRevision: ( + params: PublishEntryRevisionParams + ) => Promise; + unpublishEntryRevision: ( + params: UnpublishEntryRevisionParams + ) => Promise; + deleteEntry: (params: DeleteEntryParams) => Promise; +} + +export interface ContentEntryContext extends ContentEntriesContext, ContentEntryCrud { entry: CmsContentEntry; - setEntry: (entry: CmsContentEntry) => void; - form: ContentEntryContextFormRef; - setFormRef: (form: Pick) => void; loading: boolean; - setLoading: Dispatch>; revisions: CmsContentEntryRevision[]; refetchContent: () => void; setActiveTab(index: number): void; @@ -49,9 +62,14 @@ export interface ContentEntryContext extends ContentEntriesContext { showEmptyView: boolean; } -export const Context = React.createContext(undefined); +export const ContentEntryContext = React.createContext(undefined); export interface ContentEntryContextProviderProps extends Partial { + /** + * This prop is used when you need to mount this provider outside the main content entry view, with limited features. + * Example: Model Editor "preview" tab. + */ + readonly?: boolean; children: React.ReactNode; } @@ -81,22 +99,19 @@ export const useContentEntryProviderProps = (): UseContentEntryProviderProps => export const ContentEntryProvider = ({ children, isNewEntry, + readonly, getContentId }: ContentEntryContextProviderProps) => { + const { isMounted } = useIsMounted(); const [activeTab, setActiveTab] = useState(0); const [entry, setEntry] = useState(); - const { contentModel, canCreate } = useContentEntries(); - - const { search } = useRouter(); - const [query] = search; - - const formRef = useRef({ - submit: async () => { - return null; - } - }); + const { contentModel: model, canCreate } = useContentEntries(); const { history } = useRouter(); const { showSnackbar } = useSnackbar(); + const cms = useCms(); + const { addRecordToCache, updateRecordInCache, removeRecordFromCache } = readonly + ? useMockRecords() + : useRecords(); const [isLoading, setLoading] = useState(false); const contentEntryProviderProps = useContentEntryProviderProps(); @@ -125,34 +140,15 @@ export const ContentEntryProvider = ({ const { READ_CONTENT } = useMemo(() => { return { - READ_CONTENT: createReadQuery(contentModel) + READ_CONTENT: createReadQuery(model) }; - }, [contentModel.modelId]); + }, [model.modelId]); const { GET_REVISIONS } = useMemo(() => { return { - GET_REVISIONS: createRevisionsQuery(contentModel) + GET_REVISIONS: createRevisionsQuery(model) }; - }, [contentModel.modelId]); - - const setFormRef = useCallback( - form => { - formRef.current = form; - }, - [formRef] - ); - - const folderIdPath = useMemo(() => { - const folderId = query.get("folderId"); - if (!folderId) { - return ""; - } - return `&folderId=${encodeURIComponent(folderId)}`; - }, [query]); - - const createEntry = useCallback((): void => { - history.push(`/cms/content-entries/${contentModel.modelId}?new=true${folderIdPath}`); - }, [contentModel.modelId, folderIdPath]); + }, [model.modelId]); let variables: CmsEntryGetQueryVariables | undefined; if (version === null && entryId) { @@ -165,12 +161,13 @@ export const ContentEntryProvider = ({ }; } - const getEntry = useQuery(READ_CONTENT, { + // TODO: refactor to use `getEntry` from useCms() + const loadEntry = useQuery(READ_CONTENT, { variables, skip: !revisionId, - fetchPolicy: getFetchPolicy(contentModel), + fetchPolicy: getFetchPolicy(model), onCompleted: response => { - if (!response) { + if (!response || !isMounted()) { return; } @@ -179,7 +176,7 @@ export const ContentEntryProvider = ({ setEntry(data); return; } - history.push(`/cms/content-entries/${contentModel.modelId}`); + history.push(`/cms/content-entries/${model.modelId}`); showSnackbar(error.message); } }); @@ -194,6 +191,8 @@ export const ContentEntryProvider = ({ skip: !entryId }); + const loading = isLoading || loadEntry.loading || getRevisions.loading; + useEffect(() => { if (getRevisions.loading || !entryId) { return; @@ -203,26 +202,97 @@ export const ContentEntryProvider = ({ }); }, [revisionId, getRevisions]); - const loading = isLoading || getEntry.loading || getRevisions.loading; + // CRUD methods + const getEntry: ContentEntryCrud["getEntry"] = async ({ id }) => { + return await cms.getEntry({ model, id }); + }; + + const createEntry: ContentEntryCrud["createEntry"] = async ({ entry, options }) => { + setLoading(true); + const response = await cms.createEntry({ + model, + entry, + options: { skipValidators: options?.skipValidators } + }); + setLoading(false); + if (response.entry && options?.addItemToListCache) { + addRecordToCache(response.entry); + } + return response; + }; + + const createEntryRevisionFrom: ContentEntryCrud["createEntryRevisionFrom"] = async params => { + setLoading(true); + const response = await cms.createEntryRevisionFrom({ model, ...params }); + setLoading(false); + if (response.entry) { + setEntry(response.entry); + updateRecordInCache(response.entry); + } + return response; + }; + + const updateEntryRevision: ContentEntryCrud["updateEntryRevision"] = async params => { + setLoading(true); + const response = await cms.updateEntryRevision({ model, ...params }); + setLoading(false); + if (response.entry) { + setEntry(response.entry); + updateRecordInCache(response.entry); + } + return response; + }; + + const deleteEntry: ContentEntryCrud["deleteEntry"] = async params => { + setLoading(true); + const response = await cms.deleteEntry({ model, ...params }); + setLoading(false); + removeRecordFromCache(params.id); + return response; + }; + + const publishEntryRevision: ContentEntryCrud["publishEntryRevision"] = async params => { + setLoading(true); + const response = await cms.publishEntryRevision({ model, ...params }); + setLoading(false); + if (response.entry) { + setEntry(response.entry); + updateRecordInCache(response.entry); + } + return response; + }; + + const unpublishEntryRevision: ContentEntryCrud["unpublishEntryRevision"] = async params => { + setLoading(true); + const response = await cms.unpublishEntryRevision({ model, ...params }); + setLoading(false); + if (response.entry) { + setEntry(response.entry); + updateRecordInCache(response.entry); + } + return response; + }; const value: ContentEntryContext = { + activeTab, canCreate, - contentModel, + contentModel: model, + getEntry, createEntry, + createEntryRevisionFrom, + deleteEntry, entry: (entry || {}) as CmsContentEntry, - setEntry, - form: formRef, loading, + publishEntryRevision, + refetchContent: loadEntry.refetch, revisions: get(getRevisions, "data.revisions.data") || [], - refetchContent: getEntry.refetch, - setFormRef, - setLoading, setActiveTab, - activeTab, - showEmptyView: !newEntry && !loading && !revisionId + showEmptyView: !newEntry && !loading && !revisionId, + unpublishEntryRevision, + updateEntryRevision }; - return {children}; + return {children}; }; ContentEntryProvider.displayName = "ContentEntryProvider"; diff --git a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/PublishEntryRevisionListItem.tsx b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList/PublishEntryRevisionListItem.tsx similarity index 100% rename from packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/PublishEntryRevisionListItem.tsx rename to packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList/PublishEntryRevisionListItem.tsx diff --git a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionListItem.tsx b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList/RevisionListItem.tsx similarity index 100% rename from packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionListItem.tsx rename to packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList/RevisionListItem.tsx diff --git a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList.tsx b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList/RevisionsList.tsx similarity index 96% rename from packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList.tsx rename to packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList/RevisionsList.tsx index ab87b340619..c802f58e3bf 100644 --- a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList.tsx +++ b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList/RevisionsList.tsx @@ -31,7 +31,7 @@ const style = { }) }; -const RevisionsList = () => { +export const RevisionsList = () => { const { entry, revisions, loading } = useContentEntry(); return ( @@ -49,5 +49,3 @@ const RevisionsList = () => { ); }; - -export default RevisionsList; diff --git a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/useRevision.tsx b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList/useRevision.tsx similarity index 52% rename from packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/useRevision.tsx rename to packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList/useRevision.tsx index 5f837b39189..9974eb7f3fa 100644 --- a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/useRevision.tsx +++ b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/RevisionsList/useRevision.tsx @@ -1,19 +1,10 @@ -import React, { useMemo } from "react"; +import React from "react"; import { useRouter } from "@webiny/react-router"; import { useHandlers } from "@webiny/app/hooks/useHandlers"; import { useSnackbar } from "@webiny/app-admin/hooks/useSnackbar"; -import { makeDecoratable } from "@webiny/app-admin"; -import { useRecords } from "@webiny/app-aco"; import { CmsContentEntry } from "~/types"; -import { - CmsEntryCreateFromMutationResponse, - CmsEntryCreateFromMutationVariables, - createCreateFromMutation -} from "@webiny/app-headless-cms-common"; -import { useApolloClient, useCms } from "~/admin/hooks"; import { useContentEntry } from "~/admin/views/contentEntries/hooks/useContentEntry"; -import { getFetchPolicy } from "~/utils/getFetchPolicy"; -import { OnEntryPublishResponse } from "~/admin/contexts/Cms"; +import { PublishEntryRevisionResponse } from "~/admin/contexts/Cms"; export interface CreateRevisionHandler { (id?: string): Promise; @@ -27,7 +18,7 @@ export interface DeleteRevisionHandler { (id?: string): Promise; } export interface PublishRevisionHandler { - (id?: string): Promise; + (id?: string): Promise; } export interface UnpublishRevisionHandler { (id?: string): Promise; @@ -46,23 +37,13 @@ export interface UseRevisionProps { }; } -export const useRevision = makeDecoratable(({ revision }: UseRevisionProps) => { - const { publishEntryRevision, unpublishEntryRevision, deleteEntry } = useCms(); - const { contentModel, entry, setEntry, setLoading } = useContentEntry(); - +export const useRevision = ({ revision }: UseRevisionProps) => { + const contentEntry = useContentEntry(); const { history } = useRouter(); const { showSnackbar } = useSnackbar(); - const client = useApolloClient(); + const { entry, contentModel } = contentEntry; const { modelId } = contentModel; - const { updateRecordInCache } = useRecords(); - - const { CREATE_REVISION } = useMemo(() => { - return { - CREATE_REVISION: createCreateFromMutation(contentModel) - }; - }, [modelId]); - const { createRevision, editRevision, deleteRevision, publishRevision, unpublishRevision } = useHandlers( { entry }, @@ -70,39 +51,17 @@ export const useRevision = makeDecoratable(({ revision }: UseRevisionProps) => { createRevision: (): CreateRevisionHandler => async (id): Promise => { - setLoading(true); - const createResponse = await client.mutate< - CmsEntryCreateFromMutationResponse, - CmsEntryCreateFromMutationVariables - >({ - mutation: CREATE_REVISION, - variables: { - revision: id || revision.id - }, - fetchPolicy: getFetchPolicy(contentModel) + const { entry, error } = await contentEntry.createEntryRevisionFrom({ + id: id || revision.id }); - setLoading(false); - - if (!createResponse || !createResponse.data) { - showSnackbar(`Missing response data in Create Revision Callable.`); - return; - } - - const { data, error } = createResponse.data.content; - if (error) { showSnackbar(error.message); return; - } else if (!data) { - showSnackbar(`Missing data in Create Revision callable.`); - return; } - updateRecordInCache(data); - history.push( - `/cms/content-entries/${modelId}?id=${encodeURIComponent(data.id)}` + `/cms/content-entries/${modelId}?id=${encodeURIComponent(entry.id)}` ); }, editRevision: @@ -117,48 +76,37 @@ export const useRevision = makeDecoratable(({ revision }: UseRevisionProps) => { deleteRevision: ({ entry }): DeleteRevisionHandler => async (id): Promise => { - setLoading(true); - - const { error, entry: targetRevision } = await deleteEntry({ - model: contentModel, - entry, - id: id || entry.id + const revisionId = id || entry.id; + const response = await contentEntry.deleteEntry({ + id: revisionId }); - setLoading(false); - - if (error) { - showSnackbar(error.message); + if (typeof response === "boolean") { + // Redirect to the first revision in the list of all entry revisions. + const targetRevision = contentEntry.revisions.filter( + rev => rev.id !== revisionId + )[0]; + history.push( + `/cms/content-entries/${modelId}?id=` + + encodeURIComponent(targetRevision!.id) + ); return; } - // Redirect to the first revision in the list of all entry revisions. - history.push( - `/cms/content-entries/${modelId}?id=` + - encodeURIComponent(targetRevision!.id) - ); + showSnackbar(response.error.message); }, publishRevision: ({ entry }): PublishRevisionHandler => async id => { - setLoading(true); - - const response = await publishEntryRevision({ - model: contentModel, - entry, + const response = await contentEntry.publishEntryRevision({ id: id || entry.id }); - setLoading(false); - if (response.error) { showSnackbar(response.error.message); return response; } - setEntry(response.entry); - updateRecordInCache(response.entry); - showSnackbar( Successfully published revision{" "} @@ -171,25 +119,15 @@ export const useRevision = makeDecoratable(({ revision }: UseRevisionProps) => { unpublishRevision: ({ entry }): UnpublishRevisionHandler => async id => { - setLoading(true); - - const response = await unpublishEntryRevision({ - model: contentModel, - entry, + const { error } = await contentEntry.unpublishEntryRevision({ id: id || entry.id }); - setLoading(false); - - const { error, entry: entryResult } = response; - if (error) { showSnackbar(error.message); return; } - updateRecordInCache(entryResult); - showSnackbar( Successfully unpublished revision{" "} @@ -207,4 +145,4 @@ export const useRevision = makeDecoratable(({ revision }: UseRevisionProps) => { publishRevision, unpublishRevision }; -}); +}; diff --git a/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/useMockRecords.ts b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/useMockRecords.ts new file mode 100644 index 00000000000..24220bc4a5c --- /dev/null +++ b/packages/app-headless-cms/src/admin/views/contentEntries/ContentEntry/useMockRecords.ts @@ -0,0 +1,5 @@ +import { useRecords } from "@webiny/app-aco"; + +export const useMockRecords = () => { + return {} as ReturnType; +}; diff --git a/packages/app-headless-cms/src/admin/views/contentEntries/hooks/useContentEntries.ts b/packages/app-headless-cms/src/admin/views/contentEntries/hooks/useContentEntries.ts index b254dedaf29..a985ae03a35 100644 --- a/packages/app-headless-cms/src/admin/views/contentEntries/hooks/useContentEntries.ts +++ b/packages/app-headless-cms/src/admin/views/contentEntries/hooks/useContentEntries.ts @@ -1,8 +1,8 @@ import { useContext } from "react"; -import { Context } from "../ContentEntriesContext"; +import { ContentEntriesContext } from "../ContentEntriesContext"; export function useContentEntries() { - const context = useContext(Context); + const context = useContext(ContentEntriesContext); if (!context) { throw Error(`Missing "ContentEntriesContext" provider in the component tree!`); } diff --git a/packages/app-headless-cms/src/admin/views/contentEntries/hooks/useContentEntry.ts b/packages/app-headless-cms/src/admin/views/contentEntries/hooks/useContentEntry.ts index 5ff7d8c4553..059773e8638 100644 --- a/packages/app-headless-cms/src/admin/views/contentEntries/hooks/useContentEntry.ts +++ b/packages/app-headless-cms/src/admin/views/contentEntries/hooks/useContentEntry.ts @@ -1,12 +1,13 @@ import { useContext } from "react"; -import { Context } from "../ContentEntry/ContentEntryContext"; +import { makeDecoratable } from "@webiny/app-admin"; +import { ContentEntryContext } from "../ContentEntry/ContentEntryContext"; -export function useContentEntry() { - const context = useContext(Context); +export const useContentEntry = makeDecoratable(() => { + const context = useContext(ContentEntryContext); if (!context) { throw Error( `useContentEntry() hook can only be used within the ContentEntryContext provider.` ); } return context; -} +}); diff --git a/packages/app-headless-cms/src/components.ts b/packages/app-headless-cms/src/components.ts index 8fa6454a7d2..dc093c9e253 100644 --- a/packages/app-headless-cms/src/components.ts +++ b/packages/app-headless-cms/src/components.ts @@ -9,17 +9,15 @@ import { import { ContentEntryForm as BaseContentEntryForm } from "./admin/components/ContentEntryForm/ContentEntryForm"; import { ContentEntryFormPreview } from "./admin/components/ContentEntryForm/ContentEntryFormPreview"; import { useContentEntryForm } from "./admin/components/ContentEntryForm/useContentEntryForm"; -import { useRevision } from "./admin/views/contentEntries/ContentEntry/useRevision"; import { useContentEntry } from "~/admin/views/contentEntries/hooks"; export const Components = { ContentEntry: { + useContentEntry, ContentEntryForm: Object.assign(BaseContentEntryForm, { useContentEntryForm, - useContentEntry }), - ContentEntryFormPreview, - useRevision + ContentEntryFormPreview }, FieldRenderers: { DynamicZone: { diff --git a/packages/app-serverless-cms/src/Admin.tsx b/packages/app-serverless-cms/src/Admin.tsx index 7609b75d93d..62db575ac32 100644 --- a/packages/app-serverless-cms/src/Admin.tsx +++ b/packages/app-serverless-cms/src/Admin.tsx @@ -22,7 +22,7 @@ import fileStorageS3Plugin from "@webiny/app-file-manager-s3"; import { createApolloClient as defaultApolloClientFactory } from "./apolloClientFactory"; import apolloLinks from "./apolloLinks"; import { createViewCompositionProvider } from "@webiny/app-admin/base/providers/ViewCompositionProvider"; -import { AdvancedPublishingWorkflow } from "@webiny/app-apw"; +// import { AdvancedPublishingWorkflow } from "@webiny/app-apw"; import { TenantManager } from "@webiny/app-tenant-manager"; import { AuditLogs } from "@webiny/app-audit-logs"; import { LexicalEditorPlugin } from "@webiny/lexical-editor-pb-element"; @@ -30,7 +30,7 @@ import { LexicalEditorActions } from "@webiny/lexical-editor-actions"; import { Module as MailerSettings } from "@webiny/app-mailer"; import { Folders } from "@webiny/app-aco"; import { Websockets } from "@webiny/app-websockets"; -import { LockingMechanism } from "@webiny/app-locking-mechanism"; +// import { LockingMechanism } from "@webiny/app-locking-mechanism"; export interface AdminProps extends Omit { createApolloClient?: BaseAdminProps["createApolloClient"]; @@ -57,13 +57,13 @@ const App = (props: AdminProps) => { - + {/**/} - + {/**/} From 1d1b8721f4e0e230b4e0f261144b31850b9183b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Zori=C4=87?= Date: Fri, 3 May 2024 12:53:56 +0200 Subject: [PATCH 03/11] feat: update to react 18 (#3771) --- .gitignore | 1 + apps/admin/package.json | 6 +- apps/admin/src/index.tsx | 8 +- .../layouts/forms/DefaultFormLayout/Cell.tsx | 21 +- apps/theme/package.json | 2 +- apps/website/package.json | 6 +- apps/website/src/index.tsx | 8 +- .../admin/formBuilder/forms/createForm.cy.ts | 8 +- .../forms/publishAndUnpublishForm.cy.js | 16 +- .../contentModel/contentModels.cy.js | 32 +- .../contentModel/searchSortModels.cy.js | 16 +- .../headlessCms/contentModelGroups.cy.js | 36 +- .../cypress/e2e/admin/i18n/createLocale.cy.js | 12 +- .../cypress/e2e/admin/i18n/deleteLocale.cy.js | 4 +- .../cypress/e2e/admin/login/login.cy.js | 8 +- .../admin/pageBuilder/blocks/pageBlocks.cy.ts | 4 +- .../pageBuilder/blocks/pageBlocksCrud.cy.ts | 18 +- .../blocks/pageBlocksExportImport.cy.ts | 18 +- .../pageCategoriesSearchAndSort.cy.ts | 4 +- .../admin/pageBuilder/menus/menuCrud.cy.ts | 6 +- .../admin/pageBuilder/menus/menuItems.cy.ts | 36 +- .../menus/pagesListMenuItemType.cy.ts | 8 +- .../menus/searchAndSortMenus.cy.ts | 40 +- .../admin/pageBuilder/page/createPage.cy.js | 6 +- .../pageBuilder/page/importExportPages.cy.js | 8 +- .../templates/pageTemplatesCrud.cy.ts | 8 +- .../templates/pageTemplatesExportImport.cy.ts | 8 +- .../templates/pageTemplatesSorting.cy.ts | 16 +- .../e2e/admin/security/apiKey/apiKey.cy.js | 6 +- .../security/apiKey/searchAndSortApiKey.cy.js | 40 +- .../e2e/admin/security/group/role.cy.js | 6 +- .../security/group/searchAndSortRole.cy.js | 40 +- .../security/user/searchAndSortUsers.cy.js | 40 +- .../e2e/admin/security/user/users.cy.js | 6 +- .../blocks/pageBlocksExportImport.cy.ts | 18 +- cypress-tests/package.json | 2 +- package.json | 23 +- packages/api-aco/package.json | 2 +- packages/api-admin-users/package.json | 2 +- packages/api-apw/package.json | 2 +- packages/api-file-manager/package.json | 2 +- packages/api-form-builder/package.json | 2 +- packages/api-headless-cms-ddb-es/package.json | 2 +- packages/api-headless-cms-ddb/package.json | 2 +- packages/api-headless-cms/package.json | 2 +- packages/api-mailer/package.json | 2 +- packages/api-page-builder-aco/package.json | 2 +- .../package.json | 2 +- .../api-page-builder-so-ddb-es/package.json | 2 +- packages/api-page-builder-so-ddb/package.json | 2 +- packages/api-page-builder/package.json | 2 +- .../api-prerendering-service-aws/package.json | 2 +- .../api-prerendering-service/package.json | 2 +- packages/app-aco/package.json | 10 +- packages/app-aco/src/Folders.tsx | 2 +- .../QueryBuilderDrawer.styled.tsx | 4 +- .../SelectedFilter/SelectedFilter.tsx | 3 +- .../src/contexts/navigateFolderWithRouter.tsx | 2 +- .../app-aco/src/dialogs/useCreateDialog.tsx | 10 +- .../src/dialogs/useSetPermissionsDialog.tsx | 24 +- packages/app-admin-auth0/package.json | 6 +- .../src/modules/userMenu/userInfo.tsx | 12 +- packages/app-admin-cognito/package.json | 4 +- .../app-admin-cognito/src/components/View.tsx | 4 +- packages/app-admin-okta/package.json | 6 +- packages/app-admin-okta/src/Okta.tsx | 11 +- .../src/modules/userMenu/userInfo.tsx | 12 +- packages/app-admin-rmwc/package.json | 14 +- .../src/modules/Dashboard/Welcome.tsx | 17 +- .../src/modules/Navigation/Styled.ts | 8 + .../src/modules/Navigation/index.tsx | 2 +- .../renderers/MenuGroupRenderer.tsx | 4 +- .../Navigation/renderers/MenuLinkRenderer.tsx | 2 +- .../renderers/MenuSectionItemRenderer.tsx | 4 +- .../src/modules/Overlays/OmniSearch.tsx | 2 +- .../src/modules/Overlays/index.tsx | 2 +- packages/app-admin-users-cognito/package.json | 6 +- .../src/plugins/installation.tsx | 25 +- .../AdminUsersPermissions.tsx | 6 +- .../src/plugins/userMenu/userInfo.tsx | 12 +- .../src/ui/views/Users/UsersDataList.tsx | 16 +- .../src/ui/views/Users/hooks/useUserForm.ts | 9 +- packages/app-admin/package.json | 8 +- packages/app-admin/src/base/Version.tsx | 2 +- .../src/base/providers/TelemetryProvider.tsx | 3 +- .../src/base/providers/UiStateProvider.tsx | 3 +- .../providers/ViewCompositionProvider.tsx | 3 +- .../app-admin/src/base/ui/FileManager.tsx | 7 +- packages/app-admin/src/base/ui/Menu.tsx | 7 +- packages/app-admin/src/base/ui/Navigation.tsx | 5 +- packages/app-admin/src/base/ui/Search.tsx | 3 +- packages/app-admin/src/base/ui/UserMenu.tsx | 3 +- .../components/AppInstaller/AppInstaller.tsx | 4 +- .../src/components/AppInstaller/index.tsx | 4 +- .../src/components/AppInstaller/styled.tsx | 3 +- .../useDialogWithReport/DialogMessage.tsx | 4 +- .../src/components/Dialogs/DialogsContext.tsx | 17 +- .../OptionsMenu/OptionsMenu.styled.tsx | 8 +- .../app-admin/src/components/SearchUI.tsx | 5 +- .../src/components/SimpleUI/InputField.tsx | 5 +- .../src/components/SplitView/SplitView.tsx | 6 +- packages/app-admin/src/hooks/index.ts | 1 + .../src/hooks/useStateWithCallback.ts | 27 + packages/app-admin/src/index.ts | 2 + .../src/plugins/globalSearch/styled.ts | 4 +- .../styles/material-theme-assignments.scss | 96 +- packages/app-admin/src/types.ts | 2 + packages/app-admin/src/ui/views/SplitView.tsx | 4 +- packages/app-apw/package.json | 8 +- .../ChangeRequest/ApwFile.tsx | 6 +- .../ChangeRequest/ChangeRequest.tsx | 2 +- .../ChangeRequest/ChangeRequestListItem.tsx | 2 +- .../ContentReviewEditor/Comment/Comments.tsx | 2 +- .../src/hooks/usePublishingWorkflowForm.ts | 18 +- .../plugins/editor/defaultBar/BackButton.tsx | 21 +- .../ContentReviewDataList.tsx | 2 +- packages/app-audit-logs/package.json | 6 +- .../app-audit-logs/src/components/Text.tsx | 7 +- .../AuditLogsPermissions.tsx | 2 +- .../src/views/Logs/Table/Table.tsx | 23 +- .../app-cognito-authenticator/package.json | 6 +- .../src/Authenticator.tsx | 14 +- .../src/hooks/useRequireNewPassword.ts | 5 +- .../src/hooks/useSignIn.ts | 5 +- packages/app-file-manager/package.json | 8 +- .../FileManagerView/FileManagerView.tsx | 24 +- .../modules/HeadlessCms/fileRenderer/File.tsx | 4 +- .../HeadlessCms/fileRenderer/fileField.tsx | 4 +- .../HeadlessCms/fileRenderer/fileFields.tsx | 2 +- packages/app-form-builder/package.json | 19 +- .../components/FormEditor/DragPreview.tsx | 3 +- .../admin/components/FormEditor/Draggable.tsx | 21 +- .../admin/components/FormEditor/Droppable.tsx | 4 +- .../plugins/editor/defaultBar/BackButton.tsx | 33 +- .../FormSettings/FormSettingsStyled.tsx | 4 +- .../plugins/editor/defaultBar/Revisions.tsx | 2 +- .../formFields/components/OptionsList.tsx | 184 +- .../OptionsListComponents/OptionsListItem.tsx | 84 +- .../formRevisions/RevisionsList.tsx | 4 +- .../FormSubmissionDialog.tsx | 1 + .../HeaderComponents/RevisionSelector.tsx | 2 +- .../ExportFormLoadingDialogContent.tsx | 6 +- .../ImportButton/useImportFormDialog.tsx | 6 +- .../useImportFormLoadingDialog.tsx | 7 +- .../FormBuilderPermissions.tsx | 3 +- .../src/admin/views/Forms/FormDetails.tsx | 2 +- .../src/admin/views/Forms/FormsDataList.tsx | 4 +- .../admin/views/Forms/hooks/useImportForm.ts | 2 +- .../src/components/Form/FormRender.tsx | 3 +- .../components/createReCaptchaComponent.tsx | 4 +- packages/app-form-builder/src/types.ts | 5 + packages/app-graphql-playground/package.json | 4 +- .../src/plugins/Playground.tsx | 16 +- packages/app-headless-cms-common/package.json | 6 +- .../src/types/index.ts | 8 +- packages/app-headless-cms/package.json | 15 +- packages/app-headless-cms/src/HeadlessCMS.tsx | 3 +- .../Header/DeleteEntry/useDeleteEntry.tsx | 2 +- .../RevisionSelector.styles.tsx | 2 +- .../ContentEntryForm/useContentEntryForm.ts | 18 +- .../src/admin/components/DelayedOnChange.ts | 4 +- .../src/admin/components/Dialog.tsx | 2 +- .../src/admin/components/DragPreview.tsx | 5 +- .../src/admin/components/Draggable.tsx | 12 +- .../src/admin/components/Droppable.tsx | 11 +- .../EditFieldDialog/AppearanceTab.tsx | 39 +- .../EditFieldDialog/GeneralTab.tsx | 2 +- .../FieldEditor/FieldEditorContext.tsx | 18 +- .../src/admin/components/IconPicker.tsx | 32 +- .../admin/elements/NothingToShowElement.tsx | 4 +- .../src/admin/menus/NothingToShowElement.tsx | 4 +- .../plugins/editor/defaultBar/BackButton.tsx | 21 +- .../FormSettings/FormSettingsStyled.tsx | 4 +- .../lexicalText/lexicalTextInput.tsx | 21 +- .../fieldRenderers/object/multipleObjects.tsx | 8 +- .../object/multipleObjectsAccordion.tsx | 8 +- .../advanced/components/ReferencesDialog.tsx | 2 +- .../ContentEntriesMultiAutoComplete.tsx | 2 +- .../src/admin/plugins/fields/ref.tsx | 2 +- .../permissionRenderer/CmsPermissions.tsx | 2 +- .../views/contentEntries/ContentEntry.tsx | 2 +- .../ContentEntry/ContentEntryContext.tsx | 7 +- .../ContentEntry/RevisionsList.tsx | 4 +- .../ContentModelGroupsDataList.tsx | 19 +- .../views/contentModels/ContentModels.tsx | 4 +- .../contentModels/ContentModelsDataList.tsx | 2 +- packages/app-headless-cms/src/types.ts | 31 + packages/app-i18n-content/package.json | 4 +- packages/app-i18n/package.json | 8 +- packages/app-i18n/src/I18N.tsx | 2 +- .../src/admin/views/locales/hooks/graphql.ts | 23 + .../views/locales/hooks/useLocalesList.ts | 17 +- packages/app-mailer/package.json | 6 +- packages/app-page-builder-editor/package.json | 14 +- .../app-page-builder-editor/src/index.tsx | 61 +- .../app-page-builder-elements/package.json | 4 +- .../components/createReCaptchaComponent.tsx | 6 +- packages/app-page-builder/package.json | 19 +- .../admin/graphql/blockImportExport.gql.ts | 13 + .../admin/graphql/templateImportExport.gql.ts | 13 + .../src/admin/plugins/installation.tsx | 9 +- .../pageOptionsMenu/PageOptionsMenu.tsx | 11 +- .../revisionSelector/RevisionSelector.tsx | 2 +- .../pageRevisions/RevisionsList.tsx | 4 +- .../PageBuilderPermissions.tsx | 2 +- .../BlockCategoriesDataList.tsx | 6 +- .../BlockCategories/BlockCategoriesForm.tsx | 4 +- .../views/BlockCategories/IconPicker.tsx | 22 +- .../views/Categories/CategoriesDataList.tsx | 6 +- .../admin/views/Categories/CategoriesForm.tsx | 10 +- .../src/admin/views/Menus/MenusDataList.tsx | 6 +- .../src/admin/views/Menus/MenusForm.tsx | 5 +- .../admin/views/Menus/MenusForm/MenuItems.tsx | 171 +- .../MenusForm/MenuItems/MenuItemRenderer.tsx | 237 +- .../MenusForm/MenuItems/MenuItemsList.tsx | 91 +- .../views/Menus/MenusForm/MenuItems/Styled.ts | 65 +- .../src/admin/views/Menus/types.ts | 6 + .../PageBlocks/BlocksByCategoriesDataList.tsx | 13 +- .../views/PageBlocks/PageBlocksDataList.tsx | 4 +- .../hooks/useFilteredCategoriesListData.ts | 6 +- .../views/PageBlocks/hooks/useImportBlock.ts | 2 +- .../CreatePageTemplateDialog.tsx | 11 +- .../PageTemplates/PageTemplateDetails.tsx | 2 +- .../views/PageTemplates/PageTemplates.tsx | 23 +- .../PageTemplates/PageTemplatesDataList.tsx | 4 +- .../PageTemplates/hooks/useImportTemplate.ts | 2 +- .../src/admin/views/Pages/PageDetails.tsx | 2 +- .../admin/views/Pages/PageTemplatesDialog.tsx | 2 +- .../views/Pages/PageTemplatesDialogStyled.tsx | 4 +- .../admin/views/Pages/hooks/useImportPage.ts | 76 +- .../BlockSettings/BlockSettingsModal.tsx | 2 +- .../EventActionHandlerDecorator.tsx | 2 +- .../app-page-builder/src/editor/Editor.tsx | 2 +- .../components/ColorPicker/ColorPicker.tsx | 10 +- .../editor/components/ColorPicker/index.tsx | 10 +- .../src/editor/components/Draggable.tsx | 25 +- .../src/editor/components/Droppable.ts | 3 +- .../src/editor/components/Element.tsx | 2 +- .../src/editor/components/IconPicker.tsx | 25 +- .../src/editor/components/Text/PeText.tsx | 2 +- .../editor/config/Sidebar/useActiveGroup.ts | 2 +- .../Toolbar/AddElement/AddElementDrawer.tsx | 34 +- .../Toolbar/Navigator/DragBlockIndicator.tsx | 1 + .../Toolbar/Navigator/TreeView.tsx | 4 +- .../Toolbar/Navigator/navigatorHooks.ts | 38 +- .../app-page-builder/src/editor/helpers.ts | 2 +- .../src/editor/hooks/useDisplayMode.tsx | 3 +- .../ExportBlockLoadingDialogContent.tsx | 18 +- .../ExportTemplateLoadingDialogContent.tsx | 72 +- .../block/useImportBlockDialog.tsx | 2 +- .../block/useImportBlockLoadingDialog.tsx | 16 +- .../ImportButton/page/useImportPageDialog.tsx | 2 +- .../template/useImportTemplateDialog.tsx | 2 +- .../useImportTemplateLoadingDialog.tsx | 74 +- .../background/BackgroundSettings.tsx | 5 +- .../elementSettings/border/BorderSettings.tsx | 4 +- .../elementSettings/components/Accordion.tsx | 1 + .../elementSettings/components/BoxInputs.tsx | 2 +- .../components/MarginPaddingSettings.tsx | 2 +- .../components/SelectField.tsx | 3 +- .../elementSettings/grid/CounterInput.tsx | 2 +- .../plugins/elementSettings/grid/GridSize.tsx | 2 +- .../variable/MultipleImageVariableInput.tsx | 6 +- .../elements/button/ButtonSettings.tsx | 12 +- .../elements/button/ButtonSettingsV2.tsx | 12 +- .../plugins/elements/icon/IconSettings.tsx | 7 +- .../plugins/elements/image/ImageSettings.tsx | 11 +- .../imagesList/ImagesListImagesSettings.tsx | 6 +- .../PageSettingsTabElementRenderer.tsx | 2 +- .../PageSettingsTabsElementRenderer.tsx | 2 +- .../src/hooks/useSortableList.ts | 27 +- .../config/TopBar/BackButton/BackButton.tsx | 15 +- .../PageSettings/PageSettingsStyled.tsx | 2 +- .../PublishPageButton/PublishPageButton.tsx | 3 +- .../config/TopBar/Revisions/Revisions.tsx | 2 +- .../EventActionHandlerDecorator.tsx | 2 +- .../src/pageEditor/helpers.ts | 2 +- .../src/pageEditor/hooks/usePageSettings.ts | 3 +- .../Content/BlocksBrowser/SearchBlocks.tsx | 7 +- .../BlocksBrowser/SearchBlocksStyled.tsx | 4 +- .../TemplateSettingsModal.tsx | 2 +- .../EventActionHandlerDecorator.tsx | 2 +- packages/app-page-builder/src/types.ts | 17 +- .../src/utils/createConfigPortal.tsx | 6 +- .../package.json | 4 +- packages/app-record-locking/package.json | 2 +- packages/app-record-locking/src/index.tsx | 2 +- .../package.json | 8 +- .../SecurityPermissions.tsx | 12 +- .../src/types.ts | 4 + .../src/ui/views/ApiKeys/ApiKeyForm.tsx | 12 +- .../src/ui/views/ApiKeys/ApiKeysDataList.tsx | 12 +- .../src/ui/views/ApiKeys/graphql.ts | 9 + .../src/ui/views/Groups/GroupsDataList.tsx | 12 +- .../src/ui/views/Groups/GroupsForm.tsx | 15 +- .../src/ui/views/Groups/graphql.ts | 9 + .../src/ui/views/Teams/TeamsDataList.tsx | 12 +- .../src/ui/views/Teams/TeamsForm.tsx | 11 +- .../src/ui/views/Teams/graphql.ts | 9 + packages/app-security/package.json | 4 +- packages/app-security/src/Security.tsx | 2 +- packages/app-serverless-cms/package.json | 4 +- packages/app-tenancy/package.json | 6 +- packages/app-tenancy/src/Tenancy.tsx | 2 +- packages/app-tenant-manager/package.json | 8 +- .../src/components/CurrentTenant/useTenant.ts | 7 +- .../modules/tenants/hooks/useTenantForm.ts | 3 +- .../modules/tenants/hooks/useTenantsList.ts | 23 +- packages/app-theme-manager/package.json | 8 +- .../src/components/ThemeLoader.tsx | 8 +- .../src/components/ThemeManagerProvider.tsx | 10 +- packages/app-theme/package.json | 4 +- packages/app-trash-bin/package.json | 2 +- packages/app-wcp/package.json | 4 +- packages/app-website/package.json | 4 +- packages/app-websockets/package.json | 2 +- packages/app-websockets/src/index.tsx | 2 +- packages/app/package.json | 8 +- packages/app/src/App.tsx | 11 +- packages/app/src/index.ts | 1 + packages/app/src/renderApp.tsx | 9 + .../buildPackages/MultiplePackagesBuilder.js | 2 +- .../commands/deploy/executeDeploy.js | 2 +- .../utils/createPulumiCommand.js | 36 +- .../pendingOperationsInfo.js | 4 +- .../template/code/package.json | 6 +- .../template/code/src/index.tsx | 8 +- .../template/package.json | 4 +- packages/cli/cli.js | 38 +- packages/cwp-template-aws/package.json | 2 +- .../template/common/apps/admin/package.json | 6 +- .../template/common/apps/admin/src/index.tsx | 9 +- .../template/common/apps/theme/package.json | 2 +- .../template/common/apps/website/package.json | 6 +- .../common/apps/website/src/index.tsx | 9 +- .../template/ddb-es/dependencies.json | 10 +- .../template/ddb-os/dependencies.json | 10 +- .../template/ddb/dependencies.json | 10 +- packages/db-dynamodb/package.json | 2 +- packages/form/package.json | 4 +- packages/i18n-react/package.json | 4 +- packages/i18n/package.json | 4 +- packages/lexical-editor-actions/package.json | 4 +- .../LexicalColorPicker/LexicalColorPicker.tsx | 10 +- .../lexical-editor-pb-element/package.json | 8 +- .../src/LexicalEditor.tsx | 3 +- .../src/components/LexicalText.tsx | 2 +- packages/lexical-editor/package.json | 4 +- packages/lexical-nodes/package.json | 2 +- packages/migrations/package.json | 2 +- .../bundling/app/config/webpack.config.js | 2 +- packages/project-utils/package.json | 6 +- packages/pulumi-aws/package.json | 2 +- packages/pulumi-sdk/package.json | 2 +- packages/pulumi/package.json | 2 +- packages/react-composition/package.json | 6 +- packages/react-composition/src/Context.tsx | 25 +- packages/react-properties/package.json | 4 +- .../package.json | 4 +- .../react-rich-text-renderer/package.json | 4 +- packages/react-router/package.json | 6 +- packages/react-router/src/usePrompt.ts | 2 +- packages/theme/package.json | 4 +- packages/ui-composer/package.json | 4 +- packages/ui/package.json | 58 +- packages/ui/src/Accordion/Accordion.tsx | 4 +- packages/ui/src/Accordion/AccordionItem.tsx | 8 +- .../ui/src/AutoComplete/MultiAutoComplete.tsx | 2 +- .../ui/src/Button/IconButton/IconButton.tsx | 10 +- packages/ui/src/Checkbox/Checkbox.tsx | 15 +- packages/ui/src/Chips/Chip.tsx | 35 +- packages/ui/src/Chips/Chips.tsx | 19 +- packages/ui/src/Chips/styles.ts | 18 +- .../ui/src/DataTable/ColumnsVisibility.tsx | 4 +- packages/ui/src/DataTable/DataTable.tsx | 78 +- packages/ui/src/Dialog/Dialog.tsx | 24 +- packages/ui/src/Input/Input.tsx | 5 +- .../ui/src/Input/__tests__/Input.test.tsx | 5 +- .../ui/src/List/CollapsibleList/index.css | 6 +- packages/ui/src/List/DataList/DataList.tsx | 19 +- .../List/DataList/DataListWithSections.tsx | 19 +- packages/ui/src/List/List.tsx | 7 +- packages/ui/src/Menu/Menu.tsx | 26 +- packages/ui/src/Radio/Radio.tsx | 2 +- packages/ui/src/Select/Select.tsx | 16 +- packages/ui/src/Select/styled.tsx | 4 + packages/ui/src/Slider/Slider.tsx | 4 + packages/ui/src/Switch/Switch.tsx | 6 +- packages/ui/src/styles.scss | 2 +- packages/validation/package.json | 2 +- scripts/prepublishOnly/package.json | 2 +- yarn.lock | 2517 +++++++++-------- 392 files changed, 3914 insertions(+), 3050 deletions(-) create mode 100644 packages/app-admin/src/hooks/useStateWithCallback.ts create mode 100644 packages/app/src/renderApp.tsx diff --git a/.gitignore b/.gitignore index edbd1daf085..9407e8ac062 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ apps/**/build/ cypress.json cypress.config.ts cypress/screenshots/* +cypress-tests/cypress/screenshots/* .pulumi Pulumi.* !Pulumi.yaml diff --git a/apps/admin/package.json b/apps/admin/package.json index 233f80a7626..2b5fe4d7213 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -7,7 +7,7 @@ "@editorjs/list": "^1.6.0", "@editorjs/quote": "^2.4.0", "@editorjs/underline": "^1.0.0", - "@types/react": "17.0.39", + "@types/react": "18.2.65", "@webiny/app-admin": "0.0.0", "@webiny/app-admin-users-cognito": "0.0.0", "@webiny/app-form-builder": "0.0.0", @@ -22,8 +22,8 @@ "core-js": "^3.0.1", "cross-fetch": "^3.0.4", "prop-types": "^15.7.2", - "react": "17.0.2", - "react-dom": "17.0.2", + "react": "18.2.0", + "react-dom": "18.2.0", "regenerator-runtime": "^0.13.5", "theme": "^0.1.0", "tslib": "^2.4.0" diff --git a/apps/admin/src/index.tsx b/apps/admin/src/index.tsx index a19179aa928..b54cc84bb8f 100644 --- a/apps/admin/src/index.tsx +++ b/apps/admin/src/index.tsx @@ -1,10 +1,10 @@ +import React from "react"; import "cross-fetch/polyfill"; import "core-js/stable"; import "regenerator-runtime/runtime"; -import React from "react"; -import ReactDOM from "react-dom"; import { App } from "./App"; import "./plugins"; -const render = module.hot ? ReactDOM.render : ReactDOM.hydrate; -render(, document.getElementById("root")); +import { renderApp } from "@webiny/app-admin"; + +renderApp(); diff --git a/apps/theme/layouts/forms/DefaultFormLayout/Cell.tsx b/apps/theme/layouts/forms/DefaultFormLayout/Cell.tsx index 1280aa46fde..e9294ca20e5 100644 --- a/apps/theme/layouts/forms/DefaultFormLayout/Cell.tsx +++ b/apps/theme/layouts/forms/DefaultFormLayout/Cell.tsx @@ -1,18 +1,17 @@ import styled from "@emotion/styled"; export const Cell = styled.div` - width: 100%; - background-color: ${props => props.theme.styles.colors["color6"]}; - padding: 15px; + width: 100%; + background-color: ${props => props.theme.styles.colors["color6"]}; + padding: 15px; - ${props => props.theme.breakpoints["desktop"]} { - &:first-of-type { - padding-left: 0; - } + ${props => props.theme.breakpoints["desktop"]} { + &:first-of-type { + padding-left: 0; + } - &:last-child { - padding-right: 0; + &:last-child { + padding-right: 0; + } } - } -} `; diff --git a/apps/theme/package.json b/apps/theme/package.json index cec8e8b5c7d..ab18507beac 100644 --- a/apps/theme/package.json +++ b/apps/theme/package.json @@ -18,7 +18,7 @@ "@webiny/utils": "0.0.0", "@webiny/validation": "0.0.0", "graphql": "^15.7.2", - "react": "17.0.2", + "react": "18.2.0", "react-hamburger-menu": "^1.1.1" }, "devDependencies": { diff --git a/apps/website/package.json b/apps/website/package.json index e89732ff557..bef2b3bca1d 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -4,7 +4,7 @@ "private": true, "dependencies": { "@emotion/react": "^11.10.6", - "@types/react": "17.0.39", + "@types/react": "18.2.65", "@webiny/app": "0.0.0", "@webiny/app-form-builder": "0.0.0", "@webiny/app-page-builder": "0.0.0", @@ -20,8 +20,8 @@ "cross-fetch": "^3.0.4", "graphql": "^15.7.2", "graphql-tag": "^2.12.6", - "react": "17.0.2", - "react-dom": "17.0.2", + "react": "18.2.0", + "react-dom": "18.2.0", "regenerator-runtime": "^0.13.5", "theme": "^0.1.0" }, diff --git a/apps/website/src/index.tsx b/apps/website/src/index.tsx index 90e89ce5194..a63788cd627 100644 --- a/apps/website/src/index.tsx +++ b/apps/website/src/index.tsx @@ -1,11 +1,11 @@ +import React from "react"; import "cross-fetch/polyfill"; import "core-js/stable"; import "regenerator-runtime/runtime"; -import React from "react"; -import ReactDOM from "react-dom"; import { App } from "./App"; import "./plugins"; import "theme/global.scss"; -const render = module.hot ? ReactDOM.render : ReactDOM.hydrate; -render(, document.getElementById("root")); +import { renderApp } from "@webiny/app"; + +renderApp(); diff --git a/cypress-tests/cypress/e2e/admin/formBuilder/forms/createForm.cy.ts b/cypress-tests/cypress/e2e/admin/formBuilder/forms/createForm.cy.ts index 74c76dd7bd1..5bbf093f5b2 100644 --- a/cypress-tests/cypress/e2e/admin/formBuilder/forms/createForm.cy.ts +++ b/cypress-tests/cypress/e2e/admin/formBuilder/forms/createForm.cy.ts @@ -52,7 +52,9 @@ context("Forms Creation", () => { .first() .within(() => { cy.findByText(newFormTitle2).should("be.visible"); - cy.findByTestId("edit-form-action").click({ force: true }); + // Workaround for "@rmwc/icon-button" v14 issue: props duplication onto , causing multiple elements with same `data-testid`. + // Now targeting