Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OFMCC-5947 - Message screenshots #424

Merged
merged 4 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const authRouter = require('./routes/auth')
const userRouter = require('./routes/user')
const configRouter = require('./routes/config')
const documentsRouter = require('./routes/documents')
const filesRouter = require('./routes/files')
const healthCheckRouter = require('./routes/healthCheck')
const messageRouter = require('./routes/message')
const notificationRouter = require('./routes/notification')
Expand Down Expand Up @@ -263,6 +264,7 @@ apiRouter.use('/auth', authRouter)
apiRouter.use('/config', configRouter)
apiRouter.use('/documents', documentsRouter)
apiRouter.use('/facilities', facilitiesRouter)
apiRouter.use('/files', filesRouter)
apiRouter.use('/funding-agreements', fundingAgreementsRouter)
apiRouter.use('/health', healthCheckRouter)
apiRouter.use('/irregular', irregularApplicationsRouter)
Expand Down
22 changes: 22 additions & 0 deletions backend/src/components/files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict'
const { getOperation, handleError } = require('./utils')
const HttpStatus = require('http-status-codes')

async function getFile(req, res) {
try {
let operation = `msdyn_richtextfiles(${req.params.fileId})/`
if (req.query.image) {
operation += 'msdyn_imageblob/$value?size=full'
} else {
operation += 'msdyn_fileblob'
}
const response = await getOperation(operation)
return res.status(HttpStatus.OK).json(response?.value)
} catch (e) {
handleError(res, e)
}
}

module.exports = {
getFile,
}
28 changes: 28 additions & 0 deletions backend/src/routes/files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const express = require('express')
const passport = require('passport')
const router = express.Router()
const auth = require('../components/auth')
const isValidBackendToken = auth.isValidBackendToken()
const { getFile } = require('../components/files')
const { param, query, validationResult } = require('express-validator')
const validatePermission = require('../middlewares/validatePermission.js')
const { PERMISSIONS } = require('../util/constants')

module.exports = router

/**
* Get the file by id
*/
router.get(
'/:fileId',
passport.authenticate('jwt', { session: false }),
isValidBackendToken,
[param('fileId', 'URL param: [fileId] is required').notEmpty().isUUID()],
query('image').optional().isBoolean(),
validatePermission(PERMISSIONS.MANAGE_NOTIFICATIONS),
(req, res) => {
validationResult(req).throw()
return getFile(req, res)
},
)
module.exports = router
36 changes: 32 additions & 4 deletions frontend/src/components/messages/RequestConversations.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,18 @@

<script>
import { mapState, mapActions } from 'pinia'
import { useAuthStore } from '@/stores/auth'
import { useMessagesStore } from '@/stores/messages'
import ReplyRequestDialog from '@/components/messages/ReplyRequestDialog.vue'
import CloseRequestBanner from '@/components/messages/CloseRequestBanner.vue'
import ReplyRequestDialog from '@/components/messages/ReplyRequestDialog.vue'
import AppButton from '@/components/ui/AppButton.vue'
import alertMixin from '@/mixins/alertMixin'
import format from '@/utils/format'
import FileService from '@/services/fileService'
import { useAuthStore } from '@/stores/auth'
import { useMessagesStore } from '@/stores/messages'
import { ASSISTANCE_REQUEST_STATUS_CODES, OFM_PROGRAM } from '@/utils/constants'
import { deriveImageType } from '@/utils/file'
import format from '@/utils/format'

const ID_REGEX = /\(([^)]+)\)/

export default {
components: { AppButton, ReplyRequestDialog, CloseRequestBanner },
Expand Down Expand Up @@ -148,6 +152,7 @@ export default {
assistanceRequestId: async function (newVal) {
this.loading = true
await this.getAssistanceRequestConversation(this.assistanceRequestId)
await this.formatConversation()
this.sortConversation()
this.loading = false
this.assistanceRequest = this.assistanceRequests.find((item) => item.assistanceRequestId === newVal)
Expand Down Expand Up @@ -204,6 +209,29 @@ export default {
deriveFromDisplay(item) {
return item.ofmSourceSystem ? `${this.userInfo?.firstName ?? ''} ${this.userInfo?.lastName}` : OFM_PROGRAM
},
async formatConversation() {
const parser = new DOMParser()
for (const conversation of this.assistanceRequestConversation) {
const document = parser.parseFromString(conversation.message, 'text/html')
// Update CRM img tags to load correctly
for (const img of document.querySelectorAll('img[src*="/api/data"]')) {
const matches = ID_REGEX.exec(img.getAttribute('src'))
if (matches) {
const fileId = matches[1]
const image = await FileService.getFile(fileId)
const imageType = deriveImageType(image)
img.setAttribute('src', `data:image/${imageType};base64,${image}`)
} else {
img.remove()
}
}
// Remove CRM links for now until we can support them
document.querySelectorAll('a[href*="/api/data"]').forEach((link) => link.remove())

// Update the message content
conversation.message = document.documentElement.innerHTML
}
},
},
}
</script>
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/services/fileService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import ApiService from '@/common/apiService'
import { ApiRoutes } from '@/utils/constants'

export default {
async getFile(fileId, image = false) {
try {
if (!fileId) return null
const response = await ApiService.apiAxios.get(`${ApiRoutes.FILES}/${fileId}?image=${image}`)
return response.data
} catch (error) {
console.log(`Failed to get file - ${error}`)
throw error
}
},
}
1 change: 1 addition & 0 deletions frontend/src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const ApiRoutes = Object.freeze({
FACILITIES: baseRoot + '/facilities',
FACILITIES_CONTACTS: baseRoot + '/facilities/:facilityId/contacts',
FACILITIES_LICENCES: baseRoot + '/facilities/:facilityId/licences',
FILES: baseRoot + '/files',
FUNDING_AGREEMENTS: baseRoot + '/funding-agreements',
LICENCES: baseRoot + '/licences',
LOOKUP: baseRoot + '/config/lookup',
Expand Down
20 changes: 19 additions & 1 deletion frontend/src/utils/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* @param {*} bytes
* @param {*} decimals
*/

export function humanFileSize(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes'
const k = 1024
Expand Down Expand Up @@ -40,3 +39,22 @@ export function updateHeicFileNameToJpg(filename) {
const regex = /\.heic(?![\s\S]*\.heic)/i //looks for last occurrence of .heic case-insensitive
return filename?.replace(regex, '.jpg')
}

/**
* Quick and dirty way to determine image type based on base64 encoding.
* @param image The base64 encoded image
*/
export function deriveImageType(image) {
switch (image.charAt(0)) {
case '/':
return 'jpg'
case 'i':
return 'png'
case 'R':
return 'gif'
case 'U':
return 'webp'
default:
return '*'
}
}
Loading