import {inject} from '@loopback/core'; import { Count, CountSchema, Filter, FilterExcludingWhere, Where, } from '@loopback/repository'; import { post, param, get, getModelSchemaRef, patch, put, del, requestBody, HttpErrors, RestBindings, Request, } from '@loopback/rest'; import { STATUS_CODE, IAuthUserWithPermissions, PermissionKey, CONTENT_TYPE, ErrorCodes, SuccessResponse, } from '@sourcefuse-npm/alivio-lib'; import { PatientCaregiverUserService, PatientOnboardingService, PatientService, UsersService, ERROR_KEYS, FoodLog, } from '@sourcefuse-npm/patient-facade'; import { authenticate, AuthenticationBindings, STRATEGY, } from 'loopback4-authentication'; import {authorize, AuthorizeErrorKeys} from 'loopback4-authorization'; import { CheckInContentCard, CheckInContentCardTypes, CheckInContentCardTypesMap, DailyCheckInDto, DailyCheckinLog, DailyCheckInLogStatus, DailyCheckIns, MoodFactorLogs, Selfi, Sleep, SleepLogs, } from '../models'; import { CheckInUsersService, DailyCheckInsService, MoodFactorLogsService, } from '../services'; const dailyCheckInPath = '/daily-check-ins'; import moment from 'moment-timezone'; import {AWSS3Bindings} from 'loopback4-s3'; import * as AWS from 'aws-sdk'; export class DailyCheckInsDetailsController { constructor( @inject('services.DailyCheckInsService') public dailyCheckIns: DailyCheckInsService, @inject('services.PatientService') protected patientService: PatientService, @inject('services.PatientOnboardingService') protected patientOnbService: PatientOnboardingService, @inject('services.PatientCaregiverUserService') private readonly patientCgUserService: PatientCaregiverUserService, @inject('services.UsersService') protected usersService: UsersService, @inject('services.MoodFactorLogsService') protected moodFactorLogsService: MoodFactorLogsService, @inject('services.CheckInUsersService') protected checkInUsersService: CheckInUsersService, ) {} private updatePreSignedUrl( imageUrl: string, s3: AWS.S3, bucket: string, ): string { if (!imageUrl) { throw new HttpErrors.NotFound('Image Url not found.'); } if (!process.env.PRE_SIGNED_URL_VALIDITY_IN_MINUTES) { throw new HttpErrors.NotFound( 'Pre-signed url validity time not defined.', ); } const urlValiditityTimeInMinutes = parseInt( process.env.PRE_SIGNED_URL_VALIDITY_IN_MINUTES, ); const secPerMinute = 60; const signedUrlExpireSeconds = secPerMinute * urlValiditityTimeInMinutes; return s3.getSignedUrl('getObject', { Bucket: bucket, Key: imageUrl, Expires: signedUrlExpireSeconds, }); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: [PermissionKey.ViewOwnPatient]}) @post(`${dailyCheckInPath}`, { responses: { [STATUS_CODE.OK]: { description: 'DailyCheckIns model instance', content: { 'application/json': { schema: getModelSchemaRef(DailyCheckIns), }, }, }, }, }) async create( @requestBody({ content: { 'application/json': { schema: getModelSchemaRef(DailyCheckInDto, { title: 'NewDailyCheckIns', }), }, }, }) dailyCheckIns: DailyCheckInDto, @inject(AuthenticationBindings.CURRENT_USER) currentUser: IAuthUserWithPermissions, @param.header.string('Authorization') token: string, @inject(RestBindings.Http.REQUEST) bindingRequest: Request, ): Promise { if (!currentUser.externalId) { throw new HttpErrors.Forbidden(AuthorizeErrorKeys.NotAllowedAccess); } const patientDetails = await this.patientOnbService.findPatientCGByExtId( currentUser.externalId, token, ); if (!patientDetails || !patientDetails.id) { throw new HttpErrors.Forbidden(AuthorizeErrorKeys.NotAllowedAccess); } // sonarignore:start const isAccessAllowed = await this.patientCgUserService.isPatientAccessAllowed( currentUser, patientDetails.id, // eslint-disable-next-line @typescript-eslint/no-explicit-any PermissionKey.CreateOwnMoodLogs as any, token, ); // sonarignore:end if (!isAccessAllowed) { throw new HttpErrors.Forbidden(AuthorizeErrorKeys.NotAllowedAccess); } const moods = await this.usersService.getMoods({}, token); const moodData = dailyCheckIns.moodsLog; const mood = moods.find(m => moodData.mood === m.name); if (!mood) { throw new HttpErrors.BadRequest(ERROR_KEYS.InvalidMood); } moodData.alertKey = mood.alertKey; if (moodData.dateTime) { if (moment(moodData.dateTime).isSameOrAfter(moment())) { throw new HttpErrors.UnprocessableEntity(ERROR_KEYS.FutureDate); } } const moodLog = await this.patientService.logmood(moodData, token); const checkIn = new DailyCheckIns(); checkIn.comment = dailyCheckIns.comment; checkIn.moodLogId = Number(moodLog.id); checkIn.comment = dailyCheckIns.comment; const moodFactorsList = await this.checkInUsersService.getMoodFactors( {}, token, ); let moodFactors: MoodFactorLogs[] = dailyCheckIns.moodFactorLogs; moodFactors = moodFactors.filter(mf => moodFactorsList.find(mfl => mfl.name === mf.moodFactor), ); if (!moodFactors?.length) { throw new HttpErrors.BadRequest('Invalid Mood Factor'); } if (moodFactors?.length) { moodFactors = await Promise.all( moodFactors.map(async mf => { mf.mood = moodLog.mood; return this.moodFactorLogsService.logMoodFactors(mf, token); }), ); } checkIn.moodFactorLogId = `{${moodFactors .map(m => Number(m.id)) .join(',')}}`; const sleepList: Sleep[] = await this.checkInUsersService.getSleep( {}, token, ); let sleepLog: SleepLogs = dailyCheckIns.sleepLogs; const sleep = sleepList.findIndex(s => s.name === sleepLog.sleep) > -1; if (!sleep) { throw new HttpErrors.BadRequest('Invalid Sleep Value'); } if (sleep) { sleepLog = await this.moodFactorLogsService.logSleep(sleepLog, token); } checkIn.sleepLogId = Number(sleepLog.id); // sonarignore:start const isAllowed = await this.patientCgUserService.isPatientAccessAllowed( currentUser, patientDetails.id, // eslint-disable-next-line @typescript-eslint/no-explicit-any PermissionKey.CreateOwnFoodLogs as any, token, ); // sonarignore:end if (!isAllowed) { throw new HttpErrors.Forbidden(AuthorizeErrorKeys.NotAllowedAccess); } const foodlogs: FoodLog[] = await Promise.all( dailyCheckIns.foodLogs.map(async fl => { const foodLogData = new FoodLog({ meal: fl.meal, patientId: patientDetails.id, date: fl.date ?? new Date(), alertKey: fl.alertKey, }); return this.patientService.createFoodLogs( bindingRequest.headers.authorization, patientDetails.id, foodLogData, ); }), ); checkIn.foodLogId = `{${foodlogs.map(f => Number(f.id)).join(',')}}`; checkIn.patientId = patientDetails.id; const medicineLogObj = await this.dailyCheckIns.getCheckInLogs( { where: { patientId: checkIn.patientId, type: CheckInContentCardTypes.DAILY_CHECK_IN, }, order: ['checkinTime DESC'], limit: 1, offset: 0, }, token, ); // sonarignore:end if (medicineLogObj.length) { await this.dailyCheckIns.updateCheckInLog( Number(medicineLogObj[0].id), { status: DailyCheckInLogStatus.DONE, lastLoggedDate: new Date(), }, token, ); } return this.dailyCheckIns.create(checkIn, token); } getStartEndDateTimezoneOffset() { const startDatetz = new Date( moment .tz(new Date(), process.env.TIMEZONE ?? 'UTC') .startOf('day') .toString(), ); const endDatetz = new Date( moment .tz(new Date(), process.env.TIMEZONE ?? 'UTC') .endOf('day') .toString(), ); return { startDate: startDatetz, endDate: endDatetz, }; } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: ['*']}) @get(`${dailyCheckInPath}/count`, { responses: { [STATUS_CODE.OK]: { description: 'DailyCheckIns model count', content: {'application/json': {schema: CountSchema}}, }, }, }) async count( @param.where(DailyCheckIns) where?: Where, @param.header.string('Authorization') token?: string, ): Promise { return this.dailyCheckIns.count(where, token); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: ['*']}) @get(`${dailyCheckInPath}`, { responses: { [STATUS_CODE.OK]: { description: 'Array of DailyCheckIns model instances', content: { 'application/json': { schema: { type: 'array', items: getModelSchemaRef(DailyCheckIns, { includeRelations: true, }), }, }, }, }, }, }) async find( @param.filter(DailyCheckIns) filter?: Filter, @param.header.string('Authorization') token?: string, ): Promise { return this.dailyCheckIns.find(filter, token); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: ['*']}) @patch(`${dailyCheckInPath}`, { responses: { [STATUS_CODE.OK]: { description: 'DailyCheckIns PATCH success count', content: {'application/json': {schema: CountSchema}}, }, }, }) async updateAll( @requestBody({ content: { 'application/json': { schema: getModelSchemaRef(DailyCheckIns, {partial: true}), }, }, }) dailyCheckIns: DailyCheckIns, @param.header.string('Authorization') token?: string, @param.where(DailyCheckIns) where?: Where, ): Promise { return this.dailyCheckIns.updateAll(dailyCheckIns, where, token); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: ['*']}) @get(`${dailyCheckInPath}/{id}`, { responses: { [STATUS_CODE.OK]: { description: 'DailyCheckIns model instance', content: { 'application/json': { schema: getModelSchemaRef(DailyCheckIns, { includeRelations: true, }), }, }, }, }, }) async findById( @param.path.number('id') id: number, @param.filter(DailyCheckIns, {exclude: 'where'}) filter?: FilterExcludingWhere, @param.header.string('Authorization') token?: string, ): Promise { return this.dailyCheckIns.findById(id, filter, token); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: ['*']}) @patch(`${dailyCheckInPath}/{id}`, { responses: { [STATUS_CODE.NO_CONTENT]: { description: 'DailyCheckIns PATCH success', }, }, }) async updateById( @param.path.number('id') id: number, @requestBody({ content: { 'application/json': { schema: getModelSchemaRef(DailyCheckIns, {partial: true}), }, }, }) dailyCheckIns: DailyCheckIns, @param.header.string('Authorization') token?: string, ): Promise { await this.dailyCheckIns.updateById(id, dailyCheckIns, token); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: ['*']}) @put(`${dailyCheckInPath}/{id}`, { responses: { [STATUS_CODE.NO_CONTENT]: { description: 'DailyCheckIns PUT success', }, }, }) async replaceById( @param.path.number('id') id: number, @requestBody() dailyCheckIns: DailyCheckIns, @param.header.string('Authorization') token?: string, ): Promise { await this.dailyCheckIns.replaceById(id, dailyCheckIns, token); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: ['*']}) @del(`${dailyCheckInPath}/{id}`, { responses: { [STATUS_CODE.NO_CONTENT]: { description: 'DailyCheckIns DELETE success', }, }, }) async deleteById( @param.path.number('id') id: number, @param.header.string('Authorization') token?: string, ): Promise { await this.dailyCheckIns.deleteById(id, token); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({ permissions: ['*'], }) @get(`${dailyCheckInPath}/content-cards`, { description: `To get Medicine log on the basis of Prescription Medicine`, responses: { [STATUS_CODE.OK]: { description: `Array of MedicineLog's belonging to Prescription Medicines`, content: { [CONTENT_TYPE.JSON]: { schema: {type: 'array', items: getModelSchemaRef(DailyCheckinLog)}, }, }, }, ...ErrorCodes, }, }) async getDailyCheckInLog( @param.query.object('filter') filter: Filter, @inject(AuthenticationBindings.CURRENT_USER) currentUser: IAuthUserWithPermissions, @inject(AWSS3Bindings.AwsS3Provider) s3: AWS.S3, @param.header.string('Authorization') token: string, ): Promise { // sonarignore:start const isAccessAllowed = await this.patientCgUserService.isPatientAccessAllowed( currentUser, filter?.where ? (filter.where as any).patientId : 0, // need this as typescript confuses it with AndClause or OrClause PermissionKey.ViewPatient as any, token, ); if (!isAccessAllowed) { throw new HttpErrors.Forbidden(AuthorizeErrorKeys.NotAllowedAccess); } const modfilter: Filter = filter; const checinDetails = await this.dailyCheckIns.getCheckInLogs( modfilter, token, ); const Two = 2; const latestSelfies = await this.moodFactorLogsService.getSefies( filter?.where ? (filter.where as any).patientId : 0, token, Two, 0, ); latestSelfies.forEach(selfi => { selfi.selfiUrl = this.updatePreSignedUrl( selfi.selfiUrl, s3, `${process.env.SELFI_BUCKET}`, ); }); // sonarignore:end return this.prepareCheckInResponse(checinDetails, latestSelfies); } prepareCheckInResponse( checinDetails: DailyCheckinLog[], latestSelfies: Selfi[], ) { const contentCards: CheckInContentCard[] = []; checinDetails.forEach(m => { if (m.type) { const checinContentCard = new CheckInContentCard(); checinContentCard.cardType = CheckInContentCardTypesMap[m.type].label; checinContentCard.cardPosition = CheckInContentCardTypesMap[m.type].value; checinContentCard.checkInLogId = m.id; if (Number(m.type) === CheckInContentCardTypes.SELFIE_LIST) { checinContentCard.latestTwoSelfies = latestSelfies; } if (Number(m.type) === CheckInContentCardTypes.LATEST_SELFIE) { const latestSelfiContent = { latestSelfiImage: latestSelfies[0]?.selfiUrl ?? '', latestQuote: 'Self care is how you take your power back', }; checinContentCard.latestSelfiContent = latestSelfiContent; } if (Number(m.type) === Number(CheckInContentCardTypes.DAILY_QUOTE)) { checinContentCard.dailyQuote = 'You may not be there yet,but you are closed than yesterday'; } if (Number(m.type) !== CheckInContentCardTypes.DAILY_QUOTE) { checinContentCard.lastLoggedDate = m.lastLoggedDate; } contentCards.push(checinContentCard); } }); return contentCards; } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: [PermissionKey.UpdateOwnPatient]}) @patch('/checkin-content-card/{id}', { responses: { [STATUS_CODE.OK]: { description: 'To update CHeckin log by id', }, }, }) async updateSelfeLog( @param.path.number('id') id: number, @param.header.string('Authorization') token: string, @requestBody({ content: { [CONTENT_TYPE.JSON]: { schema: getModelSchemaRef(DailyCheckinLog, {partial: true}), }, }, }) checkInLog: DailyCheckinLog, ): Promise { const medicineLogObj = await this.dailyCheckIns.getCheckInLogs( { where: {id}, limit: 1, offset: 0, }, token, ); if (medicineLogObj.length > 0) { await this.dailyCheckIns.updateCheckInLog(id, checkInLog, token); } else { throw new HttpErrors.BadRequest('Wrong Details'); } return new SuccessResponse({ success: true, }); } }