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

alkem-io / server / #7054

16 May 2024 06:17PM UTC coverage: 13.632%. First build
#7054

Pull #3933

travis-ci

Pull Request #3933: smarter generation of nameIDs

114 of 4240 branches covered (2.69%)

Branch coverage included in aggregate %.

11 of 100 new or added lines in 14 files covered. (11.0%)

1746 of 9404 relevant lines covered (18.57%)

2.83 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';
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';
NEW
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

×
NEW
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
      );
NEW
269
    }
×
270
    return templateWithInnovationFlow.contentSpace.collaboration.innovationFlow
271
      .states;
272
  }
NEW
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