diff --git a/packages/events/src/index.test.ts b/packages/events/src/index.test.ts index f3690e08bd6..2054466830e 100644 --- a/packages/events/src/index.test.ts +++ b/packages/events/src/index.test.ts @@ -28,21 +28,80 @@ afterEach(async () => { resetServer() }) -test('creating a declaration is an idempotent operation', async () => { +function createClient() { const createCaller = createCallerFactory(appRouter) - const caller = createCaller({}) + return caller +} + +const client = createClient() +test('event can be created and fetched', async () => { + const event = await client.event.create({ + transactionId: '1', + event: { type: 'birth' } + }) + + const fetchedEvent = await client.event.get(event.id) + + expect(fetchedEvent).toEqual(event) +}) + +test('creating an event is an idempotent operation', async () => { const db = await getClient() - await caller.event.create({ + await client.event.create({ transactionId: '1', - record: { type: 'birth', fields: [] } + event: { type: 'birth' } }) - await caller.event.create({ + await client.event.create({ transactionId: '1', - record: { type: 'birth', fields: [] } + event: { type: 'birth' } }) expect(await db.collection('events').find().toArray()).toHaveLength(1) }) + +test('stored events can be modified', async () => { + const originalEvent = await client.event.create({ + transactionId: '1', + event: { type: 'birth' } + }) + + const event = await client.event.patch({ + id: originalEvent.id, + type: 'death' + }) + + expect(event.updatedAt).not.toBe(originalEvent.updatedAt) + expect(event.type).toBe('death') +}) + +test('actions can be added to created events', async () => { + const originalEvent = await client.event.create({ + transactionId: '1', + event: { type: 'birth' } + }) + + const event = await client.event.actions.create({ + eventId: originalEvent.id, + action: { + type: 'REGISTERED', + fields: [], + identifiers: { + trackingId: '123', + registrationNumber: '456' + } + } + }) + + expect(event.actions).toContainEqual( + expect.objectContaining({ + type: 'REGISTERED', + identifiers: { + trackingId: '123', + registrationNumber: '456' + } + }) + ) +}) diff --git a/packages/events/src/router.ts b/packages/events/src/router.ts index e94a3eaf249..95a55b3f45c 100644 --- a/packages/events/src/router.ts +++ b/packages/events/src/router.ts @@ -12,7 +12,15 @@ import { initTRPC } from '@trpc/server' import superjson from 'superjson' import { z } from 'zod' -import { createEvent, EventInput, getEventById } from './service/events' +import { + ActionInput, + addAction, + createEvent, + EventInput, + EventInputWithId, + getEventById, + patchEvent +} from './service/events' export const t = initTRPC.create({ transformer: superjson @@ -32,14 +40,29 @@ export const appRouter = router({ .input( z.object({ transactionId: z.string(), - record: EventInput + event: EventInput }) ) .mutation(async (options) => { - return createEvent(options.input.record, options.input.transactionId) + return createEvent(options.input.event, options.input.transactionId) }), + patch: publicProcedure.input(EventInputWithId).mutation(async (options) => { + return patchEvent(options.input) + }), get: publicProcedure.input(z.string()).query(async ({ input }) => { return getEventById(input) + }), + actions: router({ + create: publicProcedure + .input( + z.object({ + eventId: z.string(), + action: ActionInput + }) + ) + .mutation(async (options) => { + return addAction(options.input.eventId, options.input.action) + }) }) }) }) diff --git a/packages/events/src/service/events.ts b/packages/events/src/service/events.ts index cdba3f7e7e8..40034353196 100644 --- a/packages/events/src/service/events.ts +++ b/packages/events/src/service/events.ts @@ -14,27 +14,14 @@ import { getUUID } from '@opencrvs/commons' import { z } from 'zod' export const EventInput = z.object({ - type: z.string(), - fields: z.array( - z.object({ - id: z.string(), - value: z.union([ - z.string(), - z.number(), - z.array( - z.object({ - optionValues: z.array(z.string()), - type: z.string(), - data: z.string(), - fileSize: z.number() - }) - ) - ]) - }) - ) + type: z.string() +}) + +export const EventInputWithId = EventInput.extend({ + id: z.string() }) -const ActionBase = z.object({ +const ActionInputBase = z.object({ type: z.enum([ 'CREATED', 'ASSIGNMENT', @@ -44,8 +31,6 @@ const ActionBase = z.object({ 'CORRECTION', 'DUPLICATES_DETECTED' ]), - createdAt: z.date(), - createdBy: z.string(), fields: z.array( z.object({ id: z.string(), @@ -65,11 +50,11 @@ const ActionBase = z.object({ ) }) -const Action = z.union([ - ActionBase.extend({ +export const ActionInput = z.union([ + ActionInputBase.extend({ type: z.enum(['CREATED']) }), - ActionBase.extend({ + ActionInputBase.extend({ type: z.enum(['REGISTERED']), identifiers: z.object({ trackingId: z.string(), @@ -78,10 +63,20 @@ const Action = z.union([ }) ]) +const Action = ActionInput.and( + z.object({ + createdAt: z.date(), + createdBy: z.string() + }) +) + +type ActionInput = z.infer + export const Event = EventInput.extend({ id: z.string(), type: z.string(), // Should be replaced by a reference to a form version createdAt: z.date(), + updatedAt: z.date(), actions: z.array(Action) }) export type Event = z.infer @@ -107,6 +102,7 @@ class EventNotFoundError extends Error { export async function getEventById(id: string) { const db = await getClient() + const collection = db.collection>('events') const event = await collection.findOne({ id: id }) if (!event) { @@ -136,6 +132,7 @@ export async function createEvent( id, transactionId, createdAt: now, + updatedAt: now, actions: [ { type: 'CREATED', @@ -148,3 +145,57 @@ export async function createEvent( return getEventById(id) } + +export async function addAction(eventId: string, action: ActionInput) { + const db = await getClient() + const collection = db.collection>('events') + + const now = new Date() + + await collection.updateOne( + { + id: eventId + }, + { + $push: { + actions: { + ...action, + createdAt: now, + createdBy: '123-123-123' + } + } + } + ) + + return getEventById(eventId) +} + +export async function patchEvent( + event: z.infer +): Promise { + const existingEvent = await getEventById(event.id) + + if (!existingEvent) { + throw new EventNotFoundError(event.id) + } + + const db = await getClient() + const collection = + db.collection>('events') + + const now = new Date() + + await collection.updateOne( + { + id: event.id + }, + { + $set: { + ...event, + updatedAt: now + } + } + ) + + return getEventById(event.id) +}