From 1e8f135d65ce31406e6a59ae9ede0bef8e63d608 Mon Sep 17 00:00:00 2001 From: Valentin Yanakiev Date: Thu, 31 Oct 2024 16:40:39 +0200 Subject: [PATCH 1/2] Remove azure workflows --- .../workflows/build-deploy-k8s-dev-azure.yml | 109 ------------------ .../build-deploy-k8s-sandbox-azure.yml | 108 ----------------- .../workflows/build-deploy-k8s-test-azure.yml | 108 ----------------- 3 files changed, 325 deletions(-) delete mode 100644 .github/workflows/build-deploy-k8s-dev-azure.yml delete mode 100644 .github/workflows/build-deploy-k8s-sandbox-azure.yml delete mode 100644 .github/workflows/build-deploy-k8s-test-azure.yml diff --git a/.github/workflows/build-deploy-k8s-dev-azure.yml b/.github/workflows/build-deploy-k8s-dev-azure.yml deleted file mode 100644 index cf92ea415a..0000000000 --- a/.github/workflows/build-deploy-k8s-dev-azure.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Build, Migrate & Deploy to Dev - -on: - push: - branches: [develop] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v3.0.2 - - - name: 'Login into ACR' - uses: azure/docker-login@v1.0.1 - with: - login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }} - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} - - - name: 'Build & Push image' - run: | - docker build -f Dockerfile . -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:latest - docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} - migrate: - needs: build - runs-on: ubuntu-latest - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v3.0.2 - - - name: 'Login via Azure CLI' - uses: azure/login@v1.4.7 - with: - creds: ${{ secrets.AZURE_CRED_K8S_NEW }} - - - uses: Azure/aks-set-context@v3.2 - with: - cluster-name: ${{ secrets.CLUSTER_NAME }} - resource-group: ${{ secrets.RESOURCE_GROUP_K8S }} - - - uses: Azure/k8s-create-secret@v4.0 - with: - container-registry-url: ${{ secrets.REGISTRY_LOGIN_SERVER }} - container-registry-username: ${{ secrets.REGISTRY_USERNAME }} - container-registry-password: ${{ secrets.REGISTRY_PASSWORD }} - secret-name: alkemio-server-secret - - - uses: azure/setup-kubectl@v3.2 - with: - version: 'v1.22.0' # default is latest stable, fixing it to a compatible version - id: install - - - uses: Azure/k8s-deploy@v4.10 - with: - manifests: | - manifests/26-server-migration.yaml - images: | - ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} - imagepullsecrets: | - alkemio-server-secret - - - name: Delete old job - continue-on-error: true - run: | - kubectl delete job alkemio-server-migration-job - - - name: Create migration job - run: | - kubectl create job --from=cronjob/alkemio-server-migration alkemio-server-migration-job - - deploy: - needs: migrate - runs-on: ubuntu-latest - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v3.0.2 - - - name: 'Login via Azure CLI' - uses: azure/login@v1.4.7 - with: - creds: ${{ secrets.AZURE_CRED_K8S_NEW }} - - - uses: Azure/aks-set-context@v3.2 - with: - cluster-name: ${{ secrets.CLUSTER_NAME }} - resource-group: ${{ secrets.RESOURCE_GROUP_K8S }} - - - uses: Azure/k8s-create-secret@v4.0 - with: - container-registry-url: ${{ secrets.REGISTRY_LOGIN_SERVER }} - container-registry-username: ${{ secrets.REGISTRY_USERNAME }} - container-registry-password: ${{ secrets.REGISTRY_PASSWORD }} - secret-name: alkemio-server-secret - - - uses: azure/setup-kubectl@v3.2 - with: - version: 'v1.22.0' # default is latest stable, fixing it to a compatible version - id: install - - - uses: Azure/k8s-deploy@v4.10 - with: - manifests: | - manifests/25-server-deployment-dev.yaml - manifests/30-server-service.yaml - images: | - ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} - imagepullsecrets: | - alkemio-server-secret diff --git a/.github/workflows/build-deploy-k8s-sandbox-azure.yml b/.github/workflows/build-deploy-k8s-sandbox-azure.yml deleted file mode 100644 index 633909f555..0000000000 --- a/.github/workflows/build-deploy-k8s-sandbox-azure.yml +++ /dev/null @@ -1,108 +0,0 @@ -name: Build, Migrate & Deploy to Sandbox on Azure - -on: - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v3.0.2 - - - name: 'Login into ACR' - uses: azure/docker-login@v1.0.1 - with: - login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }} - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} - - - name: 'Build & Push image' - run: | - docker build -f Dockerfile . -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:latest - docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} - migrate: - needs: build - runs-on: ubuntu-latest - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v3.0.2 - - - name: 'Login via Azure CLI' - uses: azure/login@v1.4.7 - with: - creds: ${{ secrets.AZURE_CRED_K8S_NEW }} - - - uses: Azure/aks-set-context@v3.2 - with: - cluster-name: k8s-sandbox - resource-group: res-grp-k8s-sandbox - - - uses: Azure/k8s-create-secret@v4.0 - with: - container-registry-url: ${{ secrets.REGISTRY_LOGIN_SERVER }} - container-registry-username: ${{ secrets.REGISTRY_USERNAME }} - container-registry-password: ${{ secrets.REGISTRY_PASSWORD }} - secret-name: alkemio-server-secret - - - uses: azure/setup-kubectl@v3.2 - with: - version: 'v1.22.0' # default is latest stable, fixing it to a compatible version - id: install - - - uses: Azure/k8s-deploy@v4.10 - with: - manifests: | - manifests/26-server-migration.yaml - images: | - ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} - imagepullsecrets: | - alkemio-server-secret - - - name: Delete old job - continue-on-error: true - run: | - kubectl delete job alkemio-server-migration-job - - - name: Create migration job - run: | - kubectl create job --from=cronjob/alkemio-server-migration alkemio-server-migration-job - - deploy: - needs: migrate - runs-on: ubuntu-latest - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v3.0.2 - - - name: 'Login via Azure CLI' - uses: azure/login@v1.4.7 - with: - creds: ${{ secrets.AZURE_CRED_K8S_NEW }} - - - uses: Azure/aks-set-context@v3.2 - with: - cluster-name: k8s-sandbox - resource-group: res-grp-k8s-sandbox - - - uses: Azure/k8s-create-secret@v4.0 - with: - container-registry-url: ${{ secrets.REGISTRY_LOGIN_SERVER }} - container-registry-username: ${{ secrets.REGISTRY_USERNAME }} - container-registry-password: ${{ secrets.REGISTRY_PASSWORD }} - secret-name: alkemio-server-secret - - - uses: azure/setup-kubectl@v3.2 - with: - version: 'v1.22.0' # default is latest stable, fixing it to a compatible version - id: install - - - uses: Azure/k8s-deploy@v4.10 - with: - manifests: | - manifests/25-server-deployment-dev.yaml - manifests/30-server-service.yaml - images: | - ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} - imagepullsecrets: | - alkemio-server-secret diff --git a/.github/workflows/build-deploy-k8s-test-azure.yml b/.github/workflows/build-deploy-k8s-test-azure.yml deleted file mode 100644 index 23a89c17b0..0000000000 --- a/.github/workflows/build-deploy-k8s-test-azure.yml +++ /dev/null @@ -1,108 +0,0 @@ -name: Build, Migrate & Deploy to Test on Azure - -on: - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v3.0.2 - - - name: 'Login into ACR' - uses: azure/docker-login@v1.0.1 - with: - login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }} - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} - - - name: 'Build & Push image' - run: | - docker build -f Dockerfile . -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} -t ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:latest - docker push ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} - migrate: - needs: build - runs-on: ubuntu-latest - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v3.0.2 - - - name: 'Login via Azure CLI' - uses: azure/login@v1.4.7 - with: - creds: ${{ secrets.AZURE_CRED_K8S_NEW }} - - - uses: Azure/aks-set-context@v3.2 - with: - cluster-name: k8s-test - resource-group: res-grp-k8s-test - - - uses: Azure/k8s-create-secret@v4.0 - with: - container-registry-url: ${{ secrets.REGISTRY_LOGIN_SERVER }} - container-registry-username: ${{ secrets.REGISTRY_USERNAME }} - container-registry-password: ${{ secrets.REGISTRY_PASSWORD }} - secret-name: alkemio-server-secret - - - uses: azure/setup-kubectl@v3.2 - with: - version: 'v1.22.0' # default is latest stable, fixing it to a compatible version - id: install - - - uses: Azure/k8s-deploy@v4.10 - with: - manifests: | - manifests/26-server-migration.yaml - images: | - ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} - imagepullsecrets: | - alkemio-server-secret - - - name: Delete old job - continue-on-error: true - run: | - kubectl delete job alkemio-server-migration-job - - - name: Create migration job - run: | - kubectl create job --from=cronjob/alkemio-server-migration alkemio-server-migration-job - - deploy: - needs: migrate - runs-on: ubuntu-latest - steps: - - name: 'Checkout GitHub Action' - uses: actions/checkout@v3.0.2 - - - name: 'Login via Azure CLI' - uses: azure/login@v1.4.7 - with: - creds: ${{ secrets.AZURE_CRED_K8S_NEW }} - - - uses: Azure/aks-set-context@v3.2 - with: - cluster-name: k8s-test - resource-group: res-grp-k8s-test - - - uses: Azure/k8s-create-secret@v4.0 - with: - container-registry-url: ${{ secrets.REGISTRY_LOGIN_SERVER }} - container-registry-username: ${{ secrets.REGISTRY_USERNAME }} - container-registry-password: ${{ secrets.REGISTRY_PASSWORD }} - secret-name: alkemio-server-secret - - - uses: azure/setup-kubectl@v3.2 - with: - version: 'v1.22.0' # default is latest stable, fixing it to a compatible version - id: install - - - uses: Azure/k8s-deploy@v4.10 - with: - manifests: | - manifests/25-server-deployment-dev.yaml - manifests/30-server-service.yaml - images: | - ${{ secrets.REGISTRY_LOGIN_SERVER }}/alkemio-server:${{ github.sha }} - imagepullsecrets: | - alkemio-server-secret From 2e007597a93159ae3908ae3fa744a7df362e5aee Mon Sep 17 00:00:00 2001 From: Neil Smyth <30729240+techsmyth@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:36:47 +0100 Subject: [PATCH 2/2] Subspace Templates (#4571) * first pass at two new modules for TemplatesManager, TemplateDefault * added templates manager to space; removed the SpaceDefaults entity (module still present until move all data to be via defaults * added templatesManager to platform * moved creating of default innovatin flow input to space defaults * back out space type on Template; tidy up Template module to use switch statements * created template applier module * tidy up naming * updated set of default template types * fixed circular dependency; moved logic for creating collaboration input to space defaults * removed loading of defaults from files for collaboration content * removed code based addition of callouts, innovation flow states * tidy up naming * added loading of default templates at platform level in to bootstrap * removed option to create new innovation flow template * added in migration: * loading in templates on bootstrap * added field for collaboration templates on templatesSet; added lookup for templatesManager * added mutation to create template from collaboration; added logic to prevent template used as default to be deleted; fixed removal of template set on template manager * fixed auth cascade for templates of type post * moved tempaltesManager to last migration in the list * fixed retrieval of template when creating collaboration * added logging * fixed bootstrap setting of templates * refactored inputCreator to do the data loading closer to usage; fixed picking up of templates; fixed bootstrap usage of templates * fixed url generation for templates inside of TempaltesManager * fixed bootstrap order to create forum earlier * ensure collaboration creation on template provides some defaults for callouts * fix deletion of templates of type post * ensure more data is defaulted inside of template service for collaboration; add setting of isTemplate field on Collaboration, and also on contained Callouts * ensure isTempalte is passed to Collaboration entity * fixed groups in bootstrap space template; updated signature for creating callout from collaboration * fixed missing field * fixed type on mutation to create from collaboration * fixed typo * reworked applying collaboraiton template to collaboration * improved error message in wrong type of ID passed in * fixed build * made migration last in the list * small fixes - move the migration to the end - rename templates.manager.dto.create..ts to .ts * renamed platfrom to platform * Address coderabbitai comment. UseGuards decorator should be imported from '@nestjs/common' * Address coderabbit comments * Rename some innovaton to innovation * Fix create * Update whiteboard templates while disallowing updating whiteboard contents directly * address comments * fix whiteboard templates * Circumvent typeorm bug * Fix collaboration not adding Callouts * Check the flag addCallouts * Fix callouts falling on invalid groups or flowstates * Address CR comments * address comments * Create callouts when empty nameID * Address comment --------- Co-authored-by: Carlos Cano --- .../mutations/update/update-space-defaults | 13 - src/app.module.ts | 2 + src/common/enums/authorization.policy.type.ts | 3 +- src/common/enums/template.default.type.ts | 13 + src/core/bootstrap/bootstrap.module.ts | 6 + src/core/bootstrap/bootstrap.service.ts | 144 ++++++- ...ootstrap.space.tutorials.callout.groups.ts | 21 + .../bootstrap.space.tutorials.callouts.ts} | 18 +- ...space.tutorials.innovation.flow.states.ts} | 9 +- .../space/bootstrap.space.callout.groups.ts} | 2 +- .../space/bootstrap.space.callouts.ts} | 6 +- .../space/bootstrap.space.innovation.flow.ts | 28 ++ ...trap.subspace.knowledge.callout.groups.ts} | 2 +- .../bootstrap.subspace.knowledge.callouts.ts} | 6 +- ...space.knowledge.innovation.flow.states.ts} | 2 +- .../bootstrap.subspace.callout.groups.ts} | 2 +- .../subspace/bootstrap.subspace.callouts.ts} | 4 +- ...otstrap.subspace.innovation.flow.states.ts | 37 ++ .../callout.framing.resolver.fields.ts | 2 +- .../callout.framing.service.ts | 18 +- .../dto/callout.framing.dto.update.ts | 11 +- .../callout/callout.interface.ts | 6 + .../collaboration/collaboration.entity.ts | 3 + .../collaboration/collaboration.interface.ts | 8 +- .../collaboration/collaboration.module.ts | 2 - .../collaboration.resolver.fields.ts | 2 +- .../collaboration/collaboration.service.ts | 88 +++- .../dto/collaboration.dto.create.ts | 2 + .../innovation.flow.state.module.ts | 2 +- ...ce.ts => innovation.flow.state.service.ts} | 0 .../innovation-flow/innovation.flow.module.ts | 2 +- .../innovation.flow.resolver.fields.ts | 2 +- .../innovation.flow.resolver.mutations.ts | 2 +- .../innovation.flow.service.spec.ts | 2 +- ....service.ts => innovation.flow.service.ts} | 2 +- .../link/link.resolver.fields.ts | 2 +- .../common/profile/profile.resolver.fields.ts | 2 +- .../dto/whiteboard.dto.update.entity.ts | 8 +- .../whiteboard/dto/whiteboard.dto.update.ts | 9 +- .../whiteboard/whiteboard.resolver.fields.ts | 2 +- .../user-group/user-group.resolver.fields.ts | 2 +- .../account/account.resolver.mutations.ts | 2 +- src/domain/space/account/account.service.ts | 8 +- ...ace.defaults.callout.groups.blank.slate.ts | 9 - ...ce.defaults.innovation.flow.blank.slate.ts | 24 -- ...pace.defaults.innovation.flow.challenge.ts | 38 -- ...space.defaults.callout.groups.knowledge.ts | 9 - .../space.defaults.callouts.opportunity.ts | 180 -------- ...ce.defaults.innovation.flow.opportunity.ts | 38 -- .../definitions/space.defaults.templates.ts | 65 +-- .../dto/space.defaults.dto.update.ts | 18 - .../space.defaults/space.defaults.entity.ts | 18 - .../space.defaults.interface.ts | 13 - .../space.defaults/space.defaults.module.ts | 17 +- .../space.defaults/space.defaults.service.ts | 302 ++++++------- .../dto/space.dto.create.collaboration.ts | 19 +- src/domain/space/space/space.entity.ts | 15 +- src/domain/space/space/space.interface.ts | 6 +- src/domain/space/space/space.module.ts | 11 +- .../space/space/space.resolver.fields.ts | 26 +- .../space/space/space.resolver.mutations.ts | 43 +- .../space/space.service.authorization.ts | 28 +- src/domain/space/space/space.service.spec.ts | 2 + src/domain/space/space/space.service.ts | 198 ++------- ...mplate.applier.dto.update.collaboration.ts | 22 + .../template.applier.module.ts | 29 ++ .../template.applier.resolver.mutations.ts | 76 ++++ .../template.applier.service.ts | 130 ++++++ .../dto/template.default.dto.create.ts | 11 + .../dto/template.default.dto.update.ts | 17 + .../template.default.entity.ts | 41 ++ .../template.default.interface.ts | 29 ++ .../template.default.module.ts | 20 + .../template.default.service.authorization.ts | 22 + .../template.default.service.ts | 95 +++++ .../template/dto/template.dto.create.base.ts | 14 + .../template/dto/template.dto.create.ts | 21 +- .../template.dto.update.innovation.flow.ts | 19 - .../template/dto/template.dto.update.ts | 33 +- .../template/template.resolver.mutations.ts | 39 +- .../template.service.authorization.ts | 136 +++--- .../template/template/template.service.ts | 396 ++++++++++-------- .../dto/templates.manager.dto.create.ts | 5 + .../template/templates-manager/index.ts | 2 + .../templates.manager.entity.ts | 29 ++ .../templates.manager.interface.ts | 10 + .../templates.manager.module.ts | 35 ++ .../templates.manager.resolver.fields.ts | 42 ++ .../templates.manager.resolver.mutations.ts | 91 ++++ ...templates.manager.service.authorization.ts | 78 ++++ .../templates.manager.service.ts | 193 +++++++++ ....dto.create.template.from.collaboration.ts | 15 + .../templates-set/templates.set.module.ts | 4 + .../templates.set.resolver.fields.ts | 32 +- .../templates.set.resolver.mutations.ts | 48 +++ .../templates-set/templates.set.service.ts | 21 + .../innovation-pack/innovation.pack.module.ts | 2 +- .../innovation.pack.resolver.fields.ts | 2 +- .../innovation.pack.resolver.mutations.ts | 2 +- .../innovation.pack.service.authorization.ts | 2 +- .../innovation.pack.service.spec.ts | 2 +- ....service.ts => innovation.pack.service.ts} | 0 src/library/library/library.service.ts | 2 +- .../1729511643555-templatesManager.ts | 317 ++++++++++++++ .../admin.authorization.resolver.queries.ts | 2 +- .../admin.communication.resolver.queries.ts | 2 +- src/platform/platform/platform.entity.ts | 9 + src/platform/platform/platform.interface.ts | 2 + src/platform/platform/platform.module.ts | 2 + .../platform/platform.resolver.fields.ts | 12 +- .../platform.service.authorization.ts | 15 +- src/platform/platform/platform.service.ts | 15 + .../input.creator.resolver.fields.ts | 51 +-- .../input-creator/input.creator.service.ts | 84 +++- .../lookup.by.name.resolver.fields.ts | 2 +- src/services/api/lookup/lookup.module.ts | 2 + .../api/lookup/lookup.resolver.fields.ts | 28 +- .../storage.aggregator.resolver.service.ts | 78 +++- .../url-generator/url.generator.service.ts | 30 +- 119 files changed, 2626 insertions(+), 1378 deletions(-) delete mode 100644 graphql-samples/mutations/update/update-space-defaults create mode 100644 src/common/enums/template.default.type.ts create mode 100644 src/core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.callout.groups.ts rename src/{domain/space/space.defaults/definitions/root-space/space.defaults.callouts.root.space.ts => core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.callouts.ts} (94%) rename src/{domain/space/space.defaults/definitions/root-space/space.defaults.innovation.flow.root.space.ts => core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.innovation.flow.states.ts} (56%) rename src/{domain/space/space.defaults/definitions/root-space/space.defaults.callout.groups.root.space.ts => core/bootstrap/platform-template-definitions/space/bootstrap.space.callout.groups.ts} (88%) rename src/{domain/space/space.defaults/definitions/blank-slate/space.defaults.callouts.blank.slate.ts => core/bootstrap/platform-template-definitions/space/bootstrap.space.callouts.ts} (82%) create mode 100644 src/core/bootstrap/platform-template-definitions/space/bootstrap.space.innovation.flow.ts rename src/{domain/space/space.defaults/definitions/oppportunity/space.defaults.callout.groups.opportunity.ts => core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.callout.groups.ts} (77%) rename src/{domain/space/space.defaults/definitions/knowledge/space.defaults.callouts.knowledge.ts => core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.callouts.ts} (97%) rename src/{domain/space/space.defaults/definitions/knowledge/space.defaults.innovation.flow.knowledge.ts => core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.innovation.flow.states.ts} (91%) rename src/{domain/space/space.defaults/definitions/challenge/space.defaults.callout.groups.challenge.ts => core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.callout.groups.ts} (78%) rename src/{domain/space/space.defaults/definitions/challenge/space.defaults.callouts.challenge.ts => core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.callouts.ts} (94%) create mode 100644 src/core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.innovation.flow.states.ts rename src/domain/collaboration/innovation-flow-states/{innovaton.flow.state.service.ts => innovation.flow.state.service.ts} (100%) rename src/domain/collaboration/innovation-flow/{innovaton.flow.service.ts => innovation.flow.service.ts} (99%) delete mode 100644 src/domain/space/space.defaults/definitions/blank-slate/space.defaults.callout.groups.blank.slate.ts delete mode 100644 src/domain/space/space.defaults/definitions/blank-slate/space.defaults.innovation.flow.blank.slate.ts delete mode 100644 src/domain/space/space.defaults/definitions/challenge/space.defaults.innovation.flow.challenge.ts delete mode 100644 src/domain/space/space.defaults/definitions/knowledge/space.defaults.callout.groups.knowledge.ts delete mode 100644 src/domain/space/space.defaults/definitions/oppportunity/space.defaults.callouts.opportunity.ts delete mode 100644 src/domain/space/space.defaults/definitions/oppportunity/space.defaults.innovation.flow.opportunity.ts delete mode 100644 src/domain/space/space.defaults/dto/space.defaults.dto.update.ts delete mode 100644 src/domain/space/space.defaults/space.defaults.entity.ts delete mode 100644 src/domain/space/space.defaults/space.defaults.interface.ts create mode 100644 src/domain/template/template-applier/dto/template.applier.dto.update.collaboration.ts create mode 100644 src/domain/template/template-applier/template.applier.module.ts create mode 100644 src/domain/template/template-applier/template.applier.resolver.mutations.ts create mode 100644 src/domain/template/template-applier/template.applier.service.ts create mode 100644 src/domain/template/template-default/dto/template.default.dto.create.ts create mode 100644 src/domain/template/template-default/dto/template.default.dto.update.ts create mode 100644 src/domain/template/template-default/template.default.entity.ts create mode 100644 src/domain/template/template-default/template.default.interface.ts create mode 100644 src/domain/template/template-default/template.default.module.ts create mode 100644 src/domain/template/template-default/template.default.service.authorization.ts create mode 100644 src/domain/template/template-default/template.default.service.ts create mode 100644 src/domain/template/template/dto/template.dto.create.base.ts delete mode 100644 src/domain/template/template/dto/template.dto.update.innovation.flow.ts create mode 100644 src/domain/template/templates-manager/dto/templates.manager.dto.create.ts create mode 100644 src/domain/template/templates-manager/index.ts create mode 100644 src/domain/template/templates-manager/templates.manager.entity.ts create mode 100644 src/domain/template/templates-manager/templates.manager.interface.ts create mode 100644 src/domain/template/templates-manager/templates.manager.module.ts create mode 100644 src/domain/template/templates-manager/templates.manager.resolver.fields.ts create mode 100644 src/domain/template/templates-manager/templates.manager.resolver.mutations.ts create mode 100644 src/domain/template/templates-manager/templates.manager.service.authorization.ts create mode 100644 src/domain/template/templates-manager/templates.manager.service.ts create mode 100644 src/domain/template/templates-set/dto/templates.set.dto.create.template.from.collaboration.ts rename src/library/innovation-pack/{innovaton.pack.service.ts => innovation.pack.service.ts} (100%) create mode 100644 src/migrations/1729511643555-templatesManager.ts diff --git a/graphql-samples/mutations/update/update-space-defaults b/graphql-samples/mutations/update/update-space-defaults deleted file mode 100644 index 65cabb650f..0000000000 --- a/graphql-samples/mutations/update/update-space-defaults +++ /dev/null @@ -1,13 +0,0 @@ -mutation updateSpaceDefaults($updateData: UpdateSpaceDefaultsInput!) { - updateSpaceDefaults(updateData: $updateData) { - id - } -} - -Variables: -{ - "updateData": { - "spaceID": "uuid_nameid", - "innovationFlowTemplateID": "uuid" - } -} \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index 91aa4978be..5d114a26fe 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -82,6 +82,7 @@ import { LookupByNameModule } from '@services/api/lookup-by-name'; import { PlatformHubModule } from '@platform/platform.hub/platform.hub.module'; import { AdminContributorsModule } from '@platform/admin/avatars/admin.avatar.module'; import { InputCreatorModule } from '@services/api/input-creator/input.creator.module'; +import { TemplateApplierModule } from '@domain/template/template-applier/template.applier.module'; import { Cipher, EncryptionModule } from '@hedger/nestjs-encryption'; import { AdminUsersModule } from '@platform/admin/users/admin.users.module'; @@ -293,6 +294,7 @@ import { AdminUsersModule } from '@platform/admin/users/admin.users.module'; WhiteboardIntegrationModule, FileIntegrationModule, PlatformSettingsModule, + TemplateApplierModule, ], controllers: [AppController, SsiCredentialFlowController], providers: [ diff --git a/src/common/enums/authorization.policy.type.ts b/src/common/enums/authorization.policy.type.ts index 1d01b50468..8e511bbce9 100644 --- a/src/common/enums/authorization.policy.type.ts +++ b/src/common/enums/authorization.policy.type.ts @@ -39,13 +39,14 @@ export enum AuthorizationPolicyType { ECOSYSTEM_MODEL = 'ecosystem-model', VIRTUAL_CONTRIBUTOR = 'virtual-contributor', SPACE = 'space', - SPACE_DEFAULTS = 'space-defaults', ACCOUNT = 'account', DOCUMENT = 'document', STORAGE_AGGREGATOR = 'storage-aggregator', STORAGE_BUCKET = 'storage-bucket', TEMPLATE = 'template', TEMPLATES_SET = 'templates-set', + TEMPLATES_MANAGER = 'templates-manager', + TEMPLATE_DEFAULT = 'template-default', CALENDAR = 'calendar', CALENDAR_EVENT = 'calendar-event', TIMELINE = 'timeline', diff --git a/src/common/enums/template.default.type.ts b/src/common/enums/template.default.type.ts new file mode 100644 index 0000000000..fb7ca30d0b --- /dev/null +++ b/src/common/enums/template.default.type.ts @@ -0,0 +1,13 @@ +import { registerEnumType } from '@nestjs/graphql'; + +export enum TemplateDefaultType { + PLATFORM_SPACE = 'platform-space', + PLATFORM_SPACE_TUTORIALS = 'platform-space-tutorials', + PLATFORM_SUBSPACE = 'platform-subspace', + PLATFORM_SUBSPACE_KNOWLEDGE = 'platform-subspace-knowledge', + SPACE_SUBSPACE = 'space-subspace', +} + +registerEnumType(TemplateDefaultType, { + name: 'TemplateDefaultType', +}); diff --git a/src/core/bootstrap/bootstrap.module.ts b/src/core/bootstrap/bootstrap.module.ts index 6c4ca7c565..a41197c058 100644 --- a/src/core/bootstrap/bootstrap.module.ts +++ b/src/core/bootstrap/bootstrap.module.ts @@ -16,6 +16,9 @@ import { SearchIngestModule } from '@services/api/search/v2/ingest'; import { AiServerModule } from '@services/ai-server/ai-server/ai.server.module'; import { Space } from '@domain/space/space/space.entity'; import { ContributorModule } from '@domain/community/contributor/contributor.module'; +import { TemplatesSetModule } from '@domain/template/templates-set/templates.set.module'; +import { TemplatesManagerModule } from '@domain/template/templates-manager/templates.manager.module'; +import { TemplateDefaultModule } from '@domain/template/template-default/template.default.module'; @Module({ imports: [ @@ -35,6 +38,9 @@ import { ContributorModule } from '@domain/community/contributor/contributor.mod TypeOrmModule.forFeature([Space]), NameReporterModule, SearchIngestModule, + TemplatesSetModule, + TemplatesManagerModule, + TemplateDefaultModule, ], providers: [BootstrapService], exports: [BootstrapService], diff --git a/src/core/bootstrap/bootstrap.service.ts b/src/core/bootstrap/bootstrap.service.ts index 201f7338ab..c426fd1338 100644 --- a/src/core/bootstrap/bootstrap.service.ts +++ b/src/core/bootstrap/bootstrap.service.ts @@ -35,6 +35,26 @@ import { AiServerService } from '@services/ai-server/ai-server/ai.server.service import { Space } from '@domain/space/space/space.entity'; import { AgentInfo } from '@core/authentication.agent.info/agent.info'; import { IUser } from '@domain/community/user/user.interface'; +import { TemplatesSetService } from '@domain/template/templates-set/templates.set.service'; +import { TemplateDefaultService } from '@domain/template/template-default/template.default.service'; +import { TemplatesManagerService } from '@domain/template/templates-manager/templates.manager.service'; +import { TemplateDefaultType } from '@common/enums/template.default.type'; +import { TemplateType } from '@common/enums/template.type'; +import { bootstrapSubspaceKnowledgeInnovationFlowStates } from './platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.innovation.flow.states'; +import { bootstrapSubspaceKnowledgeCallouts } from './platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.callouts'; +import { bootstrapSubspaceKnowledgeCalloutGroups } from './platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.callout.groups'; +import { ITemplateDefault } from '@domain/template/template-default/template.default.interface'; +import { ITemplatesSet } from '@domain/template/templates-set'; +import { IInnovationFlowState } from '@domain/collaboration/innovation-flow-states/innovation.flow.state.interface'; +import { bootstrapSubspaceInnovationFlowStates } from './platform-template-definitions/subspace/bootstrap.subspace.innovation.flow.states'; +import { bootstrapSubspaceCalloutGroups } from './platform-template-definitions/subspace/bootstrap.subspace.callout.groups'; +import { bootstrapSubspaceCallouts } from './platform-template-definitions/subspace/bootstrap.subspace.callouts'; +import { bootstrapSpaceInnovationFlowStates } from './platform-template-definitions/space/bootstrap.space.innovation.flow'; +import { bootstrapSpaceCalloutGroups } from './platform-template-definitions/space/bootstrap.space.callout.groups'; +import { bootstrapSpaceCallouts } from './platform-template-definitions/space/bootstrap.space.callouts'; +import { bootstrapSpaceTutorialsInnovationFlowStates } from './platform-template-definitions/space-tutorials/bootstrap.space.tutorials.innovation.flow.states'; +import { bootstrapSpaceTutorialsCalloutGroups } from './platform-template-definitions/space-tutorials/bootstrap.space.tutorials.callout.groups'; +import { bootstrapSpaceTutorialsCallouts } from './platform-template-definitions/space-tutorials/bootstrap.space.tutorials.callouts'; @Injectable() export class BootstrapService { @@ -60,7 +80,10 @@ export class BootstrapService { @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService, private aiServer: AiServerService, - private aiServerAuthorizationService: AiServerAuthorizationService + private aiServerAuthorizationService: AiServerAuthorizationService, + private templatesManagerService: TemplatesManagerService, + private templatesSetService: TemplatesSetService, + private templateDefaultService: TemplateDefaultService ) {} async bootstrap() { @@ -78,12 +101,13 @@ export class BootstrapService { } await this.bootstrapUserProfiles(); + await this.platformService.ensureForumCreated(); + await this.ensureAuthorizationsPopulated(); + await this.ensurePlatformTemplatesArePresent(); await this.ensureOrganizationSingleton(); await this.ensureSpaceSingleton(); await this.ensureSsiPopulated(); - await this.platformService.ensureForumCreated(); // reset auth as last in the actions - await this.ensureAuthorizationsPopulated(); // await this.ensureSpaceNamesInElastic(); } catch (error: any) { this.logger.error( @@ -95,6 +119,118 @@ export class BootstrapService { } } + private async ensurePlatformTemplatesArePresent() { + const templatesManager = + await this.platformService.getTemplatesManagerOrFail(); + const templateDefaults = + await this.templatesManagerService.getTemplateDefaults( + templatesManager.id + ); + const templatesSet = + await this.templatesManagerService.getTemplatesSetOrFail( + templatesManager.id + ); + let authResetNeeded = await this.ensureSubspaceKnowledgeTemplatesArePresent( + templateDefaults, + TemplateDefaultType.PLATFORM_SPACE, + templatesSet, + 'space', + bootstrapSpaceInnovationFlowStates, + bootstrapSpaceCalloutGroups, + bootstrapSpaceCallouts + ); + authResetNeeded = + (await this.ensureSubspaceKnowledgeTemplatesArePresent( + templateDefaults, + TemplateDefaultType.PLATFORM_SPACE_TUTORIALS, + templatesSet, + 'space', + bootstrapSpaceTutorialsInnovationFlowStates, + bootstrapSpaceTutorialsCalloutGroups, + bootstrapSpaceTutorialsCallouts + )) || authResetNeeded; + authResetNeeded = + (await this.ensureSubspaceKnowledgeTemplatesArePresent( + templateDefaults, + TemplateDefaultType.PLATFORM_SUBSPACE_KNOWLEDGE, + templatesSet, + 'knowledge', + bootstrapSubspaceKnowledgeInnovationFlowStates, + bootstrapSubspaceKnowledgeCalloutGroups, + bootstrapSubspaceKnowledgeCallouts + )) || authResetNeeded; + authResetNeeded = + (await this.ensureSubspaceKnowledgeTemplatesArePresent( + templateDefaults, + TemplateDefaultType.PLATFORM_SUBSPACE, + templatesSet, + 'challenge', + bootstrapSubspaceInnovationFlowStates, + bootstrapSubspaceCalloutGroups, + bootstrapSubspaceCallouts + )) || authResetNeeded; + if (authResetNeeded) { + this.logger.verbose?.( + '=== Identified that template defaults had not been reset; resetting auth now ===', + LogContext.BOOTSTRAP + ); + const updatedAuthorizations = + await this.platformAuthorizationService.applyAuthorizationPolicy(); + await this.authorizationPolicyService.saveAll(updatedAuthorizations); + } + } + + private async ensureSubspaceKnowledgeTemplatesArePresent( + templateDefaults: ITemplateDefault[], + templateDefaultType: TemplateDefaultType, + templatesSet: ITemplatesSet, + nameID: string, + flowStates: IInnovationFlowState[], + calloutGroups: any[], + callouts: any[] + ): Promise { + const knowledgeTemplateDefault = templateDefaults.find( + td => td.type === templateDefaultType + ); + if (!knowledgeTemplateDefault) { + throw new BootstrapException( + `Unable to load Template Default for ${templateDefaultType}` + ); + } + if (!knowledgeTemplateDefault.template) { + this.logger.verbose?.( + `No template set for ${templateDefaultType}, setting it...`, + LogContext.BOOTSTRAP + ); + // No template set, so create one and then set it + const template = await this.templatesSetService.createTemplate( + templatesSet, + { + profileData: { + displayName: `${nameID} Template`, + }, + type: TemplateType.COLLABORATION, + collaborationData: { + innovationFlowData: { + profile: { + displayName: `${nameID} Innovation Flow`, + }, + states: flowStates, + }, + calloutGroups: calloutGroups, + calloutsData: callouts, + defaultCalloutGroupName: calloutGroups[0].displayName, + }, + } + ); + // Set the default template + knowledgeTemplateDefault.template = template; + await this.templateDefaultService.save(knowledgeTemplateDefault); + return true; + } + return false; + } + async bootstrapUserProfiles() { const bootstrapAuthorizationEnabled = this.configService.get( 'bootstrap.authorization.enabled', @@ -235,7 +371,7 @@ export class BootstrapService { } } - async ensureAuthorizationsPopulated() { + private async ensureAuthorizationsPopulated() { // For platform const platform = await this.platformService.getPlatformOrFail(); const platformAuthorization = diff --git a/src/core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.callout.groups.ts b/src/core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.callout.groups.ts new file mode 100644 index 0000000000..f5da8418d1 --- /dev/null +++ b/src/core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.callout.groups.ts @@ -0,0 +1,21 @@ +import { CalloutGroupName } from '@common/enums/callout.group.name'; +import { ICalloutGroup } from '@domain/collaboration/callout-groups/callout.group.interface'; + +export const bootstrapSpaceTutorialsCalloutGroups: ICalloutGroup[] = [ + { + displayName: CalloutGroupName.HOME, + description: 'The Home page.', + }, + { + displayName: CalloutGroupName.COMMUNITY, + description: 'The Community page.', + }, + { + displayName: CalloutGroupName.SUBSPACES, + description: 'The Subspaces page.', + }, + { + displayName: CalloutGroupName.KNOWLEDGE, + description: 'The knowledge page.', + }, +]; diff --git a/src/domain/space/space.defaults/definitions/root-space/space.defaults.callouts.root.space.ts b/src/core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.callouts.ts similarity index 94% rename from src/domain/space/space.defaults/definitions/root-space/space.defaults.callouts.root.space.ts rename to src/core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.callouts.ts index dd6336f67a..c6fb25777a 100644 --- a/src/domain/space/space.defaults/definitions/root-space/space.defaults.callouts.root.space.ts +++ b/src/core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.callouts.ts @@ -3,10 +3,10 @@ import { CalloutState } from '@common/enums/callout.state'; import { CalloutType } from '@common/enums/callout.type'; import { CalloutGroupName } from '@common/enums/callout.group.name'; import { TagsetReservedName } from '@common/enums/tagset.reserved.name'; -import { FlowState } from './space.defaults.innovation.flow.root.space'; +import { FlowState } from './bootstrap.space.tutorials.innovation.flow.states'; import { CreateCalloutInput } from '@domain/collaboration/callout/dto/callout.dto.create'; -export const spaceDefaultsCalloutsRootSpace: CreateCalloutInput[] = [ +export const bootstrapSpaceTutorialsCallouts: CreateCalloutInput[] = [ { nameID: 'welcome', type: CalloutType.POST, @@ -23,7 +23,7 @@ export const spaceDefaultsCalloutsRootSpace: CreateCalloutInput[] = [ tagsets: [ { name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.NOT_USED], + tags: [FlowState.HOME], }, ], }, @@ -45,7 +45,7 @@ export const spaceDefaultsCalloutsRootSpace: CreateCalloutInput[] = [ tagsets: [ { name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.NOT_USED], + tags: [FlowState.HOME], }, ], }, @@ -67,7 +67,7 @@ export const spaceDefaultsCalloutsRootSpace: CreateCalloutInput[] = [ tagsets: [ { name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.NOT_USED], + tags: [FlowState.HOME], }, ], }, @@ -89,7 +89,7 @@ export const spaceDefaultsCalloutsRootSpace: CreateCalloutInput[] = [ tagsets: [ { name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.NOT_USED], + tags: [FlowState.HOME], }, ], }, @@ -111,7 +111,7 @@ export const spaceDefaultsCalloutsRootSpace: CreateCalloutInput[] = [ tagsets: [ { name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.NOT_USED], + tags: [FlowState.HOME], }, ], }, @@ -133,7 +133,7 @@ export const spaceDefaultsCalloutsRootSpace: CreateCalloutInput[] = [ tagsets: [ { name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.NOT_USED], + tags: [FlowState.HOME], }, ], }, @@ -155,7 +155,7 @@ export const spaceDefaultsCalloutsRootSpace: CreateCalloutInput[] = [ tagsets: [ { name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.NOT_USED], + tags: [FlowState.HOME], }, ], }, diff --git a/src/domain/space/space.defaults/definitions/root-space/space.defaults.innovation.flow.root.space.ts b/src/core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.innovation.flow.states.ts similarity index 56% rename from src/domain/space/space.defaults/definitions/root-space/space.defaults.innovation.flow.root.space.ts rename to src/core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.innovation.flow.states.ts index 5224a98ed0..c078a2f38b 100644 --- a/src/domain/space/space.defaults/definitions/root-space/space.defaults.innovation.flow.root.space.ts +++ b/src/core/bootstrap/platform-template-definitions/space-tutorials/bootstrap.space.tutorials.innovation.flow.states.ts @@ -1,13 +1,16 @@ import { IInnovationFlowState } from '@domain/collaboration/innovation-flow-states/innovation.flow.state.interface'; export enum FlowState { - NOT_USED = 'Not used', + HOME = 'Home', + COMMUNITY = 'Community', + SUBSPACES = 'Subspaces', + KNOWLEDGE = 'Knowledge', } -export const spaceDefaultsInnovationFlowStatesRootSpace: IInnovationFlowState[] = +export const bootstrapSpaceTutorialsInnovationFlowStates: IInnovationFlowState[] = [ { - displayName: FlowState.NOT_USED, + displayName: FlowState.HOME, description: '🔍 A journey of discovery! Gather insights through research and observation.', }, diff --git a/src/domain/space/space.defaults/definitions/root-space/space.defaults.callout.groups.root.space.ts b/src/core/bootstrap/platform-template-definitions/space/bootstrap.space.callout.groups.ts similarity index 88% rename from src/domain/space/space.defaults/definitions/root-space/space.defaults.callout.groups.root.space.ts rename to src/core/bootstrap/platform-template-definitions/space/bootstrap.space.callout.groups.ts index c1c9d529ec..2b19810be8 100644 --- a/src/domain/space/space.defaults/definitions/root-space/space.defaults.callout.groups.root.space.ts +++ b/src/core/bootstrap/platform-template-definitions/space/bootstrap.space.callout.groups.ts @@ -1,7 +1,7 @@ import { CalloutGroupName } from '@common/enums/callout.group.name'; import { ICalloutGroup } from '@domain/collaboration/callout-groups/callout.group.interface'; -export const spaceDefaultsCalloutGroupsRootSpace: ICalloutGroup[] = [ +export const bootstrapSpaceCalloutGroups: ICalloutGroup[] = [ { displayName: CalloutGroupName.HOME, description: 'The Home page.', diff --git a/src/domain/space/space.defaults/definitions/blank-slate/space.defaults.callouts.blank.slate.ts b/src/core/bootstrap/platform-template-definitions/space/bootstrap.space.callouts.ts similarity index 82% rename from src/domain/space/space.defaults/definitions/blank-slate/space.defaults.callouts.blank.slate.ts rename to src/core/bootstrap/platform-template-definitions/space/bootstrap.space.callouts.ts index 16cc64a873..b607c9a6b1 100644 --- a/src/domain/space/space.defaults/definitions/blank-slate/space.defaults.callouts.blank.slate.ts +++ b/src/core/bootstrap/platform-template-definitions/space/bootstrap.space.callouts.ts @@ -3,10 +3,10 @@ import { CalloutState } from '@common/enums/callout.state'; import { CalloutType } from '@common/enums/callout.type'; import { CalloutGroupName } from '@common/enums/callout.group.name'; import { TagsetReservedName } from '@common/enums/tagset.reserved.name'; -import { FlowState } from './space.defaults.innovation.flow.blank.slate'; +import { FlowState } from './bootstrap.space.innovation.flow'; import { CreateCalloutInput } from '@domain/collaboration/callout/dto/callout.dto.create'; -export const spaceDefaultsCalloutsBlankSlate: CreateCalloutInput[] = [ +export const bootstrapSpaceCallouts: CreateCalloutInput[] = [ { nameID: 'welcome', type: CalloutType.POST, @@ -22,7 +22,7 @@ export const spaceDefaultsCalloutsBlankSlate: CreateCalloutInput[] = [ tagsets: [ { name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.PHASE_1], + tags: [FlowState.HOME], }, ], }, diff --git a/src/core/bootstrap/platform-template-definitions/space/bootstrap.space.innovation.flow.ts b/src/core/bootstrap/platform-template-definitions/space/bootstrap.space.innovation.flow.ts new file mode 100644 index 0000000000..566fd8c94b --- /dev/null +++ b/src/core/bootstrap/platform-template-definitions/space/bootstrap.space.innovation.flow.ts @@ -0,0 +1,28 @@ +import { IInnovationFlowState } from '@domain/collaboration/innovation-flow-states/innovation.flow.state.interface'; + +export enum FlowState { + HOME = 'Home', + COMMUNITY = 'Community', + SUBSPACES = 'Subspaces', + KNOWLEDGE = 'Knowledge', +} + +export const bootstrapSpaceInnovationFlowStates: IInnovationFlowState[] = [ + { + displayName: FlowState.HOME, + description: + '🔍 A journey of discovery! Gather insights through research and observation.', + }, + { + displayName: FlowState.COMMUNITY, + description: '🔍 The next phase....', + }, + { + displayName: FlowState.SUBSPACES, + description: '🔍 And another phase!', + }, + { + displayName: FlowState.KNOWLEDGE, + description: '🔍 And another phase!', + }, +]; diff --git a/src/domain/space/space.defaults/definitions/oppportunity/space.defaults.callout.groups.opportunity.ts b/src/core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.callout.groups.ts similarity index 77% rename from src/domain/space/space.defaults/definitions/oppportunity/space.defaults.callout.groups.opportunity.ts rename to src/core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.callout.groups.ts index ec01008ff5..9489b24648 100644 --- a/src/domain/space/space.defaults/definitions/oppportunity/space.defaults.callout.groups.opportunity.ts +++ b/src/core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.callout.groups.ts @@ -1,7 +1,7 @@ import { CalloutGroupName } from '@common/enums/callout.group.name'; import { ICalloutGroup } from '@domain/collaboration/callout-groups/callout.group.interface'; -export const spaceDefaultsCalloutGroupsOpportunity: ICalloutGroup[] = [ +export const bootstrapSubspaceKnowledgeCalloutGroups: ICalloutGroup[] = [ { displayName: CalloutGroupName.HOME, description: 'The Subspace Home page.', diff --git a/src/domain/space/space.defaults/definitions/knowledge/space.defaults.callouts.knowledge.ts b/src/core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.callouts.ts similarity index 97% rename from src/domain/space/space.defaults/definitions/knowledge/space.defaults.callouts.knowledge.ts rename to src/core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.callouts.ts index 82d5bb0bad..3ca25dcdff 100644 --- a/src/domain/space/space.defaults/definitions/knowledge/space.defaults.callouts.knowledge.ts +++ b/src/core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.callouts.ts @@ -2,10 +2,10 @@ import { CalloutGroupName } from '@common/enums/callout.group.name'; import { CalloutState } from '@common/enums/callout.state'; import { CalloutType } from '@common/enums/callout.type'; import { TagsetReservedName } from '@common/enums/tagset.reserved.name'; -import { FlowState } from './space.defaults.innovation.flow.knowledge'; +import { FlowState } from './bootstrap.subspace.knowledge.innovation.flow.states'; import { CreateCalloutInput } from '@domain/collaboration/callout/dto/callout.dto.create'; -export const spaceDefaultsCalloutsKnowledge: CreateCalloutInput[] = [ +export const bootstrapSubspaceKnowledgeCallouts: CreateCalloutInput[] = [ { nameID: 'summary', type: CalloutType.POST, @@ -216,7 +216,7 @@ export const spaceDefaultsCalloutsKnowledge: CreateCalloutInput[] = [ profile: { displayName: 'Where to find the Virtual Contributor Profile', description: - 'You can find the profile of your VC in your account page. \n\n1. Go to your profile (by clicking on your profile picture in the top right of your screen and selecting MY PROFILE in the dropdown menu).\n2. Go to the settings by clicking on the gear icon right of your name.\n3. Go to the ACCOUNT tab.\n Here you can see a list of all your VC\'s and go to their profile by clicking on their name.', + 'You can find the profile of your VC in your account page. \n\n1. Go to your profile (by clicking on your profile picture in the top right of your screen and selecting MY PROFILE in the dropdown menu).\n2. Go to the settings by clicking on the gear icon right of your name.\n3. Go to the ACCOUNT tab.\n Here you can see a list of all your VCs and go to their profile by clicking on their name.', tagsets: [ { name: TagsetReservedName.FLOW_STATE, diff --git a/src/domain/space/space.defaults/definitions/knowledge/space.defaults.innovation.flow.knowledge.ts b/src/core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.innovation.flow.states.ts similarity index 91% rename from src/domain/space/space.defaults/definitions/knowledge/space.defaults.innovation.flow.knowledge.ts rename to src/core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.innovation.flow.states.ts index 17ce5974d9..ab7e057487 100644 --- a/src/domain/space/space.defaults/definitions/knowledge/space.defaults.innovation.flow.knowledge.ts +++ b/src/core/bootstrap/platform-template-definitions/subspace-knowledge/bootstrap.subspace.knowledge.innovation.flow.states.ts @@ -7,7 +7,7 @@ export enum FlowState { KNOWLEDGE_UPDATES = 'Knowledge Updates', } -export const spaceDefaultsInnovationFlowStatesKnowledge: IInnovationFlowState[] = +export const bootstrapSubspaceKnowledgeInnovationFlowStates: IInnovationFlowState[] = [ { displayName: FlowState.INTRODUCTION, diff --git a/src/domain/space/space.defaults/definitions/challenge/space.defaults.callout.groups.challenge.ts b/src/core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.callout.groups.ts similarity index 78% rename from src/domain/space/space.defaults/definitions/challenge/space.defaults.callout.groups.challenge.ts rename to src/core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.callout.groups.ts index bd1e697f4e..695f7e1c17 100644 --- a/src/domain/space/space.defaults/definitions/challenge/space.defaults.callout.groups.challenge.ts +++ b/src/core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.callout.groups.ts @@ -1,7 +1,7 @@ import { CalloutGroupName } from '@common/enums/callout.group.name'; import { ICalloutGroup } from '@domain/collaboration/callout-groups/callout.group.interface'; -export const spaceDefaultsCalloutGroupsChallenge: ICalloutGroup[] = [ +export const bootstrapSubspaceCalloutGroups: ICalloutGroup[] = [ { displayName: CalloutGroupName.HOME, description: 'The Subspace Home page.', diff --git a/src/domain/space/space.defaults/definitions/challenge/space.defaults.callouts.challenge.ts b/src/core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.callouts.ts similarity index 94% rename from src/domain/space/space.defaults/definitions/challenge/space.defaults.callouts.challenge.ts rename to src/core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.callouts.ts index 454154d9d2..e0de4e64a1 100644 --- a/src/domain/space/space.defaults/definitions/challenge/space.defaults.callouts.challenge.ts +++ b/src/core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.callouts.ts @@ -2,10 +2,10 @@ import { CalloutGroupName } from '@common/enums/callout.group.name'; import { CalloutState } from '@common/enums/callout.state'; import { CalloutType } from '@common/enums/callout.type'; import { TagsetReservedName } from '@common/enums/tagset.reserved.name'; -import { FlowState } from './space.defaults.innovation.flow.challenge'; +import { FlowState } from './bootstrap.subspace.innovation.flow.states'; import { CreateCalloutInput } from '@domain/collaboration/callout/dto/callout.dto.create'; -export const spaceDefaultsCalloutsChallenge: CreateCalloutInput[] = [ +export const bootstrapSubspaceCallouts: CreateCalloutInput[] = [ { nameID: 'welcome', type: CalloutType.POST, diff --git a/src/core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.innovation.flow.states.ts b/src/core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.innovation.flow.states.ts new file mode 100644 index 0000000000..9068ec5934 --- /dev/null +++ b/src/core/bootstrap/platform-template-definitions/subspace/bootstrap.subspace.innovation.flow.states.ts @@ -0,0 +1,37 @@ +import { IInnovationFlowState } from '@domain/collaboration/innovation-flow-states/innovation.flow.state.interface'; + +export enum FlowState { + EXPLORE = 'Explore', + DEFINE = 'Define', + BRAINSTORM = 'Brainstorm', + VALIDATE = 'Validate', + EVALUATE = 'Evaluate', +} + +export const bootstrapSubspaceInnovationFlowStates: IInnovationFlowState[] = [ + { + displayName: FlowState.EXPLORE, + description: + '🔍 A journey of discovery! Gather insights through research and observation.', + }, + { + displayName: FlowState.DEFINE, + description: + '🎯 Sharpen your focus. Define the challenge with precision and set a clear direction.', + }, + { + displayName: FlowState.BRAINSTORM, + description: + '🎨 Ignite creativity. Generate a constellation of ideas, using concepts from diverse perspectives to get inspired.', + }, + { + displayName: FlowState.VALIDATE, + description: + '🛠️ Test assumptions. Build prototypes, seek feedback, and validate your concepts. Adapt based on real-world insights.', + }, + { + displayName: FlowState.EVALUATE, + description: + '✅ Assess impact, feasibility, and alignment to make informed choices.', + }, +]; diff --git a/src/domain/collaboration/callout-framing/callout.framing.resolver.fields.ts b/src/domain/collaboration/callout-framing/callout.framing.resolver.fields.ts index ff84db6bba..cbfac845ff 100644 --- a/src/domain/collaboration/callout-framing/callout.framing.resolver.fields.ts +++ b/src/domain/collaboration/callout-framing/callout.framing.resolver.fields.ts @@ -1,5 +1,5 @@ import { Parent, ResolveField, Resolver } from '@nestjs/graphql'; -import { UseGuards } from '@nestjs/common/decorators'; +import { UseGuards } from '@nestjs/common'; import { GraphqlGuard } from '@core/authorization'; import { IProfile } from '@domain/common/profile/profile.interface'; import { ICalloutFraming } from './callout.framing.interface'; diff --git a/src/domain/collaboration/callout-framing/callout.framing.service.ts b/src/domain/collaboration/callout-framing/callout.framing.service.ts index c1c647c021..faa1148aba 100644 --- a/src/domain/collaboration/callout-framing/callout.framing.service.ts +++ b/src/domain/collaboration/callout-framing/callout.framing.service.ts @@ -108,18 +108,12 @@ export class CalloutFramingService { ); } - if (calloutFraming.whiteboard && calloutFramingData.whiteboard) { - calloutFraming.whiteboard = await this.whiteboardService.updateWhiteboard( - calloutFraming.whiteboard, - calloutFramingData.whiteboard - ); - if (calloutFramingData.whiteboard.content) { - calloutFraming.whiteboard = - await this.whiteboardService.updateWhiteboardContent( - calloutFraming.whiteboard.id, - calloutFramingData.whiteboard.content - ); - } + if (calloutFraming.whiteboard && calloutFramingData.whiteboardContent) { + calloutFraming.whiteboard = + await this.whiteboardService.updateWhiteboardContent( + calloutFraming.whiteboard.id, + calloutFramingData.whiteboardContent + ); } return calloutFraming; diff --git a/src/domain/collaboration/callout-framing/dto/callout.framing.dto.update.ts b/src/domain/collaboration/callout-framing/dto/callout.framing.dto.update.ts index a637aff147..9f5085afef 100644 --- a/src/domain/collaboration/callout-framing/dto/callout.framing.dto.update.ts +++ b/src/domain/collaboration/callout-framing/dto/callout.framing.dto.update.ts @@ -2,7 +2,7 @@ import { InputType, Field } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { IsOptional, ValidateNested } from 'class-validator'; import { UpdateProfileInput } from '@domain/common/profile/dto/profile.dto.update'; -import { UpdateWhiteboardInput } from '@domain/common/whiteboard/dto/whiteboard.dto.update'; +import { WhiteboardContent } from '@domain/common/scalars/scalar.whiteboard.content'; @InputType() export class UpdateCalloutFramingInput { @@ -15,9 +15,10 @@ export class UpdateCalloutFramingInput { @Type(() => UpdateProfileInput) profile?: UpdateProfileInput; - @Field(() => UpdateWhiteboardInput, { nullable: true }) + @Field(() => WhiteboardContent, { + nullable: true, + description: 'The new content to be used.', + }) @IsOptional() - @ValidateNested() - @Type(() => UpdateWhiteboardInput) - whiteboard?: UpdateWhiteboardInput; + whiteboardContent?: string; } diff --git a/src/domain/collaboration/callout/callout.interface.ts b/src/domain/collaboration/callout/callout.interface.ts index a8c015070f..0e8175dbe2 100644 --- a/src/domain/collaboration/callout/callout.interface.ts +++ b/src/domain/collaboration/callout/callout.interface.ts @@ -67,4 +67,10 @@ export abstract class ICallout extends IAuthorizable { publishedDate?: Date; collaboration?: ICollaboration; + + @Field(() => Boolean, { + nullable: false, + description: 'Whether this callout is a Template or not.', + }) + isTemplate!: boolean; } diff --git a/src/domain/collaboration/collaboration/collaboration.entity.ts b/src/domain/collaboration/collaboration/collaboration.entity.ts index ae2ab0d456..0dde120ee1 100644 --- a/src/domain/collaboration/collaboration/collaboration.entity.ts +++ b/src/domain/collaboration/collaboration/collaboration.entity.ts @@ -17,6 +17,9 @@ export class Collaboration }) callouts?: Callout[]; + @Column({ type: 'boolean', nullable: false, default: false }) + isTemplate!: boolean; + @OneToOne(() => TagsetTemplateSet, { eager: false, cascade: true, diff --git a/src/domain/collaboration/collaboration/collaboration.interface.ts b/src/domain/collaboration/collaboration/collaboration.interface.ts index 21e4962286..6113909c6d 100644 --- a/src/domain/collaboration/collaboration/collaboration.interface.ts +++ b/src/domain/collaboration/collaboration/collaboration.interface.ts @@ -1,4 +1,4 @@ -import { ObjectType } from '@nestjs/graphql'; +import { Field, ObjectType } from '@nestjs/graphql'; import { IAuthorizable } from '@domain/common/entity/authorizable-entity'; import { ICallout } from '@domain/collaboration/callout/callout.interface'; import { ITagsetTemplateSet } from '@domain/common/tagset-template-set'; @@ -16,4 +16,10 @@ export abstract class ICollaboration extends IAuthorizable { innovationFlow?: IInnovationFlow; groupsStr!: string; + + @Field(() => Boolean, { + nullable: false, + description: 'Whether this Collaboration is a Template or not.', + }) + isTemplate!: boolean; } diff --git a/src/domain/collaboration/collaboration/collaboration.module.ts b/src/domain/collaboration/collaboration/collaboration.module.ts index e560dd9f9c..e478afe5bd 100644 --- a/src/domain/collaboration/collaboration/collaboration.module.ts +++ b/src/domain/collaboration/collaboration/collaboration.module.ts @@ -19,7 +19,6 @@ import { TagsetTemplateSetModule } from '@domain/common/tagset-template-set/tags import { TimelineModule } from '@domain/timeline/timeline/timeline.module'; import { StorageAggregatorResolverModule } from '@services/infrastructure/storage-aggregator-resolver/storage.aggregator.resolver.module'; import { InnovationFlowModule } from '../innovation-flow/innovation.flow.module'; -import { SpaceDefaultsModule } from '@domain/space/space.defaults/space.defaults.module'; import { CalloutGroupsModule } from '../callout-groups/callout.group.module'; import { LicenseEngineModule } from '@core/license-engine/license.engine.module'; import { RoleSetModule } from '@domain/access/role-set/role.set.module'; @@ -42,7 +41,6 @@ import { TemporaryStorageModule } from '@services/infrastructure/temporary-stora TimelineModule, TagsetTemplateSetModule, InnovationFlowModule, - SpaceDefaultsModule, CalloutGroupsModule, LicenseEngineModule, TemporaryStorageModule, diff --git a/src/domain/collaboration/collaboration/collaboration.resolver.fields.ts b/src/domain/collaboration/collaboration/collaboration.resolver.fields.ts index 944e4264f4..d5524de8f9 100644 --- a/src/domain/collaboration/collaboration/collaboration.resolver.fields.ts +++ b/src/domain/collaboration/collaboration/collaboration.resolver.fields.ts @@ -5,7 +5,7 @@ import { Profiling, } from '@src/common/decorators'; import { AuthorizationPrivilege } from '@common/enums'; -import { UseGuards } from '@nestjs/common/decorators'; +import { UseGuards } from '@nestjs/common'; import { GraphqlGuard } from '@core/authorization'; import { Collaboration } from '@domain/collaboration/collaboration/collaboration.entity'; import { ICollaboration } from '@domain/collaboration/collaboration/collaboration.interface'; diff --git a/src/domain/collaboration/collaboration/collaboration.service.ts b/src/domain/collaboration/collaboration/collaboration.service.ts index 9e81a0705b..9a7afa87f7 100644 --- a/src/domain/collaboration/collaboration/collaboration.service.ts +++ b/src/domain/collaboration/collaboration/collaboration.service.ts @@ -37,11 +37,11 @@ import { CreateCalloutInput } from '../callout/dto/callout.dto.create'; import { TagsetReservedName } from '@common/enums/tagset.reserved.name'; import { TimelineService } from '@domain/timeline/timeline/timeline.service'; import { ITimeline } from '@domain/timeline/timeline/timeline.interface'; -import { keyBy } from 'lodash'; +import { compact, keyBy } from 'lodash'; import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface'; import { StorageAggregatorResolverService } from '@services/infrastructure/storage-aggregator-resolver/storage.aggregator.resolver.service'; import { CalloutType } from '@common/enums/callout.type'; -import { InnovationFlowService } from '../innovation-flow/innovaton.flow.service'; +import { InnovationFlowService } from '../innovation-flow/innovation.flow.service'; import { TagsetType } from '@common/enums/tagset.type'; import { IInnovationFlow } from '../innovation-flow/innovation.flow.interface'; import { CreateCollaborationInput } from './dto/collaboration.dto.create'; @@ -98,6 +98,7 @@ export class CollaborationService { collaboration.groupsStr = this.calloutGroupsService.serializeGroups( collaborationData.calloutGroups ); + collaboration.isTemplate = collaborationData.isTemplate || false; collaboration.tagsetTemplateSet = this.tagsetTemplateSetService.createTagsetTemplateSet(); @@ -186,7 +187,7 @@ export class CollaborationService { return tagsetTemplateDataStates; } - private async addCallouts( + public async addCallouts( collaboration: ICollaboration, calloutsData: CreateCalloutInput[], storageAggregator: IStorageAggregator, @@ -198,9 +199,23 @@ export class CollaborationService { LogContext.COLLABORATION ); } + const calloutNameIds: string[] = compact( + collaboration.callouts?.map(callout => callout.nameID) + ); const callouts: ICallout[] = []; for (const calloutDefault of calloutsData) { + if ( + !calloutDefault.nameID || + calloutNameIds.includes(calloutDefault.nameID) + ) { + calloutDefault.nameID = + this.namingService.createNameIdAvoidingReservedNameIDs( + calloutDefault.framing.profile.displayName, + calloutNameIds + ); + calloutNameIds.push(calloutDefault.nameID); + } const callout = await this.calloutService.createCallout( calloutDefault, collaboration.tagsetTemplateSet.tagsetTemplates, @@ -755,4 +770,71 @@ export class CollaborationService { return calloutsInOrder; } + + /** + * Move callouts that are not in valid groups or flowStates to the default group & first flowState + * @param defaultGroupName + * @param defaultFlowStateName + * @param callouts + */ + public moveCalloutsToCorrectGroupAndState( + defaultGroupName: string | undefined, + defaultFlowStateName: string | undefined, + validGroupNames: string[], + validFlowStateNames: string[], + callouts: { + framing: { + profile: { + tagsets?: { + name: string; + type?: TagsetType; + tags?: string[]; + }[]; + }; + }; + }[] + ): void { + for (const callout of callouts) { + if (!callout.framing.profile.tagsets) { + callout.framing.profile.tagsets = []; + } + let calloutGroupTagset = callout.framing.profile.tagsets?.find( + tagset => tagset.name === TagsetReservedName.CALLOUT_GROUP + ); + let flowStateTagset = callout.framing.profile.tagsets?.find( + tagset => tagset.name === TagsetReservedName.FLOW_STATE + ); + + if (defaultGroupName) { + if (!calloutGroupTagset) { + calloutGroupTagset = { + name: TagsetReservedName.CALLOUT_GROUP, + type: TagsetType.SELECT_ONE, + tags: [defaultGroupName], + }; + callout.framing.profile.tagsets.push(calloutGroupTagset); + } else { + const calloutGroup = calloutGroupTagset.tags?.[0]; + if (!calloutGroup || !validGroupNames.includes(calloutGroup)) { + calloutGroupTagset.tags = [defaultGroupName]; + } + } + } + if (defaultFlowStateName) { + if (!flowStateTagset) { + flowStateTagset = { + name: TagsetReservedName.FLOW_STATE, + type: TagsetType.SELECT_ONE, + tags: [defaultFlowStateName], + }; + callout.framing.profile.tagsets.push(flowStateTagset); + } else { + const flowState = flowStateTagset.tags?.[0]; + if (!flowState || !validFlowStateNames.includes(flowState)) { + flowStateTagset.tags = [defaultFlowStateName]; + } + } + } + } + } } diff --git a/src/domain/collaboration/collaboration/dto/collaboration.dto.create.ts b/src/domain/collaboration/collaboration/dto/collaboration.dto.create.ts index af2d15b0a3..7ce9a308c3 100644 --- a/src/domain/collaboration/collaboration/dto/collaboration.dto.create.ts +++ b/src/domain/collaboration/collaboration/dto/collaboration.dto.create.ts @@ -30,4 +30,6 @@ export class CreateCollaborationInput { calloutGroups?: ICalloutGroup[]; defaultCalloutGroupName?: CalloutGroupName; + + isTemplate?: boolean; } diff --git a/src/domain/collaboration/innovation-flow-states/innovation.flow.state.module.ts b/src/domain/collaboration/innovation-flow-states/innovation.flow.state.module.ts index 7c0b1c6ed6..9bc66a18a7 100644 --- a/src/domain/collaboration/innovation-flow-states/innovation.flow.state.module.ts +++ b/src/domain/collaboration/innovation-flow-states/innovation.flow.state.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { InnovationFlowStatesService } from './innovaton.flow.state.service'; +import { InnovationFlowStatesService } from './innovation.flow.state.service'; @Module({ imports: [], diff --git a/src/domain/collaboration/innovation-flow-states/innovaton.flow.state.service.ts b/src/domain/collaboration/innovation-flow-states/innovation.flow.state.service.ts similarity index 100% rename from src/domain/collaboration/innovation-flow-states/innovaton.flow.state.service.ts rename to src/domain/collaboration/innovation-flow-states/innovation.flow.state.service.ts diff --git a/src/domain/collaboration/innovation-flow/innovation.flow.module.ts b/src/domain/collaboration/innovation-flow/innovation.flow.module.ts index e25529ead9..e3df4ba889 100644 --- a/src/domain/collaboration/innovation-flow/innovation.flow.module.ts +++ b/src/domain/collaboration/innovation-flow/innovation.flow.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { InnovationFlow } from './innovation.flow.entity'; -import { InnovationFlowService } from './innovaton.flow.service'; +import { InnovationFlowService } from './innovation.flow.service'; import { InnovationFlowAuthorizationService } from './innovation.flow.service.authorization'; import { InnovationFlowResolverFields } from './innovation.flow.resolver.fields'; import { InnovationFlowResolverMutations } from './innovation.flow.resolver.mutations'; diff --git a/src/domain/collaboration/innovation-flow/innovation.flow.resolver.fields.ts b/src/domain/collaboration/innovation-flow/innovation.flow.resolver.fields.ts index b40aaef558..f0e9530b31 100644 --- a/src/domain/collaboration/innovation-flow/innovation.flow.resolver.fields.ts +++ b/src/domain/collaboration/innovation-flow/innovation.flow.resolver.fields.ts @@ -8,7 +8,7 @@ import { ProfileLoaderCreator } from '@core/dataloader/creators'; import { Loader } from '@core/dataloader/decorators'; import { ILoader } from '@core/dataloader/loader.interface'; import { InnovationFlow } from './innovation.flow.entity'; -import { InnovationFlowService } from './innovaton.flow.service'; +import { InnovationFlowService } from './innovation.flow.service'; import { IInnovationFlowState } from '../innovation-flow-states/innovation.flow.state.interface'; @Resolver(() => IInnovationFlow) diff --git a/src/domain/collaboration/innovation-flow/innovation.flow.resolver.mutations.ts b/src/domain/collaboration/innovation-flow/innovation.flow.resolver.mutations.ts index a98374f8d8..5dfc9de4ee 100644 --- a/src/domain/collaboration/innovation-flow/innovation.flow.resolver.mutations.ts +++ b/src/domain/collaboration/innovation-flow/innovation.flow.resolver.mutations.ts @@ -1,7 +1,7 @@ import { UseGuards } from '@nestjs/common'; import { Resolver, Args, Mutation } from '@nestjs/graphql'; import { CurrentUser } from '@src/common/decorators'; -import { InnovationFlowService } from './innovaton.flow.service'; +import { InnovationFlowService } from './innovation.flow.service'; import { GraphqlGuard } from '@core/authorization'; import { AgentInfo } from '@core/authentication.agent.info/agent.info'; import { AuthorizationService } from '@core/authorization/authorization.service'; diff --git a/src/domain/collaboration/innovation-flow/innovation.flow.service.spec.ts b/src/domain/collaboration/innovation-flow/innovation.flow.service.spec.ts index 72f213a9e3..724f58f9f1 100644 --- a/src/domain/collaboration/innovation-flow/innovation.flow.service.spec.ts +++ b/src/domain/collaboration/innovation-flow/innovation.flow.service.spec.ts @@ -1,5 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { InnovationFlowService } from './innovaton.flow.service'; +import { InnovationFlowService } from './innovation.flow.service'; import { MockCacheManager } from '@test/mocks/cache-manager.mock'; import { MockWinstonProvider } from '@test/mocks/winston.provider.mock'; import { InnovationFlow } from './innovation.flow.entity'; diff --git a/src/domain/collaboration/innovation-flow/innovaton.flow.service.ts b/src/domain/collaboration/innovation-flow/innovation.flow.service.ts similarity index 99% rename from src/domain/collaboration/innovation-flow/innovaton.flow.service.ts rename to src/domain/collaboration/innovation-flow/innovation.flow.service.ts index 96fc6116af..5826a02eda 100644 --- a/src/domain/collaboration/innovation-flow/innovaton.flow.service.ts +++ b/src/domain/collaboration/innovation-flow/innovation.flow.service.ts @@ -22,7 +22,7 @@ import { ITagsetTemplate } from '@domain/common/tagset-template/tagset.template. import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface'; import { UpdateInnovationFlowSelectedStateInput } from './dto/innovation.flow.dto.update.selected.state'; import { UpdateProfileSelectTagsetValueInput } from '@domain/common/profile/dto/profile.dto.update.select.tagset.value'; -import { InnovationFlowStatesService } from '../innovation-flow-states/innovaton.flow.state.service'; +import { InnovationFlowStatesService } from '../innovation-flow-states/innovation.flow.state.service'; import { IInnovationFlowState } from '../innovation-flow-states/innovation.flow.state.interface'; import { TagsetService } from '@domain/common/tagset/tagset.service'; import { UpdateInnovationFlowSingleStateInput } from './dto/innovation.flow.dto.update.single.state'; diff --git a/src/domain/collaboration/link/link.resolver.fields.ts b/src/domain/collaboration/link/link.resolver.fields.ts index f01519329c..24e7f7806b 100644 --- a/src/domain/collaboration/link/link.resolver.fields.ts +++ b/src/domain/collaboration/link/link.resolver.fields.ts @@ -1,5 +1,5 @@ import { Parent, ResolveField, Resolver } from '@nestjs/graphql'; -import { UseGuards } from '@nestjs/common/decorators'; +import { UseGuards } from '@nestjs/common'; import { GraphqlGuard } from '@core/authorization'; import { IProfile } from '@domain/common/profile/profile.interface'; import { ILink } from './link.interface'; diff --git a/src/domain/common/profile/profile.resolver.fields.ts b/src/domain/common/profile/profile.resolver.fields.ts index fb5b9ce5cd..a1f44c6da4 100644 --- a/src/domain/common/profile/profile.resolver.fields.ts +++ b/src/domain/common/profile/profile.resolver.fields.ts @@ -1,7 +1,7 @@ import { CurrentUser, Profiling } from '@common/decorators'; import { Args, Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { IVisual } from '@domain/common/visual/visual.interface'; -import { UseGuards } from '@nestjs/common/decorators/core/use-guards.decorator'; +import { UseGuards } from '@nestjs/common'; import { GraphqlGuard } from '@core/authorization/graphql.guard'; import { IReference } from '@domain/common/reference/reference.interface'; import { ITagset } from '@domain/common/tagset/tagset.interface'; diff --git a/src/domain/common/whiteboard/dto/whiteboard.dto.update.entity.ts b/src/domain/common/whiteboard/dto/whiteboard.dto.update.entity.ts index d74c5eb637..7e0a5c16a3 100644 --- a/src/domain/common/whiteboard/dto/whiteboard.dto.update.entity.ts +++ b/src/domain/common/whiteboard/dto/whiteboard.dto.update.entity.ts @@ -1,13 +1,9 @@ import { UUID } from '@domain/common/scalars/scalar.uuid'; -import { InputType, Field, OmitType } from '@nestjs/graphql'; +import { InputType, Field } from '@nestjs/graphql'; import { UpdateWhiteboardInput } from './whiteboard.dto.update'; @InputType() -// omit the content from this input type -export class UpdateWhiteboardEntityInput extends OmitType( - UpdateWhiteboardInput, - ['content'] -) { +export class UpdateWhiteboardEntityInput extends UpdateWhiteboardInput { @Field(() => UUID, { nullable: false }) ID!: string; } diff --git a/src/domain/common/whiteboard/dto/whiteboard.dto.update.ts b/src/domain/common/whiteboard/dto/whiteboard.dto.update.ts index 5e33eea28d..7f2ecb4238 100644 --- a/src/domain/common/whiteboard/dto/whiteboard.dto.update.ts +++ b/src/domain/common/whiteboard/dto/whiteboard.dto.update.ts @@ -2,7 +2,6 @@ import { SMALL_TEXT_LENGTH } from '@common/constants'; import { ContentUpdatePolicy } from '@common/enums/content.update.policy'; import { UpdateProfileInput } from '@domain/common/profile/dto/profile.dto.update'; import { NameID } from '@domain/common/scalars/scalar.nameid'; -import { WhiteboardContent } from '@domain/common/scalars/scalar.whiteboard.content'; import { Field, InputType } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { IsOptional, MaxLength, ValidateNested } from 'class-validator'; @@ -30,10 +29,6 @@ export class UpdateWhiteboardInput { @Type(() => UpdateProfileInput) profile?: UpdateProfileInput; - @Field(() => WhiteboardContent, { - nullable: true, - description: 'The new content to be used.', - }) - @IsOptional() - content?: string; + // Don't update whiteboard's content from here. + // Whiteboards are now updated through the whiteboard-collaboration-service } diff --git a/src/domain/common/whiteboard/whiteboard.resolver.fields.ts b/src/domain/common/whiteboard/whiteboard.resolver.fields.ts index 5e62e872f0..8e2a395b74 100644 --- a/src/domain/common/whiteboard/whiteboard.resolver.fields.ts +++ b/src/domain/common/whiteboard/whiteboard.resolver.fields.ts @@ -2,7 +2,7 @@ import { Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { Inject, LoggerService } from '@nestjs/common'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; import { Profiling } from '@src/common/decorators'; -import { UseGuards } from '@nestjs/common/decorators/core/use-guards.decorator'; +import { UseGuards } from '@nestjs/common'; import { GraphqlGuard } from '@src/core/authorization/graphql.guard'; import { IUser } from '@domain/community/user/user.interface'; import { LogContext } from '@common/enums/logging.context'; diff --git a/src/domain/community/user-group/user-group.resolver.fields.ts b/src/domain/community/user-group/user-group.resolver.fields.ts index fb50dbbc18..61d788afea 100644 --- a/src/domain/community/user-group/user-group.resolver.fields.ts +++ b/src/domain/community/user-group/user-group.resolver.fields.ts @@ -6,7 +6,7 @@ import { IUser } from '@domain/community/user/user.interface'; import { UserGroup, IUserGroup } from '@domain/community/user-group'; import { IGroupable } from '@domain/common/interfaces/groupable.interface'; import { AuthorizationPrivilege } from '@common/enums'; -import { UseGuards } from '@nestjs/common/decorators'; +import { UseGuards } from '@nestjs/common'; import { GraphqlGuard } from '@core/authorization'; @Resolver(() => IUserGroup) diff --git a/src/domain/space/account/account.resolver.mutations.ts b/src/domain/space/account/account.resolver.mutations.ts index 60db1ffe0b..edda44bbd5 100644 --- a/src/domain/space/account/account.resolver.mutations.ts +++ b/src/domain/space/account/account.resolver.mutations.ts @@ -25,7 +25,7 @@ import { InnovationHubAuthorizationService } from '@domain/innovation-hub/innova import { IInnovationPack } from '@library/innovation-pack/innovation.pack.interface'; import { CreateInnovationPackOnAccountInput } from './dto/account.dto.create.innovation.pack'; import { InnovationPackAuthorizationService } from '@library/innovation-pack/innovation.pack.service.authorization'; -import { InnovationPackService } from '@library/innovation-pack/innovaton.pack.service'; +import { InnovationPackService } from '@library/innovation-pack/innovation.pack.service'; import { SpaceAuthorizationService } from '../space/space.service.authorization'; import { ISpace } from '../space/space.interface'; import { diff --git a/src/domain/space/account/account.service.ts b/src/domain/space/account/account.service.ts index c2bf214646..9a372fb294 100644 --- a/src/domain/space/account/account.service.ts +++ b/src/domain/space/account/account.service.ts @@ -24,7 +24,7 @@ import { CreateInnovationHubOnAccountInput } from './dto/account.dto.create.inno import { IInnovationHub } from '@domain/innovation-hub/innovation.hub.interface'; import { InnovationHubService } from '@domain/innovation-hub/innovation.hub.service'; import { SpaceLevel } from '@common/enums/space.level'; -import { InnovationPackService } from '@library/innovation-pack/innovaton.pack.service'; +import { InnovationPackService } from '@library/innovation-pack/innovation.pack.service'; import { CreateInnovationPackOnAccountInput } from './dto/account.dto.create.innovation.pack'; import { IInnovationPack } from '@library/innovation-pack/innovation.pack.interface'; import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface'; @@ -93,11 +93,7 @@ export class AccountService { spaceData.level = SpaceLevel.SPACE; spaceData.storageAggregatorParent = account.storageAggregator; - let space = await this.spaceService.createSpace( - spaceData, - undefined, - agentInfo - ); + let space = await this.spaceService.createSpace(spaceData, agentInfo); space.account = account; space = await this.spaceService.save(space); diff --git a/src/domain/space/space.defaults/definitions/blank-slate/space.defaults.callout.groups.blank.slate.ts b/src/domain/space/space.defaults/definitions/blank-slate/space.defaults.callout.groups.blank.slate.ts deleted file mode 100644 index 3c15d94771..0000000000 --- a/src/domain/space/space.defaults/definitions/blank-slate/space.defaults.callout.groups.blank.slate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { CalloutGroupName } from '@common/enums/callout.group.name'; -import { ICalloutGroup } from '@domain/collaboration/callout-groups/callout.group.interface'; - -export const spaceDefaultsCalloutGroupsBlankSlate: ICalloutGroup[] = [ - { - displayName: CalloutGroupName.HOME, - description: 'The Home page.', - }, -]; diff --git a/src/domain/space/space.defaults/definitions/blank-slate/space.defaults.innovation.flow.blank.slate.ts b/src/domain/space/space.defaults/definitions/blank-slate/space.defaults.innovation.flow.blank.slate.ts deleted file mode 100644 index aff2fea29a..0000000000 --- a/src/domain/space/space.defaults/definitions/blank-slate/space.defaults.innovation.flow.blank.slate.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { IInnovationFlowState } from '@domain/collaboration/innovation-flow-states/innovation.flow.state.interface'; - -export enum FlowState { - PHASE_1 = 'Phase 1', - PHASE_2 = 'Phase 2', - PHASE_3 = 'Phase 3', -} - -export const spaceDefaultsInnovationFlowStatesBlankSlate: IInnovationFlowState[] = - [ - { - displayName: FlowState.PHASE_1, - description: - '🔍 A journey of discovery! Gather insights through research and observation.', - }, - { - displayName: FlowState.PHASE_2, - description: '🔍 The next phase....', - }, - { - displayName: FlowState.PHASE_3, - description: '🔍 And another phase!', - }, - ]; diff --git a/src/domain/space/space.defaults/definitions/challenge/space.defaults.innovation.flow.challenge.ts b/src/domain/space/space.defaults/definitions/challenge/space.defaults.innovation.flow.challenge.ts deleted file mode 100644 index e826e5bc8e..0000000000 --- a/src/domain/space/space.defaults/definitions/challenge/space.defaults.innovation.flow.challenge.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { IInnovationFlowState } from '@domain/collaboration/innovation-flow-states/innovation.flow.state.interface'; - -export enum FlowState { - EXPLORE = 'Explore', - DEFINE = 'Define', - BRAINSTORM = 'Brainstorm', - VALIDATE = 'Validate', - EVALUATE = 'Evaluate', -} - -export const spaceDefaultsInnovationFlowStatesChallenge: IInnovationFlowState[] = - [ - { - displayName: FlowState.EXPLORE, - description: - '🔍 A journey of discovery! Gather insights through research and observation.', - }, - { - displayName: FlowState.DEFINE, - description: - '🎯 Sharpen your focus. Define the challenge with precision and set a clear direction.', - }, - { - displayName: FlowState.BRAINSTORM, - description: - '🎨 Ignite creativity. Generate a constellation of ideas, using concepts from diverse perspectives to get inspired.', - }, - { - displayName: FlowState.VALIDATE, - description: - '🛠️ Test assumptions. Build prototypes, seek feedback, and validate your concepts. Adapt based on real-world insights.', - }, - { - displayName: FlowState.EVALUATE, - description: - '✅ Assess impact, feasibility, and alignment to make informed choices.', - }, - ]; diff --git a/src/domain/space/space.defaults/definitions/knowledge/space.defaults.callout.groups.knowledge.ts b/src/domain/space/space.defaults/definitions/knowledge/space.defaults.callout.groups.knowledge.ts deleted file mode 100644 index 4e991042ed..0000000000 --- a/src/domain/space/space.defaults/definitions/knowledge/space.defaults.callout.groups.knowledge.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { CalloutGroupName } from '@common/enums/callout.group.name'; -import { ICalloutGroup } from '@domain/collaboration/callout-groups/callout.group.interface'; - -export const spaceDefaultsCalloutGroupsKnowledge: ICalloutGroup[] = [ - { - displayName: CalloutGroupName.HOME, - description: 'The Subspace Home page.', - }, -]; diff --git a/src/domain/space/space.defaults/definitions/oppportunity/space.defaults.callouts.opportunity.ts b/src/domain/space/space.defaults/definitions/oppportunity/space.defaults.callouts.opportunity.ts deleted file mode 100644 index da731112a2..0000000000 --- a/src/domain/space/space.defaults/definitions/oppportunity/space.defaults.callouts.opportunity.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { CalloutGroupName } from '@common/enums/callout.group.name'; -import { CalloutState } from '@common/enums/callout.state'; -import { CalloutType } from '@common/enums/callout.type'; -import { EMPTY_WHITEBOARD_CONTENT } from '@domain/common/whiteboard/empty.whiteboard.content'; -import { TagsetReservedName } from '@common/enums/tagset.reserved.name'; -import { FlowState } from './space.defaults.innovation.flow.opportunity'; -import { CreateCalloutInput } from '@domain/collaboration/callout/dto/callout.dto.create'; - -export const spaceDefaultsCalloutsOpportunity: CreateCalloutInput[] = [ - { - nameID: 'general-chat', - type: CalloutType.POST, - contributionPolicy: { - state: CalloutState.OPEN, - }, - sortOrder: 2, - groupName: CalloutGroupName.HOME, - framing: { - profile: { - displayName: 'General chat 💬', - description: 'Things you would like to discuss with the community.', - tagsets: [ - { - name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.EXPLORE], - }, - ], - }, - }, - }, - { - nameID: 'getting-started', - type: CalloutType.LINK_COLLECTION, - contributionPolicy: { - state: CalloutState.CLOSED, - }, - sortOrder: 1, - groupName: CalloutGroupName.HOME, - framing: { - profile: { - displayName: 'Getting Started', - description: '⬇️ Here are some quick links to help you get started', - tagsets: [ - { - name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.EXPLORE], - }, - ], - }, - }, - }, - { - nameID: 'contributor-profiles', - type: CalloutType.POST_COLLECTION, - contributionPolicy: { - state: CalloutState.OPEN, - }, - sortOrder: 2, - groupName: CalloutGroupName.HOME, - framing: { - profile: { - displayName: '👥 This is us!', - description: - 'Here you will find the profiles of all contributors to this Space. Are you joining us? 👋 Nice to meet you! Please also provide your details below.', - tagsets: [ - { - name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.EXPLORE], - }, - ], - }, - }, - contributionDefaults: { - postDescription: - 'Hi! I am...

In daily life I...

And I also like to...

You can contact me for anything related to...

My wish for this Space is..

And of course feel invited to insert a nice picture!', - }, - }, - { - nameID: 'news', - type: CalloutType.POST_COLLECTION, - contributionPolicy: { - state: CalloutState.OPEN, - }, - sortOrder: 1, - groupName: CalloutGroupName.HOME, - framing: { - profile: { - displayName: 'Relevant news, research or use cases 📰', - description: - 'Please share any relevant insights to help us better understand the Space. You can describe why it is relevant and add a link or upload a document with the article. You can also comment on the insights already submitted by other community members!', - tagsets: [ - { - name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.EXPLORE], - }, - ], - }, - }, - contributionDefaults: { - postDescription: - '✍️ Please share your contribution. The more details the better!', - }, - }, - { - nameID: 'stakeholder-map', - type: CalloutType.WHITEBOARD, - contributionPolicy: { - state: CalloutState.OPEN, - }, - sortOrder: 2, - groupName: CalloutGroupName.HOME, - framing: { - profile: { - displayName: 'Who are the stakeholders?', - description: - 'Choose one of the templates from the library to map your stakeholders here!', - tagsets: [ - { - name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.EXPLORE], - }, - ], - }, - whiteboard: { - content: EMPTY_WHITEBOARD_CONTENT, - nameID: 'stakeholders', - profileData: { - displayName: 'stakeholder map', - }, - }, - }, - }, - { - nameID: 'documents', - type: CalloutType.LINK_COLLECTION, - contributionPolicy: { - state: CalloutState.OPEN, - }, - sortOrder: 3, - groupName: CalloutGroupName.HOME, - framing: { - profile: { - displayName: 'Reference / important documents', - description: 'Please add links to documents with reference material.💥', - tagsets: [ - { - name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.EXPLORE], - }, - ], - }, - }, - }, - { - nameID: 'proposals', - type: CalloutType.POST_COLLECTION, - contributionPolicy: { - state: CalloutState.OPEN, - }, - sortOrder: 1, - groupName: CalloutGroupName.HOME, - framing: { - profile: { - displayName: 'Proposals', - description: - 'What are the 💡 Opportunities that you think we should be working on? Please add them below and use the template provided.', - tagsets: [ - { - name: TagsetReservedName.FLOW_STATE, - tags: [FlowState.EXPLORE], - }, - ], - }, - }, - contributionDefaults: { - postDescription: - '💡 Title

💬 Description

🗣️ Who to involve

🌟 Why this has great potential', - }, - }, -]; diff --git a/src/domain/space/space.defaults/definitions/oppportunity/space.defaults.innovation.flow.opportunity.ts b/src/domain/space/space.defaults/definitions/oppportunity/space.defaults.innovation.flow.opportunity.ts deleted file mode 100644 index bd567dcf94..0000000000 --- a/src/domain/space/space.defaults/definitions/oppportunity/space.defaults.innovation.flow.opportunity.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { IInnovationFlowState } from '@domain/collaboration/innovation-flow-states/innovation.flow.state.interface'; - -export enum FlowState { - EXPLORE = 'Explore', - DEFINE = 'Define', - BRAINSTORM = 'Brainstorm', - VALIDATE = 'Validate', - EVALUATE = 'Evaluate', -} - -export const spaceDefaultsInnovationFlowStatesOpportunity: IInnovationFlowState[] = - [ - { - displayName: 'Explore', - description: - '🔍 A journey of discovery! Gather insights through research and observation.', - }, - { - displayName: 'Define', - description: - '🎯 Sharpen your focus. Define the challenge with precision and set a clear direction.', - }, - { - displayName: 'Brainstorm', - description: - '🎨 Ignite creativity. Generate a constellation of ideas, using concepts from diverse perspectives to get inspired.', - }, - { - displayName: 'Validate', - description: - '🛠️ Test assumptions. Build prototypes, seek feedback, and validate your concepts. Adapt based on real-world insights.', - }, - { - displayName: 'Evaluate', - description: - '✅ Assess impact, feasibility, and alignment to make informed choices.', - }, - ]; diff --git a/src/domain/space/space.defaults/definitions/space.defaults.templates.ts b/src/domain/space/space.defaults/definitions/space.defaults.templates.ts index 42ff39ba02..012788c22c 100644 --- a/src/domain/space/space.defaults/definitions/space.defaults.templates.ts +++ b/src/domain/space/space.defaults/definitions/space.defaults.templates.ts @@ -1,8 +1,7 @@ import { TemplateType } from '@common/enums/template.type'; -import { spaceDefaultsInnovationFlowStatesChallenge } from './challenge/space.defaults.innovation.flow.challenge'; import { CreateTemplateInput } from '@domain/template/template/dto/template.dto.create'; -const posts: CreateTemplateInput[] = [ +export const posts: CreateTemplateInput[] = [ { nameID: 'meeting-notes', profileData: { @@ -40,65 +39,3 @@ const posts: CreateTemplateInput[] = [ '💬**What is blocking this space at the moment?**\n\n*Uncover the current challenges and obstacles that hinder progress in this space. What barriers are present, and how do they impact the community?*\n\n📢 **Describe your call to action**:\n\n*What steps can we take to address these challenges, and how can we overcome these obstacles?*\n\n📚 **Type of knowledge, expertise, and resources:**\n\n*Specify the types of knowledge, expertise, and resources needed to navigate and overcome the identified challenges*\n\n✏️ **Additional context:**\n\n*providing additional context*\n\nTogether, lets transform challenges into pportunities and propel this space forward! 🚀💪\n', }, ]; - -const innovationFlows: CreateTemplateInput[] = [ - { - nameID: 'default-innovation-flow', - profileData: { - displayName: 'Default innovationFlow', - description: 'Default innovationFlow', - }, - tags: ['default'], - type: TemplateType.INNOVATION_FLOW, - innovationFlowData: { - profile: { - displayName: 'Default innovationFlow', - description: 'Default innovationFlow', - }, - states: spaceDefaultsInnovationFlowStatesChallenge, - }, - }, - { - nameID: 'coordination-flow', - profileData: { - displayName: 'Coordination Flow', - description: - 'This flow helps you to quickly structure your Challenge when using it for Coordination purposes', - }, - tags: ['coordination'], - type: TemplateType.INNOVATION_FLOW, - innovationFlowData: { - profile: { - displayName: 'Default innovationFlow', - description: 'Default innovationFlow', - }, - states: [ - { - displayName: 'Key Insights', - description: '👍 Reviewing essential concepts and discoveries', - }, - { - displayName: 'Brainstorm', - description: '💡 Organizing ideas and strategies', - }, - { - displayName: 'Notes', - description: '📝 Capturing thoughts and observations', - }, - { - displayName: 'To Do', - description: '☑️ Managing tasks and priorities', - }, - { - displayName: 'Other', - description: '🌟 A flexible space for miscellaneous content', - }, - ], - }, - }, -]; - -export const templatesSetDefaults: any = { - posts, - innovationFlows, -}; diff --git a/src/domain/space/space.defaults/dto/space.defaults.dto.update.ts b/src/domain/space/space.defaults/dto/space.defaults.dto.update.ts deleted file mode 100644 index 3c55c33c56..0000000000 --- a/src/domain/space/space.defaults/dto/space.defaults.dto.update.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { InputType, Field } from '@nestjs/graphql'; -import { UUID } from '@domain/common/scalars/scalar.uuid'; - -@InputType() -export class UpdateSpaceDefaultsInput { - @Field(() => UUID, { - nullable: false, - description: 'The identifier for the SpaceDefaults to be updated.', - }) - spaceDefaultsID!: string; - - @Field(() => UUID, { - nullable: false, - description: - 'The ID for the InnovationFlowtemplate to use for new Subspaces.', - }) - flowTemplateID?: string; -} diff --git a/src/domain/space/space.defaults/space.defaults.entity.ts b/src/domain/space/space.defaults/space.defaults.entity.ts deleted file mode 100644 index 0e9b20a33d..0000000000 --- a/src/domain/space/space.defaults/space.defaults.entity.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Entity, JoinColumn, OneToOne } from 'typeorm'; -import { AuthorizableEntity } from '@domain/common/entity/authorizable-entity/authorizable.entity'; -import { ISpaceDefaults } from './space.defaults.interface'; -import { Template } from '@domain/template/template/template.entity'; - -@Entity() -export class SpaceDefaults - extends AuthorizableEntity - implements ISpaceDefaults -{ - @OneToOne(() => Template, { - eager: true, - cascade: false, // important not to cascade - onDelete: 'SET NULL', - }) - @JoinColumn() - innovationFlowTemplate?: Template; -} diff --git a/src/domain/space/space.defaults/space.defaults.interface.ts b/src/domain/space/space.defaults/space.defaults.interface.ts deleted file mode 100644 index 078470ed98..0000000000 --- a/src/domain/space/space.defaults/space.defaults.interface.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { IAuthorizable } from '@domain/common/entity/authorizable-entity/authorizable.interface'; -import { ITemplate } from '@domain/template/template/template.interface'; -import { Field, ObjectType } from '@nestjs/graphql'; - -@ObjectType('SpaceDefaults') -export abstract class ISpaceDefaults extends IAuthorizable { - @Field(() => ITemplate, { - nullable: true, - description: - 'The innovation flow template to use for new Challenges / Opportunities.', - }) - innovationFlowTemplate?: ITemplate; -} diff --git a/src/domain/space/space.defaults/space.defaults.module.ts b/src/domain/space/space.defaults/space.defaults.module.ts index 3d115d8f40..938ab0809e 100644 --- a/src/domain/space/space.defaults/space.defaults.module.ts +++ b/src/domain/space/space.defaults/space.defaults.module.ts @@ -1,15 +1,18 @@ import { Module } from '@nestjs/common'; import { SpaceDefaultsService } from './space.defaults.service'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { SpaceDefaults } from './space.defaults.entity'; -import { AuthorizationPolicyModule } from '@domain/common/authorization-policy/authorization.policy.module'; -import { AuthorizationModule } from '@core/authorization/authorization.module'; +import { TemplateModule } from '@domain/template/template/template.module'; +import { InputCreatorModule } from '@services/api/input-creator/input.creator.module'; +import { PlatformModule } from '@platform/platform/platform.module'; +import { TemplatesManagerModule } from '@domain/template/templates-manager/templates.manager.module'; +import { CollaborationModule } from '@domain/collaboration/collaboration/collaboration.module'; @Module({ imports: [ - AuthorizationModule, - AuthorizationPolicyModule, - TypeOrmModule.forFeature([SpaceDefaults]), + TemplateModule, + TemplatesManagerModule, + InputCreatorModule, + CollaborationModule, + PlatformModule, ], providers: [SpaceDefaultsService], exports: [SpaceDefaultsService], diff --git a/src/domain/space/space.defaults/space.defaults.service.ts b/src/domain/space/space.defaults/space.defaults.service.ts index b798c4b45a..5b5b86d983 100644 --- a/src/domain/space/space.defaults/space.defaults.service.ts +++ b/src/domain/space/space.defaults/space.defaults.service.ts @@ -1,156 +1,170 @@ import { Injectable } from '@nestjs/common'; -import { ISpaceDefaults } from './space.defaults.interface'; -import { AuthorizationPolicy } from '@domain/common/authorization-policy/authorization.policy.entity'; -import { InjectRepository } from '@nestjs/typeorm'; -import { FindOneOptions, Repository } from 'typeorm'; -import { EntityNotFoundException } from '@common/exceptions/entity.not.found.exception'; import { LogContext } from '@common/enums/logging.context'; -import { UUID_LENGTH } from '@common/constants/entity.field.length.constants'; -import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service'; -import { SpaceDefaults } from './space.defaults.entity'; -import { CreateCalloutInput } from '@domain/collaboration/callout/dto/callout.dto.create'; import { ISpaceSettings } from '../space.settings/space.settings.interface'; -import { ICalloutGroup } from '@domain/collaboration/callout-groups/callout.group.interface'; import { subspaceCommunityRoles } from './definitions/subspace.community.roles'; import { spaceCommunityRoles } from './definitions/space.community.roles'; import { CreateFormInput } from '@domain/common/form/dto/form.dto.create'; import { subspceCommunityApplicationForm } from './definitions/subspace.community.role.application.form'; import { spaceCommunityApplicationForm } from './definitions/space.community.role.application.form'; import { ProfileType } from '@common/enums'; -import { CalloutGroupName } from '@common/enums/callout.group.name'; import { SpaceLevel } from '@common/enums/space.level'; import { EntityNotInitializedException } from '@common/exceptions/entity.not.initialized.exception'; import { SpaceType } from '@common/enums/space.type'; -import { spaceDefaultsCalloutGroupsChallenge } from './definitions/challenge/space.defaults.callout.groups.challenge'; -import { spaceDefaultsCalloutGroupsOpportunity } from './definitions/oppportunity/space.defaults.callout.groups.opportunity'; -import { spaceDefaultsCalloutGroupsRootSpace } from './definitions/root-space/space.defaults.callout.groups.root.space'; -import { spaceDefaultsCalloutGroupsKnowledge } from './definitions/knowledge/space.defaults.callout.groups.knowledge'; -import { spaceDefaultsCalloutsOpportunity } from './definitions/oppportunity/space.defaults.callouts.opportunity'; -import { spaceDefaultsCalloutsChallenge } from './definitions/challenge/space.defaults.callouts.challenge'; -import { spaceDefaultsCalloutsRootSpace } from './definitions/root-space/space.defaults.callouts.root.space'; -import { spaceDefaultsCalloutsKnowledge } from './definitions/knowledge/space.defaults.callouts.knowledge'; import { spaceDefaultsSettingsRootSpace } from './definitions/root-space/space.defaults.settings.root.space'; import { spaceDefaultsSettingsOpportunity } from './definitions/oppportunity/space.defaults.settings.opportunity'; import { spaceDefaultsSettingsChallenge } from './definitions/challenge/space.defaults.settings.challenge'; import { spaceDefaultsSettingsKnowledge } from './definitions/knowledge/space.defaults.settings.knowledge'; -import { spaceDefaultsInnovationFlowStatesChallenge } from './definitions/challenge/space.defaults.innovation.flow.challenge'; -import { spaceDefaultsInnovationFlowStatesOpportunity } from './definitions/oppportunity/space.defaults.innovation.flow.opportunity'; -import { spaceDefaultsInnovationFlowStatesRootSpace } from './definitions/root-space/space.defaults.innovation.flow.root.space'; -import { spaceDefaultsInnovationFlowStatesKnowledge } from './definitions/knowledge/space.defaults.innovation.flow.knowledge'; -import { IInnovationFlowState } from '@domain/collaboration/innovation-flow-states/innovation.flow.state.interface'; -import { spaceDefaultsCalloutGroupsBlankSlate } from './definitions/blank-slate/space.defaults.callout.groups.blank.slate'; -import { spaceDefaultsCalloutsBlankSlate } from './definitions/blank-slate/space.defaults.callouts.blank.slate'; import { spaceDefaultsSettingsBlankSlate } from './definitions/blank-slate/space.defaults.settings.blank.slate'; -import { spaceDefaultsInnovationFlowStatesBlankSlate } from './definitions/blank-slate/space.defaults.innovation.flow.blank.slate'; -import { AuthorizationPolicyType } from '@common/enums/authorization.policy.type'; -import { ITemplate } from '@domain/template/template/template.interface'; import { CreateRoleInput } from '@domain/access/role/dto/role.dto.create'; +import { CreateCollaborationOnSpaceInput } from '../space/dto/space.dto.create.collaboration'; +import { CreateCollaborationInput } from '@domain/collaboration/collaboration/dto/collaboration.dto.create'; +import { TemplateService } from '@domain/template/template/template.service'; +import { InputCreatorService } from '@services/api/input-creator/input.creator.service'; +import { PlatformService } from '@platform/platform/platform.service'; +import { TemplatesManagerService } from '@domain/template/templates-manager/templates.manager.service'; +import { TemplateDefaultType } from '@common/enums/template.default.type'; +import { ValidationException } from '@common/exceptions'; +import { CollaborationService } from '@domain/collaboration/collaboration/collaboration.service'; @Injectable() export class SpaceDefaultsService { constructor( - private authorizationPolicyService: AuthorizationPolicyService, - @InjectRepository(SpaceDefaults) - private spaceDefaultsRepository: Repository + private templateService: TemplateService, + private inputCreatorService: InputCreatorService, + private platformService: PlatformService, + private collaborationService: CollaborationService, + private templatesManagerService: TemplatesManagerService ) {} - public async createSpaceDefaults(): Promise { - const spaceDefaults: ISpaceDefaults = new SpaceDefaults(); - spaceDefaults.authorization = new AuthorizationPolicy( - AuthorizationPolicyType.SPACE_DEFAULTS - ); - - return spaceDefaults; - } - - public async updateSpaceDefaults( - spaceDefaults: ISpaceDefaults, - innovationFlowTemplate: ITemplate - ): Promise { - spaceDefaults.innovationFlowTemplate = innovationFlowTemplate; - - return await this.save(spaceDefaults); - } - - async deleteSpaceDefaults(spaceDefaultsId: string): Promise { - const spaceDefaults = await this.getSpaceDefaultsOrFail(spaceDefaultsId, { - relations: { - authorization: true, - }, - }); - - if (spaceDefaults.authorization) { - await this.authorizationPolicyService.delete(spaceDefaults.authorization); + public async createCollaborationInput( + collaborationData: CreateCollaborationOnSpaceInput, + spaceType: SpaceType + ): Promise { + const platformTemplatesManager = + await this.platformService.getTemplatesManagerOrFail(); + let templateID = collaborationData.collaborationTemplateID; + if (!templateID) { + switch (spaceType) { + case SpaceType.CHALLENGE: + case SpaceType.OPPORTUNITY: { + const subspaceTemplate = + await this.templatesManagerService.getTemplateFromTemplateDefault( + platformTemplatesManager.id, + TemplateDefaultType.PLATFORM_SUBSPACE + ); + templateID = subspaceTemplate.id; + break; + } + case SpaceType.SPACE: { + const levelZeroTemplate = + await this.templatesManagerService.getTemplateFromTemplateDefault( + platformTemplatesManager.id, + TemplateDefaultType.PLATFORM_SPACE + ); + templateID = levelZeroTemplate.id; + break; + } + case SpaceType.KNOWLEDGE: { + const knowledgeTemplate = + await this.templatesManagerService.getTemplateFromTemplateDefault( + platformTemplatesManager.id, + TemplateDefaultType.PLATFORM_SUBSPACE_KNOWLEDGE + ); + templateID = knowledgeTemplate.id; + break; + } + } + } + let collaborationTemplateInput: CreateCollaborationInput | undefined = + undefined; + if (templateID) { + const collaborationFromTemplate = + await this.templateService.getCollaboration(templateID); + collaborationTemplateInput = + await this.inputCreatorService.buildCreateCollaborationInputFromCollaboration( + collaborationFromTemplate.id + ); + } + if (!collaborationData.innovationFlowData) { + // TODO: need to pick up the default template + innovation flow properly + if (collaborationTemplateInput) { + collaborationData.innovationFlowData = + collaborationTemplateInput.innovationFlowData; + } else { + throw new ValidationException( + 'No innovation flow data provided', + LogContext.SPACES + ); + } + } + if (collaborationTemplateInput && collaborationData.addCallouts) { + if (!collaborationData.calloutsData) { + collaborationData.calloutsData = + collaborationTemplateInput?.calloutsData; + } else if (collaborationTemplateInput?.calloutsData) { + // The request includes the calloutsData, so merge template callouts with request callouts + collaborationData.calloutsData.push( + ...collaborationTemplateInput.calloutsData + ); + } + } else { + collaborationData.calloutsData = []; } - // Note: do not remove the innovation flow template here, as that is the responsibility of the Space Library - - const result = await this.spaceDefaultsRepository.remove( - spaceDefaults as SpaceDefaults - ); - result.id = spaceDefaultsId; - return result; - } + if (!collaborationData.calloutGroups && collaborationTemplateInput) { + collaborationData.calloutGroups = + collaborationTemplateInput?.calloutGroups; + } - async save(spaceDefaults: ISpaceDefaults): Promise { - return await this.spaceDefaultsRepository.save(spaceDefaults); - } + // Add in tutorials if needed - public async getSpaceDefaultsOrFail( - spaceDefaultsID: string, - options?: FindOneOptions - ): Promise { - let spaceDefaults: ISpaceDefaults | null = null; - if (spaceDefaultsID.length === UUID_LENGTH) { - spaceDefaults = await this.spaceDefaultsRepository.findOne({ - where: { id: spaceDefaultsID }, - ...options, - }); + if (collaborationData.addTutorialCallouts) { + const tutorialsTemplate = + await this.templatesManagerService.getTemplateFromTemplateDefault( + platformTemplatesManager.id, + TemplateDefaultType.PLATFORM_SPACE_TUTORIALS + ); + if (tutorialsTemplate) { + const tutorialsCollaborationTemplate = + await this.templateService.getCollaboration(tutorialsTemplate.id); + const tutorialsCollaborationTemplateInput = + await this.inputCreatorService.buildCreateCollaborationInputFromCollaboration( + tutorialsCollaborationTemplate.id + ); + if (tutorialsCollaborationTemplateInput.calloutsData) { + collaborationData.calloutsData?.push( + ...tutorialsCollaborationTemplateInput.calloutsData + ); + } + } + } + if (collaborationTemplateInput) { + collaborationData.defaultCalloutGroupName = + collaborationTemplateInput.defaultCalloutGroupName; } - if (!spaceDefaults) - throw new EntityNotFoundException( - `No SpaceDefaults found with the given id: ${spaceDefaultsID}`, - LogContext.COLLABORATION + // Move callouts that are not in valid groups or flowStates to the default group & first flowState + const defaultGroupName = + collaborationTemplateInput?.defaultCalloutGroupName; + const defaultFlowStateName = + collaborationData.innovationFlowData?.states?.[0]?.displayName; + const validGroupNames = collaborationData.calloutGroups?.map( + group => group.displayName + ); + const validFlowStateNames = + collaborationData.innovationFlowData?.states?.map( + state => state.displayName ); - return spaceDefaults; - } - public getCalloutGroups(spaceType: SpaceType): ICalloutGroup[] { - switch (spaceType) { - case SpaceType.CHALLENGE: - return spaceDefaultsCalloutGroupsChallenge; - case SpaceType.OPPORTUNITY: - return spaceDefaultsCalloutGroupsOpportunity; - case SpaceType.SPACE: - return spaceDefaultsCalloutGroupsRootSpace; - case SpaceType.KNOWLEDGE: - return spaceDefaultsCalloutGroupsKnowledge; - case SpaceType.BLANK_SLATE: - return spaceDefaultsCalloutGroupsBlankSlate; - default: - throw new EntityNotInitializedException( - `Invalid space type: ${spaceType}`, - LogContext.ROLES - ); - } - } + this.collaborationService.moveCalloutsToCorrectGroupAndState( + defaultGroupName, + defaultFlowStateName, + validGroupNames ?? [], + validFlowStateNames ?? [], + collaborationData.calloutsData ?? [] + ); - public getCalloutGroupDefault(spaceType: SpaceType): CalloutGroupName { - switch (spaceType) { - case SpaceType.CHALLENGE: - case SpaceType.KNOWLEDGE: - case SpaceType.OPPORTUNITY: - case SpaceType.BLANK_SLATE: - return CalloutGroupName.HOME; - case SpaceType.SPACE: - return CalloutGroupName.KNOWLEDGE; - default: - throw new EntityNotInitializedException( - `Invalid space type: ${spaceType}`, - LogContext.ROLES - ); - } + return collaborationData; } public getRoleSetCommunityRoles(spaceLevel: SpaceLevel): CreateRoleInput[] { @@ -191,32 +205,6 @@ export class SpaceDefaultsService { } } - public getDefaultCallouts(spaceType: SpaceType): CreateCalloutInput[] { - switch (spaceType) { - case SpaceType.CHALLENGE: - return spaceDefaultsCalloutsChallenge; - case SpaceType.OPPORTUNITY: - return spaceDefaultsCalloutsOpportunity; - case SpaceType.SPACE: - return spaceDefaultsCalloutsRootSpace; - case SpaceType.KNOWLEDGE: - return spaceDefaultsCalloutsKnowledge; - case SpaceType.BLANK_SLATE: - return spaceDefaultsCalloutsBlankSlate; - default: - throw new EntityNotInitializedException( - `Invalid space type: ${spaceType}`, - LogContext.ROLES - ); - } - } - - public getDefaultInnovationFlowTemplate( - spaceDefaults: ISpaceDefaults - ): ITemplate | undefined { - return spaceDefaults.innovationFlowTemplate; - } - public getDefaultSpaceSettings(spaceType: SpaceType): ISpaceSettings { switch (spaceType) { case SpaceType.CHALLENGE: @@ -236,26 +224,4 @@ export class SpaceDefaultsService { ); } } - - public getDefaultInnovationFlowStates( - spaceType: SpaceType - ): IInnovationFlowState[] { - switch (spaceType) { - case SpaceType.CHALLENGE: - return spaceDefaultsInnovationFlowStatesChallenge; - case SpaceType.OPPORTUNITY: - return spaceDefaultsInnovationFlowStatesOpportunity; - case SpaceType.SPACE: - return spaceDefaultsInnovationFlowStatesRootSpace; - case SpaceType.KNOWLEDGE: - return spaceDefaultsInnovationFlowStatesKnowledge; - case SpaceType.BLANK_SLATE: - return spaceDefaultsInnovationFlowStatesBlankSlate; - default: - throw new EntityNotInitializedException( - `Invalid space type: ${spaceType}`, - LogContext.ROLES - ); - } - } } diff --git a/src/domain/space/space/dto/space.dto.create.collaboration.ts b/src/domain/space/space/dto/space.dto.create.collaboration.ts index 6fd7954300..00eb7d1cc3 100644 --- a/src/domain/space/space/dto/space.dto.create.collaboration.ts +++ b/src/domain/space/space/dto/space.dto.create.collaboration.ts @@ -1,11 +1,26 @@ import { CreateCollaborationInput } from '@domain/collaboration/collaboration/dto/collaboration.dto.create'; +import { UUID } from '@domain/common/scalars/scalar.uuid'; import { Field, InputType } from '@nestjs/graphql'; @InputType() export class CreateCollaborationOnSpaceInput extends CreateCollaborationInput { @Field(() => Boolean, { nullable: true, - description: 'Add default callouts to the Collaboration; defaults to true.', + description: + 'Add tutorial callouts to the Collaboration; defaults to false.', }) - addDefaultCallouts? = true; + addTutorialCallouts? = false; + + @Field(() => Boolean, { + nullable: true, + description: + 'Add callouts from the template to the Collaboration; defaults to true.', + }) + addCallouts? = true; + + @Field(() => UUID, { + nullable: true, + description: 'The Template to use for instantiating the Collaboration.', + }) + collaborationTemplateID?: string; } diff --git a/src/domain/space/space/space.entity.ts b/src/domain/space/space/space.entity.ts index e407264036..bc75aa8015 100644 --- a/src/domain/space/space/space.entity.ts +++ b/src/domain/space/space/space.entity.ts @@ -18,9 +18,8 @@ import { Account } from '../account/account.entity'; import { Context } from '@domain/context/context/context.entity'; import { Agent } from '@domain/agent/agent/agent.entity'; import { SpaceVisibility } from '@common/enums/space.visibility'; -import { TemplatesSet } from '@domain/template/templates-set/templates.set.entity'; -import { SpaceDefaults } from '../space.defaults/space.defaults.entity'; import { Profile } from '@domain/common/profile'; +import { TemplatesManager } from '@domain/template/templates-manager'; import { SpaceLevel } from '@common/enums/space.level'; @Entity() export class Space extends NameableEntity implements ISpace { @@ -111,21 +110,13 @@ export class Space extends NameableEntity implements ISpace { }) visibility!: SpaceVisibility; - @OneToOne(() => TemplatesSet, { + @OneToOne(() => TemplatesManager, { eager: false, cascade: true, onDelete: 'SET NULL', }) @JoinColumn() - library?: TemplatesSet; - - @OneToOne(() => SpaceDefaults, { - eager: false, - cascade: true, - onDelete: 'SET NULL', - }) - @JoinColumn() - defaults?: SpaceDefaults; + templatesManager?: TemplatesManager; constructor() { super(); diff --git a/src/domain/space/space/space.interface.ts b/src/domain/space/space/space.interface.ts index 887caaa712..43d1967c5e 100644 --- a/src/domain/space/space/space.interface.ts +++ b/src/domain/space/space/space.interface.ts @@ -8,8 +8,7 @@ import { IContext } from '@domain/context/context/context.interface'; import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface'; import { IAccount } from '../account/account.interface'; import { SpaceVisibility } from '@common/enums/space.visibility'; -import { ITemplatesSet } from '@domain/template/templates-set/templates.set.interface'; -import { ISpaceDefaults } from '../space.defaults/space.defaults.interface'; +import { ITemplatesManager } from '@domain/template/templates-manager'; import { SpaceLevel } from '@common/enums/space.level'; @ObjectType('Space') @@ -55,6 +54,5 @@ export class ISpace extends INameable { }) levelZeroSpaceID!: string; - library?: ITemplatesSet; - defaults?: ISpaceDefaults; + templatesManager?: ITemplatesManager; } diff --git a/src/domain/space/space/space.module.ts b/src/domain/space/space/space.module.ts index 526f2e468f..d528fab4d2 100644 --- a/src/domain/space/space/space.module.ts +++ b/src/domain/space/space/space.module.ts @@ -22,16 +22,15 @@ import { CommunityModule } from '@domain/community/community/community.module'; import { StorageAggregatorModule } from '@domain/storage/storage-aggregator/storage.aggregator.module'; import { PlatformAuthorizationPolicyModule } from '@platform/authorization/platform.authorization.policy.module'; import { NamingModule } from '@services/infrastructure/naming/naming.module'; -import { SpaceDefaultsModule } from '../space.defaults/space.defaults.module'; import { SpaceSettingssModule } from '../space.settings/space.settings.module'; -import { TemplatesSetModule } from '@domain/template/templates-set/templates.set.module'; import { AccountHostModule } from '../account.host/account.host.module'; import { LicensingModule } from '@platform/licensing/licensing.module'; import { LicenseEngineModule } from '@core/license-engine/license.engine.module'; import { LicenseIssuerModule } from '@platform/license-issuer/license.issuer.module'; -import { TemplateModule } from '@domain/template/template/template.module'; import { InputCreatorModule } from '@services/api/input-creator/input.creator.module'; import { RoleSetModule } from '@domain/access/role-set/role.set.module'; +import { TemplatesManagerModule } from '@domain/template/templates-manager/templates.manager.module'; +import { SpaceDefaultsModule } from '../space.defaults/space.defaults.module'; @Module({ imports: [ @@ -47,8 +46,7 @@ import { RoleSetModule } from '@domain/access/role-set/role.set.module'; LicenseEngineModule, NamingModule, PlatformAuthorizationPolicyModule, - SpaceDefaultsModule, - TemplatesSetModule, + TemplatesManagerModule, SpaceSettingssModule, StorageAggregatorModule, ContributionReporterModule, @@ -57,10 +55,9 @@ import { RoleSetModule } from '@domain/access/role-set/role.set.module'; SpaceFilterModule, ActivityAdapterModule, LoaderCreatorModule, - TemplateModule, RoleSetModule, - InputCreatorModule, NameReporterModule, + SpaceDefaultsModule, TypeOrmModule.forFeature([Space]), ], providers: [ diff --git a/src/domain/space/space/space.resolver.fields.ts b/src/domain/space/space/space.resolver.fields.ts index 63c5e1c21b..9807b6d106 100644 --- a/src/domain/space/space/space.resolver.fields.ts +++ b/src/domain/space/space/space.resolver.fields.ts @@ -31,12 +31,11 @@ import { AgentInfo } from '@core/authentication.agent.info/agent.info'; import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface'; import { EntityNotFoundException } from '@common/exceptions/entity.not.found.exception'; import { ISpaceSettings } from '../space.settings/space.settings.interface'; -import { ITemplatesSet } from '@domain/template/templates-set/templates.set.interface'; -import { ISpaceDefaults } from '../space.defaults/space.defaults.interface'; import { IAccount } from '../account/account.interface'; import { IContributor } from '@domain/community/contributor/contributor.interface'; import { LicensePrivilege } from '@common/enums/license.privilege'; import { ISpaceSubscription } from './space.license.subscription.interface'; +import { ITemplatesManager } from '@domain/template/templates-manager'; @Resolver(() => ISpace) export class SpaceResolverFields { @@ -254,26 +253,15 @@ export class SpaceResolverFields { } @AuthorizationAgentPrivilege(AuthorizationPrivilege.READ) - @ResolveField('library', () => ITemplatesSet, { + @ResolveField('templatesManager', () => ITemplatesManager, { nullable: true, - description: 'The Library in use by this Space', + description: 'The TemplatesManager in use by this Space', }) @UseGuards(GraphqlGuard) - async library(@Parent() space: Space): Promise { - return await this.spaceService.getLibraryOrFail(space.levelZeroSpaceID); - } - - @AuthorizationAgentPrivilege(AuthorizationPrivilege.READ) - @ResolveField('defaults', () => ISpaceDefaults, { - nullable: true, - description: 'The defaults in use by this Space', - }) - @UseGuards(GraphqlGuard) - async defaults( - @CurrentUser() agentInfo: AgentInfo, - @Parent() space: Space - ): Promise { - return await this.spaceService.getDefaultsOrFail(space.levelZeroSpaceID); + async templatesManager(@Parent() space: ISpace): Promise { + return await this.spaceService.getTemplatesManagerOrFail( + space.levelZeroSpaceID + ); } @AuthorizationAgentPrivilege(AuthorizationPrivilege.READ) diff --git a/src/domain/space/space/space.resolver.mutations.ts b/src/domain/space/space/space.resolver.mutations.ts index 147d6fc750..02c7625512 100644 --- a/src/domain/space/space/space.resolver.mutations.ts +++ b/src/domain/space/space/space.resolver.mutations.ts @@ -20,10 +20,6 @@ import { UpdateSpacePlatformSettingsInput } from './dto/space.dto.update.platfor import { SUBSCRIPTION_SUBSPACE_CREATED } from '@common/constants/providers'; import { UpdateSpaceSettingsInput } from './dto/space.dto.update.settings'; import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service'; -import { UpdateSpaceDefaultsInput } from '../space.defaults/dto/space.defaults.dto.update'; -import { ISpaceDefaults } from '../space.defaults/space.defaults.interface'; -import { SpaceDefaultsService } from '../space.defaults/space.defaults.service'; -import { TemplateService } from '@domain/template/template/template.service'; @Resolver() export class SpaceResolverMutations { @@ -34,11 +30,9 @@ export class SpaceResolverMutations { private authorizationPolicyService: AuthorizationPolicyService, private spaceService: SpaceService, private spaceAuthorizationService: SpaceAuthorizationService, - private spaceDefaultsService: SpaceDefaultsService, @Inject(SUBSCRIPTION_SUBSPACE_CREATED) private subspaceCreatedSubscription: PubSubEngine, - private namingReporter: NameReporterService, - private templateService: TemplateService + private namingReporter: NameReporterService ) {} @UseGuards(GraphqlGuard) @@ -227,39 +221,4 @@ export class SpaceResolverMutations { return this.spaceService.getSpaceOrFail(subspace.id); } - - @UseGuards(GraphqlGuard) - @Mutation(() => ISpaceDefaults, { - description: 'Updates the specified SpaceDefaults.', - }) - async updateSpaceDefaults( - @CurrentUser() agentInfo: AgentInfo, - @Args('spaceDefaultsData') - spaceDefaultsData: UpdateSpaceDefaultsInput - ): Promise { - const spaceDefaults = - await this.spaceDefaultsService.getSpaceDefaultsOrFail( - spaceDefaultsData.spaceDefaultsID, - {} - ); - - this.authorizationService.grantAccessOrFail( - agentInfo, - spaceDefaults.authorization, - AuthorizationPrivilege.UPDATE, - `update spaceDefaults: ${spaceDefaults.id}` - ); - - if (spaceDefaultsData.flowTemplateID) { - const innovationFlowTemplate = - await this.templateService.getTemplateOrFail( - spaceDefaultsData.flowTemplateID - ); - return await this.spaceDefaultsService.updateSpaceDefaults( - spaceDefaults, - innovationFlowTemplate - ); - } - return spaceDefaults; - } } diff --git a/src/domain/space/space/space.service.authorization.ts b/src/domain/space/space/space.service.authorization.ts index f955d59ce6..abc8aa8e0c 100644 --- a/src/domain/space/space/space.service.authorization.ts +++ b/src/domain/space/space/space.service.authorization.ts @@ -35,9 +35,9 @@ import { SpaceLevel } from '@common/enums/space.level'; import { AgentAuthorizationService } from '@domain/agent/agent/agent.service.authorization'; import { IAgent } from '@domain/agent/agent/agent.interface'; import { ISpaceSettings } from '../space.settings/space.settings.interface'; -import { TemplatesSetAuthorizationService } from '@domain/template/templates-set/templates.set.service.authorization'; import { RoleSetService } from '@domain/access/role-set/role.set.service'; import { IRoleSet } from '@domain/access/role-set'; +import { TemplatesManagerAuthorizationService } from '@domain/template/templates-manager/templates.manager.service.authorization'; import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; @Injectable() @@ -51,7 +51,7 @@ export class SpaceAuthorizationService { private contextAuthorizationService: ContextAuthorizationService, private communityAuthorizationService: CommunityAuthorizationService, private collaborationAuthorizationService: CollaborationAuthorizationService, - private templatesSetAuthorizationService: TemplatesSetAuthorizationService, + private templatesManagerAuthorizationService: TemplatesManagerAuthorizationService, private spaceService: SpaceService, private spaceSettingsService: SpaceSettingsService, @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService @@ -78,8 +78,7 @@ export class SpaceAuthorizationService { profile: true, storageAggregator: true, subspaces: true, - library: true, - defaults: true, + templatesManager: true, }, }); if ( @@ -207,7 +206,6 @@ export class SpaceAuthorizationService { const childAuthorzations = await this.propagateAuthorizationToChildEntities( space, levelZeroSpaceAgent, - space.community.roleSet, spaceSettings, spaceMembershipAllowed ); @@ -247,7 +245,6 @@ export class SpaceAuthorizationService { public async propagateAuthorizationToChildEntities( space: ISpace, levelZeroSpaceAgent: IAgent, - roleSet: IRoleSet, spaceSettings: ISpaceSettings, spaceMembershipAllowed: boolean ): Promise { @@ -308,26 +305,19 @@ export class SpaceAuthorizationService { // Level zero space only entities if (space.level === SpaceLevel.SPACE) { - if (!space.library || !space.defaults) { + if (!space.templatesManager) { throw new RelationshipNotFoundException( - `Unable to load space level zero entities on auth reset for space base ${space.id} `, + `Unable to load templatesManager on level zero space for auth reset ${space.id} `, LogContext.SPACES ); } - const libraryAuthorizations = - await this.templatesSetAuthorizationService.applyAuthorizationPolicy( - space.library, - space.authorization - ); - updatedAuthorizations.push(...libraryAuthorizations); - - const defaultsAuthorizations = - this.authorizationPolicyService.inheritParentAuthorization( - space.defaults.authorization, + const templatesManagerAuthorizations = + await this.templatesManagerAuthorizationService.applyAuthorizationPolicy( + space.templatesManager.id, space.authorization ); - updatedAuthorizations.push(defaultsAuthorizations); + updatedAuthorizations.push(...templatesManagerAuthorizations); } /// For fields that always should be available diff --git a/src/domain/space/space/space.service.spec.ts b/src/domain/space/space/space.service.spec.ts index a9e5fc10b7..d870dd0779 100644 --- a/src/domain/space/space/space.service.spec.ts +++ b/src/domain/space/space/space.service.spec.ts @@ -110,6 +110,7 @@ const getSubspacesMock = ( visibility: SpaceVisibility.ACTIVE, collaboration: { id: '', + isTemplate: false, groupsStr: JSON.stringify([ { displayName: 'HOME', @@ -205,6 +206,7 @@ const getSubsubspacesMock = (subsubspaceId: string, count: number): Space[] => { visibility: SpaceVisibility.ACTIVE, collaboration: { id: '', + isTemplate: false, groupsStr: JSON.stringify([ { displayName: 'HOME', diff --git a/src/domain/space/space/space.service.ts b/src/domain/space/space/space.service.ts index d31da861f8..70cacce356 100644 --- a/src/domain/space/space/space.service.ts +++ b/src/domain/space/space/space.service.ts @@ -58,9 +58,6 @@ import { UpdateSpaceSettingsInput } from './dto/space.dto.update.settings'; import { IContributor } from '@domain/community/contributor/contributor.interface'; import { CommunityContributorType } from '@common/enums/community.contributor.type'; import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface'; -import { TemplatesSetService } from '@domain/template/templates-set/templates.set.service'; -import { ITemplatesSet } from '@domain/template/templates-set/templates.set.interface'; -import { ISpaceDefaults } from '../space.defaults/space.defaults.interface'; import { AgentType } from '@common/enums/agent.type'; import { StorageAggregatorType } from '@common/enums/storage.aggregator.type'; import { AccountHostService } from '../account.host/account.host.service'; @@ -74,12 +71,13 @@ import { LicensingService } from '@platform/licensing/licensing.service'; import { LicensePlanType } from '@common/enums/license.plan.type'; import { TemplateType } from '@common/enums/template.type'; import { CreateCollaborationInput } from '@domain/collaboration/collaboration/dto/collaboration.dto.create'; -import { CreateInnovationFlowInput } from '@domain/collaboration/innovation-flow/dto/innovation.flow.dto.create'; -import { TemplateService } from '@domain/template/template/template.service'; -import { templatesSetDefaults } from '../space.defaults/definitions/space.defaults.templates'; -import { InputCreatorService } from '@services/api/input-creator/input.creator.service'; import { RoleSetService } from '@domain/access/role-set/role.set.service'; import { IRoleSet } from '@domain/access/role-set/role.set.interface'; +import { TemplatesManagerService } from '@domain/template/templates-manager/templates.manager.service'; +import { CreateTemplateDefaultInput } from '@domain/template/template-default/dto/template.default.dto.create'; +import { TemplateDefaultType } from '@common/enums/template.default.type'; +import { CreateTemplatesManagerInput } from '@domain/template/templates-manager/dto/templates.manager.dto.create'; +import { ITemplatesManager } from '@domain/template/templates-manager'; import { Activity } from '@platform/activity'; const EXPLORE_SPACES_LIMIT = 30; @@ -100,12 +98,10 @@ export class SpaceService { private spaceSettingsService: SpaceSettingsService, private spaceDefaultsService: SpaceDefaultsService, private storageAggregatorService: StorageAggregatorService, - private templatesSetService: TemplatesSetService, + private templatesManagerService: TemplatesManagerService, private collaborationService: CollaborationService, private licensingService: LicensingService, private licenseEngineService: LicenseEngineService, - private templateService: TemplateService, - private inputCreatorService: InputCreatorService, @InjectRepository(Space) private spaceRepository: Repository, @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService @@ -113,7 +109,6 @@ export class SpaceService { public async createSpace( spaceData: CreateSpaceInput, - spaceDefaults?: ISpaceDefaults, agentInfo?: AgentInfo ): Promise { if (!spaceData.type) { @@ -215,29 +210,16 @@ export class SpaceService { space.levelZeroSpaceID = space.id; } - //// Collaboration - const collaborationData: CreateCollaborationInput = + // Collaboration: + let collaborationData: CreateCollaborationInput = spaceData.collaborationData; - if (!collaborationData.innovationFlowData) { - // TODO: need to pick up the default template + innovation flow properly - collaborationData.innovationFlowData = - await this.getDefaultInnovationStates(space.type, spaceDefaults); - } - if (!collaborationData.calloutsData) { - collaborationData.calloutsData = []; - } - const addDefaultCallouts = spaceData.collaborationData.addDefaultCallouts; - if (addDefaultCallouts === undefined || addDefaultCallouts) { - const defaultCallouts = this.spaceDefaultsService.getDefaultCallouts( + collaborationData.isTemplate = false; + // Pick up the default template that is applicable + collaborationData = + await this.spaceDefaultsService.createCollaborationInput( + collaborationData, space.type ); - collaborationData.calloutsData.push(...defaultCallouts); - } - - collaborationData.calloutGroups = - this.spaceDefaultsService.getCalloutGroups(space.type); - collaborationData.defaultCalloutGroupName = - this.spaceDefaultsService.getCalloutGroupDefault(space.type); space.collaboration = await this.collaborationService.createCollaboration( collaborationData, space.storageAggregator, @@ -249,10 +231,10 @@ export class SpaceService { }); if (space.level === SpaceLevel.SPACE) { - await this.addLevelZeroSpaceEntities(space); + space.templatesManager = await this.createTemplatesManager(); } - ////// Community + // Community: // set immediate community parent + resourceID if (!space.community || !space.community.roleSet) { throw new RelationshipNotFoundException( @@ -269,41 +251,20 @@ export class SpaceService { return await this.save(space); } - private async addDefaultTemplatesToSpaceLibrary( - templatesSet: ITemplatesSet, - storageAggregator: IStorageAggregator - ): Promise { - return await this.templatesSetService.addTemplates( - templatesSet, - templatesSetDefaults.posts, - templatesSetDefaults.innovationFlows, - storageAggregator - ); - } - - private async addLevelZeroSpaceEntities(space: ISpace) { - if (!space.storageAggregator) { - throw new EntityNotInitializedException( - `'storage aggregator not set on level zero space '${space.id}'`, - LogContext.SPACES - ); - } - space.library = await this.templatesSetService.createTemplatesSet(); - space.defaults = await this.spaceDefaultsService.createSpaceDefaults(); + private async createTemplatesManager(): Promise { + const templateDefaultData: CreateTemplateDefaultInput = { + type: TemplateDefaultType.SPACE_SUBSPACE, + allowedTemplateType: TemplateType.COLLABORATION, + }; + const templatesManagerData: CreateTemplatesManagerInput = { + templateDefaultsData: [templateDefaultData], + }; - // And set the defaults - space.library = await this.addDefaultTemplatesToSpaceLibrary( - space.library, - space.storageAggregator - ); - if (space.defaults && space.library && space.library.templates) { - const innovationFlowTemplates = space.library.templates.filter( - template => template.type === TemplateType.INNOVATION_FLOW + const templatesManager = + await this.templatesManagerService.createTemplatesManager( + templatesManagerData ); - if (innovationFlowTemplates.length > 0) { - space.defaults.innovationFlowTemplate = innovationFlowTemplates[0]; - } - } + return templatesManager; } async save(space: ISpace): Promise { @@ -320,8 +281,7 @@ export class SpaceService { agent: true, profile: true, storageAggregator: true, - library: true, - defaults: true, + templatesManager: true, }, }); @@ -357,14 +317,15 @@ export class SpaceService { await this.authorizationPolicyService.delete(space.authorization); if (space.level === SpaceLevel.SPACE) { - if (!space.library || !space.defaults) { + if (!space.templatesManager) { throw new RelationshipNotFoundException( `Unable to load entities to delete base subspace: ${space.id} `, LogContext.SPACES ); } - await this.templatesSetService.deleteTemplatesSet(space.library.id); - await this.spaceDefaultsService.deleteSpaceDefaults(space.defaults.id); + await this.templatesManagerService.deleteTemplatesManager( + space.templatesManager.id + ); } await this.storageAggregatorService.delete(space.storageAggregator.id); @@ -374,54 +335,6 @@ export class SpaceService { return result; } - private async getDefaultInnovationStates( - spaceType: SpaceType, - spaceDefaults?: ISpaceDefaults - ): Promise { - if ( - spaceDefaults && - (spaceType === SpaceType.CHALLENGE || spaceType === SpaceType.OPPORTUNITY) - ) { - // If no argument is provided, then use the default template for the space, if set - // for spaces of type challenge or opportunity - const innovationFlowTemplateID = spaceDefaults.innovationFlowTemplate?.id; - if (innovationFlowTemplateID) { - const template = await this.templateService.getTemplateOrFail( - innovationFlowTemplateID, - { - relations: { - innovationFlow: { - profile: true, - }, - }, - } - ); - const innovationFlow = template.innovationFlow; - if (!innovationFlow) { - throw new RelationshipNotFoundException( - `unable to get innovation flow for template ${template.id}`, - LogContext.SPACES - ); - } - return await this.inputCreatorService.buildCreateInnovationFlowInputFromInnovationFlow( - innovationFlow - ); - } - } - - // If no default template is set, then pick up the default based on the specified type - const innovationFlowStatesDefault = - this.spaceDefaultsService.getDefaultInnovationFlowStates(spaceType); - const result: CreateInnovationFlowInput = { - profile: { - displayName: 'default', - description: 'default flow', - }, - states: innovationFlowStatesDefault, - }; - return result; - } - public async getSpacesForInnovationHub({ id, type, @@ -990,17 +903,11 @@ export class SpaceService { ); } } - // Get the defaults to use - const spaceDefaults = await this.getDefaultsOrFail(space.levelZeroSpaceID); // Update the subspace data being passed in to set the storage aggregator to use subspaceData.storageAggregatorParent = space.storageAggregator; subspaceData.level = space.level + 1; - let subspace = await this.createSpace( - subspaceData, - spaceDefaults, - agentInfo - ); + let subspace = await this.createSpace(subspaceData, agentInfo); subspace = await this.addSubspaceToSpace(space, subspace); subspace = await this.save(subspace); @@ -1308,22 +1215,23 @@ export class SpaceService { return community.roleSet; } - async getLibraryOrFail(rootSpaceID: string): Promise { - const levelZeroSpaceWithLibrary = await this.getSpaceOrFail(rootSpaceID, { + async getTemplatesManagerOrFail( + rootSpaceID: string + ): Promise { + const levelZeroSpace = await this.getSpaceOrFail(rootSpaceID, { relations: { - library: true, + templatesManager: true, }, }); - const templatesSet = levelZeroSpaceWithLibrary.library; - if (!templatesSet) { + if (!levelZeroSpace?.templatesManager) { throw new EntityNotFoundException( - `Unable to find templatesSet for level zero space with id: ${rootSpaceID}`, - LogContext.ACCOUNT + `Unable to find templatesManager for level zero space with id: ${rootSpaceID}`, + LogContext.SPACES ); } - return templatesSet; + return levelZeroSpace.templatesManager; } public async activeSubscription( @@ -1353,28 +1261,6 @@ export class SpaceService { .sort((a, b) => b.plan!.sortOrder - a.plan!.sortOrder)?.[0]?.subscription; } - async getDefaultsOrFail(rootSpaceID: string): Promise { - const levelZeroSpaceWithDefaults = await this.getSpaceOrFail(rootSpaceID, { - relations: { - defaults: { - innovationFlowTemplate: { - profile: true, - }, - }, - }, - }); - const defaults = levelZeroSpaceWithDefaults.defaults; - - if (!defaults) { - throw new EntityNotFoundException( - `Unable to find Defaults for level zero space with id: ${rootSpaceID}`, - LogContext.ACCOUNT - ); - } - - return defaults; - } - public async getProvider(spaceInput: ISpace): Promise { const space = await this.spaceRepository.findOne({ where: { diff --git a/src/domain/template/template-applier/dto/template.applier.dto.update.collaboration.ts b/src/domain/template/template-applier/dto/template.applier.dto.update.collaboration.ts new file mode 100644 index 0000000000..84c9c24678 --- /dev/null +++ b/src/domain/template/template-applier/dto/template.applier.dto.update.collaboration.ts @@ -0,0 +1,22 @@ +import { Field, InputType } from '@nestjs/graphql'; +import { UUID } from '@domain/common/scalars/scalar.uuid'; + +@InputType() +export class UpdateCollaborationFromTemplateInput { + @Field(() => UUID, { + description: 'ID of the Collaboration to be updated', + }) + collaborationID!: string; + + @Field(() => UUID, { + nullable: false, + description: + 'The Collaboration Template that will be used for updates to the Collaboration', + }) + collaborationTemplateID!: string; + + @Field(() => Boolean, { + description: 'Add the Callouts from the Collaboration Template', + }) + addCallouts = false; +} diff --git a/src/domain/template/template-applier/template.applier.module.ts b/src/domain/template/template-applier/template.applier.module.ts new file mode 100644 index 0000000000..7304a8995d --- /dev/null +++ b/src/domain/template/template-applier/template.applier.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { TemplateApplierService } from './template.applier.service'; +import { TemplateApplierResolverMutations } from './template.applier.resolver.mutations'; +import { TemplateModule } from '../template/template.module'; +import { AuthorizationModule } from '@core/authorization/authorization.module'; +import { CollaborationModule } from '@domain/collaboration/collaboration/collaboration.module'; +import { InnovationFlowModule } from '@domain/collaboration/innovation-flow/innovation.flow.module'; +import { InputCreatorModule } from '@services/api/input-creator/input.creator.module'; +import { StorageAggregatorResolverModule } from '@services/infrastructure/storage-aggregator-resolver/storage.aggregator.resolver.module'; +import { NamingModule } from '@services/infrastructure/naming/naming.module'; +import { CalloutModule } from '@domain/collaboration/callout/callout.module'; +import { AuthorizationPolicyModule } from '@domain/common/authorization-policy/authorization.policy.module'; + +@Module({ + imports: [ + AuthorizationPolicyModule, + AuthorizationModule, + TemplateModule, + CollaborationModule, + InnovationFlowModule, + InputCreatorModule, + StorageAggregatorResolverModule, + CalloutModule, + NamingModule, + ], + providers: [TemplateApplierService, TemplateApplierResolverMutations], + exports: [], +}) +export class TemplateApplierModule {} diff --git a/src/domain/template/template-applier/template.applier.resolver.mutations.ts b/src/domain/template/template-applier/template.applier.resolver.mutations.ts new file mode 100644 index 0000000000..032e9a748d --- /dev/null +++ b/src/domain/template/template-applier/template.applier.resolver.mutations.ts @@ -0,0 +1,76 @@ +import { Inject, LoggerService, UseGuards } from '@nestjs/common'; +import { Args, Mutation, Resolver } from '@nestjs/graphql'; +import { AuthorizationService } from '@core/authorization/authorization.service'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { GraphqlGuard } from '@core/authorization/graphql.guard'; +import { CurrentUser } from '@common/decorators/current-user.decorator'; +import { AgentInfo } from '@core/authentication.agent.info/agent.info'; +import { AuthorizationPrivilege } from '@common/enums/authorization.privilege'; +import { UpdateCollaborationFromTemplateInput } from './dto/template.applier.dto.update.collaboration'; +import { TemplateApplierService } from './template.applier.service'; +import { CollaborationService } from '@domain/collaboration/collaboration/collaboration.service'; +import { ICollaboration } from '@domain/collaboration/collaboration'; +import { StorageAggregatorResolverService } from '@services/infrastructure/storage-aggregator-resolver/storage.aggregator.resolver.service'; + +@Resolver() +export class TemplateApplierResolverMutations { + constructor( + private authorizationService: AuthorizationService, + private collaborationService: CollaborationService, + private templateApplierService: TemplateApplierService, + private storageAggregatorResolverService: StorageAggregatorResolverService, + @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService + ) {} + + @UseGuards(GraphqlGuard) + @Mutation(() => ICollaboration, { + description: + 'Updates the Collaboration, including InnovationFlow states, from the specified Collaboration Template.', + }) + async updateCollaborationFromTemplate( + @CurrentUser() agentInfo: AgentInfo, + @Args('updateData') + updateData: UpdateCollaborationFromTemplateInput + ): Promise { + const collaboration = + await this.collaborationService.getCollaborationOrFail( + updateData.collaborationID, + { + relations: { + tagsetTemplateSet: true, + callouts: { + framing: { + profile: { + tagsets: true, + }, + }, + }, + innovationFlow: { + profile: { + tagsets: true, + }, + }, + }, + } + ); + + await this.authorizationService.grantAccessOrFail( + agentInfo, + collaboration.authorization, + AuthorizationPrivilege.UPDATE, + `update InnovationFlow states from template: ${collaboration.id}` + ); + + const storageAggregator = + await this.storageAggregatorResolverService.getStorageAggregatorForCollaboration( + collaboration.id + ); + + return await this.templateApplierService.updateCollaborationFromTemplate( + updateData, + collaboration, + storageAggregator, + agentInfo.userID + ); + } +} diff --git a/src/domain/template/template-applier/template.applier.service.ts b/src/domain/template/template-applier/template.applier.service.ts new file mode 100644 index 0000000000..cf27ce2674 --- /dev/null +++ b/src/domain/template/template-applier/template.applier.service.ts @@ -0,0 +1,130 @@ +import { Injectable } from '@nestjs/common'; +import { UpdateCollaborationFromTemplateInput } from './dto/template.applier.dto.update.collaboration'; +import { TemplateService } from '../template/template.service'; +import { InnovationFlowService } from '@domain/collaboration/innovation-flow/innovation.flow.service'; +import { ICollaboration } from '@domain/collaboration/collaboration/collaboration.interface'; +import { CollaborationService } from '@domain/collaboration/collaboration/collaboration.service'; +import { RelationshipNotFoundException } from '@common/exceptions'; +import { LogContext } from '@common/enums/logging.context'; +import { InputCreatorService } from '@services/api/input-creator/input.creator.service'; +import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface'; +import { CalloutAuthorizationService } from '@domain/collaboration/callout/callout.service.authorization'; +import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service'; +import { IAuthorizationPolicy } from '@domain/common/authorization-policy'; +import { NamingService } from '@services/infrastructure/naming/naming.service'; +import { TagsetReservedName } from '@common/enums/tagset.reserved.name'; + +@Injectable() +export class TemplateApplierService { + constructor( + private templateService: TemplateService, + private innovationFlowService: InnovationFlowService, + private collaborationService: CollaborationService, + private inputCreatorService: InputCreatorService, + private calloutAuthorizationService: CalloutAuthorizationService, + private namingService: NamingService, + private authorizationPolicyService: AuthorizationPolicyService + ) {} + + async updateCollaborationFromTemplate( + updateData: UpdateCollaborationFromTemplateInput, + targetCollaboration: ICollaboration, + storageAggregator: IStorageAggregator, + userID: string + ): Promise { + const collaborationTemplate = await this.templateService.getCollaboration( + updateData.collaborationTemplateID + ); + const collaborationFromTemplate = + await this.collaborationService.getCollaborationOrFail( + collaborationTemplate.id, + { + relations: { + innovationFlow: true, + callouts: true, + }, + } + ); + if ( + !collaborationFromTemplate.innovationFlow || + !targetCollaboration.innovationFlow || + !targetCollaboration.callouts || + !targetCollaboration.authorization + ) { + throw new RelationshipNotFoundException( + `Template cannot be applied on uninitialized collaboration templateId:'${collaborationTemplate.id}' TargetCollaboration.id='${targetCollaboration.id}'`, + LogContext.TEMPLATES + ); + } + const newStatesStr = collaborationFromTemplate.innovationFlow.states; + targetCollaboration.innovationFlow = + await this.innovationFlowService.updateInnovationFlowStates( + targetCollaboration.innovationFlow.id, + newStatesStr + ); + + if (updateData.addCallouts) { + const calloutsFromTemplate = + await this.inputCreatorService.buildCreateCalloutInputsFromCallouts( + collaborationFromTemplate.callouts ?? [] + ); + + const newCallouts = await this.collaborationService.addCallouts( + targetCollaboration, + calloutsFromTemplate, + storageAggregator, + userID + ); + targetCollaboration.callouts?.push(...newCallouts); + + const defaultGroupName = + targetCollaboration.tagsetTemplateSet?.tagsetTemplates.find( + tagset => tagset.name === TagsetReservedName.CALLOUT_GROUP + )?.defaultSelectedValue; + const validGroupNames = + targetCollaboration.tagsetTemplateSet?.tagsetTemplates.find( + tagset => tagset.name === TagsetReservedName.CALLOUT_GROUP + )?.allowedValues; + const defaultFlowState = this.innovationFlowService.getStates( + targetCollaboration.innovationFlow + )?.[0].displayName; + const validFlowStates = this.innovationFlowService + .getStates(targetCollaboration.innovationFlow) + ?.map(state => state.displayName); + + this.collaborationService.moveCalloutsToCorrectGroupAndState( + defaultGroupName, + defaultFlowState, + validGroupNames ?? [], + validFlowStates ?? [], + targetCollaboration.callouts + ); + + const authorizations: IAuthorizationPolicy[] = []; + + const { roleSet: communityPolicy, spaceSettings } = + await this.namingService.getRoleSetAndSettingsForCollaboration( + targetCollaboration.id + ); + + // Need to save before applying authorization policy to get the callout ids + const result = await this.collaborationService.save(targetCollaboration); + + for (const callout of newCallouts) { + const calloutAuthorizations = + await this.calloutAuthorizationService.applyAuthorizationPolicy( + callout.id, + targetCollaboration.authorization, + communityPolicy, + spaceSettings + ); + authorizations.push(...calloutAuthorizations); + } + await this.authorizationPolicyService.saveAll(authorizations); + + return result; + } else { + return await this.collaborationService.save(targetCollaboration); + } + } +} diff --git a/src/domain/template/template-default/dto/template.default.dto.create.ts b/src/domain/template/template-default/dto/template.default.dto.create.ts new file mode 100644 index 0000000000..dd67acbbe1 --- /dev/null +++ b/src/domain/template/template-default/dto/template.default.dto.create.ts @@ -0,0 +1,11 @@ +import { TemplateDefaultType } from '@common/enums/template.default.type'; +import { TemplateType } from '@common/enums/template.type'; +import { ITemplate } from '@domain/template/template/template.interface'; + +export class CreateTemplateDefaultInput { + type!: TemplateDefaultType; + + template?: ITemplate; + + allowedTemplateType!: TemplateType; +} diff --git a/src/domain/template/template-default/dto/template.default.dto.update.ts b/src/domain/template/template-default/dto/template.default.dto.update.ts new file mode 100644 index 0000000000..cdb8f5efec --- /dev/null +++ b/src/domain/template/template-default/dto/template.default.dto.update.ts @@ -0,0 +1,17 @@ +import { InputType, Field } from '@nestjs/graphql'; +import { UUID } from '@domain/common/scalars/scalar.uuid'; + +@InputType() +export class UpdateTemplateDefaultTemplateInput { + @Field(() => UUID, { + nullable: false, + description: 'The identifier for the TemplateDefault to be updated.', + }) + templateDefaultID!: string; + + @Field(() => UUID, { + nullable: false, + description: 'The ID for the Template to use.', + }) + templateID!: string; +} diff --git a/src/domain/template/template-default/template.default.entity.ts b/src/domain/template/template-default/template.default.entity.ts new file mode 100644 index 0000000000..d46aa12d69 --- /dev/null +++ b/src/domain/template/template-default/template.default.entity.ts @@ -0,0 +1,41 @@ +import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm'; +import { ITemplateDefault } from './template.default.interface'; +import { ENUM_LENGTH } from '@common/constants/entity.field.length.constants'; +import { TemplateDefaultType } from '@common/enums/template.default.type'; +import { Template } from '../template/template.entity'; +import { AuthorizableEntity } from '@domain/common/entity/authorizable-entity'; +import { TemplateType } from '@common/enums/template.type'; +import { TemplatesManager } from '../templates-manager/templates.manager.entity'; +import { IsEnum } from 'class-validator'; + +@Entity() +export class TemplateDefault + extends AuthorizableEntity + implements ITemplateDefault +{ + @ManyToOne( + () => TemplatesManager, + templatesManager => templatesManager.templateDefaults, + { + eager: false, + cascade: false, + onDelete: 'NO ACTION', + } + ) + templatesManager?: TemplatesManager; + + @Column('varchar', { length: ENUM_LENGTH, nullable: false }) + type!: TemplateDefaultType; + + @OneToOne(() => Template, { + eager: true, + cascade: false, // important not to cascade + onDelete: 'SET NULL', + }) + @JoinColumn() + template?: Template; + + @Column('varchar', { length: ENUM_LENGTH, nullable: false }) + @IsEnum(TemplateType) + allowedTemplateType!: TemplateType; +} diff --git a/src/domain/template/template-default/template.default.interface.ts b/src/domain/template/template-default/template.default.interface.ts new file mode 100644 index 0000000000..5fdc629cde --- /dev/null +++ b/src/domain/template/template-default/template.default.interface.ts @@ -0,0 +1,29 @@ +import { TemplateDefaultType } from '@common/enums/template.default.type'; +import { Field, ObjectType } from '@nestjs/graphql'; +import { ITemplate } from '../template/template.interface'; +import { TemplateType } from '@common/enums/template.type'; +import { IAuthorizable } from '@domain/common/entity/authorizable-entity'; +import { ITemplatesManager } from '../templates-manager'; + +@ObjectType('TemplateDefault') +export abstract class ITemplateDefault extends IAuthorizable { + @Field(() => TemplateDefaultType, { + nullable: false, + description: 'The type of this TemplateDefault.', + }) + type!: TemplateDefaultType; + + @Field(() => ITemplate, { + nullable: true, + description: 'The template accessible via this TemplateDefault, if any.', + }) + template?: ITemplate; + + @Field(() => TemplateType, { + nullable: false, + description: 'The type of any Template stored here.', + }) + allowedTemplateType!: TemplateType; + + templatesManager?: ITemplatesManager; +} diff --git a/src/domain/template/template-default/template.default.module.ts b/src/domain/template/template-default/template.default.module.ts new file mode 100644 index 0000000000..5ab6498bb2 --- /dev/null +++ b/src/domain/template/template-default/template.default.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TemplateDefault } from './template.default.entity'; +import { TemplateDefaultService } from './template.default.service'; +import { AuthorizationModule } from '@core/authorization/authorization.module'; +import { TemplateModule } from '../template/template.module'; +import { TemplateDefaultAuthorizationService } from './template.default.service.authorization'; +import { AuthorizationPolicyModule } from '@domain/common/authorization-policy/authorization.policy.module'; + +@Module({ + imports: [ + AuthorizationModule, + AuthorizationPolicyModule, + TemplateModule, + TypeOrmModule.forFeature([TemplateDefault]), + ], + providers: [TemplateDefaultService, TemplateDefaultAuthorizationService], + exports: [TemplateDefaultService, TemplateDefaultAuthorizationService], +}) +export class TemplateDefaultModule {} diff --git a/src/domain/template/template-default/template.default.service.authorization.ts b/src/domain/template/template-default/template.default.service.authorization.ts new file mode 100644 index 0000000000..ef82920e9e --- /dev/null +++ b/src/domain/template/template-default/template.default.service.authorization.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service'; +import { IAuthorizationPolicy } from '@domain/common/authorization-policy/authorization.policy.interface'; +import { ITemplateDefault } from './template.default.interface'; + +@Injectable() +export class TemplateDefaultAuthorizationService { + constructor(private authorizationPolicyService: AuthorizationPolicyService) {} + + applyAuthorizationPolicy( + templateDefault: ITemplateDefault, + parentAuthorization: IAuthorizationPolicy | undefined + ): IAuthorizationPolicy { + templateDefault.authorization = + this.authorizationPolicyService.inheritParentAuthorization( + templateDefault.authorization, + parentAuthorization + ); + + return templateDefault.authorization; + } +} diff --git a/src/domain/template/template-default/template.default.service.ts b/src/domain/template/template-default/template.default.service.ts new file mode 100644 index 0000000000..49b5f73178 --- /dev/null +++ b/src/domain/template/template-default/template.default.service.ts @@ -0,0 +1,95 @@ +import { Inject, Injectable, LoggerService } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston'; +import { FindOneOptions, Repository } from 'typeorm'; +import { TemplateDefault } from './template.default.entity'; +import { ITemplateDefault } from './template.default.interface'; +import { CreateTemplateDefaultInput } from './dto/template.default.dto.create'; +import { UpdateTemplateDefaultTemplateInput } from './dto/template.default.dto.update'; +import { TemplateService } from '../template/template.service'; +import { + EntityNotFoundException, + ValidationException, +} from '@common/exceptions'; +import { LogContext } from '@common/enums'; +import { UUID_LENGTH } from '@common/constants/entity.field.length.constants'; +import { AuthorizationPolicy } from '@domain/common/authorization-policy/authorization.policy.entity'; +import { AuthorizationPolicyType } from '@common/enums/authorization.policy.type'; + +@Injectable() +export class TemplateDefaultService { + constructor( + @InjectRepository(TemplateDefault) + private templateDefaultRepository: Repository, + private templateService: TemplateService, + @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService + ) {} + + public createTemplateDefault( + templateDefaultData: CreateTemplateDefaultInput + ): ITemplateDefault { + const templateDefault: ITemplateDefault = new TemplateDefault(); + + templateDefault.authorization = new AuthorizationPolicy( + AuthorizationPolicyType.TEMPLATE_DEFAULT + ); + + templateDefault.type = templateDefaultData.type; + templateDefault.template = templateDefaultData.template; + templateDefault.allowedTemplateType = + templateDefaultData.allowedTemplateType; + + return templateDefault; + } + + public async updateTemplateDefaultTemplate( + templateDefault: ITemplateDefault, + templateDefaultData: UpdateTemplateDefaultTemplateInput + ): Promise { + const template = await this.templateService.getTemplateOrFail( + templateDefaultData.templateID + ); + if (template.type !== templateDefault.allowedTemplateType) { + throw new ValidationException( + `Template type(${template.type}) does not match allowed template type(${templateDefault.allowedTemplateType})`, + LogContext.TEMPLATES + ); + } + templateDefault.template = template; + + return await this.save(templateDefault); + } + + public async getTemplateDefaultOrFail( + templateDefaultID: string, + options?: FindOneOptions + ): Promise { + let templateDefault: ITemplateDefault | null = null; + if (templateDefaultID.length === UUID_LENGTH) { + templateDefault = await this.templateDefaultRepository.findOne({ + where: { id: templateDefaultID }, + ...options, + }); + } + + if (!templateDefault) + throw new EntityNotFoundException( + `No TemplateDefault found with the given id: ${templateDefaultID}`, + LogContext.TEMPLATES + ); + return templateDefault; + } + + public async removeTemplateDefault( + templateDefault: ITemplateDefault + ): Promise { + await this.templateDefaultRepository.remove( + templateDefault as TemplateDefault + ); + return true; + } + + public save(templateDefault: ITemplateDefault): Promise { + return this.templateDefaultRepository.save(templateDefault); + } +} diff --git a/src/domain/template/template/dto/template.dto.create.base.ts b/src/domain/template/template/dto/template.dto.create.base.ts new file mode 100644 index 0000000000..172aef60f0 --- /dev/null +++ b/src/domain/template/template/dto/template.dto.create.base.ts @@ -0,0 +1,14 @@ +import { CreateNameableInput } from '@domain/common/entity/nameable-entity'; +import { Field, InputType } from '@nestjs/graphql'; +import { IsOptional } from 'class-validator'; + +@InputType() +export class CreateTemplateBaseInput extends CreateNameableInput { + @Field(() => [String], { nullable: true }) + @IsOptional() + tags?: string[]; + + @Field(() => String, { nullable: true }) + @IsOptional() + visualUri?: string; +} diff --git a/src/domain/template/template/dto/template.dto.create.ts b/src/domain/template/template/dto/template.dto.create.ts index 940450b978..cd70693dbb 100644 --- a/src/domain/template/template/dto/template.dto.create.ts +++ b/src/domain/template/template/dto/template.dto.create.ts @@ -2,28 +2,19 @@ import { SMALL_TEXT_LENGTH, VERY_LONG_TEXT_LENGTH, } from '@common/constants/entity.field.length.constants'; -import { TemplateType } from '@common/enums/template.type'; import { CreateCalloutInput } from '@domain/collaboration/callout'; import { CreateCollaborationInput } from '@domain/collaboration/collaboration/dto/collaboration.dto.create'; -import { CreateInnovationFlowInput } from '@domain/collaboration/innovation-flow/dto/innovation.flow.dto.create'; -import { CreateNameableInput } from '@domain/common/entity/nameable-entity'; import { Markdown } from '@domain/common/scalars/scalar.markdown'; import { CreateWhiteboardInput } from '@domain/common/whiteboard/dto/whiteboard.dto.create'; import { CreateCommunityGuidelinesInput } from '@domain/community/community-guidelines/dto/community.guidelines.dto.create'; import { Field, InputType } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { IsOptional, MaxLength, ValidateNested } from 'class-validator'; +import { CreateTemplateBaseInput } from './template.dto.create.base'; +import { TemplateType } from '@common/enums/template.type'; @InputType() -export class CreateTemplateInput extends CreateNameableInput { - @Field(() => [String], { nullable: true }) - @IsOptional() - tags?: string[]; - - @Field(() => String, { nullable: true }) - @IsOptional() - visualUri?: string; - +export class CreateTemplateInput extends CreateTemplateBaseInput { @Field(() => TemplateType, { nullable: false, description: 'The type of the Template to be created.', @@ -39,12 +30,6 @@ export class CreateTemplateInput extends CreateNameableInput { @MaxLength(VERY_LONG_TEXT_LENGTH) postDefaultDescription?: string; - @Field(() => CreateInnovationFlowInput, { nullable: true }) - @IsOptional() - @ValidateNested() - @Type(() => CreateInnovationFlowInput) - innovationFlowData?: CreateInnovationFlowInput; - @Field(() => CreateCommunityGuidelinesInput, { nullable: true, description: 'The Community guidelines to associate with this template.', diff --git a/src/domain/template/template/dto/template.dto.update.innovation.flow.ts b/src/domain/template/template/dto/template.dto.update.innovation.flow.ts deleted file mode 100644 index f61cd84d59..0000000000 --- a/src/domain/template/template/dto/template.dto.update.innovation.flow.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Field, InputType } from '@nestjs/graphql'; -import { IsOptional } from 'class-validator'; -import { UUID } from '@domain/common/scalars/scalar.uuid'; - -@InputType() -export class UpdateInnovationFlowFromTemplateInput { - @Field(() => UUID, { - description: 'ID of the Innovation Flow', - }) - @IsOptional() - innovationFlowID!: string; - - @Field(() => UUID, { - nullable: false, - description: - 'The InnovationFlow template whose State definition will be used for the Innovation Flow', - }) - innovationFlowTemplateID!: string; -} diff --git a/src/domain/template/template/dto/template.dto.update.ts b/src/domain/template/template/dto/template.dto.update.ts index 700be3ba45..70a10e0a4c 100644 --- a/src/domain/template/template/dto/template.dto.update.ts +++ b/src/domain/template/template/dto/template.dto.update.ts @@ -1,11 +1,8 @@ import { VERY_LONG_TEXT_LENGTH } from '@common/constants/entity.field.length.constants'; -import { UpdateCalloutInput } from '@domain/collaboration/callout/dto/callout.dto.update'; -import { UpdateInnovationFlowInput } from '@domain/collaboration/innovation-flow/dto/innovation.flow.dto.update'; import { UpdateBaseAlkemioInput } from '@domain/common/entity/base-entity/dto/base.alkemio.dto.update'; import { UpdateProfileInput } from '@domain/common/profile/dto/profile.dto.update'; import { Markdown } from '@domain/common/scalars/scalar.markdown'; -import { UpdateWhiteboardInput } from '@domain/common/whiteboard/dto/whiteboard.dto.update'; -import { UpdateCommunityGuidelinesInput } from '@domain/community/community-guidelines/dto/community.guidelines.dto.update'; +import { WhiteboardContent } from '@domain/common/scalars/scalar.whiteboard.content'; import { Field, InputType } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { IsOptional, MaxLength, ValidateNested } from 'class-validator'; @@ -30,32 +27,10 @@ export class UpdateTemplateInput extends UpdateBaseAlkemioInput { @MaxLength(VERY_LONG_TEXT_LENGTH) postDefaultDescription!: string; - @Field(() => UpdateInnovationFlowInput, { nullable: true }) - @ValidateNested({ each: true }) - @Type(() => UpdateInnovationFlowInput) - innovationFlow!: UpdateInnovationFlowInput; - - @Field(() => UpdateCommunityGuidelinesInput, { - nullable: true, - description: 'The Community guidelines to associate with this template.', - }) - @IsOptional() - @Type(() => UpdateCommunityGuidelinesInput) - communityGuidelines?: UpdateCommunityGuidelinesInput; - - @Field(() => UpdateCalloutInput, { - nullable: true, - description: 'The Callout for this template.', - }) - @IsOptional() - @Type(() => UpdateCalloutInput) - callout?: UpdateCalloutInput; - - @Field(() => UpdateWhiteboardInput, { + @Field(() => WhiteboardContent, { nullable: true, - description: 'The Whiteboard for this template.', + description: 'The new content to be used.', }) @IsOptional() - @Type(() => UpdateWhiteboardInput) - whiteboard?: UpdateWhiteboardInput; + whiteboardContent?: string; } diff --git a/src/domain/template/template/template.resolver.mutations.ts b/src/domain/template/template/template.resolver.mutations.ts index 12da4fc706..fea6fc848d 100644 --- a/src/domain/template/template/template.resolver.mutations.ts +++ b/src/domain/template/template/template.resolver.mutations.ts @@ -10,15 +10,13 @@ import { AgentInfo } from '@core/authentication.agent.info/agent.info'; import { UpdateTemplateInput } from './dto/template.dto.update'; import { DeleteTemplateInput } from './dto/template.dto.delete'; import { AuthorizationPrivilege } from '@common/enums/authorization.privilege'; -import { IInnovationFlow } from '@domain/collaboration/innovation-flow/innovation.flow.interface'; -import { InnovationFlowService } from '@domain/collaboration/innovation-flow/innovaton.flow.service'; -import { UpdateInnovationFlowFromTemplateInput } from './dto/template.dto.update.innovation.flow'; +import { LogContext } from '@common/enums/logging.context'; +import { ValidationException } from '@common/exceptions/validation.exception'; @Resolver() export class TemplateResolverMutations { constructor( private authorizationService: AuthorizationService, - private innovationFlowService: InnovationFlowService, private templateService: TemplateService, @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService ) {} @@ -67,32 +65,15 @@ export class TemplateResolverMutations { AuthorizationPrivilege.DELETE, `template delete: ${template.id}` ); - return await this.templateService.delete(template); - } - - @UseGuards(GraphqlGuard) - @Mutation(() => IInnovationFlow, { - description: - 'Updates the InnovationFlow states from the specified template.', - }) - async updateInnovationFlowStatesFromTemplate( - @CurrentUser() agentInfo: AgentInfo, - @Args('innovationFlowData') - innovationFlowData: UpdateInnovationFlowFromTemplateInput - ): Promise { - const innovationFlow = - await this.innovationFlowService.getInnovationFlowOrFail( - innovationFlowData.innovationFlowID + const usedInTemplateDefault = + await this.templateService.isTemplateInUseInTemplateDefault(template.id); + if (usedInTemplateDefault) { + throw new ValidationException( + `Template is in use in TemplateDefault: ${template.id}`, + LogContext.TEMPLATES ); - await this.authorizationService.grantAccessOrFail( - agentInfo, - innovationFlow.authorization, - AuthorizationPrivilege.UPDATE, - `updateInnovationFlow from template: ${innovationFlow.id}` - ); + } - return await this.templateService.updateInnovationFlowStatesFromTemplate( - innovationFlowData - ); + return await this.templateService.delete(template); } } diff --git a/src/domain/template/template/template.service.authorization.ts b/src/domain/template/template/template.service.authorization.ts index 557e8ff64f..fc7d567822 100644 --- a/src/domain/template/template/template.service.authorization.ts +++ b/src/domain/template/template/template.service.authorization.ts @@ -11,6 +11,7 @@ import { LogContext } from '@common/enums/logging.context'; import { CalloutAuthorizationService } from '@domain/collaboration/callout/callout.service.authorization'; import { WhiteboardAuthorizationService } from '@domain/common/whiteboard/whiteboard.service.authorization'; import { CollaborationAuthorizationService } from '@domain/collaboration/collaboration/collaboration.service.authorization'; +import { EntityNotFoundException } from '@common/exceptions/entity.not.found.exception'; import { InnovationFlowAuthorizationService } from '@domain/collaboration/innovation-flow/innovation.flow.service.authorization'; @Injectable() @@ -84,68 +85,91 @@ export class TemplateAuthorizationService { ); updatedAuthorizations.push(...profileAuthorizations); - if (template.type == TemplateType.COMMUNITY_GUIDELINES) { - if (!template.communityGuidelines) { - throw new RelationshipNotFoundException( - `Unable to load Community Guidelines on Template of that type: ${template.id} `, - LogContext.TEMPLATES - ); + switch (template.type) { + case TemplateType.COMMUNITY_GUIDELINES: { + if (!template.communityGuidelines) { + throw new RelationshipNotFoundException( + `Unable to load Community Guidelines on Template of that type: ${template.id} `, + LogContext.TEMPLATES + ); + } + const guidelineAuthorizations = + await this.communityGuidelinesAuthorizationService.applyAuthorizationPolicy( + template.communityGuidelines, + template.authorization + ); + updatedAuthorizations.push(...guidelineAuthorizations); + break; } - // Cascade - const guidelineAuthorizations = - await this.communityGuidelinesAuthorizationService.applyAuthorizationPolicy( - template.communityGuidelines, - template.authorization - ); - updatedAuthorizations.push(...guidelineAuthorizations); - } - - if (template.type == TemplateType.CALLOUT) { - if (!template.callout) { - throw new RelationshipNotFoundException( - `Unable to load Callout on Template of that type: ${template.id} `, - LogContext.TEMPLATES - ); + case TemplateType.CALLOUT: { + if (!template.callout) { + throw new RelationshipNotFoundException( + `Unable to load Callout on Template of that type: ${template.id} `, + LogContext.TEMPLATES + ); + } + const calloutAuthorizations = + await this.calloutAuthorizationService.applyAuthorizationPolicy( + template.callout.id, + template.authorization + ); + updatedAuthorizations.push(...calloutAuthorizations); + break; } - // Cascade - const calloutAuthorizations = - await this.calloutAuthorizationService.applyAuthorizationPolicy( - template.callout.id, - template.authorization - ); - updatedAuthorizations.push(...calloutAuthorizations); - } - - if (template.type == TemplateType.WHITEBOARD) { - if (!template.whiteboard) { - throw new RelationshipNotFoundException( - `Unable to load Whiteboard on Template of that type: ${template.id} `, - LogContext.TEMPLATES - ); + case TemplateType.WHITEBOARD: { + if (!template.whiteboard) { + throw new RelationshipNotFoundException( + `Unable to load Whiteboard on Template of that type: ${template.id} `, + LogContext.TEMPLATES + ); + } + const whiteboardAuthorizations = + await this.whiteboardAuthorizationService.applyAuthorizationPolicy( + template.whiteboard.id, + template.authorization + ); + updatedAuthorizations.push(...whiteboardAuthorizations); + break; } - // Cascade - const whiteboardAuthorizations = - await this.whiteboardAuthorizationService.applyAuthorizationPolicy( - template.whiteboard.id, - template.authorization - ); - updatedAuthorizations.push(...whiteboardAuthorizations); - } - - if (template.type == TemplateType.COLLABORATION) { - if (!template.collaboration) { - throw new RelationshipNotFoundException( - `Unable to load Collaboration on Template of that type: ${template.id} `, + case TemplateType.COLLABORATION: { + if (!template.collaboration) { + throw new RelationshipNotFoundException( + `Unable to load Collaboration on Template of that type: ${template.id} `, + LogContext.TEMPLATES + ); + } + const collaborationAuthorizations = + await this.collaborationAuthorizationService.applyAuthorizationPolicy( + template.collaboration, + template.authorization + ); + updatedAuthorizations.push(...collaborationAuthorizations); + break; + } + case TemplateType.INNOVATION_FLOW: { + if (!template.innovationFlow) { + throw new RelationshipNotFoundException( + `Unable to load InnovationFlow on Template of that type: ${template.id} `, + LogContext.TEMPLATES + ); + } + const innovationFlowAuthorizations = + await this.innovationFlowAuthorizationService.applyAuthorizationPolicy( + template.innovationFlow, + template.authorization + ); + updatedAuthorizations.push(...innovationFlowAuthorizations); + break; + } + case TemplateType.POST: { + break; + } + default: { + throw new EntityNotFoundException( + `Unable to reset auth on template of type: ${template.type}`, LogContext.TEMPLATES ); } - // Cascade - const collaborationAuthorizations = - await this.collaborationAuthorizationService.applyAuthorizationPolicy( - template.collaboration, - template.authorization - ); - updatedAuthorizations.push(...collaborationAuthorizations); } if (template.type == TemplateType.INNOVATION_FLOW) { diff --git a/src/domain/template/template/template.service.ts b/src/domain/template/template/template.service.ts index 4dc1313576..112e3cfa28 100644 --- a/src/domain/template/template/template.service.ts +++ b/src/domain/template/template/template.service.ts @@ -1,6 +1,6 @@ import { Inject, Injectable, LoggerService } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { FindOneOptions, Repository } from 'typeorm'; +import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; +import { EntityManager, FindOneOptions, Repository } from 'typeorm'; import { EntityNotFoundException, RelationshipNotFoundException, @@ -19,7 +19,7 @@ import { ProfileService } from '@domain/common/profile/profile.service'; import { AuthorizationPolicy } from '@domain/common/authorization-policy/authorization.policy.entity'; import { AuthorizationPolicyType } from '@common/enums/authorization.policy.type'; import { TemplateType } from '@common/enums/template.type'; -import { InnovationFlowService } from '@domain/collaboration/innovation-flow/innovaton.flow.service'; +import { InnovationFlowService } from '@domain/collaboration/innovation-flow/innovation.flow.service'; import { CommunityGuidelinesService } from '@domain/community/community-guidelines/community.guidelines.service'; import { CreateCommunityGuidelinesInput } from '@domain/community/community-guidelines/dto/community.guidelines.dto.create'; import { ICommunityGuidelines } from '@domain/community/community-guidelines/community.guidelines.interface'; @@ -28,10 +28,12 @@ import { CalloutService } from '@domain/collaboration/callout/callout.service'; import { WhiteboardService } from '@domain/common/whiteboard'; import { IWhiteboard } from '@domain/common/whiteboard/whiteboard.interface'; import { IInnovationFlow } from '@domain/collaboration/innovation-flow/innovation.flow.interface'; -import { UpdateInnovationFlowFromTemplateInput } from './dto/template.dto.update.innovation.flow'; import { randomUUID } from 'crypto'; import { ICollaboration } from '@domain/collaboration/collaboration'; +import { CollaborationService } from '@domain/collaboration/collaboration/collaboration.service'; import { CalloutVisibility } from '@common/enums/callout.visibility'; +import { TemplateDefault } from '../template-default/template.default.entity'; +import { CalloutGroupName } from '@common/enums/callout.group.name'; @Injectable() export class TemplateService { @@ -41,8 +43,11 @@ export class TemplateService { private communityGuidelinesService: CommunityGuidelinesService, private calloutService: CalloutService, private whiteboardService: WhiteboardService, + private collaborationService: CollaborationService, @InjectRepository(Template) private templateRepository: Repository