Skip to content

Commit

Permalink
feat(hasura): scheduled event support
Browse files Browse the repository at this point in the history
fix #175
  • Loading branch information
GavinRay97 authored and WonderPanda committed Sep 2, 2020
1 parent 8aef962 commit d677854
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 6 deletions.
8 changes: 8 additions & 0 deletions packages/hasura/src/hasura.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ export interface HasuraEventHandlerConfig {
triggerName?: string;
}

export interface HasuraScheduledEventPayload<T = Record<string, any>> {
scheduled_time: Date;
payload: T;
name: string;
created_at: Date;
id: string;
}

export interface HasuraModuleConfig {
secretHeader: string;
secretFactory: (() => string) | string;
Expand Down
26 changes: 21 additions & 5 deletions packages/hasura/src/hasura.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,19 @@ import {
HasuraEvent,
HasuraEventHandlerConfig,
HasuraModuleConfig,
HasuraScheduledEventPayload,
} from './hasura.interfaces';

function isHasuraEvent(value: any): value is HasuraEvent {
return ['trigger', 'table', 'event'].every((it) => it in value);
}

function isHasuraScheduledEventPayload(
value: any
): value is HasuraScheduledEventPayload {
return ['name', 'scheduled_time', 'payload'].every((it) => it in value);
}

@Module({
imports: [DiscoveryModule],
controllers: [EventHandlerController],
Expand Down Expand Up @@ -118,11 +129,16 @@ export class HasuraModule

const eventHandlerService = eventHandlerServiceInstance as EventHandlerService;

const handleEvent = (evt: Partial<HasuraEvent>) => {
const keys = [
evt.trigger?.name,
`${evt?.table?.schema}-${evt?.table?.name}`,
];
const handleEvent = (
evt: Partial<HasuraEvent> | HasuraScheduledEventPayload
) => {
const keys = isHasuraEvent(evt)
? [evt.trigger?.name, `${evt?.table?.schema}-${evt?.table?.name}`]
: isHasuraScheduledEventPayload(evt)
? [evt.name]
: null;
if (!keys) throw new Error('Not a Hasura Event');

// TODO: this should use a map for faster lookups
const handlers = eventHandlers.filter((x) => keys.includes(x.key));

Expand Down
39 changes: 38 additions & 1 deletion packages/hasura/src/tests/hasura.module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { HasuraEventHandler } from '../hasura.decorators';
import { HasuraModule } from '../hasura.module';
import { HasuraModuleConfig } from '../hasura.interfaces';
import {
HasuraModuleConfig,
HasuraScheduledEventPayload,
} from '../hasura.interfaces';
import { pick } from 'lodash';

const tableBoundEventHandler = jest.fn();
const triggerBoundEventHandler = jest.fn();
const scheduledEventHandler = jest.fn();
const triggerName = 'user_created';
const scheduled_trigger = 'scheduled_trigger';
const defaultHasuraEndpoint = '/hasura/events';

@Injectable()
Expand All @@ -25,6 +31,13 @@ class UserEventService {
handleUserCreatedTrigger(evt) {
triggerBoundEventHandler(evt);
}

@HasuraEventHandler({
triggerName: scheduled_trigger,
})
handleScheduledEvent(evt) {
scheduledEventHandler(evt);
}
}

const secretHeader = 'api-secret-header';
Expand All @@ -49,6 +62,14 @@ const eventPayloadMissingTableAndTrigger = {
trigger: { name: 'unbound_trigger' },
};

const scheduledEventPayload: HasuraScheduledEventPayload = {
name: scheduled_trigger,
created_at: new Date(),
id: 'id',
scheduled_time: new Date(),
payload: {},
};

type ModuleType = 'forRoot' | 'forRootAsync';
const cases: [ModuleType, string | undefined][] = [
['forRoot', undefined],
Expand Down Expand Up @@ -92,6 +113,7 @@ describe.each(cases)(
afterEach(() => {
tableBoundEventHandler.mockReset();
triggerBoundEventHandler.mockReset();
scheduledEventHandler.mockReset();
});

it('should return forbidden if the secret api header is missing', () => {
Expand Down Expand Up @@ -129,5 +151,20 @@ describe.each(cases)(
expect(triggerBoundEventHandler).toHaveBeenCalledTimes(1);
expect(triggerBoundEventHandler).toHaveBeenCalledWith(eventPayload);
});

it('should pass the scheduled event payload to the correct handler', async () => {
const response = await request(app.getHttpServer())
.post(hasuraEndpoint)
.set(secretHeader, secret)
.send(scheduledEventPayload);

expect(response.status).toEqual(202);
expect(scheduledEventHandler).toHaveBeenCalledTimes(1);
expect(scheduledEventHandler).toHaveBeenCalledWith(
expect.objectContaining(
pick(scheduledEventPayload, ['name', 'payload'])
)
);
});
}
);

0 comments on commit d677854

Please sign in to comment.