• 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

19.28
/src/domain/collaboration/callout-contribution/callout.contribution.service.ts
1
import { Injectable } from '@nestjs/common';
17✔
2
import { CreateCalloutContributionInput } from './dto/callout.contribution.dto.create';
3
import { ICalloutContribution } from './callout.contribution.interface';
4
import { CalloutContribution } from './callout.contribution.entity';
17✔
5
import { AuthorizationPolicy } from '@domain/common/authorization-policy/authorization.policy.entity';
17✔
6
import { InjectRepository } from '@nestjs/typeorm';
17✔
7
import { FindOneOptions, FindOptionsRelations, Repository } from 'typeorm';
17✔
8
import { EntityNotFoundException } from '@common/exceptions/entity.not.found.exception';
17✔
9
import { LogContext } from '@common/enums/logging.context';
17✔
10
import { WhiteboardService } from '@domain/common/whiteboard/whiteboard.service';
17✔
11
import { IWhiteboard } from '@domain/common/whiteboard/types';
17✔
12
import { PostService } from '../post/post.service';
13
import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service';
17✔
14
import { IPost } from '../post';
17✔
15
import { ICalloutSettingsContribution } from '../callout-settings/callout.settings.contribution.interface';
16
import { CalloutContributionType } from '@common/enums/callout.contribution.type';
17
import {
17✔
18
  RelationshipNotFoundException,
17✔
19
  ValidationException,
20
} from '@common/exceptions';
17✔
21
import { IStorageAggregator } from '@domain/storage/storage-aggregator/storage.aggregator.interface';
22
import { LinkService } from '../link/link.service';
17✔
23
import { ILink } from '../link/link.interface';
24
import { AuthorizationPolicyType } from '@common/enums/authorization.policy.type';
25
import { IStorageBucket } from '@domain/storage/storage-bucket/storage.bucket.interface';
17✔
26
import { IProfile } from '@domain/common/profile/profile.interface';
27

×
28
@Injectable()
×
29
export class CalloutContributionService {
×
30
  constructor(
×
31
    private authorizationPolicyService: AuthorizationPolicyService,
32
    private postService: PostService,
×
33
    private whiteboardService: WhiteboardService,
34
    private linkService: LinkService,
35
    @InjectRepository(CalloutContribution)
36
    private contributionRepository: Repository<CalloutContribution>
37
  ) {}
38

39
  public async createCalloutContributions(
40
    calloutContributionsData: CreateCalloutContributionInput[],
41
    storageAggregator: IStorageAggregator,
×
42
    contributionSettings: ICalloutSettingsContribution,
43
    userID: string
44
  ): Promise<ICalloutContribution[]> {
NEW
45
    const contributions: ICalloutContribution[] = [];
×
46

47
    for (const calloutContributionData of calloutContributionsData) {
48
      const contribution = await this.createCalloutContribution(
×
49
        calloutContributionData,
50
        storageAggregator,
×
51
        contributionSettings,
52
        userID
×
53
      );
×
54
      contributions.push(contribution);
55
    }
56

57
    return contributions;
×
58
  }
59

60
  public async createCalloutContribution(
61
    calloutContributionData: CreateCalloutContributionInput,
62
    storageAggregator: IStorageAggregator,
63
    contributionSettings: ICalloutSettingsContribution,
64
    userID: string
×
65
  ): Promise<ICalloutContribution> {
×
66
    this.validateContributionType(
67
      calloutContributionData,
68
      contributionSettings
69
    );
70
    const contribution: ICalloutContribution = CalloutContribution.create(
×
71
      calloutContributionData
72
    );
73

74
    contribution.authorization = new AuthorizationPolicy(
75
      AuthorizationPolicyType.CALLOUT_CONTRIBUTION
76
    );
77
    contribution.createdBy = userID;
×
78
    contribution.sortOrder = calloutContributionData.sortOrder ?? 0;
×
79

80
    const { post, whiteboard, link } = calloutContributionData;
81

82
    if (whiteboard) {
83
      contribution.whiteboard = await this.whiteboardService.createWhiteboard(
×
84
        whiteboard,
85
        storageAggregator,
86
        userID
87
      );
88
    }
89

×
90
    if (post) {
91
      contribution.post = await this.postService.createPost(
92
        post,
93
        storageAggregator,
94
        userID
95
      );
96
    }
×
97

98
    if (link) {
99
      contribution.link = await this.linkService.createLink(
×
100
        link,
101
        storageAggregator
102
      );
103
    }
104

105
    return contribution;
106
  }
107

108
  private validateContributionType(
109
    calloutContributionData: CreateCalloutContributionInput,
×
110
    contributionSettings: ICalloutSettingsContribution
×
111
  ) {
112
    if (
113
      !contributionSettings.allowedTypes?.includes(calloutContributionData.type)
114
    ) {
115
      throw new ValidationException(
116
        `Attempted to create a contribution of type '${calloutContributionData.type}', which is not in the allowed types: ${contributionSettings.allowedTypes}`,
117
        LogContext.COLLABORATION
118
      );
119
    }
120

×
121
    // Map contribution types to their corresponding data fields
×
122
    const contributionTypeFields: Record<
123
      CalloutContributionType,
124
      keyof CreateCalloutContributionInput
×
125
    > = {
×
126
      [CalloutContributionType.POST]: 'post',
127
      [CalloutContributionType.LINK]: 'link',
128
      [CalloutContributionType.WHITEBOARD]: 'whiteboard',
×
129
      [CalloutContributionType.MEMO]: 'memo',
×
130
    };
131

132
    const declaredType = calloutContributionData.type;
×
133
    const requiredField = contributionTypeFields[declaredType];
×
134

135
    // Check if the required field for the declared type is present
136
    if (!calloutContributionData[requiredField]) {
×
137
      throw new ValidationException(
138
        `CalloutContribution type is "${declaredType}" but no ${requiredField} data was provided`,
139
        LogContext.COLLABORATION
×
140
      );
×
141
    }
142

143
    // Check that no other contribution type fields are present
144
    const otherFields = Object.entries(contributionTypeFields)
145
      .filter(([type]) => type !== declaredType)
146
      .map(([, field]) => field);
×
147

148
    const conflictingFields = otherFields.filter(
149
      field => calloutContributionData[field] !== undefined
150
    );
151

152
    if (conflictingFields.length > 0) {
153
      throw new ValidationException(
×
154
        `CalloutContribution type is "${declaredType}" but conflicting data was provided: ${conflictingFields.join(', ')}. Only ${requiredField} data should be present.`,
×
155
        LogContext.COLLABORATION
×
156
      );
157
    }
158
  }
159

160
  async delete(contributionID: string): Promise<ICalloutContribution> {
161
    const contribution = await this.getCalloutContributionOrFail(
×
162
      contributionID,
×
163
      {
164
        relations: {
165
          post: true,
166
          whiteboard: true,
×
167
          link: true,
168
        },
169
      }
170
    );
171
    if (contribution.post) {
172
      await this.postService.deletePost(contribution.post.id);
×
173
    }
174

175
    if (contribution.whiteboard) {
176
      await this.whiteboardService.deleteWhiteboard(contribution.whiteboard.id);
177
    }
178

179
    if (contribution.link) {
180
      await this.linkService.deleteLink(contribution.link.id);
181
    }
182

183
    if (contribution.authorization) {
×
184
      await this.authorizationPolicyService.delete(contribution.authorization);
185
    }
186

187
    const result = await this.contributionRepository.remove(
188
      contribution as CalloutContribution
189
    );
×
190
    result.id = contributionID;
×
191
    return result;
192
  }
193

×
194
  async save(
195
    calloutContribution: ICalloutContribution
196
  ): Promise<ICalloutContribution>;
197
  async save(
198
    calloutContribution: ICalloutContribution[]
199
  ): Promise<ICalloutContribution[]>;
200
  async save(
×
201
    calloutContribution: ICalloutContribution | ICalloutContribution[]
202
  ): Promise<ICalloutContribution | ICalloutContribution[]> {
203
    const isParamArray = Array.isArray(calloutContribution);
204
    const contributionsArray = isParamArray
205
      ? calloutContribution
206
      : [calloutContribution];
×
207
    const results = await this.contributionRepository.save(contributionsArray);
×
208

209
    return isParamArray ? results : results[0];
210
  }
×
211

212
  public async getCalloutContributionOrFail(
213
    calloutContributionID: string,
214
    options?: FindOneOptions<CalloutContribution>
215
  ): Promise<ICalloutContribution | never> {
216
    const calloutContribution = await this.contributionRepository.findOne({
217
      where: { id: calloutContributionID },
×
218
      ...options,
219
    });
220

221
    if (!calloutContribution)
222
      throw new EntityNotFoundException(
223
        `No CalloutContribution found with the given id: ${calloutContributionID}`,
×
224
        LogContext.COLLABORATION
×
225
      );
226
    return calloutContribution;
227
  }
×
228

229
  public async getContributionsInCalloutCount(
230
    calloutID: string
231
  ): Promise<number> {
232
    return await this.contributionRepository.countBy({
233
      callout: {
234
        id: calloutID,
235
      },
236
    });
237
  }
238

239
  public async getWhiteboard(
240
    calloutContributionInput: ICalloutContribution,
241
    relations?: FindOptionsRelations<ICalloutContribution>
242
  ): Promise<IWhiteboard | null> {
243
    const calloutContribution = await this.getCalloutContributionOrFail(
244
      calloutContributionInput.id,
245
      {
246
        relations: { whiteboard: true, ...relations },
247
      }
248
    );
249
    if (!calloutContribution.whiteboard) {
250
      return null;
251
    }
252

253
    return calloutContribution.whiteboard;
254
  }
255

256
  public async getLink(
257
    calloutContributionInput: ICalloutContribution,
258
    relations?: FindOptionsRelations<ICalloutContribution>
259
  ): Promise<ILink | null> {
260
    const calloutContribution = await this.getCalloutContributionOrFail(
261
      calloutContributionInput.id,
262
      {
263
        relations: { link: true, ...relations },
264
      }
265
    );
266
    if (!calloutContribution.link) {
267
      return null;
268
    }
269

270
    return calloutContribution.link;
271
  }
272

273
  public async getPost(
274
    calloutContributionInput: ICalloutContribution,
275
    relations?: FindOptionsRelations<ICalloutContribution>
276
  ): Promise<IPost | null> {
277
    const calloutContribution = await this.getCalloutContributionOrFail(
278
      calloutContributionInput.id,
279
      {
280
        relations: { post: true, ...relations },
281
      }
282
    );
283
    if (!calloutContribution.post) {
284
      return null;
285
    }
286

287
    return calloutContribution.post;
288
  }
289

290
  /**
291
   * Retrieves the storage bucket associated with a specific contribution.
292
   * @param contributionID The ID of the contribution.
293
   * @returns The storage bucket associated with the contribution.
294
   * @throws RelationshipNotFoundException if no profile with a storage bucket is found for the contribution.
295
   */
296
  public async getStorageBucketForContribution(
297
    contributionID: string
298
  ): Promise<IStorageBucket> {
299
    const contribution = await this.getCalloutContributionOrFail(
300
      contributionID,
301
      {
302
        relations: {
303
          post: {
304
            profile: {
305
              storageBucket: true,
306
            },
307
          },
308
          link: {
309
            profile: {
310
              storageBucket: true,
311
            },
312
          },
313
          whiteboard: {
314
            profile: {
315
              storageBucket: true,
316
            },
317
          },
318
        },
319
      }
320
    );
321

322
    const profile = this.getProfileFromContribution(contribution);
323
    if (!profile || !profile.storageBucket) {
324
      throw new RelationshipNotFoundException(
325
        `Unable to find profile with storage bucket for callout contribution: ${contributionID}`,
326
        LogContext.COLLABORATION
327
      );
328
    }
329
    return profile.storageBucket;
330
  }
331

332
  private getProfileFromContribution(
333
    contribution: ICalloutContribution
334
  ): IProfile | undefined {
335
    if (contribution.post) {
336
      return contribution.post.profile;
337
    } else if (contribution.link) {
338
      return contribution.link.profile;
339
    } else if (contribution.whiteboard) {
340
      return contribution.whiteboard.profile;
341
    }
342
    return undefined;
343
  }
344
}
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