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/streams api #658

Merged
merged 12 commits into from
Sep 8, 2022
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"moralis",
"@moralisweb3/core",
"@moralisweb3/auth",
"@moralisweb3/streams",
"@moralisweb3/api-utils",
"@moralisweb3/evm-utils",
"@moralisweb3/sol-utils",
Expand Down
8 changes: 8 additions & 0 deletions .changeset/quiet-peas-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@moralisweb3/api-utils': minor
'@moralisweb3/core': minor
'moralis': minor
'@moralisweb3/streams': minor
---

Intergrating stream API in code base, creating a new package @moralisweb3/streams
2 changes: 1 addition & 1 deletion packages/apiUtils/src/resolvers/Endpoint.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MoralisCore } from '@moralisweb3/core';

export type EndpointMethod = 'get' | 'post' | 'put';
export type EndpointMethod = 'get' | 'post' | 'put' | 'delete';

export enum EndpointBodyType {
PROPERTY = 'property',
Expand Down
15 changes: 15 additions & 0 deletions packages/apiUtils/src/resolvers/EndpointResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ export class EndpointResolver<ApiParams, Params, ApiResult, AdaptedResult, JSONR
return new ApiResultAdapter(result, this.endpoint.apiToResult, this.endpoint.resultToJson, params);
};

private delete = async (params: Params) => {
const url = this.createUrl(params);
const apiParams = this.endpoint.parseParams(params);

const searchParams = this.paramsReader.getSearchParams(apiParams);

const result = await this.requestController.delete<ApiResult>(url, searchParams, {
headers: this.createHeaders(),
});

return new ApiResultAdapter(result, this.endpoint.apiToResult, this.endpoint.resultToJson, params);
};

private createUrl(params: Params): string {
return this.baseUrl + this.endpoint.getUrl(params);
}
Expand Down Expand Up @@ -97,6 +110,8 @@ export class EndpointResolver<ApiParams, Params, ApiResult, AdaptedResult, JSONR
return this.post(params);
case 'put':
return this.put(params);
case 'delete':
return this.delete(params);
default:
return this.get(params);
}
Expand Down
1 change: 1 addition & 0 deletions packages/apiUtils/src/resolvers/PaginatedEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface PaginatedResult<ApiResult> {
page_size: number;
cursor: string;
result: ApiResult;
data: ApiResult;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should request the API squad to normalize this property? Here should be also result as everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, to have consistency across Moralis Apis. @ErnoW what do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the best option. Otherwise we should modify the PaginatedEndpoint interface. The current state is wrong. We don't have result and data in the same type as the definition says.

}

export interface PaginatedEndpoint<ApiParams, Params extends PaginatedParams, ApiResult, AdaptedResult, JSONResult>
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/controllers/RequestController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,19 @@ export class RequestController {
signal: abortSignal,
});
}

public async delete<Response>(
url: string,
searchParams?: Record<string, unknown>,
options?: RequestOptions,
abortSignal?: AbortController['signal'],
): Promise<Response> {
return this.request<unknown, Response>({
url,
params: searchParams,
method: 'DELETE',
headers: options?.headers,
signal: abortSignal,
});
}
}
1 change: 1 addition & 0 deletions packages/integration/mockRequests/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const MOCK_API_KEY = 'test-api-key';
export const EVM_API_ROOT = 'https://deep-index.moralis.io/api/v2';
export const SOL_API_ROOT = 'https://solana-gateway.moralis.io';
export const STREAM_API_ROOT = 'https://streams-api.aws-prod-streams-master-1.moralis.io';
19 changes: 19 additions & 0 deletions packages/integration/mockRequests/mockRequestsStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { setupServer } from 'msw/node';

import { mockCreateStream } from './streamApi/createStream';
import { mockDeleteStream } from './streamApi/deleteStream';
import { mockGetStreams } from './streamApi/getStreams';
import { mockUpdateStream } from './streamApi/updateStream';
import { mockSetSettings } from './streamApi/setSettings';
import { mockGetSettings } from './streamApi/getSettings';

const handlers = [
mockCreateStream,
mockGetStreams,
mockUpdateStream,
mockDeleteStream,
mockSetSettings,
mockGetSettings,
];

export const mockServer = setupServer(...handlers);
23 changes: 23 additions & 0 deletions packages/integration/mockRequests/streamApi/createStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { rest } from 'msw';
import { STREAM_API_ROOT, MOCK_API_KEY } from '../config';

export const mockCreateStreamOutput: Record<string, string> = {
address: '0x992eCcC191D6F74E8Be187ed6B6AC196b08314f7',
chainId: '0x3',
};

export const mockCreateStream = rest.put(`${STREAM_API_ROOT}/streams`, (req, res, ctx) => {
const apiKey = req.headers.get('x-api-key');

if (apiKey !== MOCK_API_KEY) {
return res(ctx.status(401));
}

const value = mockCreateStreamOutput;

if (!value) {
return res(ctx.status(404));
}

return res(ctx.status(200), ctx.json(value));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should create mocks based on a real API response. Check this file:

That allows us to create better integration tests:

});
28 changes: 28 additions & 0 deletions packages/integration/mockRequests/streamApi/deleteStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { rest } from 'msw';
import { STREAM_API_ROOT, MOCK_API_KEY } from '../config';

export const mockDeleteStreamOutput: Record<string, string> = {
'3fa85f64-5717-4562-b3fc-2c963f66afa6': '0x3',
};

export const mockDeleteStream = rest.delete(`${STREAM_API_ROOT}/streams/:id`, (req, res, ctx) => {
const id = req.params.id as string;
const apiKey = req.headers.get('x-api-key');

if (apiKey !== MOCK_API_KEY) {
return res(ctx.status(401));
}

const value = mockDeleteStreamOutput[id];

if (!value) {
return res(ctx.status(404));
}

return res(
ctx.status(200),
ctx.json({
chainId: value,
}),
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a real response mock.

});
23 changes: 23 additions & 0 deletions packages/integration/mockRequests/streamApi/getSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { rest } from 'msw';
import { STREAM_API_ROOT, MOCK_API_KEY } from '../config';

export const mockGetSettingsOutput: Record<string, string> = {
secretKey: 'top_secret',
region: 'us-east-1',
};

export const mockGetSettings = rest.get(`${STREAM_API_ROOT}/settings`, (req, res, ctx) => {
const apiKey = req.headers.get('x-api-key');

if (apiKey !== MOCK_API_KEY) {
return res(ctx.status(401));
}

const value = mockGetSettingsOutput;

if (!value) {
return res(ctx.status(404));
}

return res(ctx.status(200), ctx.json(value));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a real response mock.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
  "secretKey": "top_secret",
  "region": "us-east-1"
}

is a real response for this endpoint

Copy link
Collaborator

@b4rtaz b4rtaz Sep 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see.

 const value = mockGetSettingsOutput;

  if (!value) {

This condition is always false.

});
25 changes: 25 additions & 0 deletions packages/integration/mockRequests/streamApi/getStreams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { rest } from 'msw';
import { STREAM_API_ROOT, MOCK_API_KEY } from '../config';

export const mockGetStreamsOutput = 20;

export const mockGetStreams = rest.get(`${STREAM_API_ROOT}/streams`, (req, res, ctx) => {
const apiKey = req.headers.get('x-api-key');

if (apiKey !== MOCK_API_KEY) {
return res(ctx.status(401));
}

const value = mockGetStreamsOutput;

if (!value) {
return res(ctx.status(404));
}

return res(
ctx.status(200),
ctx.json({
total: value,
}),
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a real response mock.

});
20 changes: 20 additions & 0 deletions packages/integration/mockRequests/streamApi/setSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { rest } from 'msw';
import { STREAM_API_ROOT, MOCK_API_KEY } from '../config';

export const mockSetSettingsOutput: Record<string, string> = {};

export const mockSetSettings = rest.post(`${STREAM_API_ROOT}/settings`, (req, res, ctx) => {
const apiKey = req.headers.get('x-api-key');

if (apiKey !== MOCK_API_KEY) {
return res(ctx.status(401));
}

const value = mockSetSettingsOutput;

if (!value) {
return res(ctx.status(404));
}

return res(ctx.status(200), ctx.json(value));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a real response mock.

});
28 changes: 28 additions & 0 deletions packages/integration/mockRequests/streamApi/updateStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { rest } from 'msw';
import { STREAM_API_ROOT, MOCK_API_KEY } from '../config';

export const mockUpdateStreamOutput: Record<string, string> = {
'3fa85f64-5717-4562-b3fc-2c963f66afa6': '0x3',
};

export const mockUpdateStream = rest.post(`${STREAM_API_ROOT}/streams/:id`, (req, res, ctx) => {
const id = req.params.id as string;
const apiKey = req.headers.get('x-api-key');

if (apiKey !== MOCK_API_KEY) {
return res(ctx.status(401));
}

const value = mockUpdateStreamOutput[id];

if (!value) {
return res(ctx.status(404));
}

return res(
ctx.status(200),
ctx.json({
chainId: value,
}),
);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a real response mock.

});
6 changes: 3 additions & 3 deletions packages/integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
"typescript": "^4.5.5"
},
"dependencies": {
"@moralisweb3/core": "^2.3.1",
"@moralisweb3/evm-api": "^2.3.1",
"@moralisweb3/evm-utils": "^2.3.1",
"@moralisweb3/core": "^2.2.0",
"@moralisweb3/evm-api": "^2.2.0",
"@moralisweb3/evm-utils": "^2.2.0",
"eventemitter3": "^4.0.7"
}
}
58 changes: 58 additions & 0 deletions packages/integration/test/streamApi/createStream.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { MoralisStreams } from '@moralisweb3/streams';
import { cleanStreamsApi, setupStreamApi } from './setup';

describe('Create stream', () => {
let StreamApi: MoralisStreams;

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

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

it('should create a stream ', async () => {
const result = await StreamApi.add({
chainId: '0x3',
address: '0x992eCcC191D6F74E8Be187ed6B6AC196b08314f7',
tag: 'test',
description: 'test',
type: 'tx',
webhookUrl: 'https://webhook.site/4f1b1b1b-1b1b-4f1b-1b1b-1b1b1b1b1b1b',
});

expect(result).toBeDefined();
expect(result).toEqual(expect.objectContaining({}));
expect(result.result.chainId).toEqual('0x3');
});

it('should not create stream', async () => {
const failedResult = await StreamApi.add({
chainId: 'invalid_chain',
address: '0x992eCcC191D6F74E8Be187ed6B6AC196b08314f7',
tag: 'test',
description: 'test',
type: 'tx',
webhookUrl: 'https://webhook.site/4f1b1b1b-1b1b-4f1b-1b1b-1b1b1b1b1b1b',
})
.then()
.catch((err: any) => {
return err;
});

expect(failedResult).toBeDefined();
expect(
StreamApi.add({
chainId: 'invalid_chain',
address: '0x992eCcC191D6F74E8Be187ed6B6AC196b08314f7',
tag: 'test',
description: 'test',
type: 'tx',
webhookUrl: 'https://webhook.site/4f1b1b1b-1b1b-4f1b-1b1b-1b1b1b1b1b1b',
}),
).rejects.toThrowErrorMatchingInlineSnapshot(
`"[C0005] Invalid provided chain, value must be a positive number, or a hex-string starting with '0x'"`,
);
});
});
24 changes: 24 additions & 0 deletions packages/integration/test/streamApi/deleteStream.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MoralisStreams } from '@moralisweb3/streams';
import { cleanStreamsApi, setupStreamApi } from './setup';

describe('Delete stream', () => {
let StreamApi: MoralisStreams;

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

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

it('should delete a stream ', async () => {
const result = await StreamApi.delete({
id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
});

expect(result).toBeDefined();
expect(result).toEqual(expect.objectContaining({}));
expect(result.result.chainId).toEqual('0x3');
});
});
22 changes: 22 additions & 0 deletions packages/integration/test/streamApi/getSettings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { MoralisStreams } from '@moralisweb3/streams';
import { cleanStreamsApi, setupStreamApi } from './setup';

describe('Get settings', () => {
let StreamApi: MoralisStreams;

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

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

it('should get stream settings ', async () => {
const result = await StreamApi.readSettings();

expect(result).toBeDefined();
expect(result).toEqual(expect.objectContaining({}));
expect(result.result.region).toEqual('us-east-1');
});
});
Loading