Skip to content

Commit

Permalink
Merge pull request #28 from bcgov/feat/log-notes
Browse files Browse the repository at this point in the history
feat: adding functionality for notes to be added in a log
  • Loading branch information
kyle1morel authored Feb 15, 2024
2 parents ab8a381 + 4ccea8d commit f1bbd58
Show file tree
Hide file tree
Showing 25 changed files with 558 additions and 17 deletions.
2 changes: 1 addition & 1 deletion app/src/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export function mixedQueryToArray(param: string | Array<string>): Array<string>
if (!param) return undefined;

const parsed = Array.isArray(param) ? param.flatMap((p) => parseCSV(p)) : parseCSV(param);
const unique = [...new Set(parsed)];
const unique = Array.from(new Set(parsed));

return unique.length ? unique : undefined;
}
Expand Down
1 change: 1 addition & 0 deletions app/src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as chefsController } from './chefs';
export { default as documentController } from './document';
export { default as noteController } from './note';
export { default as permitController } from './permit';
export { default as userController } from './user';
33 changes: 33 additions & 0 deletions app/src/controllers/note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { NIL } from 'uuid';

import { noteService, userService } from '../services';
import { getCurrentIdentity } from '../components/utils';

import type { NextFunction, Request, Response } from '../interfaces/IExpress';

const controller = {
createNote: async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = await userService.getCurrentUserId(getCurrentIdentity(req.currentUser, NIL), NIL);

// TODO: define body type in request
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const body = req.body as any;
const response = await noteService.createNote({ ...body, createdBy: userId });
res.status(200).send(response);
} catch (e: unknown) {
next(e);
}
},

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

export default controller;
32 changes: 32 additions & 0 deletions app/src/db/migrations/20231212000000_init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,29 @@ export async function up(knex: Knex): Promise<void> {
for each row execute procedure public.set_updatedAt();`)
)

.then(() =>
knex.schema.createTable('note', (table) => {
table.uuid('note_id').primary();
table
.uuid('submission_id')
.notNullable()
.references('submissionId')
.inTable('submission')
.onUpdate('CASCADE')
.onDelete('CASCADE');
table.text('note').defaultTo('').notNullable();
table.text('note_type').defaultTo('').notNullable();
table.text('title').defaultTo('').notNullable();
stamps(knex, table);
})
)

.then(() =>
knex.schema.raw(`create trigger before_update_note_trigger
before update on public.note
for each row execute procedure public.set_updatedAt();`)
)

// Create public schema functions
.then(() =>
knex.schema.raw(`create or replace function public.get_activity_statistics(
Expand Down Expand Up @@ -352,6 +375,12 @@ export async function up(knex: Knex): Promise<void> {
FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();`)
)

.then(() =>
knex.schema.raw(`CREATE TRIGGER audit_note_trigger
AFTER UPDATE OR DELETE ON note
FOR EACH ROW EXECUTE PROCEDURE audit.if_modified_func();`)
)

// Populate Baseline Data
.then(() => {
const users = ['system'];
Expand Down Expand Up @@ -620,13 +649,16 @@ export async function down(knex: Knex): Promise<void> {
.then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_submission_trigger ON submission'))
.then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_user_trigger ON "user"'))
.then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_identity_provider_trigger ON identity_provider'))
.then(() => knex.schema.raw('DROP TRIGGER IF EXISTS audit_note_trigger ON note'))
// Drop audit schema and logged_actions table
.then(() => knex.schema.raw('DROP FUNCTION IF EXISTS audit.if_modified_func'))
.then(() => knex.schema.withSchema('audit').dropTableIfExists('logged_actions'))
.then(() => knex.schema.dropSchemaIfExists('audit'))
// Drop public schema functions
.then(() => knex.schema.raw('DROP FUNCTION IF EXISTS public.get_activity_statistics'))
// Drop public schema tables and triggers
.then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_note_trigger ON note'))
.then(() => knex.schema.dropTableIfExists('note'))
.then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_permit_trigger ON permit'))
.then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_insert_permit_trigger ON permit'))
.then(() => knex.schema.dropTableIfExists('permit'))
Expand Down
1 change: 1 addition & 0 deletions app/src/db/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as document } from './document';
export { default as identity_provider } from './identity_provider';
export { default as note } from './note';
export { default as permit } from './permit';
export { default as permit_type } from './permit_type';
export { default as submission } from './submission';
Expand Down
56 changes: 56 additions & 0 deletions app/src/db/models/note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Prisma } from '@prisma/client';
import { default as submission } from './submission';

import type { ChefsSubmissionForm, Note } from '../../types';

// Define types
const _note = Prisma.validator<Prisma.noteDefaultArgs>()({});
const _noteWithGraph = Prisma.validator<Prisma.noteDefaultArgs>()({
include: { submission: { include: { user: true } } }
});

type SubmissionRelation = {
submission: {
connect: {
submissionId: string;
};
};
};

type PrismaRelationNote = Omit<Prisma.noteGetPayload<typeof _note>, 'submission_id'> & SubmissionRelation;

type PrismaGraphNote = Prisma.noteGetPayload<typeof _noteWithGraph>;

export default {
toPrismaModel(input: Note): PrismaRelationNote {
// Note: submissionId conversion to submission_id will be required here
return {
note_id: input.noteId as string,
note: input.note,
note_type: input.noteType,
submission: { connect: { submissionId: input.submissionId } },
title: input.title,
createdAt: input.createdAt ? new Date(input.createdAt) : null,
createdBy: input.createdBy as string,
updatedAt: input.updatedAt ? new Date(input.updatedAt) : null,
updatedBy: input.updatedBy as string
};
},

fromPrismaModel(input: PrismaGraphNote | null): Note | null {
if (!input) return null;

return {
noteId: input.note_id,
note: input.note || '',
noteType: input.note_type || '',
submission: submission.fromPrismaModel(input.submission) as ChefsSubmissionForm,
submissionId: input.submission_id as string,
title: input.title || '',
createdAt: input.createdAt?.toISOString() ?? null,
createdBy: input.createdBy,
updatedAt: input.updatedAt?.toISOString() ?? null,
updatedBy: input.updatedBy
};
}
};
14 changes: 14 additions & 0 deletions app/src/db/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ model identity_provider {
user user[]
}

model note {
note_id String @id @db.Uuid
submission_id String @db.Uuid
note String @default("")
note_type String @default("")
title String @default("")
createdBy String? @default("00000000-0000-0000-0000-000000000000")
createdAt DateTime? @default(now()) @db.Timestamptz(6)
updatedBy String?
updatedAt DateTime? @db.Timestamptz(6)
submission submission @relation(fields: [submission_id], references: [submissionId], onDelete: Cascade, map: "note_submission_id_foreign")
}

model permit {
permitId String @id @db.Uuid
permitTypeId Int
Expand Down Expand Up @@ -132,6 +145,7 @@ model submission {
updatedBy String?
updatedAt DateTime? @db.Timestamptz(6)
document document[]
note note[]
permit permit[]
user user? @relation(fields: [assignedToUserId], references: [userId], onDelete: Cascade, map: "submission_assignedtouserid_foreign")
}
Expand Down
4 changes: 3 additions & 1 deletion app/src/routes/v1/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { hasAccess } from '../../middleware/authorization';
import express from 'express';
import chefs from './chefs';
import document from './document';
import note from './note';
import permit from './permit';
import user from './user';

Expand All @@ -13,12 +14,13 @@ router.use(hasAccess);
// Base v1 Responder
router.get('/', (_req, res) => {
res.status(200).json({
endpoints: ['/chefs', '/document', '/permit', '/user']
endpoints: ['/chefs', '/document', '/note', '/permit', '/user']
});
});

router.use('/chefs', chefs);
router.use('/document', document);
router.use('/note', note);
router.use('/permit', permit);
router.use('/user', user);

Expand Down
20 changes: 20 additions & 0 deletions app/src/routes/v1/note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import express from 'express';
import { noteController } from '../../controllers';
import { requireSomeAuth } from '../../middleware/requireSomeAuth';

import type { NextFunction, Request, Response } from '../../interfaces/IExpress';

const router = express.Router();
router.use(requireSomeAuth);

// note create endpoint
router.put('/', (req: Request, res: Response, next: NextFunction): void => {
noteController.createNote(req, res, next);
});

// note list by submission endpoint
router.get('/list/:submissionId', (req: Request, res: Response, next: NextFunction): void => {
noteController.listNotes(req, res, next);
});

export default router;
1 change: 1 addition & 0 deletions app/src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as chefsService } from './chefs';
export { default as documentService } from './document';
export { default as noteService } from './note';
export { default as permitService } from './permit';
export { default as userService } from './user';
56 changes: 56 additions & 0 deletions app/src/services/note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import prisma from '../db/dataConnection';
import { note } from '../db/models';
import { v4 as uuidv4 } from 'uuid';

import type { Note } from '../types';

const service = {
/**
* @function createNote
* Creates a note
* @param data Note Object
* @param identityId string
* @returns {Promise<object>} The result of running the findUnique operation
*/
createNote: async (data: Note) => {
const newNote = {
...data,
noteId: uuidv4()
};
const create = await prisma.note.create({
include: {
submission: {
include: { user: true }
}
},
data: note.toPrismaModel(newNote)
});

return note.fromPrismaModel(create);
},

/**
* @function listNotes
* Retrieve a list of permits associated with a given submission
* @param submissionId PCNS Submission ID
* @returns {Promise<object>} Array of documents associated with the submission
*/
listNotes: async (submissionId: string) => {
const response = await prisma.note.findMany({
include: {
submission: {
include: { user: true }
}
},
orderBy: {
createdAt: 'desc'
},
where: {
submission_id: submissionId
}
});
return response.map((x) => note.fromPrismaModel(x));
}
};

export default service;
11 changes: 11 additions & 0 deletions app/src/types/Note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { IStamps } from '../interfaces/IStamps';
import type { ChefsSubmissionForm } from './ChefsSubmissionForm';

export type Note = {
noteId: string; // Primary Key
submissionId: string;
note: string;
noteType: string;
submission: ChefsSubmissionForm;
title: string;
} & Partial<IStamps>;
1 change: 1 addition & 0 deletions app/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type { ChefsSubmissionFormExport } from './ChefsSubmissionFormExport';
export type { CurrentUser } from './CurrentUser';
export type { Document } from './Document';
export type { IdentityProvider } from './IdentityProvider';
export type { Note } from './Note';
export type { Permit } from './Permit';
export type { PermitType } from './PermitType';
export type { SubmissionSearchParameters } from './SubmissionSearchParameters';
Expand Down
Loading

0 comments on commit f1bbd58

Please sign in to comment.