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: introduce trades endpoint to recreate MV #261

Merged
merged 12 commits into from
Mar 10, 2025

Conversation

juanmahidalgo
Copy link
Collaborator

Add Endpoint to Recreate Trades Materialized View

Overview

This PR adds a new protected endpoint that allows administrators to manually trigger the recreation of the trades materialized view. This provides an on-demand way to refresh the view without requiring a database migration or server restart.

Changes

  • Added a new endpoint: POST /v1/trades/materialized-view/recreate
  • Implemented API token authentication to protect the endpoint
  • Created a dedicated middleware for trades view authentication
  • Added unit tests for the new handler

Technical Details

  • The endpoint is protected by an API token that must be provided in the x-api-token header
  • The token value is configured via the TRADES_API_TOKEN environment variable
  • All operations are executed within a transaction to ensure data consistency
  • The endpoint returns appropriate status codes:
    • 200 OK when the view is successfully recreated
    • 401 Unauthorized when the API token is invalid
    • 500 Internal Server Error when an error occurs during recreation

Why

The trades materialized view occasionally needs to be refreshed to ensure data consistency, especially after a squid promotion. Previously, this required a migration or manual database operation. This endpoint provides a safer, more controlled way to perform this operation through the API.

Testing

  • Unit tests verify both successful recreation and error handling
  • The endpoint has been manually tested in development environment

Copy link
Contributor

@LautaroPetaccio LautaroPetaccio left a comment

Choose a reason for hiding this comment

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

Great job! This will surely improve a lot our querying times 🚀

const isValid = await validateApiToken(context, apiTokenHeaderName)

if (!isValid) {
console.log('[tradesViewAuthMiddleware] Invalid token, returning unauthorized')
Copy link
Contributor

Choose a reason for hiding this comment

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

Would you mind changing the console.log here for a logger? Logs made with the logger can be traced using the tracer component.

Suggested change
console.log('[tradesViewAuthMiddleware] Invalid token, returning unauthorized')
console.log('[tradesViewAuthMiddleware] Invalid token, returning unauthorized')

Comment on lines 350 to 403
describe('recreateTradesMaterializedViewHandler', () => {
it('should recreate the materialized view successfully', async () => {
const recreateMaterializedViewMock = jest.fn().mockResolvedValue(undefined)
const mockTrades = {
recreateMaterializedView: recreateMaterializedViewMock
}

const context = {
components: {
trades: mockTrades
}
}

const result = await recreateTradesMaterializedViewHandler(
context as unknown as Pick<HandlerContextWithPath<'trades', '/v1/trades/materialized-view/recreate'>, 'components'>
)

expect(result).toEqual({
status: StatusCode.OK,
body: {
ok: true,
message: 'Materialized view recreated successfully'
}
})
expect(recreateMaterializedViewMock).toHaveBeenCalledTimes(1)
})

it('should handle errors when recreating the materialized view', async () => {
const error = new Error('Test error')
const recreateMaterializedViewMock = jest.fn().mockRejectedValue(error)
const mockTrades = {
recreateMaterializedView: recreateMaterializedViewMock
}

const context = {
components: {
trades: mockTrades
}
}

const result = await recreateTradesMaterializedViewHandler(
context as unknown as Pick<HandlerContextWithPath<'trades', '/v1/trades/materialized-view/recreate'>, 'components'>
)

expect(result).toEqual({
status: StatusCode.INTERNAL_SERVER_ERROR,
body: {
ok: false,
message: 'Test error'
}
})
expect(recreateMaterializedViewMock).toHaveBeenCalledTimes(1)
})
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Would you mind re-writting the tests to fit our standard?
Here's an example on how they should look:

Suggested change
describe('recreateTradesMaterializedViewHandler', () => {
it('should recreate the materialized view successfully', async () => {
const recreateMaterializedViewMock = jest.fn().mockResolvedValue(undefined)
const mockTrades = {
recreateMaterializedView: recreateMaterializedViewMock
}
const context = {
components: {
trades: mockTrades
}
}
const result = await recreateTradesMaterializedViewHandler(
context as unknown as Pick<HandlerContextWithPath<'trades', '/v1/trades/materialized-view/recreate'>, 'components'>
)
expect(result).toEqual({
status: StatusCode.OK,
body: {
ok: true,
message: 'Materialized view recreated successfully'
}
})
expect(recreateMaterializedViewMock).toHaveBeenCalledTimes(1)
})
it('should handle errors when recreating the materialized view', async () => {
const error = new Error('Test error')
const recreateMaterializedViewMock = jest.fn().mockRejectedValue(error)
const mockTrades = {
recreateMaterializedView: recreateMaterializedViewMock
}
const context = {
components: {
trades: mockTrades
}
}
const result = await recreateTradesMaterializedViewHandler(
context as unknown as Pick<HandlerContextWithPath<'trades', '/v1/trades/materialized-view/recreate'>, 'components'>
)
expect(result).toEqual({
status: StatusCode.INTERNAL_SERVER_ERROR,
body: {
ok: false,
message: 'Test error'
}
})
expect(recreateMaterializedViewMock).toHaveBeenCalledTimes(1)
})
})
describe('when handling a materialized view recreation request', () => {
let context: Pick<HandlerContextWithPath<'trades', '/v1/trades/materialized-view/recreate'>, 'components'>
beforeEach(() => {
context = {
components: {
trades: {
recreateMaterializedView: () => undefined
}
}
}
})
describe('and the recreation is successful', () => {
beforeEach(() => {
context.components.trades.recreateMaterializedView = jest.fn().mockResolvedValue(undefined)
})
it('should respond with a 200 status code', async () => {
const result = await recreateTradesMaterializedViewHandler(context)
expect(result).toEqual({
status: StatusCode.OK,
body: {
ok: true,
message: 'Materialized view recreated successfully'
}
})
expect(recreateMaterializedViewMock).toHaveBeenCalledTimes(1)
})
describe('and the recreation fails', () => {
beforeEach(() => {
context.components.trades.recreateMaterializedView = jest.fn().mockRejectedValue(new Error('Test error'))
})
it('should respond with a 500 status code', async () => {
const result = await recreateTradesMaterializedViewHandler(context)
expect(result).toEqual({
status: StatusCode.INTERNAL_SERVER_ERROR,
body: {
ok: false,
message: 'Test error'
}
})
expect(recreateMaterializedViewMock).toHaveBeenCalledTimes(1)
})
})
})

@coveralls
Copy link

coveralls commented Mar 6, 2025

Pull Request Test Coverage Report for Build 13762414085

Details

  • 19 of 55 (34.55%) changed or added relevant lines in 6 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.3%) to 52.188%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/controllers/routes.ts 2 3 66.67%
src/ports/trades/component.ts 1 2 50.0%
src/logic/http/auth.ts 3 16 18.75%
src/logic/trades/materialized-view.ts 5 26 19.23%
Totals Coverage Status
Change from base Build 13547940360: -0.3%
Covered Lines: 1758
Relevant Lines: 3010

💛 - Coveralls

@juanmahidalgo juanmahidalgo merged commit 745028b into main Mar 10, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants