From 76a454119ce3499e07c39981ec686988eced718e Mon Sep 17 00:00:00 2001 From: Neil Smyth Date: Sun, 29 Sep 2024 16:16:01 +0200 Subject: [PATCH 001/105] first pass at two new modules for TemplatesManager, TemplateDefault --- src/common/enums/authorization.policy.type.ts | 2 + src/common/enums/template.default.type.ts | 13 ++ .../space/space/space.resolver.mutations.ts | 43 +---- .../dto/template.default.dto.create.ts | 11 ++ .../dto/template.default.dto.update.ts | 17 ++ .../template.default.entity.ts | 39 +++++ .../template.default.interface.ts | 26 +++ .../template.default.module.ts | 18 ++ .../template.default.resolver.mutations.ts | 47 +++++ .../template.default.service.authorization.ts | 22 +++ .../template.default.service.ts | 95 ++++++++++ .../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 | 31 ++++ .../templates.manager.resolver.fields.ts | 42 +++++ ...templates.manager.service.authorization.ts | 78 +++++++++ .../templates.manager.service.ts | 164 ++++++++++++++++++ 19 files changed, 652 insertions(+), 42 deletions(-) create mode 100644 src/common/enums/template.default.type.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.resolver.mutations.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/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.service.authorization.ts create mode 100644 src/domain/template/templates-manager/templates.manager.service.ts diff --git a/src/common/enums/authorization.policy.type.ts b/src/common/enums/authorization.policy.type.ts index 1d01b50468..1e82c12c43 100644 --- a/src/common/enums/authorization.policy.type.ts +++ b/src/common/enums/authorization.policy.type.ts @@ -46,6 +46,8 @@ export enum AuthorizationPolicyType { 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..e56edcc2ce --- /dev/null +++ b/src/common/enums/template.default.type.ts @@ -0,0 +1,13 @@ +import { registerEnumType } from '@nestjs/graphql'; + +export enum TemplateDefaultType { + SPACE = 'space', + SUBSPACE = 'subspace', + SPACE_TUTORIALS = 'space-tutorials', + SUBSPACE_TUTORIALS = 'subspace-tutorials', + KNOWLEDGE = 'knowledge', +} + +registerEnumType(TemplateDefaultType, { + name: 'TemplateDefaultType', +}); 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/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..f3b88c5668 --- /dev/null +++ b/src/domain/template/template-default/template.default.entity.ts @@ -0,0 +1,39 @@ +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'; + +@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 }) + 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..30bf87b55d --- /dev/null +++ b/src/domain/template/template-default/template.default.interface.ts @@ -0,0 +1,26 @@ +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'; + +@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; +} 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..238170ec34 --- /dev/null +++ b/src/domain/template/template-default/template.default.module.ts @@ -0,0 +1,18 @@ +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'; + +@Module({ + imports: [ + AuthorizationModule, + TemplateModule, + TypeOrmModule.forFeature([TemplateDefault]), + ], + providers: [TemplateDefaultService, TemplateDefaultAuthorizationService], + exports: [TemplateDefaultService, TemplateDefaultAuthorizationService], +}) +export class TemplateDefaultModule {} diff --git a/src/domain/template/template-default/template.default.resolver.mutations.ts b/src/domain/template/template-default/template.default.resolver.mutations.ts new file mode 100644 index 0000000000..a63416a4f5 --- /dev/null +++ b/src/domain/template/template-default/template.default.resolver.mutations.ts @@ -0,0 +1,47 @@ +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 { CurrentUser } from '@common/decorators/current-user.decorator'; +import { AgentInfo } from '@core/authentication.agent.info/agent.info'; +import { UpdateTemplateDefaultTemplateInput } from './dto/template.default.dto.update'; +import { ITemplateDefault } from './template.default.interface'; +import { GraphqlGuard } from '@core/authorization/graphql.guard'; +import { AuthorizationPrivilege } from '@common/enums/authorization.privilege'; +import { TemplateDefaultService } from './template.default.service'; + +@Resolver() +export class TemplatesDefaultResolverMutations { + constructor( + private authorizationService: AuthorizationService, + private templatesDefaultService: TemplateDefaultService, + @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService + ) {} + + @UseGuards(GraphqlGuard) + @Mutation(() => ITemplateDefault, { + description: 'Updates the specified SpaceDefaults.', + }) + async updateTemplateDefault( + @CurrentUser() agentInfo: AgentInfo, + @Args('templateDefaultData') + templateDefaultData: UpdateTemplateDefaultTemplateInput + ): Promise { + const templateDefault = + await this.templatesDefaultService.getTemplateDefaultOrFail( + templateDefaultData.templateDefaultID + ); + + this.authorizationService.grantAccessOrFail( + agentInfo, + templateDefault.authorization, + AuthorizationPrivilege.UPDATE, + `update templateDefault of type ${templateDefault.type}: ${templateDefault.id}` + ); + + return this.templatesDefaultService.updateTemplateDefaultTemplate( + templateDefault, + templateDefaultData + ); + } +} 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..ebde2a534e --- /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 TempalteDefault 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/templates-manager/dto/templates.manager.dto.create..ts b/src/domain/template/templates-manager/dto/templates.manager.dto.create..ts new file mode 100644 index 0000000000..a5ae916571 --- /dev/null +++ b/src/domain/template/templates-manager/dto/templates.manager.dto.create..ts @@ -0,0 +1,5 @@ +import { CreateTemplateDefaultInput } from '@domain/template/template-default/dto/template.default.dto.create'; + +export class CreateTemplatesManagerInput { + templateDefaultsData!: CreateTemplateDefaultInput[]; +} diff --git a/src/domain/template/templates-manager/index.ts b/src/domain/template/templates-manager/index.ts new file mode 100644 index 0000000000..3c6b4f82f0 --- /dev/null +++ b/src/domain/template/templates-manager/index.ts @@ -0,0 +1,2 @@ +export * from './templates.manager.entity'; +export * from './templates.manager.interface'; diff --git a/src/domain/template/templates-manager/templates.manager.entity.ts b/src/domain/template/templates-manager/templates.manager.entity.ts new file mode 100644 index 0000000000..dcd22c1292 --- /dev/null +++ b/src/domain/template/templates-manager/templates.manager.entity.ts @@ -0,0 +1,29 @@ +import { Entity, JoinColumn, OneToMany, OneToOne } from 'typeorm'; +import { AuthorizableEntity } from '@domain/common/entity/authorizable-entity'; +import { ITemplatesManager } from './templates.manager.interface'; +import { TemplateDefault } from '../template-default/template.default.entity'; +import { TemplatesSet } from '../templates-set/templates.set.entity'; + +@Entity() +export class TemplatesManager + extends AuthorizableEntity + implements ITemplatesManager +{ + @OneToOne(() => TemplatesSet, { + eager: false, + cascade: true, + onDelete: 'SET NULL', + }) + @JoinColumn() + templatesSet?: TemplatesSet; + + @OneToMany( + () => TemplateDefault, + templateDefault => templateDefault.templatesManager, + { + eager: false, + cascade: true, + } + ) + templateDefaults!: TemplateDefault[]; +} diff --git a/src/domain/template/templates-manager/templates.manager.interface.ts b/src/domain/template/templates-manager/templates.manager.interface.ts new file mode 100644 index 0000000000..ca714c2a4c --- /dev/null +++ b/src/domain/template/templates-manager/templates.manager.interface.ts @@ -0,0 +1,10 @@ +import { IAuthorizable } from '@domain/common/entity/authorizable-entity'; +import { ObjectType } from '@nestjs/graphql'; +import { ITemplatesSet } from '../templates-set/templates.set.interface'; +import { ITemplateDefault } from '../template-default/template.default.interface'; + +@ObjectType('TemplatesManager') +export abstract class ITemplatesManager extends IAuthorizable { + templatesSet?: ITemplatesSet; + templateDefaults!: ITemplateDefault[]; +} diff --git a/src/domain/template/templates-manager/templates.manager.module.ts b/src/domain/template/templates-manager/templates.manager.module.ts new file mode 100644 index 0000000000..5912af97dc --- /dev/null +++ b/src/domain/template/templates-manager/templates.manager.module.ts @@ -0,0 +1,31 @@ +import { AuthorizationModule } from '@core/authorization/authorization.module'; +import { AuthorizationPolicyModule } from '@domain/common/authorization-policy/authorization.policy.module'; +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { TemplateModule } from '../template/template.module'; +import { TemplatesManager } from './templates.manager.entity'; +import { TemplatesManagerResolverFields } from './templates.manager.resolver.fields'; +import { TemplatesManagerService } from './templates.manager.service'; +import { TemplatesManagerAuthorizationService } from './templates.manager.service.authorization'; +import { TemplatesSetModule } from '../templates-set/templates.set.module'; + +@Module({ + imports: [ + AuthorizationPolicyModule, + AuthorizationModule, + TemplatesSetModule, + TemplateModule, + TypeOrmModule.forFeature([TemplatesManager]), + ], + providers: [ + TemplatesManagerService, + TemplatesManagerAuthorizationService, + TemplatesManagerResolverFields, + ], + exports: [ + TemplatesManagerService, + TemplatesManagerAuthorizationService, + TemplatesManagerResolverFields, + ], +}) +export class TemplatesManagerModule {} diff --git a/src/domain/template/templates-manager/templates.manager.resolver.fields.ts b/src/domain/template/templates-manager/templates.manager.resolver.fields.ts new file mode 100644 index 0000000000..96b639d7f4 --- /dev/null +++ b/src/domain/template/templates-manager/templates.manager.resolver.fields.ts @@ -0,0 +1,42 @@ +import { Parent, ResolveField, Resolver } from '@nestjs/graphql'; +import { UseGuards } from '@nestjs/common/decorators/core/use-guards.decorator'; +import { GraphqlGuard } from '@core/authorization/graphql.guard'; +import { TemplatesManagerService } from './templates.manager.service'; +import { ITemplatesManager } from './templates.manager.interface'; +import { ITemplateDefault } from '../template-default/template.default.interface'; +import { AuthorizationAgentPrivilege } from '@common/decorators/authorization.agent.privilege'; +import { AuthorizationPrivilege } from '@common/enums/authorization.privilege'; +import { ITemplatesSet } from '../templates-set/templates.set.interface'; + +@Resolver(() => ITemplatesManager) +export class TemplatesManagerResolverFields { + constructor(private templatesManagerService: TemplatesManagerService) {} + + @AuthorizationAgentPrivilege(AuthorizationPrivilege.READ) + @UseGuards(GraphqlGuard) + @ResolveField('templateDefaults', () => [ITemplateDefault], { + nullable: false, + description: 'The TemplateDefaultss in this TemplatesManager.', + }) + async templateDefaults( + @Parent() templatesManager: ITemplatesManager + ): Promise { + return this.templatesManagerService.getTemplateDefaults( + templatesManager.id + ); + } + + @AuthorizationAgentPrivilege(AuthorizationPrivilege.READ) + @ResolveField('templatesSet', () => ITemplatesSet, { + nullable: true, + description: 'The templatesSet in use by this InnovationPack', + }) + @UseGuards(GraphqlGuard) + async templatesSet( + @Parent() templatesManager: ITemplatesManager + ): Promise { + return await this.templatesManagerService.getTemplatesSetOrFail( + templatesManager.id + ); + } +} diff --git a/src/domain/template/templates-manager/templates.manager.service.authorization.ts b/src/domain/template/templates-manager/templates.manager.service.authorization.ts new file mode 100644 index 0000000000..9cb038d7d1 --- /dev/null +++ b/src/domain/template/templates-manager/templates.manager.service.authorization.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@nestjs/common'; +import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service'; +import { TemplatesManagerService } from './templates.manager.service'; +import { IAuthorizationPolicy } from '@domain/common/authorization-policy/authorization.policy.interface'; +import { ITemplatesManager } from '.'; +import { TemplateDefaultAuthorizationService } from '../template-default/template.default.service.authorization'; +import { TemplatesSetAuthorizationService } from '../templates-set/templates.set.service.authorization'; +import { RelationshipNotFoundException } from '@common/exceptions'; +import { LogContext } from '@common/enums'; + +@Injectable() +export class TemplatesManagerAuthorizationService { + constructor( + private authorizationPolicyService: AuthorizationPolicyService, + private templatesManagerService: TemplatesManagerService, + private templateDefaultAuthorizationService: TemplateDefaultAuthorizationService, + private templatesSetAuthorizationService: TemplatesSetAuthorizationService + ) {} + + async applyAuthorizationPolicy( + templatesManagerID: string, + parentAuthorization: IAuthorizationPolicy | undefined + ): Promise { + const templatesManager: ITemplatesManager = + await this.templatesManagerService.getTemplatesManagerOrFail( + templatesManagerID, + { + relations: { + templateDefaults: { + authorization: true, + }, + templatesSet: { + authorization: true, + }, + }, + } + ); + + if ( + !templatesManager || + !templatesManager.templatesSet || + !templatesManager.templateDefaults + ) { + throw new RelationshipNotFoundException( + `Unable to load entities to reset auth on templates manager ${templatesManagerID}`, + LogContext.TEMPLATES + ); + } + + const updatedAuthorizations: IAuthorizationPolicy[] = []; + + // Inherit from the parent + templatesManager.authorization = + this.authorizationPolicyService.inheritParentAuthorization( + templatesManager.authorization, + parentAuthorization + ); + updatedAuthorizations.push(templatesManager.authorization); + + for (const templateDefault of templatesManager.templateDefaults) { + const templateDefaultAuthorizations = + await this.templateDefaultAuthorizationService.applyAuthorizationPolicy( + templateDefault, + parentAuthorization + ); + updatedAuthorizations.push(templateDefaultAuthorizations); + } + + const templatesSetUpdatedAuthorizations = + await this.templatesSetAuthorizationService.applyAuthorizationPolicy( + templatesManager.templatesSet, + parentAuthorization + ); + updatedAuthorizations.push(...templatesSetUpdatedAuthorizations); + + return updatedAuthorizations; + } +} diff --git a/src/domain/template/templates-manager/templates.manager.service.ts b/src/domain/template/templates-manager/templates.manager.service.ts new file mode 100644 index 0000000000..ee9996a56c --- /dev/null +++ b/src/domain/template/templates-manager/templates.manager.service.ts @@ -0,0 +1,164 @@ +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 { + EntityNotFoundException, + RelationshipNotFoundException, +} from '@common/exceptions'; +import { LogContext } from '@common/enums'; +import { AuthorizationPolicy } from '@domain/common/authorization-policy/authorization.policy.entity'; +import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service'; +import { TemplatesManager } from './templates.manager.entity'; +import { ITemplatesManager } from './templates.manager.interface'; +import { AuthorizationPolicyType } from '@common/enums/authorization.policy.type'; +import { TemplatesSetService } from '../templates-set/templates.set.service'; +import { CreateTemplatesManagerInput } from './dto/templates.manager.dto.create.'; +import { TemplateDefaultService } from '../template-default/template.default.service'; +import { ITemplateDefault } from '../template-default/template.default.interface'; +import { TemplateDefaultType } from '@common/enums/template.default.type'; +import { ITemplatesSet } from '../templates-set/templates.set.interface'; + +@Injectable() +export class TemplatesManagerService { + constructor( + private authorizationPolicyService: AuthorizationPolicyService, + @InjectRepository(TemplatesManager) + private templatesManagerRepository: Repository, + private templatesSetService: TemplatesSetService, + private templateDefaultService: TemplateDefaultService, + @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService + ) {} + + async createTemplatesManager( + templatesManagerData: CreateTemplatesManagerInput + ): Promise { + const templatesManager: ITemplatesManager = TemplatesManager.create(); + templatesManager.authorization = new AuthorizationPolicy( + AuthorizationPolicyType.TEMPLATES_MANAGER + ); + templatesManager.templateDefaults = []; + templatesManager.templatesSet = + await this.templatesSetService.createTemplatesSet(); + + for (const templateDefaultData of templatesManagerData.templateDefaultsData) { + const templateDefault = + this.templateDefaultService.createTemplateDefault(templateDefaultData); + templatesManager.templateDefaults.push(templateDefault); + } + + return await this.templatesManagerRepository.save(templatesManager); + } + + async getTemplatesManagerOrFail( + templatesManagerID: string, + options?: FindOneOptions + ): Promise { + const templatesManager = await TemplatesManager.findOne({ + where: { id: templatesManagerID }, + ...options, + }); + if (!templatesManager) + throw new EntityNotFoundException( + `TemplatesManager with id(${templatesManagerID}) not found!`, + LogContext.TEMPLATES + ); + return templatesManager; + } + + async deleteTemplatesManager( + templatesManagerID: string + ): Promise { + const templatesManager = await this.getTemplatesManagerOrFail( + templatesManagerID, + { + relations: { + authorization: true, + templateDefaults: true, + }, + } + ); + + if (!templatesManager.authorization || !templatesManager.templateDefaults) { + throw new EntityNotFoundException( + `Unable to find authorization on TemplatesManager with id: ${templatesManagerID}`, + LogContext.TEMPLATES + ); + } + + await this.authorizationPolicyService.delete( + templatesManager.authorization + ); + + for (const template of templatesManager.templateDefaults) { + await this.templateDefaultService.removeTemplateDefault(template); + } + + const result = await this.templatesManagerRepository.remove( + templatesManager as TemplatesManager + ); + result.id = templatesManagerID; + return result; + } + + public async getTemplateDefault( + templatesManagerID: string, + templateDefaultType: TemplateDefaultType + ): Promise { + const templateDefaults = await this.getTemplateDefaults(templatesManagerID); + const templateDefault = templateDefaults.find( + td => td.type === templateDefaultType + ); + if (!templateDefault) { + throw new EntityNotFoundException( + `No TemplateDefault found with type: ${templateDefaultType} in TemplatesManager with id: ${templatesManagerID}`, + LogContext.TEMPLATES + ); + } + return templateDefault; + } + + public async getTemplateDefaults( + templatesManagerID: string + ): Promise { + const templatesManager = await this.getTemplatesManagerOrFail( + templatesManagerID, + { + relations: { templateDefaults: true }, + } + ); + if (!templatesManager.templateDefaults) { + throw new RelationshipNotFoundException( + `No TemplateDefaults found in TemplatesManager with id: ${templatesManagerID}`, + LogContext.TEMPLATES + ); + } + return templatesManager.templateDefaults; + } + + public async save( + templatesManager: ITemplatesManager + ): Promise { + return await this.templatesManagerRepository.save(templatesManager); + } + + async getTemplatesSetOrFail( + templatesManagerID: string + ): Promise { + const templatesManager = await this.getTemplatesManagerOrFail( + templatesManagerID, + { + relations: { templatesSet: true }, + } + ); + + if (!templatesManager || !templatesManager.templatesSet) { + throw new RelationshipNotFoundException( + `Unable to find templatesSet for TemplatesManager with id: ${templatesManager.id}`, + LogContext.TEMPLATES + ); + } + + return templatesManager?.templatesSet; + } +} From d01f3784ee4564f8f0bd7677f81892d688fb7e2d Mon Sep 17 00:00:00 2001 From: Neil Smyth Date: Sun, 29 Sep 2024 16:40:53 +0200 Subject: [PATCH 002/105] added templates manager to space; removed the SpaceDefaults entity (module still present until move all data to be via defaults --- src/common/enums/authorization.policy.type.ts | 1 - src/common/enums/template.type.ts | 1 + src/domain/space/account/account.service.ts | 6 +- .../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 | 10 +- .../space.defaults/space.defaults.service.ts | 84 +--------- 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.service.authorization.ts | 26 +-- src/domain/space/space/space.service.ts | 154 ++++-------------- .../template.default.module.ts | 2 + .../templates.manager.module.ts | 4 +- .../storage.aggregator.resolver.service.ts | 2 +- .../url-generator/url.generator.service.ts | 2 +- 18 files changed, 70 insertions(+), 329 deletions(-) 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 diff --git a/src/common/enums/authorization.policy.type.ts b/src/common/enums/authorization.policy.type.ts index 1e82c12c43..8e511bbce9 100644 --- a/src/common/enums/authorization.policy.type.ts +++ b/src/common/enums/authorization.policy.type.ts @@ -39,7 +39,6 @@ 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', diff --git a/src/common/enums/template.type.ts b/src/common/enums/template.type.ts index b33cee520f..cb3547f4a8 100644 --- a/src/common/enums/template.type.ts +++ b/src/common/enums/template.type.ts @@ -7,6 +7,7 @@ export enum TemplateType { COMMUNITY_GUIDELINES = 'community-guidelines', INNOVATION_FLOW = 'innovation-flow', COLLABORATION = 'collaboration', + SPACE = 'space', } registerEnumType(TemplateType, { diff --git a/src/domain/space/account/account.service.ts b/src/domain/space/account/account.service.ts index c2bf214646..eb9d40f4fe 100644 --- a/src/domain/space/account/account.service.ts +++ b/src/domain/space/account/account.service.ts @@ -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/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..a5a735e647 100644 --- a/src/domain/space/space.defaults/space.defaults.module.ts +++ b/src/domain/space/space.defaults/space.defaults.module.ts @@ -1,16 +1,8 @@ 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'; @Module({ - imports: [ - AuthorizationModule, - AuthorizationPolicyModule, - TypeOrmModule.forFeature([SpaceDefaults]), - ], + imports: [], 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..2ba816e674 100644 --- a/src/domain/space/space.defaults/space.defaults.service.ts +++ b/src/domain/space/space.defaults/space.defaults.service.ts @@ -1,13 +1,5 @@ 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'; @@ -42,79 +34,11 @@ import { spaceDefaultsCalloutGroupsBlankSlate } from './definitions/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'; @Injectable() export class SpaceDefaultsService { - constructor( - private authorizationPolicyService: AuthorizationPolicyService, - @InjectRepository(SpaceDefaults) - private spaceDefaultsRepository: Repository - ) {} - - 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); - } - - // 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; - } - - async save(spaceDefaults: ISpaceDefaults): Promise { - return await this.spaceDefaultsRepository.save(spaceDefaults); - } - - 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 (!spaceDefaults) - throw new EntityNotFoundException( - `No SpaceDefaults found with the given id: ${spaceDefaultsID}`, - LogContext.COLLABORATION - ); - return spaceDefaults; - } + constructor() {} public getCalloutGroups(spaceType: SpaceType): ICalloutGroup[] { switch (spaceType) { @@ -211,12 +135,6 @@ export class SpaceDefaultsService { } } - public getDefaultInnovationFlowTemplate( - spaceDefaults: ISpaceDefaults - ): ITemplate | undefined { - return spaceDefaults.innovationFlowTemplate; - } - public getDefaultSpaceSettings(spaceType: SpaceType): ISpaceSettings { switch (spaceType) { case SpaceType.CHALLENGE: diff --git a/src/domain/space/space/space.entity.ts b/src/domain/space/space/space.entity.ts index 1ec80e23f6..e580cf6902 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'; @Entity() export class Space extends NameableEntity implements ISpace { @OneToOne(() => Profile, { @@ -110,21 +109,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 0778e459da..4faf7891ab 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'; @ObjectType('Space') export class ISpace extends INameable { @@ -54,6 +53,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..87b959f627 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: Space): Promise { + return await this.spaceService.getTemplatesManagerOrFail( + space.levelZeroSpaceID + ); } @AuthorizationAgentPrivilege(AuthorizationPrivilege.READ) diff --git a/src/domain/space/space/space.service.authorization.ts b/src/domain/space/space/space.service.authorization.ts index 709b6b007d..1f06e9dca7 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'; @Injectable() export class SpaceAuthorizationService { @@ -50,7 +50,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 ) {} @@ -76,8 +76,7 @@ export class SpaceAuthorizationService { profile: true, storageAggregator: true, subspaces: true, - library: true, - defaults: true, + templatesManager: true, }, }); if ( @@ -300,26 +299,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, + const templatesManagerAuthorizations = + await this.templatesManagerAuthorizationService.applyAuthorizationPolicy( + space.templatesManager.id, space.authorization ); - updatedAuthorizations.push(...libraryAuthorizations); - - const defaultsAuthorizations = - this.authorizationPolicyService.inheritParentAuthorization( - space.defaults.authorization, - space.authorization - ); - updatedAuthorizations.push(defaultsAuthorizations); + updatedAuthorizations.push(...templatesManagerAuthorizations); } /// For fields that always should be available diff --git a/src/domain/space/space/space.service.ts b/src/domain/space/space/space.service.ts index bbc53c926c..554f110524 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'; @@ -75,11 +72,13 @@ 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'; @Injectable() export class SpaceService { @@ -96,12 +95,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 @@ -109,7 +106,6 @@ export class SpaceService { public async createSpace( spaceData: CreateSpaceInput, - spaceDefaults?: ISpaceDefaults, agentInfo?: AgentInfo ): Promise { if (!spaceData.type) { @@ -217,7 +213,7 @@ export class SpaceService { if (!collaborationData.innovationFlowData) { // TODO: need to pick up the default template + innovation flow properly collaborationData.innovationFlowData = - await this.getDefaultInnovationStates(space.type, spaceDefaults); + await this.getDefaultInnovationStates(space.type); } if (!collaborationData.calloutsData) { collaborationData.calloutsData = []; @@ -245,7 +241,7 @@ export class SpaceService { }); if (space.level === SpaceLevel.SPACE) { - await this.addLevelZeroSpaceEntities(space); + space.templatesManager = await this.createTemplatesManager(); } ////// Community @@ -265,41 +261,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.SUBSPACE, + allowedTemplateType: TemplateType.INNOVATION_FLOW, + }; + 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 { @@ -316,8 +291,7 @@ export class SpaceService { agent: true, profile: true, storageAggregator: true, - library: true, - defaults: true, + templatesManager: true, }, }); @@ -353,14 +327,15 @@ export class SpaceService { await this.authorizationPolicyService.delete(space.authorization); if (space.level === SpaceLevel.SPACE) { - if (!space.library || !space.defaults) { + if (!space.templatesManager || !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); @@ -371,40 +346,8 @@ export class SpaceService { } private async getDefaultInnovationStates( - spaceType: SpaceType, - spaceDefaults?: ISpaceDefaults + spaceType: SpaceType ): 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); @@ -961,17 +904,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); @@ -1279,22 +1216,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 || !levelZeroSpace.templatesManager) { throw new EntityNotFoundException( - `Unable to find templatesSet for level zero space with id: ${rootSpaceID}`, + `Unable to find templatesManager for level zero space with id: ${rootSpaceID}`, LogContext.ACCOUNT ); } - return templatesSet; + return levelZeroSpace.templatesManager; } public async activeSubscription( @@ -1324,28 +1262,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-default/template.default.module.ts b/src/domain/template/template-default/template.default.module.ts index 238170ec34..5ab6498bb2 100644 --- a/src/domain/template/template-default/template.default.module.ts +++ b/src/domain/template/template-default/template.default.module.ts @@ -5,10 +5,12 @@ 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]), ], diff --git a/src/domain/template/templates-manager/templates.manager.module.ts b/src/domain/template/templates-manager/templates.manager.module.ts index 5912af97dc..06408f26b7 100644 --- a/src/domain/template/templates-manager/templates.manager.module.ts +++ b/src/domain/template/templates-manager/templates.manager.module.ts @@ -2,19 +2,19 @@ import { AuthorizationModule } from '@core/authorization/authorization.module'; import { AuthorizationPolicyModule } from '@domain/common/authorization-policy/authorization.policy.module'; import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { TemplateModule } from '../template/template.module'; import { TemplatesManager } from './templates.manager.entity'; import { TemplatesManagerResolverFields } from './templates.manager.resolver.fields'; import { TemplatesManagerService } from './templates.manager.service'; import { TemplatesManagerAuthorizationService } from './templates.manager.service.authorization'; import { TemplatesSetModule } from '../templates-set/templates.set.module'; +import { TemplateDefaultModule } from '../template-default/template.default.module'; @Module({ imports: [ AuthorizationPolicyModule, AuthorizationModule, TemplatesSetModule, - TemplateModule, + TemplateDefaultModule, TypeOrmModule.forFeature([TemplatesManager]), ], providers: [ diff --git a/src/services/infrastructure/storage-aggregator-resolver/storage.aggregator.resolver.service.ts b/src/services/infrastructure/storage-aggregator-resolver/storage.aggregator.resolver.service.ts index bc98c3346a..4aaa5c8d01 100644 --- a/src/services/infrastructure/storage-aggregator-resolver/storage.aggregator.resolver.service.ts +++ b/src/services/infrastructure/storage-aggregator-resolver/storage.aggregator.resolver.service.ts @@ -166,7 +166,7 @@ export class StorageAggregatorResolverService { // First try on Space const space = await this.entityManager.findOne(Space, { where: { - library: { + templatesManager: { id: templatesSetId, }, }, diff --git a/src/services/infrastructure/url-generator/url.generator.service.ts b/src/services/infrastructure/url-generator/url.generator.service.ts index 339a5a6456..f5ae728282 100644 --- a/src/services/infrastructure/url-generator/url.generator.service.ts +++ b/src/services/infrastructure/url-generator/url.generator.service.ts @@ -403,7 +403,7 @@ export class UrlGeneratorService { ): Promise { const space = await this.entityManager.findOne(Space, { where: { - library: { + templatesManager: { id: templatesSetID, }, }, From 862e75483c24708c1b4478afa2fbf0b537151513 Mon Sep 17 00:00:00 2001 From: Neil Smyth Date: Sun, 29 Sep 2024 16:49:01 +0200 Subject: [PATCH 003/105] added templatesManager to platform --- src/domain/space/space/space.resolver.fields.ts | 2 +- .../space/space/space.service.authorization.ts | 2 -- src/domain/space/space/space.service.ts | 2 +- src/platform/platfrom/platform.entity.ts | 9 +++++++++ src/platform/platfrom/platform.interface.ts | 2 ++ src/platform/platfrom/platform.module.ts | 2 ++ src/platform/platfrom/platform.resolver.fields.ts | 15 +++++++++++++-- .../platfrom/platform.service.authorization.ts | 15 +++++++++++++-- src/platform/platfrom/platform.service.ts | 15 +++++++++++++++ 9 files changed, 56 insertions(+), 8 deletions(-) diff --git a/src/domain/space/space/space.resolver.fields.ts b/src/domain/space/space/space.resolver.fields.ts index 87b959f627..9807b6d106 100644 --- a/src/domain/space/space/space.resolver.fields.ts +++ b/src/domain/space/space/space.resolver.fields.ts @@ -258,7 +258,7 @@ export class SpaceResolverFields { description: 'The TemplatesManager in use by this Space', }) @UseGuards(GraphqlGuard) - async templatesManager(@Parent() space: Space): Promise { + async templatesManager(@Parent() space: ISpace): Promise { return await this.spaceService.getTemplatesManagerOrFail( space.levelZeroSpaceID ); diff --git a/src/domain/space/space/space.service.authorization.ts b/src/domain/space/space/space.service.authorization.ts index 1f06e9dca7..f10792792d 100644 --- a/src/domain/space/space/space.service.authorization.ts +++ b/src/domain/space/space/space.service.authorization.ts @@ -204,7 +204,6 @@ export class SpaceAuthorizationService { const childAuthorzations = await this.propagateAuthorizationToChildEntities( space, levelZeroSpaceAgent, - space.community.roleSet, spaceSettings, spaceMembershipAllowed ); @@ -238,7 +237,6 @@ export class SpaceAuthorizationService { public async propagateAuthorizationToChildEntities( space: ISpace, levelZeroSpaceAgent: IAgent, - roleSet: IRoleSet, spaceSettings: ISpaceSettings, spaceMembershipAllowed: boolean ): Promise { diff --git a/src/domain/space/space/space.service.ts b/src/domain/space/space/space.service.ts index 554f110524..e8e92bac19 100644 --- a/src/domain/space/space/space.service.ts +++ b/src/domain/space/space/space.service.ts @@ -1228,7 +1228,7 @@ export class SpaceService { if (!levelZeroSpace || !levelZeroSpace.templatesManager) { throw new EntityNotFoundException( `Unable to find templatesManager for level zero space with id: ${rootSpaceID}`, - LogContext.ACCOUNT + LogContext.SPACES ); } diff --git a/src/platform/platfrom/platform.entity.ts b/src/platform/platfrom/platform.entity.ts index 607c5ee1d4..155159157a 100644 --- a/src/platform/platfrom/platform.entity.ts +++ b/src/platform/platfrom/platform.entity.ts @@ -6,6 +6,7 @@ import { StorageAggregator } from '@domain/storage/storage-aggregator/storage.ag import { Licensing } from '@platform/licensing/licensing.entity'; import { Forum } from '@platform/forum/forum.entity'; import { PlatformInvitation } from '@platform/invitation/platform.invitation.entity'; +import { TemplatesManager } from '@domain/template/templates-manager/templates.manager.entity'; @Entity() export class Platform extends AuthorizableEntity implements IPlatform { @@ -25,6 +26,14 @@ export class Platform extends AuthorizableEntity implements IPlatform { @JoinColumn() library?: Library; + @OneToOne(() => TemplatesManager, { + eager: false, + cascade: true, + onDelete: 'SET NULL', + }) + @JoinColumn() + templatesManager?: TemplatesManager; + @OneToOne(() => StorageAggregator, { eager: false, cascade: true, diff --git a/src/platform/platfrom/platform.interface.ts b/src/platform/platfrom/platform.interface.ts index 5e0c0a05d2..73b487536d 100644 --- a/src/platform/platfrom/platform.interface.ts +++ b/src/platform/platfrom/platform.interface.ts @@ -1,5 +1,6 @@ import { IAuthorizable } from '@domain/common/entity/authorizable-entity'; import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface'; +import { ITemplatesManager } from '@domain/template/templates-manager/templates.manager.interface'; import { ILibrary } from '@library/library/library.interface'; import { ObjectType } from '@nestjs/graphql'; import { IConfig } from '@platform/configuration/config/config.interface'; @@ -17,4 +18,5 @@ export abstract class IPlatform extends IAuthorizable { storageAggregator!: IStorageAggregator; licensing?: ILicensing; platformInvitations!: IPlatformInvitation[]; + templatesManager?: ITemplatesManager; } diff --git a/src/platform/platfrom/platform.module.ts b/src/platform/platfrom/platform.module.ts index 073b7f85b3..fec66a1df1 100644 --- a/src/platform/platfrom/platform.module.ts +++ b/src/platform/platfrom/platform.module.ts @@ -16,6 +16,7 @@ import { StorageAggregatorModule } from '@domain/storage/storage-aggregator/stor import { LicensingModule } from '@platform/licensing/licensing.module'; import { ForumModule } from '@platform/forum/forum.module'; import { PlatformInvitationModule } from '@platform/invitation/platform.invitation.module'; +import { TemplatesManagerModule } from '@domain/template/templates-manager/templates.manager.module'; @Module({ imports: [ @@ -29,6 +30,7 @@ import { PlatformInvitationModule } from '@platform/invitation/platform.invitati MetadataModule, LicensingModule, PlatformInvitationModule, + TemplatesManagerModule, TypeOrmModule.forFeature([Platform]), ], providers: [ diff --git a/src/platform/platfrom/platform.resolver.fields.ts b/src/platform/platfrom/platform.resolver.fields.ts index ed81efe668..ddfea40c09 100644 --- a/src/platform/platfrom/platform.resolver.fields.ts +++ b/src/platform/platfrom/platform.resolver.fields.ts @@ -1,6 +1,6 @@ import { Parent, ResolveField, Resolver } from '@nestjs/graphql'; import { ILibrary } from '@library/library/library.interface'; -import { Profiling } from '@src/common/decorators'; +import { AuthorizationAgentPrivilege } from '@src/common/decorators'; import { IPlatform } from './platform.interface'; import { PlatformService } from './platform.service'; import { IConfig } from '@platform/configuration/config/config.interface'; @@ -14,6 +14,8 @@ import { UseGuards } from '@nestjs/common'; import { ReleaseDiscussionOutput } from './dto/release.discussion.dto'; import { ILicensing } from '@platform/licensing/licensing.interface'; import { IForum } from '@platform/forum'; +import { AuthorizationPrivilege } from '@common/enums/authorization.privilege'; +import { ITemplatesManager } from '@domain/template/templates-manager/templates.manager.interface'; @Resolver(() => IPlatform) export class PlatformResolverFields { @@ -90,10 +92,19 @@ export class PlatformResolverFields { nullable: true, description: 'The latest release discussion.', }) - @Profiling.api async latestReleaseDiscussion(): Promise< ReleaseDiscussionOutput | undefined > { return this.platformService.getLatestReleaseDiscussion(); } + + @AuthorizationAgentPrivilege(AuthorizationPrivilege.READ) + @ResolveField('templatesManager', () => ITemplatesManager, { + nullable: true, + description: 'The TemplatesManager in use by the Platform', + }) + @UseGuards(GraphqlGuard) + async templatesManager(): Promise { + return await this.platformService.getTemplatesManagerOrFail(); + } } diff --git a/src/platform/platfrom/platform.service.authorization.ts b/src/platform/platfrom/platform.service.authorization.ts index 7a3040e719..4586cbe22b 100644 --- a/src/platform/platfrom/platform.service.authorization.ts +++ b/src/platform/platfrom/platform.service.authorization.ts @@ -26,6 +26,7 @@ import { LicensingAuthorizationService } from '@platform/licensing/licensing.ser import { ForumAuthorizationService } from '@platform/forum/forum.service.authorization'; import { PlatformInvitationAuthorizationService } from '@platform/invitation/platform.invitation.service.authorization'; import { LibraryAuthorizationService } from '@library/library/library.service.authorization'; +import { TemplatesManagerAuthorizationService } from '@domain/template/templates-manager/templates.manager.service.authorization'; @Injectable() export class PlatformAuthorizationService { @@ -37,7 +38,8 @@ export class PlatformAuthorizationService { private storageAggregatorAuthorizationService: StorageAggregatorAuthorizationService, private platformInvitationAuthorizationService: PlatformInvitationAuthorizationService, private libraryAuthorizationService: LibraryAuthorizationService, - private licensingAuthorizationService: LicensingAuthorizationService + private licensingAuthorizationService: LicensingAuthorizationService, + private templatesManagerAuthorizationService: TemplatesManagerAuthorizationService ) {} async applyAuthorizationPolicy(): Promise { @@ -49,6 +51,7 @@ export class PlatformAuthorizationService { library: true, storageAggregator: true, licensing: true, + templatesManager: true, }, }); @@ -58,7 +61,8 @@ export class PlatformAuthorizationService { !platform.library || !platform.forum || !platform.storageAggregator || - !platform.licensing + !platform.licensing || + !platform.templatesManager ) throw new RelationshipNotFoundException( `Unable to load entities for platform: ${platform.id} `, @@ -85,6 +89,13 @@ export class PlatformAuthorizationService { ); updatedAuthorizations.push(libraryUpdatedAuthorization); + const templatesManagerAuthorizations = + await this.templatesManagerAuthorizationService.applyAuthorizationPolicy( + platform.templatesManager.id, + platform.authorization + ); + updatedAuthorizations.push(...templatesManagerAuthorizations); + for (const platformInvitation of platform.platformInvitations) { const updatedInvitation = await this.platformInvitationAuthorizationService.applyAuthorizationPolicy( diff --git a/src/platform/platfrom/platform.service.ts b/src/platform/platfrom/platform.service.ts index d72b38eece..3560b14af8 100644 --- a/src/platform/platfrom/platform.service.ts +++ b/src/platform/platfrom/platform.service.ts @@ -20,6 +20,7 @@ import { ForumService } from '@platform/forum/forum.service'; import { IForum } from '@platform/forum/forum.interface'; import { ForumDiscussionCategory } from '@common/enums/forum.discussion.category'; import { Discussion } from '@platform/forum-discussion/discussion.entity'; +import { ITemplatesManager } from '@domain/template/templates-manager/templates.manager.interface'; @Injectable() export class PlatformService { @@ -82,6 +83,20 @@ export class PlatformService { return forum; } + async getTemplatesManagerOrFail(): Promise { + const platform = await this.getPlatformOrFail({ + relations: { templatesManager: true }, + }); + if (!platform || !platform.templatesManager) { + throw new EntityNotFoundException( + 'Unable to find templatesManager for platform', + LogContext.PLATFORM + ); + } + + return platform.templatesManager; + } + async ensureForumCreated(): Promise { const platform = await this.getPlatformOrFail({ relations: { forum: true }, From 821a96dee49e86e543f6cf065bac661bb91fa255 Mon Sep 17 00:00:00 2001 From: Neil Smyth Date: Sun, 29 Sep 2024 16:52:35 +0200 Subject: [PATCH 004/105] moved creating of default innovatin flow input to space defaults --- .../space.defaults/space.defaults.service.ts | 19 ++++++++++++++++- src/domain/space/space/space.service.ts | 21 +++---------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/domain/space/space.defaults/space.defaults.service.ts b/src/domain/space/space.defaults/space.defaults.service.ts index 2ba816e674..6270a7d67a 100644 --- a/src/domain/space/space.defaults/space.defaults.service.ts +++ b/src/domain/space/space.defaults/space.defaults.service.ts @@ -35,6 +35,7 @@ import { spaceDefaultsCalloutsBlankSlate } from './definitions/blank-slate/space import { spaceDefaultsSettingsBlankSlate } from './definitions/blank-slate/space.defaults.settings.blank.slate'; import { spaceDefaultsInnovationFlowStatesBlankSlate } from './definitions/blank-slate/space.defaults.innovation.flow.blank.slate'; import { CreateRoleInput } from '@domain/access/role/dto/role.dto.create'; +import { CreateInnovationFlowInput } from '@domain/collaboration/innovation-flow/dto/innovation.flow.dto.create'; @Injectable() export class SpaceDefaultsService { @@ -92,6 +93,22 @@ export class SpaceDefaultsService { } } + public async getDefaultInnovationFlowInput( + spaceType: SpaceType + ): Promise { + // If no default template is set, then pick up the default based on the specified type + const innovationFlowStatesDefault = + this.getDefaultInnovationFlowStates(spaceType); + const result: CreateInnovationFlowInput = { + profile: { + displayName: 'default', + description: 'default flow', + }, + states: innovationFlowStatesDefault, + }; + return result; + } + public getProfileType(spaceLevel: SpaceLevel): ProfileType { switch (spaceLevel) { case SpaceLevel.CHALLENGE: @@ -155,7 +172,7 @@ export class SpaceDefaultsService { } } - public getDefaultInnovationFlowStates( + private getDefaultInnovationFlowStates( spaceType: SpaceType ): IInnovationFlowState[] { switch (spaceType) { diff --git a/src/domain/space/space/space.service.ts b/src/domain/space/space/space.service.ts index e8e92bac19..da342bbcdd 100644 --- a/src/domain/space/space/space.service.ts +++ b/src/domain/space/space/space.service.ts @@ -71,7 +71,6 @@ 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 { 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'; @@ -213,7 +212,9 @@ export class SpaceService { if (!collaborationData.innovationFlowData) { // TODO: need to pick up the default template + innovation flow properly collaborationData.innovationFlowData = - await this.getDefaultInnovationStates(space.type); + await this.spaceDefaultsService.getDefaultInnovationFlowInput( + space.type + ); } if (!collaborationData.calloutsData) { collaborationData.calloutsData = []; @@ -345,22 +346,6 @@ export class SpaceService { return result; } - private async getDefaultInnovationStates( - spaceType: SpaceType - ): Promise { - // 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, From fffaedfa96798a990a5c65f539c4aa9d8739a303 Mon Sep 17 00:00:00 2001 From: Neil Smyth Date: Sun, 29 Sep 2024 20:15:35 +0200 Subject: [PATCH 005/105] back out space type on Template; tidy up Template module to use switch statements --- src/common/enums/template.type.ts | 1 - .../template/dto/template.dto.create.ts | 10 + .../template/dto/template.dto.update.ts | 33 -- .../template.service.authorization.ts | 139 +++++---- .../template/template/template.service.ts | 281 ++++++++---------- 5 files changed, 218 insertions(+), 246 deletions(-) diff --git a/src/common/enums/template.type.ts b/src/common/enums/template.type.ts index cb3547f4a8..b33cee520f 100644 --- a/src/common/enums/template.type.ts +++ b/src/common/enums/template.type.ts @@ -7,7 +7,6 @@ export enum TemplateType { COMMUNITY_GUIDELINES = 'community-guidelines', INNOVATION_FLOW = 'innovation-flow', COLLABORATION = 'collaboration', - SPACE = 'space', } registerEnumType(TemplateType, { diff --git a/src/domain/template/template/dto/template.dto.create.ts b/src/domain/template/template/dto/template.dto.create.ts index 940450b978..d74b0779ba 100644 --- a/src/domain/template/template/dto/template.dto.create.ts +++ b/src/domain/template/template/dto/template.dto.create.ts @@ -10,6 +10,7 @@ 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 { CreateSpaceInput } from '@domain/space/space/dto/space.dto.create'; import { Field, InputType } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { IsOptional, MaxLength, ValidateNested } from 'class-validator'; @@ -80,4 +81,13 @@ export class CreateTemplateInput extends CreateNameableInput { @ValidateNested() @Type(() => CreateCollaborationInput) collaborationData?: CreateCollaborationInput; + + @Field(() => CreateSpaceInput, { + nullable: true, + description: 'The Space to associate with this template.', + }) + @IsOptional() + @ValidateNested() + @Type(() => CreateSpaceInput) + spaceData?: CreateSpaceInput; } diff --git a/src/domain/template/template/dto/template.dto.update.ts b/src/domain/template/template/dto/template.dto.update.ts index 700be3ba45..b3a6d669a2 100644 --- a/src/domain/template/template/dto/template.dto.update.ts +++ b/src/domain/template/template/dto/template.dto.update.ts @@ -1,11 +1,7 @@ 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 { Field, InputType } from '@nestjs/graphql'; import { Type } from 'class-transformer'; import { IsOptional, MaxLength, ValidateNested } from 'class-validator'; @@ -29,33 +25,4 @@ export class UpdateTemplateInput extends UpdateBaseAlkemioInput { @IsOptional() @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, { - nullable: true, - description: 'The Whiteboard for this template.', - }) - @IsOptional() - @Type(() => UpdateWhiteboardInput) - whiteboard?: UpdateWhiteboardInput; } diff --git a/src/domain/template/template/template.service.authorization.ts b/src/domain/template/template/template.service.authorization.ts index 113bbd7834..3378c693b4 100644 --- a/src/domain/template/template/template.service.authorization.ts +++ b/src/domain/template/template/template.service.authorization.ts @@ -11,6 +11,8 @@ 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() export class TemplateAuthorizationService { @@ -21,7 +23,8 @@ export class TemplateAuthorizationService { private communityGuidelinesAuthorizationService: CommunityGuidelinesAuthorizationService, private calloutAuthorizationService: CalloutAuthorizationService, private whiteboardAuthorizationService: WhiteboardAuthorizationService, - private collaborationAuthorizationService: CollaborationAuthorizationService + private collaborationAuthorizationService: CollaborationAuthorizationService, + private innovationFlowAuthorizationService: InnovationFlowAuthorizationService ) {} async applyAuthorizationPolicy( @@ -78,68 +81,82 @@ 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} `, + 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; + 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; + 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; + 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; + default: + throw new EntityNotFoundException( + `Unable to reset auth on template of type: ${template.type}`, LogContext.TEMPLATES ); - } - // 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 - ); - } - // 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 - ); - } - // 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} `, - LogContext.TEMPLATES - ); - } - // Cascade - const collaborationAuthorizations = - await this.collaborationAuthorizationService.applyAuthorizationPolicy( - template.collaboration, - template.authorization - ); - updatedAuthorizations.push(...collaborationAuthorizations); } return updatedAuthorizations; diff --git a/src/domain/template/template/template.service.ts b/src/domain/template/template/template.service.ts index f790aaa575..5efc396e36 100644 --- a/src/domain/template/template/template.service.ts +++ b/src/domain/template/template/template.service.ts @@ -31,6 +31,7 @@ import { IInnovationFlow } from '@domain/collaboration/innovation-flow/innovatio 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'; @Injectable() export class TemplateService { @@ -40,6 +41,7 @@ export class TemplateService { private communityGuidelinesService: CommunityGuidelinesService, private calloutService: CalloutService, private whiteboardService: WhiteboardService, + private collaborationServerice: CollaborationService, @InjectRepository(Template) private templateRepository: Repository