-
Notifications
You must be signed in to change notification settings - Fork 20
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(backend): get_historical_balance endpoint #54
Changes from 9 commits
1b80451
f08465a
5de234b
4afeacf
fe33f97
7b79001
bb00d02
e5091f3
d4240d7
9144f9d
fb7471e
9a00423
f185833
0c3e83e
7f6bbf3
bbe66e5
eaf1cf2
1d63f03
6d23f65
27989e6
db390b8
0762e04
bda917a
ca39d2e
b0daa92
89d75af
1615454
ee61dbe
48e2e2a
f18f462
3a2a790
ae87aab
6c4a5ca
f6144d0
0619a69
8ac503e
73856c2
c3232eb
89952ff
2cb6d13
c9e8390
2438634
e9e8abc
06ec9fb
4702fae
eb03992
63b4061
a408ff3
80f0fba
29e8a0b
6da1f66
58e502c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { sql } from 'drizzle-orm'; | ||
import type { FastifyInstance } from 'fastify'; | ||
import { usdcBalance } from '../db/schema'; | ||
|
||
const addressRegex = /^0x0[0-9a-fA-F]{63}$/; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move this in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What am I suppose to move here. I don't quite get it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. move the regex in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright got it! |
||
|
||
export function getHistoricalBalanceRoute(fastify: FastifyInstance) { | ||
fastify.get('/get_historical_balance', async (request, reply) => { | ||
const { address } = request.query as { address?: string }; | ||
|
||
if (!address) { | ||
return reply.status(400).send({ error: 'Address is required' }); | ||
} | ||
|
||
// Validate address format | ||
if (!addressRegex.test(address)) { | ||
return reply.status(400).send({ error: 'Invalid address format' }); | ||
} | ||
|
||
try { | ||
const historicalBalances = await fastify.db.execute( | ||
sql` | ||
SELECT balance, DATE(block_timestamp) AS date | ||
FROM ${usdcBalance} | ||
WHERE (address, block_timestamp) IN ( | ||
SELECT address, MAX(block_timestamp) AS max_timestamp | ||
FROM ${usdcBalance} | ||
WHERE address = ${address} | ||
GROUP BY address, DATE(block_timestamp) | ||
) | ||
`, | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't add the query as raw sql use the ts functions There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also we want the last balance per day. If I had 100 usdc, i send 40 at 10 am then i receive 20 at 11pm my balance for that day would be 80 (lmk if it's not clear) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and we want that for the last 30 days There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay. I assumed each entry in the database has a timestamp and corresponding balance. So I grouped the rows by address and date then extracted the entry with the latest timestamp per day assuming the entry already has the latest balance for that day. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah then this is correct, just rewrite this using the typescript functions plz There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright |
||
|
||
if (!historicalBalances) { | ||
return reply.status(404).send({ error: 'Error while retrieving historical balance' }); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this can happen, or if it can can you please add a test case for this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright. Thought as much |
||
|
||
return reply.send({ | ||
historicalBalances, | ||
}); | ||
} catch (error) { | ||
console.error(error); | ||
return reply.status(500).send({ error: 'Internal server error' }); | ||
} | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import { PostgreSqlContainer, type StartedPostgreSqlContainer } from '@testcontainers/postgresql'; | ||
import type { FastifyInstance } from 'fastify'; | ||
import { afterAll, beforeAll, describe, expect, test } from 'vitest'; | ||
|
||
import { buildApp } from '@/app'; | ||
import * as schema from '../db/schema'; | ||
|
||
describe('GET /get_historical_balance route', () => { | ||
let container: StartedPostgreSqlContainer; | ||
let app: FastifyInstance; | ||
const testAddress = '0x064a24243f2aabae8d2148fa878276e6e6e452e3941b417f3c33b1649ea83e11'; | ||
|
||
beforeAll(async () => { | ||
container = await new PostgreSqlContainer().start(); | ||
const connectionUri = container.getConnectionUri(); | ||
app = buildApp({ | ||
database: { | ||
connectionString: connectionUri, | ||
}, | ||
app: { | ||
port: 8080, | ||
}, | ||
}); | ||
|
||
await app.ready(); | ||
await app.db.insert(schema.usdcBalance).values([ | ||
{ | ||
balanceId: 'YfuanNvdfMfcmmdeklWDJO', | ||
address: testAddress, | ||
blockTimestamp: new Date('2024-04-10 13:03:05'), | ||
balance: '3.8AE83A109D0635A426BB', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't refer to the spec for the balance it's actually not hex but regular base 10 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the numbers should be formatted like that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright |
||
}, | ||
{ | ||
balanceId: 'jNEhskffmfscvdmdnQMrsz', | ||
address: testAddress, | ||
blockTimestamp: new Date('2024-04-10 14:03:05'), | ||
balance: '3.8AE83A109D0635A426BB', | ||
}, | ||
{ | ||
balanceId: 'ZkNhudkvfcfKdkgmYfkRj', | ||
address: testAddress, | ||
blockTimestamp: new Date('2024-04-11 13:03:05'), | ||
balance: '3.8AE83A109D0635A426BB', | ||
}, | ||
{ | ||
balanceId: 'ObZldfvkfgckmflKpXvrd', | ||
address: testAddress, | ||
blockTimestamp: new Date('2024-04-11 14:03:05'), | ||
balance: '3.8AE83A109D0635A426BB', | ||
}, | ||
]); | ||
}); | ||
|
||
afterAll(async () => { | ||
await app.close(); | ||
await container.stop(); | ||
}); | ||
|
||
test('should return the historical balances for a valid address', async () => { | ||
const response = await app.inject({ | ||
method: 'GET', | ||
url: `/get_historical_balance?address=${testAddress}`, | ||
}); | ||
|
||
expect(response.statusCode).toBe(200); | ||
expect(response.json()).toEqual({ | ||
historicalBalances: [ | ||
{ | ||
date: '2024-04-10', | ||
balance: '3.8AE83A109D0635A426BB', | ||
}, | ||
{ | ||
date: '2024-04-11', | ||
balance: '3.8AE83A109D0635A426BB', | ||
}, | ||
], | ||
}); | ||
}); | ||
|
||
test('should return error, invalid address format', async () => { | ||
const invalidAddress = '0x0'; | ||
const response = await app.inject({ | ||
method: 'GET', | ||
url: `/get_historical_balance?address=${invalidAddress}`, | ||
}); | ||
|
||
expect(response.statusCode).toBe(400); | ||
expect(response.json()).toHaveProperty('error', 'Invalid address format'); | ||
}); | ||
|
||
test('should return error, no address provided', async () => { | ||
const response = await app.inject({ | ||
method: 'GET', | ||
url: '/get_historical_balance', | ||
}); | ||
|
||
expect(response.statusCode).toBe(400); | ||
expect(response.json()).toHaveProperty('error', 'Address is required'); | ||
}); | ||
|
||
test('should return [], unknown address', async () => { | ||
const unknownAddress = '0x064a24243f2aabae8d2148fa878276e6e6e452e3941b417f3c33b1649ea83e12'; | ||
const response = await app.inject({ | ||
method: 'GET', | ||
url: `/get_historical_balance?address=${unknownAddress}`, | ||
}); | ||
|
||
expect(response.statusCode).toBe(200); | ||
expect(response.json()).toEqual({ historicalBalances: [] }); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove that