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

alkem-io / server / #7980

11 Aug 2024 06:30AM UTC coverage: 13.774%. First build
#7980

Pull #4388

travis-ci

Pull Request #4388: Feature account-spaces: Direct linkage to account for user + organization

79 of 4128 branches covered (1.91%)

Branch coverage included in aggregate %.

4 of 34 new or added lines in 7 files covered. (11.76%)

1914 of 10341 relevant lines covered (18.51%)

3.02 hits per line

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

0.0
/src/services/api/me/me.service.ts
1
import { Inject, Injectable, LoggerService } from '@nestjs/common';
×
2
import { groupCredentialsByEntity } from '@services/api/roles/util/group.credentials.by.entity';
×
3
import { SpaceService } from '@domain/space/space/space.service';
×
4
import { RolesService } from '../roles/roles.service';
×
5
import { ISpace } from '@domain/space/space/space.interface';
6
import { ActivityLogService } from '../activity-log';
×
7
import { AgentInfo } from '@core/authentication.agent.info/agent.info';
8
import { MySpaceResults } from './dto/my.journeys.results';
9
import { ActivityService } from '@platform/activity/activity.service';
×
NEW
10
import { LogContext } from '@common/enums';
×
11
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
×
12
import { sortSpacesByActivity } from '@domain/space/space/sort.spaces.by.activity';
×
13
import { CommunityInvitationResult } from './dto/me.invitation.result';
14
import { CommunityResolverService } from '@services/infrastructure/entity-resolver/community.resolver.service';
×
15
import { EntityNotFoundException } from '@common/exceptions';
×
16
import { CommunityApplicationResult } from './dto/me.application.result';
17
import { SpaceMembershipCollaborationInfo } from './space.membership.type';
18
import { CommunityMembershipResult } from './dto/me.membership.result';
19
import { SpaceLevel } from '@common/enums/space.level';
×
20

21
@Injectable()
22
export class MeService {
×
23
  constructor(
24
    private spaceService: SpaceService,
×
25
    private rolesService: RolesService,
×
26
    private activityLogService: ActivityLogService,
×
27
    private activityService: ActivityService,
×
28
    private communityResolverService: CommunityResolverService,
×
29
    @Inject(WINSTON_MODULE_NEST_PROVIDER)
30
    private readonly logger: LoggerService
×
31
  ) {}
32

33
  public async getCommunityInvitationsCountForUser(
34
    userId: string,
35
    states?: string[]
36
  ): Promise<number> {
37
    const invitations = await this.rolesService.getCommunityInvitationsForUser(
×
38
      userId,
39
      states
40
    );
41
    return invitations.length;
×
42
  }
×
43

×
44
  public async getCommunityInvitationsForUser(
×
45
    userId: string,
46
    states?: string[]
47
  ): Promise<CommunityInvitationResult[]> {
48
    const invitations = await this.rolesService.getCommunityInvitationsForUser(
49
      userId,
50
      states
×
51
    );
52
    const results: CommunityInvitationResult[] = [];
53
    for (const invitation of invitations) {
×
54
      if (!invitation.roleSet) {
55
        throw new EntityNotFoundException(
56
          `Community not found for invitation ${invitation.id}`,
57
          LogContext.COMMUNITY
58
        );
59
      }
×
60
      const space =
61
        await this.communityResolverService.getSpaceForRoleSetOrFail(
62
          invitation.roleSet.id
63
        );
64
      if (!space.about) {
65
        throw new EntityNotFoundException(
66
          `Missing entities on Space loaded for Invitation ${invitation.id}`,
67
          LogContext.COMMUNITY
×
68
        );
×
69
      }
×
70
      results.push({
×
71
        id: `${invitation.id}`,
×
72
        invitation: invitation,
73
        spacePendingMembershipInfo: {
74
          id: space.id,
75
          level: space.level,
76
          about: space.about,
77
          communityGuidelines: space.about?.guidelines,
×
78
        },
79
      });
80
    }
×
81
    return results;
82
  }
83

84
  public async getCommunityApplicationsForUser(
85
    userId: string,
86
    states?: string[]
×
87
  ): Promise<CommunityApplicationResult[]> {
88
    const applications =
89
      await this.rolesService.getCommunityApplicationsForUser(userId, states);
90
    const results: CommunityApplicationResult[] = [];
91
    for (const application of applications) {
92
      if (!application.roleSet) {
×
93
        throw new EntityNotFoundException(
×
94
          `Community not found for application ${application.id}`,
95
          LogContext.COMMUNITY
×
96
        );
×
97
      }
98
      const space =
×
99
        await this.communityResolverService.getSpaceForRoleSetOrFail(
100
          application.roleSet.id
×
101
        );
102
      if (!space.about) {
103
        throw new EntityNotFoundException(
104
          `Missing entities on Space loaded for Application ${application.id}`,
×
105
          LogContext.COMMUNITY
106
        );
107
      }
108
      results.push({
109
        id: `${application.id}`,
110
        application: application,
111
        spacePendingMembershipInfo: {
112
          id: space.id,
113
          level: space.level,
114
          about: space.about,
×
115
          communityGuidelines: space.about?.guidelines,
116
        },
×
117
      });
×
118
    }
×
119
    return results;
120
  }
121

×
122
  private async getSpaceMembershipsForAgentInfo(
123
    agentInfo: AgentInfo
124
  ): Promise<ISpace[]> {
125
    const credentialMap = groupCredentialsByEntity(agentInfo.credentials);
126
    const spaceIds = Array.from(credentialMap.get('spaces')?.keys() ?? []);
×
127

128
    const allSpaces = await this.spaceService.getSpacesInList(spaceIds);
129
    const validSpaces = this.filterValidSpaces(allSpaces);
×
130
    const spaceMembershipCollaborationInfo =
131
      this.getSpaceMembershipCollaborationInfo(validSpaces);
132
    const latestActivitiesPerSpace =
133
      await this.activityService.getLatestActivitiesPerSpaceMembership(
134
        agentInfo.userID,
135
        spaceMembershipCollaborationInfo
136
      );
×
137
    return sortSpacesByActivity(validSpaces, latestActivitiesPerSpace);
×
138
  }
139

×
140
  /**
×
141
   * Function that returns all spaces that are valid (L1 and L2 spaces have parents)
142
   * @param allSpaces all spaces to be filtered out
143
   * @returns spaces that are valid (L1 and L2 spaces have parents).
144
   * Orphaned spaces are logged as warnings and filtered out, also spaces without collaboration are filtered out.
145
   */
×
146
  private filterValidSpaces(allSpaces: ISpace[]) {
147
    const validSpaces = [];
×
148

149
    for (const space of allSpaces) {
150
      if (
151
        (space.level !== SpaceLevel.L0 && !space.parentSpace) ||
152
        !space.collaboration
153
      ) {
154
        this.logger.warn(
×
155
          `Space ${space.id} is missing parent space or collaboration`,
156
          LogContext.COMMUNITY
×
157
        );
158
      } else {
159
        validSpaces.push(space);
160
      }
×
161
    }
162
    return validSpaces;
163
  }
164

×
165
  public async getSpaceMembershipsFlat(
166
    agentInfo: AgentInfo
167
  ): Promise<CommunityMembershipResult[]> {
168
    const sortedFlatListSpacesWithMembership =
169
      await this.getSpaceMembershipsForAgentInfo(agentInfo);
×
170
    const spaceMemberships: CommunityMembershipResult[] = [];
×
171

172
    for (const space of sortedFlatListSpacesWithMembership) {
173
      const levelZeroMembership: CommunityMembershipResult = {
174
        id: space.id,
175
        space: space,
176
        childMemberships: [],
177
      };
178
      spaceMemberships.push(levelZeroMembership);
179
    }
×
180
    return spaceMemberships;
181
  }
182

×
183
  public async getSpaceMembershipsHierarchical(
184
    agentInfo: AgentInfo,
185
    limit?: number
186
  ): Promise<CommunityMembershipResult[]> {
×
187
    const sortedFlatListSpacesWithMembership =
188
      await this.getSpaceMembershipsForAgentInfo(agentInfo);
189

190
    const levelZeroSpacesRaw = this.filterSpacesByLevel(
191
      sortedFlatListSpacesWithMembership,
192
      SpaceLevel.L0
193
    );
194
    if (limit) {
×
195
      levelZeroSpacesRaw.splice(limit);
×
196
    }
197
    const levelOneSpaces = this.filterSpacesByLevel(
×
198
      sortedFlatListSpacesWithMembership,
199
      SpaceLevel.L1
200
    );
201
    const levelTwoSpaces = this.filterSpacesByLevel(
202
      sortedFlatListSpacesWithMembership,
203
      SpaceLevel.L2
204
    );
205

×
206
    const levelZeroMemberships = levelZeroSpacesRaw.map(levelZeroSpace => {
207
      const levelZeroMembership: CommunityMembershipResult = {
208
        id: levelZeroSpace.id,
209
        space: levelZeroSpace,
210
        childMemberships: this.getChildMemberships(
211
          levelZeroSpace,
212
          levelOneSpaces,
213
          levelTwoSpaces
×
214
        ),
215
      };
×
216
      return levelZeroMembership;
217
    });
×
218

219
    return levelZeroMemberships;
220
  }
221

222
  private filterSpacesByLevel(spaces: ISpace[], level: SpaceLevel): ISpace[] {
223
    return spaces.filter(space => space.level === level);
224
  }
225

226
  private getChildMemberships(
227
    parentSpace: ISpace,
228
    childSpaces: ISpace[],
229
    grandChildSpaces: ISpace[]
×
230
  ): CommunityMembershipResult[] {
231
    return childSpaces
×
232
      .filter(childSpace => childSpace.parentSpace?.id === parentSpace.id)
×
233
      .map(childSpace => {
×
234
        const childMembership: CommunityMembershipResult = {
235
          id: childSpace.id,
236
          space: childSpace,
237
          childMemberships: this.getGrandChildMemberships(
238
            childSpace,
×
239
            grandChildSpaces
240
          ),
241
        };
242
        return childMembership;
243
      });
244
  }
×
245

246
  private getGrandChildMemberships(
247
    parentSpace: ISpace,
248
    grandChildSpaces: ISpace[]
249
  ): CommunityMembershipResult[] {
×
250
    return grandChildSpaces
251
      .filter(
×
252
        grandChildSpace => grandChildSpace.parentSpace?.id === parentSpace.id
253
      )
254
      .map(grandChildSpace => ({
255
        id: grandChildSpace.id,
256
        space: grandChildSpace,
×
257
        childMemberships: [],
258
      }));
×
259
  }
260

×
261
  // Returns a map of all collaboration IDs with parent space ID
262
  private getSpaceMembershipCollaborationInfo(
×
263
    spaces: ISpace[]
×
264
  ): SpaceMembershipCollaborationInfo {
265
    const spaceMembershipCollaborationInfo: SpaceMembershipCollaborationInfo =
266
      new Map();
267

×
268
    for (const space of spaces) {
269
      if (!space.collaboration) {
×
270
        throw new EntityNotFoundException(
271
          `Space ${space.id} is missing collaboration`,
272
          LogContext.COMMUNITY
273
        );
274
      }
275
      spaceMembershipCollaborationInfo.set(
×
276
        space.collaboration.id,
277
        space.levelZeroSpaceID
278
      );
279
    }
280

281
    return spaceMembershipCollaborationInfo;
282
  }
283

284
  public async getMySpaces(
285
    agentInfo: AgentInfo,
286
    limit = 20
287
  ): Promise<MySpaceResults[]> {
288
    const rawActivities = await this.activityService.getMySpacesActivity(
289
      agentInfo.userID,
290
      limit * 2 //magic number, should not be needed. toDo Fix in https://app.zenhub.com/workspaces/alkemio-development-5ecb98b262ebd9f4aec4194c/issues/gh/alkem-io/server/3626
291
    );
292

293
    const mySpaceResults: MySpaceResults[] = [];
294

295
    for (const rawActivity of rawActivities) {
296
      const activityLog =
297
        await this.activityLogService.convertRawActivityToResult(rawActivity);
298

299
      if (!activityLog?.space) {
300
        this.logger.warn(
301
          `Unable to process activity entry ${rawActivity.id} because it does not have a journey.`,
302
          LogContext.ACTIVITY
303
        );
304
        continue;
305
      }
306
      mySpaceResults.push({
307
        space: activityLog.space,
308
        latestActivity: activityLog,
309
      });
310
    }
311

312
    return mySpaceResults.slice(0, limit);
313
  }
314
}
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

© 2025 Coveralls, Inc