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

SubmissionView to store + minor general refactors #68

Merged
merged 2 commits into from
Apr 23, 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: 1 addition & 1 deletion app/src/controllers/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const controller = {
req.body.mimeType,
req.body.length
);
res.status(200).json(response);
res.status(201).json(response);
} catch (e: unknown) {
next(e);
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/controllers/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const controller = {
...body,
createdBy: userId
});
res.status(200).json(response);
res.status(201).json(response);
} catch (e: unknown) {
next(e);
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/controllers/permit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const controller = {
try {
const userId = await userService.getCurrentUserId(getCurrentIdentity(req.currentUser, NIL), NIL);
const response = await permitService.createPermit({ ...(req.body as Permit), updatedBy: userId });
res.status(200).json(response);
res.status(201).json(response);
} catch (e: unknown) {
next(e);
}
Expand Down
44 changes: 22 additions & 22 deletions app/src/controllers/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,6 @@ import type { NextFunction, Request, Response } from '../interfaces/IExpress';
import type { ChefsFormConfig, ChefsFormConfigData, Submission, ChefsSubmissionExport, Permit } from '../types';

const controller = {
createEmptySubmission: async (req: Request, res: Response, next: NextFunction) => {
let testSubmissionId;
let submissionQuery;

// Testing for activityId collisions, which are truncated UUIDs
// If a collision is detected, generate new UUID and test again
do {
testSubmissionId = uuidv4();
submissionQuery = await submissionService.getSubmission(testSubmissionId.substring(0, 8).toUpperCase());
} while (submissionQuery);

try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const submitter = (req.currentUser?.tokenPayload as any)?.idir_username;
const result = await submissionService.createEmptySubmission(testSubmissionId, submitter);

res.status(201).json({ activityId: result.activity_id });
} catch (e: unknown) {
next(e);
}
},

checkAndStoreNewSubmissions: async () => {
const cfg = config.get('server.chefs.forms') as ChefsFormConfig;

Expand Down Expand Up @@ -175,6 +153,28 @@ const controller = {
notStored.map((x) => x.permits?.map(async (y) => await permitService.createPermit(y)));
},

createEmptySubmission: async (req: Request, res: Response, next: NextFunction) => {
let testSubmissionId;
let submissionQuery;

// Testing for activityId collisions, which are truncated UUIDs
// If a collision is detected, generate new UUID and test again
do {
testSubmissionId = uuidv4();
submissionQuery = await submissionService.getSubmission(testSubmissionId.substring(0, 8).toUpperCase());
} while (submissionQuery);

try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const submitter = (req.currentUser?.tokenPayload as any)?.idir_username;
const result = await submissionService.createEmptySubmission(testSubmissionId, submitter);

res.status(201).json({ activityId: result.activity_id });
} catch (e: unknown) {
next(e);
}
},

getStatistics: async (
req: Request<never, { dateFrom: string; dateTo: string; monthYear: string; userId: string }>,
res: Response,
Expand Down
28 changes: 20 additions & 8 deletions app/src/services/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ const service = {
return note.fromPrismaModel(response);
},

/**
* @function deleteNote
* Soft deletes a note by marking is as deleted
* @param {string} noteId ID of the note to delete
* @returns {Promise<Note>} The result of running the update operation
*/
deleteNote: async (noteId: string) => {
const result = await prisma.note.update({
where: {
Expand Down Expand Up @@ -76,8 +82,15 @@ const service = {
return response.map((x) => note.fromPrismaModel(x));
},

/**
* @function updateNote
* Updates a note by marking the old note as deleted and creating a new one
* @param {Note} data New Note object
* @returns {Promise<Note>} The result of running the transaction
*/
updateNote: async (data: Note) => {
return await prisma.$transaction(async (trx) => {
// Mark old note as deleted
await trx.note.update({
where: {
note_id: data.noteId
Expand All @@ -87,16 +100,15 @@ const service = {
}
});

const newNote = {
...data,
noteId: uuidv4()
};

const newCreatedNote = await trx.note.create({
data: note.toPrismaModel(newNote)
// Create new note
const response = await trx.note.create({
data: note.toPrismaModel({
...data,
noteId: uuidv4()
})
});

return note.fromPrismaModel(newCreatedNote);
return note.fromPrismaModel(response);
});
}
};
Expand Down
4 changes: 2 additions & 2 deletions app/tests/unit/controllers/document.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('createDocument', () => {
// Mock service calls
const createSpy = jest.spyOn(documentService, 'createDocument');

it('should return 200 if all good', async () => {
it('should return 201 if all good', async () => {
const req = {
body: { documentId: 'abc123', activityId: '1', filename: 'testfile', mimeType: 'imgjpg', length: 1234567 },
currentUser: CURRENT_USER
Expand All @@ -56,7 +56,7 @@ describe('createDocument', () => {
req.body.mimeType,
req.body.length
);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.status).toHaveBeenCalledWith(201);
expect(res.json).toHaveBeenCalledWith(created);
});

Expand Down
4 changes: 2 additions & 2 deletions app/tests/unit/controllers/note.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('createNote', () => {
const getCurrentIdentitySpy = jest.spyOn(utils, 'getCurrentIdentity');
const getCurrentUserIdSpy = jest.spyOn(userService, 'getCurrentUserId');

it('should return 200 if all good', async () => {
it('should return 201 if all good', async () => {
const req = {
body: {
noteId: '123-123',
Expand Down Expand Up @@ -75,7 +75,7 @@ describe('createNote', () => {
expect(getCurrentUserIdSpy).toHaveBeenCalledWith(USR_IDENTITY, NIL);
expect(createSpy).toHaveBeenCalledTimes(1);
expect(createSpy).toHaveBeenCalledWith({ ...req.body, createdBy: USR_ID });
expect(res.status).toHaveBeenCalledWith(200);
expect(res.status).toHaveBeenCalledWith(201);
expect(res.json).toHaveBeenCalledWith(created);
});

Expand Down
4 changes: 2 additions & 2 deletions app/tests/unit/controllers/permit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('createPermit', () => {
const getCurrentIdentitySpy = jest.spyOn(utils, 'getCurrentIdentity');
const getCurrentUserIdSpy = jest.spyOn(userService, 'getCurrentUserId');

it('should return 200 if all good', async () => {
it('should return 201 if all good', async () => {
const now = new Date();
const req = {
body: {
Expand Down Expand Up @@ -81,7 +81,7 @@ describe('createPermit', () => {
expect(getCurrentUserIdSpy).toHaveBeenCalledWith(USR_IDENTITY, NIL);
expect(createSpy).toHaveBeenCalledTimes(1);
expect(createSpy).toHaveBeenCalledWith({ ...req.body, updatedBy: USR_ID });
expect(res.status).toHaveBeenCalledWith(200);
expect(res.status).toHaveBeenCalledWith(201);
expect(res.json).toHaveBeenCalledWith(created);
});

Expand Down
20 changes: 12 additions & 8 deletions frontend/src/components/file/DocumentCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ref } from 'vue';

import { Button, Card, useConfirm, useToast } from '@/lib/primevue';
import { documentService } from '@/services';
import { useSubmissionStore } from '@/store';
import { FILE_CATEGORIES } from '@/utils/constants';
import { formatDateLong } from '@/utils/formatters';
import { getFileCategory } from '@/utils/utils';
Expand All @@ -22,8 +23,8 @@ import type { Document } from '@/types';

// Props
type Props = {
document: Document;
deleteButton?: boolean;
document: Document;
selectable?: boolean;
selected?: boolean;
};
Expand All @@ -35,7 +36,10 @@ const props = withDefaults(defineProps<Props>(), {
});

// Emits
const emit = defineEmits(['document:clicked', 'document:deleted']);
const emit = defineEmits(['document:clicked']);

// Store
const submissionStore = useSubmissionStore();

// State
const isSelected: Ref<boolean> = ref(props.selected);
Expand All @@ -44,18 +48,18 @@ const isSelected: Ref<boolean> = ref(props.selected);
const confirm = useConfirm();
const toast = useToast();

const confirmDelete = (documentId: string, filename: string) => {
if (documentId) {
const confirmDelete = (document: Document) => {
if (document) {
confirm.require({
message: `Please confirm that you want to delete ${filename}.`,
message: `Please confirm that you want to delete ${document.filename}.`,
header: 'Delete document?',
acceptLabel: 'Confirm',
rejectLabel: 'Cancel',
accept: () => {
documentService
.deleteDocument(documentId)
.deleteDocument(document.documentId)
.then(() => {
emit('document:deleted', documentId);
submissionStore.removeDocument(document);
toast.success('Document deleted');
})
.catch((e: any) => toast.error('Failed to deleted document', e.message));
Expand Down Expand Up @@ -133,7 +137,7 @@ function onClick() {
aria-label="Delete object"
@click="
(e) => {
confirmDelete(props.document.documentId, props.document.filename);
confirmDelete(props.document);
e.stopPropagation();
}
"
Expand Down
19 changes: 8 additions & 11 deletions frontend/src/components/file/FileUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import { ref } from 'vue';

import { FileUpload, useToast } from '@/lib/primevue';
import { documentService } from '@/services';
import { useConfigStore } from '@/store';
import { useConfigStore, useSubmissionStore } from '@/store';

import type { FileUploadUploaderEvent } from 'primevue/fileupload';
import type { Ref } from 'vue';
import type { Document } from '@/types';

// Props
type Props = {
Expand All @@ -17,35 +16,33 @@ type Props = {

const props = withDefaults(defineProps<Props>(), {});

const lastUploadedDocument = defineModel<Document>('lastUploadedDocument');

// Store
const { getConfig } = storeToRefs(useConfigStore());
const submissionStore = useSubmissionStore();

// State
const fileInput: Ref<any> = ref(null);

// Actions
const toast = useToast();

const onFileUploadDragAndDrop = (event: FileUploadUploaderEvent) => {
onUpload(Array.isArray(event.files) ? event.files[0] : event.files);
};

const onFileUploadClick = () => {
fileInput.value.click();
};

const onFileUploadDragAndDrop = (event: FileUploadUploaderEvent) => {
onUpload(Array.isArray(event.files) ? event.files[0] : event.files);
};

const onUpload = async (file: File) => {
try {
const response = (await documentService.createDocument(file, props.activityId, getConfig.value.coms.bucketId))
?.data;

if (response) {
lastUploadedDocument.value = response;
submissionStore.addDocument(response);
toast.success('Document uploaded');
}

toast.success('Document uploaded');
} catch (e: any) {
toast.error('Failed to upload document', e);
}
Expand Down
44 changes: 8 additions & 36 deletions frontend/src/components/note/NoteCard.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';

import { Button, Card, Divider, useToast } from '@/lib/primevue';
import NoteModal from '@/components/note/NoteModal.vue';
import { noteService, userService } from '@/services';
import { Button, Card, Divider } from '@/lib/primevue';
import { userService } from '@/services';
import { formatDate, formatDateShort } from '@/utils/formatters';

import type { Ref } from 'vue';
Expand All @@ -16,40 +16,11 @@ type Props = {

const props = withDefaults(defineProps<Props>(), {});

// Emits
const emit = defineEmits(['note:delete', 'note:edit']);

// State
const userName: Ref<string> = ref('');
const noteModalVisible: Ref<boolean> = ref(false);
const editNoteData: Ref<Note | undefined> = ref(undefined);
const userName: Ref<string> = ref('');

// Actions
const toast = useToast();

const editNote = (note: Note) => {
editNoteData.value = note;
noteModalVisible.value = true;
};

const onNoteSubmit = async (data: Note) => {
editNoteData.value = undefined;

try {
const newNote = (await noteService.updateNote(data)).data;
emit('note:edit', newNote, data.noteId);
toast.success('Note saved');
} catch (e: any) {
toast.error('Failed to save note', e.message);
} finally {
noteModalVisible.value = false;
}
};

const onNoteDelete = (noteId: string) => {
emit('note:delete', noteId);
};

onMounted(() => {
if (props.note.createdBy) {
userService.searchUsers({ userId: [props.note.createdBy] }).then((res) => {
Expand Down Expand Up @@ -77,7 +48,7 @@ onMounted(() => {
<Button
class="p-button-outlined"
aria-label="Edit"
@click="editNote(props.note)"
@click="noteModalVisible = true"
>
<font-awesome-icon
class="pr-2"
Expand Down Expand Up @@ -132,11 +103,12 @@ onMounted(() => {
</div>
</template>
</Card>

<NoteModal
v-if="props.note && noteModalVisible"
v-model:visible="noteModalVisible"
:note="editNoteData"
@note:delete="onNoteDelete"
@note:submit="onNoteSubmit"
:activity-id="props.note.activityId"
:note="props.note"
/>
</template>

Expand Down
Loading
Loading