Skip to content

Commit

Permalink
feat: implement getLogs
Browse files Browse the repository at this point in the history
  • Loading branch information
ErnoW committed Mar 20, 2023
1 parent 55af50d commit 121a903
Show file tree
Hide file tree
Showing 10 changed files with 264 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .changeset/eleven-suns-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@moralisweb3/common-streams-utils': minor
'@moralisweb3/streams': minor
---

Implement getLogs endpoint at `Streams.getLogs`
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import MoralisCore from '@moralisweb3/common-core';
import { getLogsOperation, GetLogsRequest } from './getLogsOperation';

describe('getLogsOperation', () => {
let core: MoralisCore;

beforeAll(() => {
core = MoralisCore.create();
});

it('serializeRequest() serializes correctly and deserializeRequest() deserializes correctly', () => {
const request: Required<GetLogsRequest> = {
limit: 100,
cursor: 'CURSOR1',
};

const serializedRequest = getLogsOperation.serializeRequest(request, core);

expect(serializedRequest.limit).toBe(request.limit);
expect(serializedRequest.cursor).toBe(request.cursor);

const deserializedRequest = getLogsOperation.deserializeRequest(serializedRequest, core);

expect(deserializedRequest.limit).toBe(request.limit);
expect(deserializedRequest.cursor).toBe(request.cursor);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Camelize, maybe, PaginatedOperation } from '@moralisweb3/common-core';
import { EvmChain } from '@moralisweb3/common-evm-utils';
import { operations } from '../openapi';

type OperationId = 'GetLogs';

type QueryParams = operations[OperationId]['parameters']['query'];
type RequestParams = QueryParams;

type SuccessResponse = operations[OperationId]['responses']['200']['content']['application/json'];

// Exports

export interface GetLogsRequest extends Camelize<RequestParams> {}

export type GetLogsJSONRequest = ReturnType<typeof serializeRequest>;

export type GetLogsJSONResponse = SuccessResponse;

export type GetLogsResponse = ReturnType<typeof deserializeResponse>;

export const getLogsOperation: PaginatedOperation<
GetLogsRequest,
GetLogsJSONRequest,
GetLogsResponse,
GetLogsJSONResponse['result']
> = {
method: 'GET',
name: 'getLogs',
id: 'GetLogs',
groupName: 'history',
urlPathPattern: '/history/logs',
urlSearchParamNames: ['limit', 'cursor'],
firstPageIndex: 0,

getRequestUrlParams,
deserializeResponse,
serializeRequest,
deserializeRequest,
};

// Methods

function getRequestUrlParams(request: GetLogsRequest) {
return {
limit: maybe(request.limit, String),
cursor: request.cursor,
};
}

function deserializeResponse(jsonResponse: GetLogsJSONResponse) {
return (jsonResponse.result ?? []).map((result) => ({
...result,
chain: EvmChain.create(result.chain),
}));
}

function serializeRequest(request: GetLogsRequest) {
return request;
}

function deserializeRequest(jsonRequest: GetLogsJSONRequest) {
return jsonRequest;
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './getHistoryOperation';
export * from './replayHistoryOperation';
export * from './getLogsOperation';
45 changes: 45 additions & 0 deletions packages/common/streamsUtils/src/operations/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export interface paths {
/** Replay a specific history. */
post: operations["ReplayHistory"];
};
"/history/logs": {
/** get all failed logs */
get: operations["GetLogs"];
};
"/settings": {
/** Get the settings for the current project based on the project api-key. */
get: operations["GetSettings"];
Expand Down Expand Up @@ -296,6 +300,30 @@ export interface components {
* See [RFC 4112](https://tools.ietf.org/html/rfc4122)
*/
"historyTypes.UUID": string;
IWebhookDeliveryLogsModel: {
id: components["schemas"]["UUID"];
streamId: string;
chain: string;
webhookUrl: string;
tag: string;
/** Format: double */
retries: number;
/** @enum {string} */
deliveryStatus: "failed" | "success";
/** Format: double */
blockNumber: number;
errorMessage: string;
/** @enum {string} */
type: "evm" | "aptos";
/** Format: date-time */
createdAt: string;
};
"historyTypes.IWebhookDeliveryLogsResponse": {
result: components["schemas"]["IWebhookDeliveryLogsModel"][];
cursor?: string;
/** Format: double */
total: number;
};
/** @enum {string} */
SettingsRegion:
| "us-east-1"
Expand Down Expand Up @@ -716,6 +744,23 @@ export interface operations {
};
};
};
/** get all failed logs */
GetLogs: {
parameters: {
query: {
limit: number;
cursor?: string;
};
};
responses: {
/** Ok */
200: {
content: {
"application/json": components["schemas"]["historyTypes.IWebhookDeliveryLogsResponse"];
};
};
};
};
/** Get the settings for the current project based on the project api-key. */
GetSettings: {
parameters: {};
Expand Down
35 changes: 35 additions & 0 deletions packages/streams/integration/mocks/endpoints/getLogs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MockScenarios } from '@moralisweb3/test-utils';
import { createPaginatedWebhookLogResponse } from '../response/webhookLogResponse';

export const mockGetLogs = MockScenarios.create(
{
method: 'get',
name: 'mockGetLogs',
url: `/history/logs`,
getParams: ({ req }) => ({
limit: req.url.searchParams.get('limit'),
cursor: req.url.searchParams.get('cursor'),
}),
},
[
{
condition: {
limit: '20',
},
response: createPaginatedWebhookLogResponse(Array(20).fill('logs-test-id-1'), 20),
},
{
condition: {
limit: '5',
},
response: createPaginatedWebhookLogResponse(Array(5).fill('logs-test-id-2'), 20, 'cursor'),
},
{
condition: {
limit: '5',
cursor: 'cursor',
},
response: createPaginatedWebhookLogResponse(Array(5).fill('logs-test-id-3'), 20, 'cursor-2'),
},
],
);
2 changes: 2 additions & 0 deletions packages/streams/integration/mocks/mockServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { mockDeleteAddressEvm } from './endpoints/evm/deleteAddress';
import { mockDeleteStreamEvm } from './endpoints/evm/deleteStream';
import { mockGetAddressesEvm } from './endpoints/evm/getAddresses';
import { mockGetHistory } from './endpoints/getHistory';
import { mockGetLogs } from './endpoints/getLogs';
import { mockGetSettings } from './endpoints/getSettings';
import { mockGetStats } from './endpoints/getStats';
import { mockGetStatsById } from './endpoints/getStatsById';
Expand Down Expand Up @@ -33,6 +34,7 @@ export const mockServer = MockServer.create({ apiKey: MOCK_API_KEY, apiRoot: STR
mockUpdateStreamEvm,
mockReplayHistory,
mockGetHistory,
mockGetLogs,
mockGetStats,
mockGetStatsById,
mockGetAddressesAptos,
Expand Down
25 changes: 25 additions & 0 deletions packages/streams/integration/mocks/response/webhookLogResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const defaultWebhookLogResponse = {
streamId: 'aa1d4d4f-549a-4235-a97d-367e7909f657',
chain: '0x1',
webhookUrl: 'https://webhook.site/9b9a8773-2129-431d-983a-f32c89854ee7',
tag: 'demo',
retries: 0,
deliveryStatus: 'failed',
blockNumber: 16866964,
errorMessage: 'Request failed with status code 500',
type: 'evm',
createdAt: '2023-03-20T06:03:27.060Z',
updatedAt: '2023-03-20T06:03:27.060Z',
id: 'e5578320-44a1-47c3-95d5-0ae6874157cf',
};

export const createWebhookLogResponse = (id: string) => ({
...defaultWebhookLogResponse,
id,
});

export const createPaginatedWebhookLogResponse = (ids: string[], total: number, cursor?: string) => ({
result: ids.map(createWebhookLogResponse),
total,
cursor,
});
57 changes: 57 additions & 0 deletions packages/streams/integration/test/getLogs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Streams } from '../../src/Streams';
import { cleanStreamsApi, setupStreamApi } from '../setup';

describe('getLogs', () => {
let StreamApi: Streams;

beforeAll(() => {
StreamApi = setupStreamApi();
});

afterAll(() => {
cleanStreamsApi();
});

it('should get all logs', async () => {
const result = await StreamApi.getLogs({
limit: 20,
});

expect(result).toBeDefined();
expect(result.result).toBeDefined();
expect(result.pagination.total).toEqual(20);
expect(result.result.length).toEqual(20);
expect(result.result[0].id).toEqual('logs-test-id-1');
});

it('should get a cursor', async () => {
const result = await StreamApi.getLogs({
limit: 5,
});

expect(result).toBeDefined();
expect(result.result).toBeDefined();
expect(result.pagination.total).toEqual(20);
expect(result.result.length).toEqual(5);
expect(result.result[0].id).toEqual('logs-test-id-2');
expect(result.pagination.cursor).toEqual('cursor');
expect(result.next).toBeDefined();
expect(result.hasNext).toBeTruthy();
});

it('should use a provided cursor', async () => {
const result = await StreamApi.getLogs({
limit: 5,
cursor: 'cursor',
});

expect(result).toBeDefined();
expect(result.result).toBeDefined();
expect(result.pagination.total).toEqual(20);
expect(result.result.length).toEqual(5);
expect(result.result[0].id).toEqual('logs-test-id-3');
expect(result.pagination.cursor).toEqual('cursor-2');
expect(result.next).toBeDefined();
expect(result.hasNext).toBeTruthy();
});
});
2 changes: 2 additions & 0 deletions packages/streams/src/Streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
getStatsOperation,
setSettingsOperation,
replayHistoryOperation,
getLogsOperation,
} from '@moralisweb3/common-streams-utils';

const BASE_URL = 'https://api.moralis-streams.com';
Expand Down Expand Up @@ -61,6 +62,7 @@ export class Streams extends ApiModule {
public readonly deleteAddress = makeDeleteAddress(this.core, BASE_URL);

public readonly getHistory = this.createPaginatedFetcher(getHistoryOperation);
public readonly getLogs = this.createPaginatedFetcher(getLogsOperation);
public readonly retry = this.createFetcher(replayHistoryOperation);

private readonly _getStats = this.createFetcher(getStatsOperation);
Expand Down

0 comments on commit 121a903

Please sign in to comment.