Skip to content

Commit

Permalink
Merge pull request #16 from bcgov/feature/doc-download
Browse files Browse the repository at this point in the history
Document download and delete
  • Loading branch information
TimCsaky authored Jan 22, 2024
2 parents 80a5648 + 90538df commit 520a282
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 106 deletions.
9 changes: 9 additions & 0 deletions app/src/controllers/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ const controller = {
}
},

async deleteDocument(req: Request<{ documentId: string }>, res: Response, next: NextFunction) {
try {
const response = await documentService.deleteDocument(req.params.documentId);
res.status(200).send(response);
} catch (e: unknown) {
next(e);
}
},

async listDocuments(req: Request<{ submissionId: string }>, res: Response, next: NextFunction) {
try {
const response = await documentService.listDocuments(req.params.submissionId);
Expand Down
4 changes: 4 additions & 0 deletions app/src/routes/v1/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ router.put('/', (req: Request, res: Response, next: NextFunction): void => {
documentController.createDocument(req, res, next);
});

router.delete('/:documentId', (req: Request, res: Response, next: NextFunction): void => {
documentController.deleteDocument(req, res, next);
});

router.get('/list/:submissionId', (req: Request, res: Response, next: NextFunction): void => {
documentController.listDocuments(req, res, next);
});
Expand Down
15 changes: 15 additions & 0 deletions app/src/services/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ const service = {
return document.fromPrismaModel(response);
},

/**
* @function deleteDocument
* Delete a document
* @param documentId Document ID
*/
deleteDocument: async (documentId: string) => {
const response = await prisma.document.delete({
where: {
documentId: documentId
}
});

return document.fromPrismaModel(response);
},

/**
* @function listDocuments
* Retrieve a list of documents associated with a given submission
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/assets/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,12 @@ label {
color: $app-primary;
font-size: 1.8rem;
}

/* hover effects */
.hover-hand:hover {
cursor: pointer;
}

.hover-shadow:hover {
box-shadow: 0 0 11px rgba(33, 33, 33, 0.2);
}
109 changes: 109 additions & 0 deletions frontend/src/components/file/DocumentCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script setup lang="ts">
import { filesize } from 'filesize';
import { Button, Card, useConfirm, useToast } from '@/lib/primevue';
import { documentService } from '@/services';
import { formatDateLong } from '@/utils/formatters';
import type { Document } from '@/types';
// Props
type Props = {
document: Document;
deleteButton?: boolean;
};
const props = withDefaults(defineProps<Props>(), {
deleteButton: true
});
// Emits
const emit = defineEmits(['document:deleted']);
// Actions
const confirm = useConfirm();
const toast = useToast();
const confirmDelete = (documentId: string, filename: string) => {
if (documentId) {
confirm.require({
message: `Please confirm that you want to delete ${filename}.`,
header: 'Delete document?',
acceptLabel: 'Confirm',
rejectLabel: 'Cancel',
accept: () => {
documentService
.deleteDocument(documentId)
.then(() => {
emit('document:deleted', documentId);
toast.success('File deleted');
})
.catch(() => {});
}
});
}
};
</script>

<template>
<Card class="pt-2 pb-1 text-center">
<template #header>
<img
alt="document header"
src="@/assets/images/bcboxy.png"
class="document-image"
/>
</template>
<template #content>
<div class="grid">
<h4
v-tooltip.bottom="props.document.filename"
class="col-12 mb-0 text-left"
style="text-overflow: ellipsis; overflow: hidden"
>
{{ props.document.filename }}
</h4>
<h6 class="col-8 text-left mt-0 mb-0">
{{ formatDateLong(props.document.createdAt as string) }}
</h6>
<h6 class="col-4 text-right mt-0 mb-0">
{{ filesize(props.document.filesize) }}
</h6>
</div>
</template>
<template #footer>
<Button
v-if="deleteButton"
v-tooltip.bottom="'Delete document'"
class="p-button-lg p-button-text p-button-danger p-0"
aria-label="Delete object"
@click="
(e) => {
confirmDelete(props.document.documentId, props.document.filename);
e.stopPropagation();
}
"
>
<font-awesome-icon icon="fa-solid fa-trash" />
</Button>
</template>
</Card>
</template>

<style lang="scss">
.document-image {
max-height: 140px;
width: auto;
height: auto;
}
.p-card-content {
padding-top: 0;
padding-bottom: 0;
}
.p-card-footer {
padding: 0;
text-align: right;
}
</style>
85 changes: 44 additions & 41 deletions frontend/src/components/file/FileUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,46 +46,48 @@ const onUpload = async (file: File) => {
</script>

<template>
<FileUpload
name="fileUpload"
:multiple="false"
:custom-upload="true"
:auto="true"
@uploader="onFileUploadDragAndDrop"
@click="onFileUploadClick"
>
<template #empty>
<div class="flex align-items-center justify-content-center flex-column mb-3">
<font-awesome-icon
icon="fa-solid fa-upload"
class="border-2 border-dashed border-circle p-5 text-7xl text-400 border-400"
/>
<p class="font-bold">Upload</p>
<p class="mt-2 mb-0">Click or drag-and-drop</p>
</div>
</template>
<template #content="{ files }">
<div
v-if="files.length > 0"
class="flex align-items-center justify-content-center flex-column mb-3"
>
<font-awesome-icon
icon="fa-solid fa-upload"
class="border-2 border-dashed border-circle p-5 text-7xl text-400 border-400"
/>
<p class="font-bold">Upload</p>
<p class="mb-0">Click or drag-and-drop</p>
</div>
</template>
</FileUpload>
<input
ref="fileInput"
type="file"
style="display: none"
accept="*"
@change="(event: any) => onUpload(event.target.files[0])"
@click="(event: any) => (event.target.value = null)"
/>
<div class="hover-hand hover-shadow">
<FileUpload
name="fileUpload"
:multiple="false"
:custom-upload="true"
:auto="true"
@uploader="onFileUploadDragAndDrop"
@click="onFileUploadClick"
>
<template #empty>
<div class="flex align-items-center justify-content-center flex-column">
<font-awesome-icon
icon="fa-solid fa-upload"
class="border-2 border-dashed border-circle p-5 text-7xl text-400 border-400"
/>
<p class="font-bold">Upload</p>
<p>Click or drag-and-drop</p>
</div>
</template>
<template #content="{ files }">
<div
v-if="files.length > 0"
class="flex align-items-center justify-content-center flex-column"
>
<font-awesome-icon
icon="fa-solid fa-upload"
class="border-2 border-dashed border-circle p-5 text-7xl text-400 border-400"
/>
<p class="font-bold">Upload</p>
<p>Click or drag-and-drop</p>
</div>
</template>
</FileUpload>
<input
ref="fileInput"
type="file"
style="display: none"
accept="*"
@change="(event: any) => onUpload(event.target.files[0])"
@click="(event: any) => (event.target.value = null)"
/>
</div>
</template>

<style lang="scss">
Expand All @@ -94,6 +96,7 @@ const onUpload = async (file: File) => {
}
.p-fileupload-content {
padding-bottom: 0px;
padding: 1rem;
border-style: dashed;
}
</style>
2 changes: 1 addition & 1 deletion frontend/src/components/layout/Spinner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ProgressSpinner } from '@/lib/primevue';
/>
</template>

<style lang="scss" scoped>
<style scoped lang="scss">
:deep(.p-progress-spinner-circle) {
stroke: #00004d !important;
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/lib/primevue/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
export { FilterMatchMode } from 'primevue/api';
export { default as Button } from 'primevue/button';
export { default as Calendar } from 'primevue/calendar';
export { default as Carousel } from 'primevue/carousel';
export { default as Card } from 'primevue/card';
export { default as Checkbox } from 'primevue/checkbox';
export { default as Column } from 'primevue/column';
export { default as ConfirmDialog } from 'primevue/confirmdialog';
Expand Down
36 changes: 36 additions & 0 deletions frontend/src/services/comsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,41 @@ export default {
}

return comsAxios(axiosOptions).put(PATH, object, config);
},

/**
* @function deleteObject
* Delete an object
* @param {string} objectId The id for the object to delete
* @param {string} versionId An optional versionId
* @returns {Promise} An axios response
*/
deleteObject(objectId: string, versionId?: string) {
return comsAxios().delete(`${PATH}/${objectId}`, {
params: {
versionId: versionId
}
});
},

/**
* @function getObject
* Get an object
* @param {string} objectId The id for the object to get
* @param {string} versionId An optional versionId
*/
getObject(objectId: string, versionId?: string) {
// Running in 'url' download mode only, could add options for other modes if needed
return comsAxios()
.get(`${PATH}/${objectId}`, {
params: {
versionId: versionId,
download: 'url'
}
})
.then((response) => {
const url = response.data;
window.open(url, '_blank');
});
}
};
36 changes: 34 additions & 2 deletions frontend/src/services/documentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,50 @@ export default {
);

// Create document link
return appAxios().put(PATH, {
return await appAxios().put(PATH, {
submissionId: submissionId,
documentId: comsResponse.data.id,
filename: comsResponse.data.name,
mimeType: comsResponse.data.mimeType,
length: comsResponse.data.length
});
} catch (e) {
// TODO: Delete COMS object if Prisma write fails
// In event of any error try to Delete COMS object if it was created
if (comsResponse) {
await comsService.deleteObject(comsResponse.data.id, comsResponse.data.versionId);
}
}
},

/**
* @function deleteDocument
* @returns {Promise} An axios response
*/
async deleteDocument(documentId: string, versionId?: string) {
try {
await comsService.deleteObject(documentId, versionId);
await appAxios().delete(`${PATH}/${documentId}`, {
params: {
versionId: versionId
}
});
} catch (e) {
// TODO: If one fails and other doesn't then??
}
},

/**
* @function downloadDocument
* @returns {Promise} An axios response
*/
async downloadDocument(documentId: string, versionId?: string) {
await comsService.getObject(documentId, versionId);
},

/**
* @function listDocuments
* @returns {Promise} An axios response
*/
async listDocuments(submissionId: string) {
return appAxios().get(`${PATH}/list/${submissionId}`);
}
Expand Down
Loading

0 comments on commit 520a282

Please sign in to comment.