• 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.0
/src/domain/timeline/calendar/calendar.service.ts
1
import { AuthorizationPrivilege } from '@common/enums/authorization.privilege';
17✔
2
import { LogContext } from '@common/enums/logging.context';
17✔
3
import { EntityNotFoundException } from '@common/exceptions/entity.not.found.exception';
17✔
4
import { ValidationException } from '@common/exceptions/validation.exception';
17✔
5
import { AgentInfo } from '@core/authentication.agent.info/agent.info';
17✔
6
import { AuthorizationService } from '@core/authorization/authorization.service';
7
import { AuthorizationPolicy } from '@domain/common/authorization-policy/authorization.policy.entity';
17✔
8
import { AuthorizationPolicyService } from '@domain/common/authorization-policy/authorization.policy.service';
17✔
9
import { Inject, Injectable, LoggerService } from '@nestjs/common';
17✔
10
import { InjectRepository } from '@nestjs/typeorm';
17✔
11
import { NamingService } from '@services/infrastructure/naming/naming.service';
17✔
12
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
17✔
13
import { FindOneOptions, Repository } from 'typeorm';
17✔
14
import { ICalendarEvent } from '../event/event.interface';
17✔
15
import { CalendarEventService } from '../event/event.service';
16
import { Calendar } from './calendar.entity';
17✔
17
import { ICalendar } from './calendar.interface';
17✔
18
import { CreateCalendarEventOnCalendarInput } from './dto/calendar.dto.create.event';
19
import { ActivityInputCalendarEventCreated } from '@services/adapters/activity-adapter/dto/activity.dto.input.calendar.event.created';
20
import { ActivityAdapter } from '@services/adapters/activity-adapter/activity.adapter';
21
import { TimelineResolverService } from '@services/infrastructure/entity-resolver/timeline.resolver.service';
22
import { ContributionReporterService } from '@services/external/elasticsearch/contribution-reporter';
17✔
23
import { StorageAggregatorResolverService } from '@services/infrastructure/storage-aggregator-resolver/storage.aggregator.resolver.service';
17✔
24
import { AuthorizationPolicyType } from '@common/enums/authorization.policy.type';
17✔
25
import { ISpace } from '@domain/space/space/space.interface';
17✔
26
import { PrefixKeys } from '@src/types';
17✔
27
import { Space } from '@domain/space/space/space.entity';
28
import { convertToEntity } from '@common/utils/convert-to-entity';
29
import { Collaboration } from '@domain/collaboration/collaboration';
17✔
30
import { Timeline } from '@domain/timeline/timeline/timeline.entity';
31
import { CalendarEvent } from '@domain/timeline/event';
×
32
import { SpaceLevel } from '@common/enums/space.level';
×
33

×
34
@Injectable()
×
35
export class CalendarService {
×
36
  constructor(
×
37
    private calendarEventService: CalendarEventService,
×
38
    private authorizationPolicyService: AuthorizationPolicyService,
×
39
    private authorizationService: AuthorizationService,
40
    private namingService: NamingService,
×
41
    private activityAdapter: ActivityAdapter,
×
42
    private contributionReporter: ContributionReporterService,
43
    private storageAggregatorResolverService: StorageAggregatorResolverService,
44
    private timelineResolverService: TimelineResolverService,
45
    @InjectRepository(Calendar)
×
NEW
46
    private calendarRepository: Repository<Calendar>,
×
47
    @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService
48
  ) {}
49

×
50
  public createCalendar(): ICalendar {
51
    const calendar: ICalendar = new Calendar();
×
52
    calendar.authorization = new AuthorizationPolicy(
53
      AuthorizationPolicyType.CALENDAR
54
    );
55
    calendar.events = [];
×
56

57
    return calendar;
58
  }
59

×
60
  async deleteCalendar(calendarID: string): Promise<ICalendar> {
×
61
    const calendar = await this.getCalendarOrFail(calendarID, {
62
      relations: { events: true },
×
63
    });
×
64

×
65
    if (calendar.authorization)
66
      await this.authorizationPolicyService.delete(calendar.authorization);
67

68
    if (calendar.events) {
69
      for (const event of calendar.events) {
70
        await this.calendarEventService.deleteCalendarEvent({
×
71
          ID: event.id,
72
        });
73
      }
74
    }
75

76
    return await this.calendarRepository.remove(calendar as Calendar);
77
  }
×
78

79
  async getCalendarOrFail(
80
    calendarID: string,
81
    options?: FindOneOptions<Calendar>
×
82
  ): Promise<ICalendar | never> {
×
83
    const calendar = await this.calendarRepository.findOne({
84
      where: { id: calendarID },
85
      ...options,
86
    });
×
87
    if (!calendar)
88
      throw new EntityNotFoundException(
89
        `Calendar not found: ${calendarID}`,
90
        LogContext.CALENDAR
91
      );
92
    return calendar;
×
93
  }
×
94

×
95
  public async getCalendarEventsFromSubspaces(
96
    rootSpaceId: string
97
  ): Promise<ICalendarEvent[]> {
98
    const result = await this.calendarRepository.manager
99
      .createQueryBuilder(Space, 'subspace')
×
100
      // if all the subspaces must be included change the statement
101
      // to be levelZeroSpace = spaceId and level > space level
102
      .where({
103
        parentSpace: { id: rootSpaceId },
104
        level: SpaceLevel.L1,
105
      })
106
      .leftJoin(
×
107
        Collaboration,
108
        'collaboration',
109
        'collaboration.id = subspace.collaborationId'
110
      )
111
      .leftJoin(Timeline, 'timeline', 'timeline.id = collaboration.timelineId')
112
      .leftJoin(Calendar, 'calendar', 'calendar.id = timeline.calendarId')
113
      .leftJoin(
114
        CalendarEvent,
×
115
        'calendarEvent',
×
116
        'calendarEvent.calendarId = calendar.id'
×
117
      )
×
118
      // cannot find alias when using relations https://github.com/typeorm/typeorm/issues/2707
×
119
      .andWhere('calendarEvent.visibleOnParentCalendar = true')
120
      .andWhere('calendarEvent.id IS NOT NULL')
121
      .select('calendarEvent.id')
122
      .getRawMany<PrefixKeys<{ id: string }, 'calendarEvent_'>>();
123

×
124
    const ids = result.map(({ calendarEvent_id }) => calendarEvent_id);
125
    return this.calendarEventService.getCalendarEvents(ids);
×
126
  }
127

128
  public async createCalendarEvent(
129
    calendarEventData: CreateCalendarEventOnCalendarInput,
130
    userID: string
131
  ): Promise<ICalendarEvent> {
×
132
    const calendar = await this.getCalendarOrFail(
133
      calendarEventData.calendarID,
134
      {
×
135
        relations: {},
136
      }
137
    );
138

139
    const reservedNameIDs =
×
140
      await this.namingService.getReservedNameIDsInCalendar(calendar.id);
×
141
    if (calendarEventData.nameID && calendarEventData.nameID.length > 0) {
142
      const nameTaken = reservedNameIDs.includes(calendarEventData.nameID);
143
      if (nameTaken)
144
        throw new ValidationException(
145
          `Unable to create CalendarEvent: the provided nameID is already taken: ${calendarEventData.nameID}`,
146
          LogContext.CALENDAR
147
        );
148
    } else {
×
149
      calendarEventData.nameID =
150
        this.namingService.createNameIdAvoidingReservedNameIDs(
151
          `${calendarEventData.profileData?.displayName}`,
×
152
          reservedNameIDs
×
153
        );
×
154
    }
155

156
    const storageAggregator =
157
      await this.storageAggregatorResolverService.getStorageAggregatorForCalendar(
158
        calendar.id
159
      );
×
160
    const calendarEvent = await this.calendarEventService.createCalendarEvent(
×
161
      calendarEventData,
162
      storageAggregator,
163
      userID
164
    );
×
165
    calendarEvent.calendar = calendar;
×
166
    return await this.calendarEventService.save(calendarEvent);
×
167
  }
×
168

×
169
  public async getCalendarEvents(
170
    calendar: ICalendar,
171
    agentInfo: AgentInfo,
×
172
    rootSpaceId?: string
×
173
  ): Promise<ICalendarEvent[]> {
174
    const calendarLoaded = await this.getCalendarOrFail(calendar.id, {
175
      relations: { events: true },
176
    });
×
177
    const events = calendarLoaded.events;
178
    if (!events) {
×
179
      throw new EntityNotFoundException(
180
        `Events not initialized on Calendar: ${calendar.id}`,
181
        LogContext.CALENDAR
182
      );
×
183
    }
×
184

185
    if (rootSpaceId) {
186
      const subspaceEvents =
×
187
        await this.getCalendarEventsFromSubspaces(rootSpaceId);
188
      events.push(...subspaceEvents);
189
    }
190

191
    // First filter the events the current user has READ privilege to
192
    return events.filter(event => this.hasAgentAccessToEvent(event, agentInfo));
193
  }
×
194

195
  public async getCalendarEvent(
196
    calendarId: string,
197
    idOrNameId: string
198
  ): Promise<ICalendarEvent> {
199
    const event = await this.calendarEventService.getCalendarEvent(
200
      calendarId,
201
      idOrNameId
202
    );
203
    if (!event) {
204
      throw new EntityNotFoundException(
205
        'Event not found in Calendar',
×
206
        LogContext.CALENDAR,
207
        { calendarId, eventId: idOrNameId }
208
      );
209
    }
210

×
211
    return event;
212
  }
×
213

214
  private hasAgentAccessToEvent(
215
    event: ICalendarEvent,
216
    agentInfo: AgentInfo
×
217
  ): boolean {
×
218
    return this.authorizationService.isAccessGranted(
219
      agentInfo,
220
      event.authorization,
221
      AuthorizationPrivilege.READ
222
    );
223
  }
224

225
  public async getSpaceFromCalendarOrFail(calendarId: string): Promise<ISpace> {
226
    const spaceAlias = 'space';
227
    const rawSpace = await this.calendarRepository
228
      .createQueryBuilder('calendar')
229
      .where({ id: calendarId })
230
      .leftJoin('timeline', 'timeline', 'timeline.calendarId = calendar.id')
231
      .leftJoin(
232
        'collaboration',
233
        'collaboration',
234
        'collaboration.timelineId = timeline.id'
235
      )
236
      .leftJoinAndSelect(
237
        'space',
238
        spaceAlias,
239
        'space.collaborationId = collaboration.id'
240
      )
241
      .getRawOne<PrefixKeys<Space, `${typeof spaceAlias}_`>>();
242

243
    if (!rawSpace) {
244
      throw new EntityNotFoundException(
245
        'Space not found for Calendar',
246
        LogContext.CALENDAR,
247
        { calendarId }
248
      );
249
    }
250
    // todo: not needed when using select instead of leftJoinAndSelect
251
    return convertToEntity(rawSpace, 'space_');
252
  }
253

254
  public async processActivityCalendarEventCreated(
255
    calendar: ICalendar,
256
    calendarEvent: ICalendarEvent,
257
    agentInfo: AgentInfo
258
  ) {
259
    const activityLogInput: ActivityInputCalendarEventCreated = {
260
      triggeredBy: agentInfo.userID,
261
      calendar: calendar,
262
      calendarEvent: calendarEvent,
263
    };
264
    this.activityAdapter.calendarEventCreated(activityLogInput);
265

266
    const spaceID = await this.timelineResolverService.getSpaceIdForCalendar(
267
      calendar.id
268
    );
269

270
    if (spaceID) {
271
      this.contributionReporter.calendarEventCreated(
272
        {
273
          id: calendarEvent.id,
274
          name: calendarEvent.profile.displayName,
275
          space: spaceID,
276
        },
277
        {
278
          id: agentInfo.userID,
279
          email: agentInfo.email,
280
        }
281
      );
282
    }
283
  }
284
}
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