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

feat: adding functionality for notes to be added in a log #28

Merged
merged 3 commits into from
Feb 15, 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/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
kyle1morel marked this conversation as resolved.
Show resolved Hide resolved
// 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) {
wilwong89 marked this conversation as resolved.
Show resolved Hide resolved
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 => {
wilwong89 marked this conversation as resolved.
Show resolved Hide resolved
noteController.createNote(req, res, next);
});

// note list by submission endpoint
router.get('/list/:submissionId', (req: Request, res: Response, next: NextFunction): void => {
wilwong89 marked this conversation as resolved.
Show resolved Hide resolved
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({
wilwong89 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading