// Copyright (c) 2023 Sourcefuse Technologies // // This software is released under the MIT License. // https://opensource.org/licenses/MIT import {inject, service} from '@loopback/core'; import { Count, CountSchema, Filter, FilterExcludingWhere, repository, Where, } from '@loopback/repository'; import { del, get, HttpErrors, param, patch, post, put, requestBody, } from '@loopback/rest'; import { CONTENT_TYPE, getModelSchemaRefSF, IAuthUserWithPermissions, OPERATION_SECURITY_SPEC, STATUS_CODE, } from '@sourceloop/core'; import { authenticate, AuthenticationBindings, STRATEGY, } from 'loopback4-authentication'; import {authorize} from 'loopback4-authorization'; import {SchedulerBindings} from '../keys'; import {Calendar, IdentifierType, Subscription, WorkingHour} from '../models'; import {CalendarDTO} from '../models/calendar.dto'; import {AccessRoleType} from '../models/enums/access-role.enum'; import {ErrorKeys} from '../models/enums/error-keys'; import {PermissionKey} from '../models/enums/permission-key.enum'; import { CalendarRepository, SubscriptionRepository, WorkingHourRepository, } from '../repositories'; import {CalendarService} from '../services/calendar.service'; import {ISchedulerConfig} from '../types'; const basePath = '/calendars'; const calendarModelInstance = 'Calendar model instance'; export class CalendarController { constructor( @repository(CalendarRepository) public calendarRepository: CalendarRepository, @repository(WorkingHourRepository) public workingHourRepository: WorkingHourRepository, @repository(SubscriptionRepository) public subscriptionRepository: SubscriptionRepository, @inject(AuthenticationBindings.CURRENT_USER) private readonly currentUser: IAuthUserWithPermissions, @service(CalendarService) public calendarService: CalendarService, @inject(SchedulerBindings.Config, { optional: true, }) private readonly schdulerConfig?: ISchedulerConfig, ) {} @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({ permissions: [ PermissionKey.CreateCalendar, PermissionKey.CreateCalendarNum, ], }) @post(basePath, { description: `This is an api to create a calendar for any user. Recommendation: Use this while adding a user to the main application, in order to create a primary calendar for that particular user.`, security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description: calendarModelInstance, content: {[CONTENT_TYPE.JSON]: {schema: getModelSchemaRefSF(Calendar)}}, }, }, }) async create( @requestBody({ content: { [CONTENT_TYPE.JSON]: { schema: getModelSchemaRefSF(CalendarDTO, { title: 'NewCalendar', exclude: ['id'], }), }, }, }) calendarDTO: Omit, ): Promise { return this.calendarService.createCalendar(calendarDTO); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({ permissions: [ PermissionKey.CreateCalendar, PermissionKey.CreateCalendarNum, ], }) @post('/calendars/calendarSubscription', { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description: calendarModelInstance, content: { [CONTENT_TYPE.JSON]: {schema: getModelSchemaRefSF(CalendarDTO)}, }, }, }, }) async createWithSubscription( @requestBody({ content: { [CONTENT_TYPE.JSON]: { schema: getModelSchemaRefSF(CalendarDTO, { title: 'NewCalendar', exclude: ['id'], }), }, }, }) calendarDTO: Omit, ): Promise { if (!calendarDTO.subscription) { throw new HttpErrors.NotFound(ErrorKeys.SubscriptionNotExist); } const subscription: Subscription = Object.assign(calendarDTO.subscription); delete calendarDTO.subscription; let response = await this.calendarService.createCalendar(calendarDTO); let identifierType = this.schdulerConfig?.identifierMappedTo; if (!identifierType) { identifierType = IdentifierType.Id; } let subscriptionIdentifier; if (subscription.identifier) { subscriptionIdentifier = subscription.identifier; } else { subscriptionIdentifier = this.currentUser[identifierType]; } const where = { and: [{identifier: subscriptionIdentifier}, {isPrimary: true}], }; const subscriptionList = await this.subscriptionRepository.find({where}); if (subscriptionList.length > 0) { subscription.isPrimary = false; } else { subscription.isPrimary = true; } if (response.id) { subscription.calendarId = response.id; if (subscriptionIdentifier) { subscription.identifier = subscriptionIdentifier; } else { throw new HttpErrors.NotFound(ErrorKeys.SubscriptionIdentifierNotExist); } subscription.accessRole = AccessRoleType.Owner; const subscriptionResponse = await this.subscriptionRepository.create(subscription); const calendarDTOResp: CalendarDTO = new CalendarDTO(); calendarDTOResp.subscription = subscriptionResponse; response = Object.assign(calendarDTOResp, response); } return response; } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({ permissions: [PermissionKey.ViewCalendar, PermissionKey.ViewCalendarNum], }) @get(`${basePath}/count`, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description: 'Calendar model count', content: {[CONTENT_TYPE.JSON]: {schema: CountSchema}}, }, }, }) async count(@param.where(Calendar) where?: Where): Promise { return this.calendarRepository.count(where); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({ permissions: [PermissionKey.ViewCalendar, PermissionKey.ViewCalendarNum], }) @get(basePath, { description: 'These requests will be available to everyone in the event to look at.', security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description: 'Array of Calendar model instances', content: { [CONTENT_TYPE.JSON]: { schema: { type: 'array', items: getModelSchemaRefSF(Calendar, {includeRelations: true}), }, }, }, }, }, }) async find( @param.filter(Calendar) filter?: Filter, ): Promise { return this.calendarRepository.find(filter); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({ permissions: [ PermissionKey.UpdateCalendar, PermissionKey.UpdateCalendarNum, ], }) @patch(basePath, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description: 'Calendar PATCH success count', content: {[CONTENT_TYPE.JSON]: {schema: CountSchema}}, }, }, }) async updateAll( @requestBody({ content: { [CONTENT_TYPE.JSON]: { schema: getModelSchemaRefSF(Calendar, {partial: true}), }, }, }) calendar: Calendar, @param.where(Calendar) where?: Where, ): Promise { return this.calendarRepository.updateAll(calendar, where); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({ permissions: [PermissionKey.ViewCalendar, PermissionKey.ViewCalendarNum], }) @get(`${basePath}/{id}`, { description: 'These requests will be available to everyone in the event to look at.', security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.OK]: { description: calendarModelInstance, content: { [CONTENT_TYPE.JSON]: { schema: getModelSchemaRefSF(Calendar, {includeRelations: true}), }, }, }, }, }) async findById( @param.path.string('id') id: string, @param.filter(Calendar, {exclude: 'where'}) filter?: FilterExcludingWhere, ): Promise { return this.calendarRepository.findById(id, filter); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({ permissions: [ PermissionKey.UpdateCalendar, PermissionKey.UpdateCalendarNum, ], }) @patch(`${basePath}/{id}`, { security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.NO_CONTENT]: { description: 'Calendar PATCH success', }, }, }) async updateById( @param.path.string('id') id: string, @requestBody({ content: { [CONTENT_TYPE.JSON]: { schema: getModelSchemaRefSF(Calendar, {partial: true}), }, }, }) calendar: Calendar, ): Promise { await this.calendarRepository.updateById(id, calendar); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({ permissions: [ PermissionKey.UpdateCalendar, PermissionKey.UpdateCalendarNum, ], }) @put(`${basePath}/{id}`, { description: `This api is to update the calendar by passing an id. This action will be allowed only to the owner of the calendar or the admin. To identify the ‘owner’ we will check for the email passed in the token and the corresponding access level, whereas to identify the admin we will check for the permission.`, security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.NO_CONTENT]: { description: 'Calendar PUT success', }, }, }) async replaceById( @param.path.string('id') id: string, @requestBody() calendarDTO: CalendarDTO, ): Promise { let workingHours: WorkingHour[] = []; if (calendarDTO.workingHours) { workingHours = calendarDTO.workingHours; await this.calendarService.checkPutValidations(workingHours, id); await this.calendarService.deleteWorkingHours(workingHours, id); const workingHoursToAdd: WorkingHour[] = []; for (const workingHour of workingHours) { if (workingHour.id === '') { workingHoursToAdd.push(workingHour); continue; } await this.workingHourRepository.replaceById( workingHour.id, workingHour, ); } for (const workingHour of workingHoursToAdd) { delete workingHour.id; await this.workingHourRepository.create(workingHour); } } delete calendarDTO.workingHours; await this.calendarRepository.updateById(id, calendarDTO); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({ permissions: [ PermissionKey.DeleteCalendar, PermissionKey.DeleteCalendarNum, ], }) @del(`${basePath}/{id}`, { description: `This api is to update the calendar by passing an id. This action will be allowed only to the owner of the calendar or the admin. To identify the ‘owner’ we will check for the email passed in the token and the corresponding access level, whereas to identify the admin we will check for the permission.`, security: OPERATION_SECURITY_SPEC, responses: { [STATUS_CODE.NO_CONTENT]: { description: 'Calendar DELETE success', }, }, }) async deleteById(@param.path.string('id') id: string): Promise { await this.calendarRepository.deleteById(id); } }