Skip to content

Commit

Permalink
OFMCC-5947 - Message screenshots (#424)
Browse files Browse the repository at this point in the history
* OFMCC-5947 initial commit.

* Removed unused import.

* Refined code.
Removed images that can't be replaced.
Removed hyperlinks to CRM attachments.

* Minor update.

---------

Co-authored-by: weskubo-cgi <Wesley.Kubo@gov.bc.ca>
  • Loading branch information
weskubo-cgi and weskubo-cgi authored Nov 22, 2024
1 parent 5950e8d commit 60ccb1a
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 5 deletions.
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 '*'
}
}

0 comments on commit 60ccb1a

Please sign in to comment.