• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

alkem-io / server / #9106

22 Jan 2025 04:03PM UTC coverage: 14.35%. First build
#9106

Pull #4814

travis-ci

Pull Request #4814: RoleSet on organizations + platform

84 of 4951 branches covered (1.7%)

Branch coverage included in aggregate %.

48 of 312 new or added lines in 27 files covered. (15.38%)

2292 of 11606 relevant lines covered (19.75%)

7.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/services/api/conversion/conversion.service.ts
1
import { Inject, LoggerService } from '@nestjs/common';
×
2
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
×
3
import { LogContext } from '@common/enums/logging.context';
4
import { ConvertSpaceL1ToSpaceL0Input } from './dto/convert.dto.space.l1.to.space.l0.input';
×
5
import { ISpace } from '@domain/space/space/space.interface';
6
import {
7
  EntityNotInitializedException,
×
8
  RelationshipNotFoundException,
9
  ValidationException,
10
} from '@common/exceptions';
NEW
11
import { SpaceService } from '@domain/space/space/space.service';
×
12
import { NamingService } from '@services/infrastructure/naming/naming.service';
13
import { SpaceLevel } from '@common/enums/space.level';
14
import { RoleSetService } from '@domain/access/role-set/role.set.service';
15
import { PlatformService } from '@platform/platform/platform.service';
×
16
import { TemplatesManagerService } from '@domain/template/templates-manager/templates.manager.service';
17
import { TemplateDefaultType } from '@common/enums/template.default.type';
×
18
import { TemplateService } from '@domain/template/template/template.service';
×
19
import { InnovationFlowService } from '@domain/collaboration/innovation-flow/innovation.flow.service';
×
20
import { RoleName } from '@common/enums/role.name';
×
21
import { ConvertSpaceL1ToSpaceL2Input } from './dto/convert.dto.space.l1.to.space.l2.input';
×
22
import { ConvertSpaceL2ToSpaceL1Input } from './dto/convert.dto.space.l2.to.space.l1.input';
23
import { IRoleSet } from '@domain/access/role-set/role.set.interface';
×
24
import { IUser } from '@domain/community/user/user.interface';
×
25
import { IOrganization } from '@domain/community/organization/organization.interface';
×
26
import { IVirtualContributor } from '@domain/community/virtual-contributor/virtual.contributor.interface';
27
import { AccountHostService } from '@domain/space/account.host/account.host.service';
28
import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface';
×
29
import { IInnovationFlowState } from '@domain/collaboration/innovation-flow-state/innovation.flow.state.interface';
30
import { CreateInnovationFlowStateInput } from '@domain/collaboration/innovation-flow-state/dto';
×
31
import { InputCreatorService } from '../input-creator/input.creator.service';
32

×
33
export class ConversionService {
×
34
  constructor(
×
35
    private spaceService: SpaceService,
×
36
    private namingService: NamingService,
×
37
    private roleSetService: RoleSetService,
×
38
    private platformService: PlatformService,
×
39
    private templateService: TemplateService,
40
    private templatesManagerService: TemplatesManagerService,
41
    private inputCreatorService: InputCreatorService,
42
    private innovationFlowService: InnovationFlowService,
43
    private accountHostService: AccountHostService,
44
    @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService
45
  ) {}
46

×
47
  async convertSpaceL1ToSpaceL0OrFail(
48
    conversionData: ConvertSpaceL1ToSpaceL0Input
49
  ): Promise<ISpace | never> {
50
    let spaceL1 = await this.spaceService.getSpaceOrFail(
51
      conversionData.spaceL1ID,
52
      {
53
        relations: {
54
          agent: true,
55
          community: {
56
            roleSet: true,
57
          },
58
          collaboration: {
59
            innovationFlow: {
60
              states: true,
61
            },
62
          },
63
          storageAggregator: true,
64
          subspaces: true,
65
          parentSpace: true, // Needed to be able to unset it
66
        },
67
      }
68
    );
69
    if (
70
      !spaceL1.community ||
×
71
      !spaceL1.community.roleSet ||
×
72
      !spaceL1.collaboration ||
73
      !spaceL1.collaboration.innovationFlow ||
74
      !spaceL1.collaboration.innovationFlow.states ||
75
      !spaceL1.storageAggregator ||
76
      !spaceL1.subspaces ||
×
77
      !spaceL1.agent
78
    ) {
79
      throw new EntityNotInitializedException(
×
80
        `Unable to locate all entities on on space L1: ${spaceL1.id}`,
81
        LogContext.CONVERSION
82
      );
83
    }
84
    const subspacesL1 = spaceL1.subspaces;
85
    const roleSetL1 = spaceL1.community.roleSet;
86
    const agentL1 = spaceL1.agent;
87
    const storageAggregatorL1 = spaceL1.storageAggregator;
×
88

89
    const spaceL0Orig = await this.spaceService.getSpaceOrFail(
90
      spaceL1.levelZeroSpaceID,
91
      {
×
92
        relations: {
93
          subspaces: true,
94
          account: {
95
            storageAggregator: true,
×
96
          },
×
97
        },
98
      }
99
    );
100
    if (
101
      !spaceL0Orig.account ||
102
      !spaceL0Orig.account.storageAggregator ||
×
103
      !spaceL0Orig.subspaces
104
    ) {
105
      throw new EntityNotInitializedException(
106
        `Unable to locate all entities on on space L0: ${spaceL0Orig.id}`,
107
        LogContext.CONVERSION
108
      );
109
    }
110

111
    // Need to get the containing account for the space
112
    const account = spaceL0Orig.account;
113
    const storageAggregatorAccount = spaceL0Orig.account.storageAggregator;
114

115
    // Remove the admins, so that also implicit subspace admin role is removed
×
116
    const userAdmins = await this.roleSetService.getUsersWithRole(
117
      roleSetL1,
×
118
      RoleName.ADMIN
119
    );
120
    for (const userAdmin of userAdmins) {
121
      await this.roleSetService.removeUserFromRole(
122
        roleSetL1,
123
        RoleName.ADMIN,
124
        userAdmin.id,
125
        false
126
      );
×
127
    }
×
128

129
    const reservedNameIDs =
130
      await this.namingService.getReservedNameIDsLevelZeroSpaces();
131
    const spaceL0NewNameID =
132
      this.namingService.createNameIdAvoidingReservedNameIDs(
133
        `${spaceL1.nameID}`,
×
134
        reservedNameIDs
135
      );
136

137
    spaceL1.level = SpaceLevel.L0;
138
    spaceL1.nameID = spaceL0NewNameID;
×
139
    spaceL1.levelZeroSpaceID = spaceL1.id;
140
    spaceL1.parentSpace = undefined; // Unfortunately this is not enough
×
141
    spaceL1.account = account;
142
    spaceL1.storageAggregator.parentStorageAggregator =
143
      storageAggregatorAccount;
144

×
145
    // Some fields on a Space L0 do not exist on Space L1 so we need to create them
146
    spaceL1.license = this.spaceService.createLicenseForSpaceL0();
147
    spaceL1.templatesManager =
148
      await this.spaceService.createTemplatesManagerForSpaceL0();
×
149

150
    // reset to default Space L0 innovation flow
151
    const resetInnovationFlowStates = await this.getInnovationFlowForSpaceL0();
152
    const newStatesInput: CreateInnovationFlowStateInput[] =
153
      this.inputCreatorService.buildCreateInnovationFlowStateInputFromInnovationFlowState(
154
        resetInnovationFlowStates
×
155
      );
156
    spaceL1.collaboration.innovationFlow =
157
      await this.innovationFlowService.updateInnovationFlowStates(
158
        spaceL1.collaboration.innovationFlow,
159
        newStatesInput
160
      );
161

162
    spaceL1 = await this.spaceService.save(spaceL1);
×
163

164
    // Ensure that the license plans for new spaces are applied
165
    spaceL1.agent = await this.accountHostService.assignLicensePlansToSpace(
166
      agentL1,
167
      spaceL1.id,
168
      account.type
169
    );
×
170
    // Need to do the roleset update after
171
    await this.roleSetService.removeParentRoleSet(roleSetL1.id);
172
    // and add back in the admins
×
173
    for (const userAdmin of userAdmins) {
×
174
      await this.roleSetService.assignUserToRole(
×
175
        roleSetL1,
×
176
        RoleName.ADMIN,
177
        userAdmin.id
178
      );
×
179
    }
×
180

×
181
    // And remove the space from the old space L0; note that setting to undefined is not enough, need to go through the parent space
×
182
    spaceL0Orig.subspaces = spaceL0Orig.subspaces.filter(
183
      subspace => subspace.id !== spaceL1.id
×
184
    );
185
    await this.spaceService.save(spaceL0Orig);
186

×
187
    // Now migrate all the child L2 spaces...note: this needs to go through a different path than the isolated conversion L2 to L1
×
188
    for (const spaceL2 of subspacesL1) {
×
189
      await this.updateChildSpaceL2ToL1(
×
190
        spaceL2.id,
191
        spaceL1,
192
        storageAggregatorL1,
×
193
        roleSetL1
×
194
      );
×
195
    }
×
196

197
    return spaceL1;
×
198
  }
×
199

200
  private async updateChildSpaceL2ToL1(
201
    spaceL2ID: string,
×
202
    spaceL0: ISpace,
×
203
    storageAggregatorL0: IStorageAggregator,
204
    roleSetL0: IRoleSet
205
  ): Promise<ISpace> {
×
206
    const spaceL2 = await this.spaceService.getSpaceOrFail(spaceL2ID, {
207
      relations: {
208
        storageAggregator: true,
209
        parentSpace: true,
210
        community: {
211
          roleSet: true,
212
        },
213
      },
×
214
    });
×
215
    if (
×
216
      !spaceL2.storageAggregator ||
217
      !spaceL2.parentSpace ||
218
      !spaceL2.community ||
×
219
      !spaceL2.community.roleSet
220
    ) {
221
      throw new EntityNotInitializedException(
×
222
        `Unable to locate all entities on on Space L2: ${spaceL2.id}`,
223
        LogContext.CONVERSION
224
      );
225
    }
226
    const roleSetL2 = spaceL2.community.roleSet;
227

228
    spaceL2.level = SpaceLevel.L1;
×
229
    spaceL2.parentSpace = spaceL0;
230
    spaceL2.levelZeroSpaceID = spaceL0.id;
231
    spaceL2.storageAggregator.parentStorageAggregator = storageAggregatorL0;
232
    spaceL2.community.roleSet =
233
      await this.roleSetService.setParentRoleSetAndCredentials(
234
        roleSetL2,
235
        roleSetL0
236
      );
237
    return await this.spaceService.save(spaceL2);
238
  }
239

240
  private async getInnovationFlowForSpaceL0(): Promise<IInnovationFlowState[]> {
241
    const platformTemplatesManager =
242
      await this.platformService.getTemplatesManagerOrFail();
243
    const levelZeroTemplate =
244
      await this.templatesManagerService.getTemplateFromTemplateDefault(
245
        platformTemplatesManager.id,
246
        TemplateDefaultType.PLATFORM_SPACE
247
      );
248
    const templateWithInnovationFlow =
249
      await this.templateService.getTemplateOrFail(levelZeroTemplate.id, {
250
        relations: {
251
          contentSpace: {
252
            collaboration: {
×
253
              innovationFlow: {
×
254
                states: true,
255
              },
256
            },
257
          },
258
        },
259
      });
260

261
    if (
×
262
      !templateWithInnovationFlow.contentSpace?.collaboration ||
263
      !templateWithInnovationFlow.contentSpace.collaboration.innovationFlow
×
264
    ) {
265
      throw new RelationshipNotFoundException(
266
        `Unable to retrieve Space L0 innovation flow template: ${levelZeroTemplate.id} is missing a relation`,
267
        LogContext.CONVERSION
268
      );
269
    }
270
    return templateWithInnovationFlow.contentSpace.collaboration.innovationFlow
×
271
      .states;
272
  }
273

274
  async convertSpaceL2ToSpaceL1OrFail(
×
275
    conversionData: ConvertSpaceL2ToSpaceL1Input
276
  ): Promise<ISpace | never> {
277
    let spaceL2 = await this.spaceService.getSpaceOrFail(
278
      conversionData.spaceL2ID,
279
      {
280
        relations: {
×
281
          community: {
×
282
            roleSet: true,
283
          },
284
        },
285
      }
286
    );
287
    if (!spaceL2.community) {
288
      throw new EntityNotInitializedException(
289
        `Unable to locate all entities on on Space L2: ${spaceL2.id}`,
290
        LogContext.CONVERSION
291
      );
292
    }
293
    const roleSetL2 = spaceL2.community.roleSet;
294

×
295
    const spaceL0 = await this.spaceService.getSpaceOrFail(
296
      spaceL2.levelZeroSpaceID,
297
      {
298
        relations: {
299
          storageAggregator: true,
×
300
          community: {
301
            roleSet: true,
302
          },
303
        },
304
      }
305
    );
306
    if (
307
      !spaceL0.storageAggregator ||
308
      !spaceL0.community ||
×
309
      !spaceL0.community.roleSet
×
310
    ) {
311
      throw new EntityNotInitializedException(
312
        `Conversion L2 to L1: Unable to locate all entities on on Space L0: ${spaceL0.id}`,
313
        LogContext.CONVERSION
314
      );
315
    }
×
316
    const storageAggregatorL0 = spaceL0.storageAggregator;
317
    const roleSetL0 = spaceL0.community.roleSet;
318

319
    // Remove the admins, so that also implicit subspace admin role is removed
320
    const userAdmins = await this.roleSetService.getUsersWithRole(
321
      roleSetL2,
×
322
      RoleName.ADMIN
×
323
    );
324
    for (const userAdmin of userAdmins) {
325
      await this.roleSetService.removeUserFromRole(
326
        roleSetL2,
×
327
        RoleName.ADMIN,
328
        userAdmin.id,
329
        false
330
      );
×
331
    }
332

333
    spaceL2 = await this.updateChildSpaceL2ToL1(
334
      spaceL2.id,
×
335
      spaceL0,
336
      storageAggregatorL0,
337
      roleSetL0
338
    );
339
    // and add back in the admins
340
    for (const userAdmin of userAdmins) {
×
341
      await this.roleSetService.assignUserToRole(
342
        roleSetL2,
343
        RoleName.ADMIN,
344
        userAdmin.id
345
      );
346
    }
347
    return await this.spaceService.getSpaceOrFail(spaceL2.id);
348
  }
349

×
350
  async convertSpaceL1ToSpaceL2OrFail(
351
    conversionData: ConvertSpaceL1ToSpaceL2Input
352
  ): Promise<ISpace | never> {
353
    let spaceL1 = await this.spaceService.getSpaceOrFail(
354
      conversionData.spaceL1ID,
×
355
      {
356
        relations: {
357
          community: {
358
            roleSet: true,
359
          },
360
          storageAggregator: true,
361
        },
×
362
      }
×
363
    );
364
    if (
365
      !spaceL1.community ||
366
      !spaceL1.community.roleSet ||
367
      !spaceL1.storageAggregator
×
368
    ) {
×
369
      throw new EntityNotInitializedException(
×
370
        `Unable to locate all entities on on Space L1: ${spaceL1.id}`,
×
371
        LogContext.CONVERSION
372
      );
373
    }
×
374
    const roleSetL1 = spaceL1.community.roleSet;
×
375

×
376
    const parentSpaceL1 = await this.spaceService.getSpaceOrFail(
×
377
      conversionData.parentSpaceL1ID,
378
      {
×
379
        relations: {
×
380
          storageAggregator: true,
381
          community: {
382
            roleSet: true,
383
          },
×
384
        },
×
385
      }
×
386
    );
×
387
    if (
388
      !parentSpaceL1.storageAggregator ||
389
      !parentSpaceL1.community ||
390
      !parentSpaceL1.community.roleSet
391
    ) {
×
392
      throw new EntityNotInitializedException(
×
393
        `Conversion L1 to L2: Unable to locate all entities on on parent Space L1: ${parentSpaceL1.id}`,
×
394
        LogContext.CONVERSION
×
395
      );
396
    }
×
397

×
398
    if (spaceL1.levelZeroSpaceID !== parentSpaceL1.levelZeroSpaceID) {
399
      throw new ValidationException(
400
        'Only can convert a L1 Space to be L2 within the same L0 Space',
×
401
        LogContext.CONVERSION
×
402
      );
403
    }
404
    const storageAggregatorParentL1 = parentSpaceL1.storageAggregator;
405
    const roleSetParentL1 = parentSpaceL1.community.roleSet;
×
406

×
407
    const spaceCommunityRoles = await this.getSpaceCommunityRoles(roleSetL1);
×
408
    await this.removeContributors(roleSetL1, spaceCommunityRoles);
409

410
    spaceL1.level = SpaceLevel.L2;
×
411
    spaceL1.parentSpace = parentSpaceL1;
412
    spaceL1.storageAggregator.parentStorageAggregator =
413
      storageAggregatorParentL1;
414
    spaceL1.community.roleSet =
415
      await this.roleSetService.setParentRoleSetAndCredentials(
416
        roleSetL1,
417
        roleSetParentL1
418
      );
419

×
420
    spaceL1 = await this.spaceService.save(spaceL1);
421
    // and add back in the admins
422
    for (const userAdmin of spaceCommunityRoles.userAdmins) {
423
      await this.roleSetService.assignUserToRole(
424
        roleSetL1,
425
        RoleName.ADMIN,
426
        userAdmin.id
427
      );
428
    }
×
429
    return spaceL1;
430
  }
431

432
  private async getSpaceCommunityRoles(
×
433
    roleSet: IRoleSet
×
434
  ): Promise<SpaceCommunityRoles> {
435
    const userMembers = await this.roleSetService.getUsersWithRole(
436
      roleSet,
437
      RoleName.MEMBER
438
    );
×
439
    const userLeads = await this.roleSetService.getUsersWithRole(
×
440
      roleSet,
×
441
      RoleName.LEAD
442
    );
443
    const userAdmins = await this.roleSetService.getUsersWithRole(
444
      roleSet,
×
445
      RoleName.ADMIN
446
    );
447
    const orgMembers = await this.roleSetService.getOrganizationsWithRole(
448
      roleSet,
449
      RoleName.MEMBER
450
    );
×
451
    const orgLeads = await this.roleSetService.getOrganizationsWithRole(
×
452
      roleSet,
453
      RoleName.LEAD
×
454
    );
×
455

456
    const vcMembers = await this.roleSetService.getVirtualContributorsWithRole(
457
      roleSet,
458
      RoleName.MEMBER
459
    );
×
460
    return {
×
461
      userMembers,
462
      userLeads,
×
463
      userAdmins,
×
464
      orgMembers,
465
      orgLeads,
×
466
      vcMembers,
×
467
    };
468
  }
469

470
  private async removeContributors(
471
    roleSet: IRoleSet,
472
    spaceCommunityRoles: SpaceCommunityRoles
×
473
  ) {
×
474
    const validatePolicyLimits = false;
475
    for (const userMember of spaceCommunityRoles.userMembers) {
476
      await this.roleSetService.removeUserFromRole(
477
        roleSet,
478
        RoleName.MEMBER,
×
479
        userMember.id,
×
480
        validatePolicyLimits
×
481
      );
482
    }
483
    for (const userLead of spaceCommunityRoles.userLeads) {
484
      await this.roleSetService.removeUserFromRole(
×
485
        roleSet,
486
        RoleName.LEAD,
487
        userLead.id,
488
        validatePolicyLimits
489
      );
490
    }
×
491
    for (const orgMember of spaceCommunityRoles.orgMembers) {
×
492
      await this.roleSetService.removeOrganizationFromRole(
493
        roleSet,
×
494
        RoleName.MEMBER,
×
495
        orgMember.id,
496
        validatePolicyLimits
497
      );
498
    }
499
    for (const orgLead of spaceCommunityRoles.orgLeads) {
×
500
      await this.roleSetService.removeOrganizationFromRole(
×
501
        roleSet,
502
        RoleName.LEAD,
×
503
        orgLead.id,
×
504
        validatePolicyLimits
505
      );
×
506
    }
×
507
    for (const vcMember of spaceCommunityRoles.vcMembers) {
508
      await this.roleSetService.removeVirtualFromRole(
509
        roleSet,
510
        RoleName.MEMBER,
511
        vcMember.id,
512
        validatePolicyLimits
513
      );
514
    }
515
  }
×
516
}
517

518
// Create a new type for usage in this service that has fields for user members + leads, org members + leads etc
×
519
type SpaceCommunityRoles = {
520
  userMembers: IUser[];
521
  userLeads: IUser[];
522
  userAdmins: IUser[];
×
523
  orgMembers: IOrganization[];
×
524
  orgLeads: IOrganization[];
525
  vcMembers: IVirtualContributor[];
×
526
};
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc