import { param, get, HttpErrors, post, getModelSchemaRef, del, requestBody, Request, Response, RestBindings, } from '@loopback/rest'; import { CONTENT_TYPE, ErrorCodes, PermissionKey, STATUS_CODE, SuccessResponse, IAuthUserWithPermissions, } from '@sourcefuse-npm/alivio-lib'; import { authenticate, AuthenticationBindings, STRATEGY, } from 'loopback4-authentication'; import {authorize, AuthorizeErrorKeys} from 'loopback4-authorization'; import { DiPatientCaregiverUserService, MoodFactorLogsService, } from '../services'; import {inject} from '@loopback/core'; import * as AWS from 'aws-sdk'; import {Selfi, SelfieDeletSchema} from '../models'; import {AWSS3Bindings} from 'loopback4-s3'; import { FileUploadBindings, IUploader, MulterS3Options, } from '@sourcefuse-npm/file-uploader'; import * as path from 'path'; import {snakeCase} from 'lodash'; const selfibucket = process.env.SELFI_BUCKET; const patientSelfiPath = '/patients/{id}/selfies'; export class SelfiController { constructor( @inject('services.MoodFactorLogsService') protected patientService: MoodFactorLogsService, @inject('services.DiPatientCaregiverUserService') protected readonly patientCgUserService: DiPatientCaregiverUserService, @inject(FileUploadBindings.MulterS3Provider) private readonly multerS3Provider: IUploader, ) {} 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.ViewPatient, PermissionKey.ViewOwnPatient], }) @get(`${patientSelfiPath}/`, { responses: { [STATUS_CODE.OK]: { description: 'Array of selfi model instances', content: { 'application/json': { schema: { items: getModelSchemaRef(Selfi, { includeRelations: true, }), }, }, }, }, }, }) async getPatientSelfies( @param.path.number('id') id: number, @inject(AWSS3Bindings.AwsS3Provider) s3: AWS.S3, @inject(AuthenticationBindings.CURRENT_USER) currentUser: IAuthUserWithPermissions, @param.header.string('Authorization') token?: string, @param.query.number('limit', {required: false}) limit?: number, @param.query.number('offset', {required: false}) offset?: number, ): Promise { const isAccessAllowed = await this.patientCgUserService.isPatientAccessAllowed( currentUser, Number(id), // eslint-disable-next-line @typescript-eslint/no-explicit-any PermissionKey.UpdateOwnPatient, token, ); if (!isAccessAllowed) { throw new HttpErrors.Forbidden(AuthorizeErrorKeys.NotAllowedAccess); } const patientSelfies = await this.patientService.getSefies( id, token, limit, offset, ); return patientSelfies.map(selfi => { if (selfi.selfiUrl) { selfi.selfiUrl = this.updatePreSignedUrl( selfi.selfiUrl, s3, `${process.env.SELFI_BUCKET}`, ); } return selfi; }); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: [PermissionKey.UpdateOwnPatient]}) @post(`${patientSelfiPath}/`, { responses: { [STATUS_CODE.OK]: { description: 'patient selfi model instance', content: {'application/json': {schema: getModelSchemaRef(Selfi)}}, }, }, }) async addPatientSelfi( @requestBody({ description: 'multipart/form-data value.', required: true, content: { 'multipart/form-data': { // Skip body parsing 'x-parser': 'stream', }, }, }) request: Request, @param.path.number('id') id: number, @inject(RestBindings.Http.RESPONSE) response: Response, @inject(RestBindings.Http.REQUEST) bindingRequest: Request, @inject(AWSS3Bindings.AwsS3Provider) s3: AWS.S3, @inject(AuthenticationBindings.CURRENT_USER) currentUser: IAuthUserWithPermissions, @param.header.string('Authorization') token?: string, ): Promise { const isAccessAllowed = await this.patientCgUserService.isPatientAccessAllowed( currentUser, Number(id), // eslint-disable-next-line @typescript-eslint/no-explicit-any PermissionKey.UpdateOwnPatient, token, ); if (!isAccessAllowed) { throw new HttpErrors.Forbidden(AuthorizeErrorKeys.NotAllowedAccess); } if (!selfibucket) { throw new HttpErrors.NotFound('Selfi bucket not defined.'); } const multerS3options: MulterS3Options = { s3, bucket: selfibucket, // Set public read permissions acl: 'public-read', // Content type check for images contentType(req, file, callback) { const ext = path.extname(file.originalname); if ( ext !== '.png' && ext !== '.jpg' && ext !== '.gif' && ext !== '.jpeg' ) { callback(new Error('Only images are allowed')); } else if (!file.mimetype) { callback(new Error('Unsupported image is not allowed')); } else { callback(null, file.mimetype); } }, //Set key/ filename as original uploaded name key(req, file, cb) { const fileSplitArr = file.originalname.split('.'); const fileExt = fileSplitArr[fileSplitArr.length - 1]; const fileName = fileSplitArr.splice(-1, 1).join('_'); const fileKey = snakeCase(fileName); cb(null, `${Date.now()}_${fileKey}.${fileExt}`); }, }; const uploadResp = await this.multerS3Provider.uploadAny( multerS3options, request, response, ); let imageUrl; // sonarignore:start if (!(uploadResp as any).files[0]) { imageUrl = undefined; } else { imageUrl = (uploadResp as any).files[0].key; } // sonarignore:end const patientSelfi = await this.patientService.addSelfi( id, imageUrl, token, ); patientSelfi.selfiUrl = this.updatePreSignedUrl( patientSelfi.selfiUrl, s3, `${process.env.SELFI_BUCKET}`, ); return patientSelfi; } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: [PermissionKey.UpdateOwnPatient]}) @del(`${patientSelfiPath}/{selfiId}`, { responses: { [STATUS_CODE.NO_CONTENT]: { description: 'delete selfi successfully', content: { [CONTENT_TYPE.JSON]: {schema: getModelSchemaRef(Selfi)}, }, }, ...ErrorCodes, }, }) async deleteSelfi( @param.path.number('id') id: number, @param.path.number('selfiId') selfiId: number, @inject(AuthenticationBindings.CURRENT_USER) currentUser: IAuthUserWithPermissions, @param.header.string('Authorization') token?: string, ): Promise { const isAccessAllowed = await this.patientCgUserService.isPatientAccessAllowed( currentUser, Number(id), // eslint-disable-next-line @typescript-eslint/no-explicit-any PermissionKey.UpdateOwnPatient, token, ); if (!isAccessAllowed) { throw new HttpErrors.Forbidden(AuthorizeErrorKeys.NotAllowedAccess); } await this.patientService.deleteSelfi(id, selfiId, token); return new SuccessResponse({ success: true, }); } @authenticate(STRATEGY.BEARER, { passReqToCallback: true, }) @authorize({permissions: [PermissionKey.UpdateOwnPatient]}) @del(`${patientSelfiPath}/all`, { responses: { [STATUS_CODE.NO_CONTENT]: { description: 'delete selfi successfully', content: {schema: getModelSchemaRef(SelfieDeletSchema)}, }, ...ErrorCodes, }, }) async deleteAllSelfi( @param.path.number('id') id: number, @param.query.object('selfieIds') selfie: SelfieDeletSchema, @inject(AuthenticationBindings.CURRENT_USER) currentUser: IAuthUserWithPermissions, @param.header.string('Authorization') token?: string, ): Promise { const isAccessAllowed = await this.patientCgUserService.isPatientAccessAllowed( currentUser, Number(id), // eslint-disable-next-line @typescript-eslint/no-explicit-any PermissionKey.UpdateOwnPatient, token, ); if (!isAccessAllowed) { throw new HttpErrors.Forbidden(AuthorizeErrorKeys.NotAllowedAccess); } const deletPromises = selfie?.ids.map(selfiId => this.patientService.deleteSelfi(id, selfiId, token), ); await Promise.all(deletPromises); return new SuccessResponse({ success: true, }); } }