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

alkem-io / server / #8050

16 Aug 2024 11:21AM UTC coverage: 13.92%. First build
#8050

Pull #4411

travis-ci

Pull Request #4411: Type added to authorization policy entity

80 of 4158 branches covered (1.92%)

Branch coverage included in aggregate %.

61 of 116 new or added lines in 50 files covered. (52.59%)

1945 of 10389 relevant lines covered (18.72%)

3.01 hits per line

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

20.69
/src/platform/forum/forum.service.ts
1
import { Inject, Injectable, LoggerService } from '@nestjs/common';
4✔
2
import { InjectRepository } from '@nestjs/typeorm';
4✔
3
import {
4✔
4
  EntityNotFoundException,
5
  EntityNotInitializedException,
6
} from '@common/exceptions';
7
import { LogContext } from '@common/enums';
4✔
8
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
4✔
9
import { FindOneOptions, Repository } from 'typeorm';
4✔
10
import { AuthorizationPolicy } from '@domain/common/authorization-policy';
4✔
11
import { IDiscussion } from '../forum-discussion/discussion.interface';
12
import { DiscussionService } from '../forum-discussion/discussion.service';
4✔
13
import { IUser } from '@domain/community/user/user.interface';
14
import { ForumCreateDiscussionInput } from './dto/forum.dto.create.discussion';
15
import { RoomType } from '@common/enums/room.type';
4✔
16
import { StorageAggregatorResolverService } from '@services/infrastructure/storage-aggregator-resolver/storage.aggregator.resolver.service';
4✔
17
import { DiscussionsOrderBy } from '@common/enums/discussions.orderBy';
4✔
18
import { Discussion } from '../forum-discussion/discussion.entity';
4✔
19
import { NamingService } from '@services/infrastructure/naming/naming.service';
20
import { Forum } from './forum.entity';
4✔
21
import { ForumDiscussionCategory } from '@common/enums/forum.discussion.category';
4✔
22
import { IForum } from './forum.interface';
23
import { ForumDiscussionCategoryException } from '@common/exceptions/forum.discussion.category.exception';
24
import { CommunicationAdapter } from '@services/adapters/communication-adapter/communication.adapter';
4✔
25
import { AuthorizationPolicyType } from '@common/enums/authorization.policy.type';
4✔
26

4✔
27
@Injectable()
28
export class ForumService {
29
  constructor(
4✔
30
    private discussionService: DiscussionService,
31
    private communicationAdapter: CommunicationAdapter,
1✔
32
    private storageAggregatorResolverService: StorageAggregatorResolverService,
1✔
33
    private namingService: NamingService,
1✔
34
    @InjectRepository(Forum)
1✔
35
    private forumRepository: Repository<Forum>,
36
    @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService
1✔
37
  ) {}
1✔
38

39
  async createForum(
40
    discussionCategories: ForumDiscussionCategory[]
41
  ): Promise<IForum> {
42
    const forum: IForum = new Forum();
43
    forum.authorization = new AuthorizationPolicy(
×
NEW
44
      AuthorizationPolicyType.FORUM
×
45
    );
46

47
    forum.discussions = [];
48
    forum.discussionCategories = discussionCategories;
×
49

×
50
    return await this.save(forum);
51
  }
×
52

53
  async save(forum: IForum): Promise<IForum> {
54
    return await this.forumRepository.save(forum);
55
  }
×
56

57
  async createDiscussion(
58
    discussionData: ForumCreateDiscussionInput,
59
    userID: string,
60
    userForumID: string
61
  ): Promise<IDiscussion> {
62
    const displayName = discussionData.profile.displayName;
63
    const forumID = discussionData.forumID;
×
64

×
65
    this.logger.verbose?.(
66
      `[Discussion] Adding discussion (${displayName}) to Forum (${forumID})`,
×
67
      LogContext.PLATFORM_FORUM
68
    );
69

70
    // Try to find the Forum
71
    const forum = await this.getForumOrFail(forumID, {
72
      relations: {},
×
73
    });
74

75
    if (!forum.discussionCategories.includes(discussionData.category)) {
76
      throw new ForumDiscussionCategoryException(
×
77
        `Invalid discussion category supplied ('${discussionData.category}'), allowed categories: ${forum.discussionCategories}`,
×
78
        LogContext.PLATFORM_FORUM
79
      );
80
    }
81

82
    const storageAggregator =
83
      await this.storageAggregatorResolverService.getStorageAggregatorForForum();
84
    const reservedNameIDs = await this.namingService.getReservedNameIDsInForum(
×
85
      forum.id
×
86
    );
87
    discussionData.nameID =
88
      this.namingService.createNameIdAvoidingReservedNameIDs(
×
89
        `${discussionData.profile.displayName}`,
90
        reservedNameIDs
91
      );
92
    let discussion = await this.discussionService.createDiscussion(
93
      discussionData,
×
94
      userID,
95
      'platform-forum',
96
      RoomType.DISCUSSION_FORUM,
97
      storageAggregator
98
    );
99
    this.logger.verbose?.(
100
      `[Discussion] Room created (${displayName}) and membership replicated from Updates (${forumID})`,
×
101
      LogContext.PLATFORM_FORUM
102
    );
103
    discussion.forum = forum;
104

×
105
    discussion = await this.discussionService.save(discussion);
106

×
107
    // Trigger a room membership request for the current user that is not awaited
108
    const room = await this.discussionService.getComments(discussion.id);
109
    await this.communicationAdapter.userAddToRooms(
×
110
      [room.externalRoomID],
×
111
      userForumID
112
    );
113

114
    return discussion;
115
  }
×
116

117
  public async getDiscussions(
118
    forum: IForum,
119
    limit?: number,
120
    orderBy: DiscussionsOrderBy = DiscussionsOrderBy.DISCUSSIONS_CREATEDATE_DESC
121
  ): Promise<IDiscussion[]> {
×
122
    const forumWithDiscussions = await this.getForumOrFail(forum.id, {
123
      relations: { discussions: true },
×
124
    });
125
    const discussions = forumWithDiscussions.discussions;
126
    if (!discussions)
×
127
      throw new EntityNotInitializedException(
×
128
        `Unable to load Discussions for Forum: ${forum.id} `,
×
129
        LogContext.PLATFORM_FORUM
130
      );
131

132
    const sortedDiscussions = (discussions as Discussion[]).sort((a, b) => {
133
      switch (orderBy) {
×
134
        case DiscussionsOrderBy.DISCUSSIONS_CREATEDATE_ASC:
×
135
          return a.createdDate.getTime() - b.createdDate.getTime();
136
        case DiscussionsOrderBy.DISCUSSIONS_CREATEDATE_DESC:
×
137
          return b.createdDate.getTime() - a.createdDate.getTime();
138
      }
×
139
      return 0;
140
    });
×
141
    return limit && limit > 0
142
      ? sortedDiscussions.slice(0, limit)
×
143
      : sortedDiscussions;
144
  }
145

146
  async getDiscussionOrFail(
147
    forum: IForum,
148
    discussionID: string
149
  ): Promise<IDiscussion> {
150
    const discussion = await this.discussionService.getDiscussionOrFail(
151
      discussionID,
×
152
      {
153
        relations: { forum: true },
×
154
      }
×
155
    );
×
156
    // Check the requested discussion is in the forum
157
    if (!discussion.forum || !(discussion.forum.id === forum.id)) {
158
      throw new EntityNotFoundException(
×
159
        `Unable to find Forum for Discussion with ID: ${discussionID}`,
160
        LogContext.PLATFORM_FORUM
×
161
      );
×
162
    }
163
    return discussion;
164
  }
165

×
166
  async getForumOrFail(
×
167
    forumID: string,
168
    options?: FindOneOptions<Forum>
169
  ): Promise<IForum | never> {
170
    const forum = await this.forumRepository.findOne({
171
      where: {
×
172
        id: forumID,
173
      },
174
      ...options,
175
    });
176
    if (!forum)
177
      throw new EntityNotFoundException(
178
        `Unable to find Forum with ID: ${forumID}`,
×
179
        LogContext.PLATFORM_FORUM
180
      );
181
    return forum;
182
  }
183

184
  async removeForum(forumID: string): Promise<boolean> {
×
185
    // Note need to load it in with all contained entities so can remove fully
×
186
    const forum = await this.getForumOrFail(forumID, {
187
      relations: { discussions: true },
188
    });
189

×
190
    // Remove all groups
191
    for (const discussion of await this.getDiscussions(forum)) {
192
      await this.discussionService.removeDiscussion({
193
        ID: discussion.id,
194
      });
×
195
    }
196

197
    await this.forumRepository.remove(forum as Forum);
198
    return true;
199
  }
×
200

×
201
  async addUserToForums(forum: IForum, forumUserID: string): Promise<boolean> {
202
    const forumRoomIDs = await this.getRoomsUsed(forum);
203
    await this.communicationAdapter.userAddToRooms(forumRoomIDs, forumUserID);
204

205
    return true;
×
206
  }
×
207

208
  async getRoomsUsed(forum: IForum): Promise<string[]> {
209
    const forumRoomIDs: string[] = [];
210
    const discussions = await this.getDiscussions(forum);
×
211
    for (const discussion of discussions) {
×
212
      const room = await this.discussionService.getComments(discussion.id);
213
      forumRoomIDs.push(room.displayName);
214
    }
215
    return forumRoomIDs;
216
  }
×
217

218
  async getForumIDsUsed(): Promise<string[]> {
219
    const forumMatches = await this.forumRepository
220
      .createQueryBuilder('forum')
×
221
      .getMany();
×
222
    const forumIDs: string[] = [];
×
223
    for (const forum of forumMatches) {
×
224
      forumIDs.push(forum.id);
×
225
    }
226
    return forumIDs;
×
227
  }
228

229
  async removeUserFromForums(forum: IForum, user: IUser): Promise<boolean> {
230
    // get the list of rooms to add the user to
×
231
    const forumRoomIDs: string[] = [];
232
    for (const discussion of await this.getDiscussions(forum)) {
233
      const room = await this.discussionService.getComments(discussion.id);
×
234
      forumRoomIDs.push(room.externalRoomID);
×
235
    }
×
236
    await this.communicationAdapter.removeUserFromRooms(
237
      forumRoomIDs,
×
238
      user.communicationID
239
    );
240

241
    return true;
242
  }
×
243
}
×
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