diff --git a/app/src/db/migrations/20231212000000_init.ts b/app/src/db/migrations/20231212000000_init.ts index bd48f34e..1a48343e 100644 --- a/app/src/db/migrations/20231212000000_init.ts +++ b/app/src/db/migrations/20231212000000_init.ts @@ -89,6 +89,7 @@ export async function up(knex: Knex): Promise { table.boolean('aaiUpdated'); table.text('waitingOn'); table.timestamp('bringForwardDate', { useTz: true }); + // TODO: REMOVE THIS WHEN DONE NOTE LOGS table.text('notes'); table.text('intakeStatus'); table.text('applicationStatus'); @@ -192,6 +193,30 @@ export async function up(knex: Knex): Promise { 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('category_type').defaultTo('').notNullable(); + table.text('note_type').defaultTo('').notNullable(); + table.text('note').defaultTo('').notNullable(); + stamps(knex, table); + table.unique(['note_id']); + }) + ) + + .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( @@ -352,6 +377,12 @@ export async function up(knex: Knex): Promise { 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']; @@ -620,6 +651,7 @@ export async function down(knex: Knex): Promise { .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')) @@ -642,6 +674,8 @@ export async function down(knex: Knex): Promise { knex.schema.raw('DROP TRIGGER IF EXISTS before_update_identity_provider_trigger ON identity_provider') ) .then(() => knex.schema.dropTableIfExists('identity_provider')) + .then(() => knex.schema.raw('DROP TRIGGER IF EXISTS before_update_note_trigger ON note')) + .then(() => knex.schema.dropTableIfExists('note')) // Drop public schema triggers .then(() => knex.schema.raw('DROP FUNCTION IF EXISTS public.set_updatedAt')) ); diff --git a/app/src/db/models/index.ts b/app/src/db/models/index.ts index 3c178171..6e05e8b9 100644 --- a/app/src/db/models/index.ts +++ b/app/src/db/models/index.ts @@ -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'; diff --git a/app/src/db/models/note.ts b/app/src/db/models/note.ts new file mode 100644 index 00000000..c5586938 --- /dev/null +++ b/app/src/db/models/note.ts @@ -0,0 +1,52 @@ +import { Prisma } from '@prisma/client'; +import disconnectRelation from '../utils/disconnectRelation'; + +import type { IStamps } from '../../interfaces/IStamps'; +import type { Note } from '../../types'; + +// Define types +const _note = Prisma.validator()({}); +const _noteWithGraph = Prisma.validator()({}); + +type SubmissionRelation = { + submission: + | { + connect: { + submissionId: string; + }; + } + | { + disconnect: boolean; + }; +}; + +type PrismaRelationNote = Omit, 'submissionId' | keyof IStamps> & + SubmissionRelation; + +type PrismaGraphNote = Prisma.noteGetPayload; + +export default { + toPrismaModel(input: Note): PrismaRelationNote { + // Note: submissionId conversion to submission_id will be required here + return { + note_id: input.note_id as string, + submission: input.submission_id ? { connect: { submissionId: input.submission_id } } : disconnectRelation, + category_type: input.category_type, + note: input.note, + note_type: input.note_type + }; + }, + + fromPrismaModel(input: PrismaGraphNote | null): Note | null { + if (!input) return null; + + return { + note_id: input.note_id, + submission_id: input.submissionId as string, + category_type: input.category_type || '', + note: input.note || '', + note_type: input.note_type || '', + createdAt: input.createdAt?.toISOString() + }; + } +}; diff --git a/app/src/db/prisma/schema.prisma b/app/src/db/prisma/schema.prisma index 927f90e2..e541797e 100644 --- a/app/src/db/prisma/schema.prisma +++ b/app/src/db/prisma/schema.prisma @@ -44,6 +44,19 @@ model identity_provider { user user[] } +model note { + note_id String @id @unique(map: "note_note_id_unique") @db.Uuid + submissionId String @db.Uuid + category_type String? + note_type String? + note String? + 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: [submissionId], references: [submissionId], onDelete: Cascade, map: "note_submissionid_foreign") +} + model permit { permitId String @id @db.Uuid permitTypeId Int @@ -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") } diff --git a/app/src/types/Note.ts b/app/src/types/Note.ts new file mode 100644 index 00000000..97893fa0 --- /dev/null +++ b/app/src/types/Note.ts @@ -0,0 +1,9 @@ +import { IStamps } from '../interfaces/IStamps'; + +export type Note = { + note_id: string; // Primary Key + submission_id: string; + category_type: string; + note: string; + note_type: string; +} & Partial; diff --git a/app/src/types/index.ts b/app/src/types/index.ts index 4fc0741c..026c7b3e 100644 --- a/app/src/types/index.ts +++ b/app/src/types/index.ts @@ -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'; diff --git a/frontend/src/types/Note.ts b/frontend/src/types/Note.ts new file mode 100644 index 00000000..a198deb5 --- /dev/null +++ b/frontend/src/types/Note.ts @@ -0,0 +1,9 @@ +import type { IStamps } from '@/interfaces'; + +export type Note = { + note_id: string; // Primary Key + submission_id: number; + note_type: string; + category_type?: string; + note?: string; +} & Partial; diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 5920b108..5eb3154e 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -5,3 +5,4 @@ export type { Permit } from './Permit'; export type { PermitType } from './PermitType'; export type { User } from './User'; export type { UserSearchParameters } from './UserSearchParameters'; +export type { Note } from './Note';