-
Notifications
You must be signed in to change notification settings - Fork 1
feat: restore api search #159
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
base: main
Are you sure you want to change the base?
Changes from 9 commits
1acc06a
533975b
757d1bb
c828f27
b95857c
cddf736
5d24b4d
814204b
998c76d
49af8db
2824fe8
80aa1ae
0ced8ec
61723c6
94ee9a0
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,49 @@ | ||
| import { getTokenListBySearchParam } from '@cowprotocol/repositories'; | ||
| import { FastifyPluginAsync } from 'fastify'; | ||
| import { | ||
| errorSchema, | ||
| ErrorSchema, | ||
| paramsSchema, | ||
| RouteSchema, | ||
| successSchema, | ||
| SuccessSchema, | ||
| } from './schemas'; | ||
|
|
||
| const root: FastifyPluginAsync = async (fastify): Promise<void> => { | ||
| // example: http://localhost:3010/1/tokens/search/USDC | ||
| fastify.get<{ | ||
| Params: RouteSchema; | ||
| Reply: SuccessSchema | ErrorSchema; | ||
| }>( | ||
| '/', | ||
| { | ||
| schema: { | ||
| params: paramsSchema, | ||
| response: { | ||
| '2XX': successSchema, | ||
| '500': errorSchema, | ||
| }, | ||
| }, | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| }, | ||
| async function (request, reply) { | ||
| const { chainId, searchParam } = request.params; | ||
|
|
||
| try { | ||
| const tokens = await getTokenListBySearchParam(chainId, searchParam); | ||
|
|
||
| fastify.log.info( | ||
| `Token search for "${searchParam}" on chain ${chainId}: ${tokens.length} tokens found` | ||
| ); | ||
|
|
||
| reply.send(tokens); | ||
| } catch (error) { | ||
| fastify.log.error('Error searching tokens:', error); | ||
| reply.code(500).send({ | ||
| message: 'Internal server error while searching tokens', | ||
| }); | ||
| } | ||
| } | ||
| ); | ||
| }; | ||
|
|
||
| export default root; | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,81 @@ | ||||||
| import { FromSchema, JSONSchema } from 'json-schema-to-ts'; | ||||||
| import { SupportedChainIdSchema } from '../../../../../schemas'; | ||||||
| import { AllChainIds } from '@cowprotocol/shared'; | ||||||
|
|
||||||
| export const paramsSchema = { | ||||||
| type: 'object', | ||||||
| required: ['chainId', 'searchParam'], | ||||||
| additionalProperties: false, | ||||||
| properties: { | ||||||
| chainId: SupportedChainIdSchema, | ||||||
| searchParam: { | ||||||
| title: 'Search Parameter', | ||||||
| description: 'Token search parameter (name, symbol, or address)', | ||||||
| type: 'string', | ||||||
| minLength: 3, | ||||||
| maxLength: 100, | ||||||
| }, | ||||||
| }, | ||||||
| } as const satisfies JSONSchema; | ||||||
|
|
||||||
| export const successSchema = { | ||||||
| type: 'array', | ||||||
| items: { | ||||||
| type: 'object', | ||||||
| required: ['chainId', 'address', 'name', 'symbol', 'decimals', 'logoURI'], | ||||||
|
Collaborator
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'd say the logoURI is optional
Suggested change
|
||||||
| additionalProperties: false, | ||||||
| properties: { | ||||||
| chainId: { | ||||||
| title: 'Chain ID', | ||||||
| description: 'Blockchain network identifier.', | ||||||
| type: 'integer', | ||||||
| enum: AllChainIds, | ||||||
| }, | ||||||
| address: { | ||||||
| title: 'Token Address', | ||||||
| description: 'Contract address of the token.', | ||||||
| type: 'string', | ||||||
| pattern: '^0x[a-fA-F0-9]{40}$', | ||||||
| }, | ||||||
| name: { | ||||||
| title: 'Name', | ||||||
| description: 'Full name of the token.', | ||||||
| type: 'string', | ||||||
| }, | ||||||
| symbol: { | ||||||
| title: 'Symbol', | ||||||
| description: 'Token symbol/ticker.', | ||||||
| type: 'string', | ||||||
| }, | ||||||
| decimals: { | ||||||
| title: 'Decimals', | ||||||
| description: 'Number of decimal places for the token.', | ||||||
| type: 'integer', | ||||||
| minimum: 0, | ||||||
| maximum: 18, | ||||||
|
Collaborator
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 standard is 18, but it's actually not the max AFAICT. https://ethereum.stackexchange.com/questions/118896/can-an-erc-20-have-more-than-18-decimals. I'd remove it:
Suggested change
|
||||||
| }, | ||||||
| logoURI: { | ||||||
| title: 'Logo URI', | ||||||
| description: 'URI to the token logo.', | ||||||
| type: 'string', | ||||||
| }, | ||||||
| }, | ||||||
| }, | ||||||
| } as const satisfies JSONSchema; | ||||||
|
|
||||||
| export const errorSchema = { | ||||||
| type: 'object', | ||||||
| required: ['message'], | ||||||
| additionalProperties: false, | ||||||
| properties: { | ||||||
| message: { | ||||||
| title: 'Message', | ||||||
| description: 'Message describing the error.', | ||||||
| type: 'string', | ||||||
| }, | ||||||
| }, | ||||||
| } as const satisfies JSONSchema; | ||||||
|
|
||||||
| export type RouteSchema = FromSchema<typeof paramsSchema>; | ||||||
| export type SuccessSchema = FromSchema<typeof successSchema>; | ||||||
| export type ErrorSchema = FromSchema<typeof errorSchema>; | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| import Fastify from 'fastify'; | ||
| import { app } from './app/app'; | ||
| import { logger } from '@cowprotocol/shared'; | ||
| import { getTokenCacheRepository } from '@cowprotocol/services'; | ||
| import { setTokenCacheRepository } from '@cowprotocol/repositories'; | ||
|
|
||
| const host = process.env.HOST ?? 'localhost'; | ||
| const port = process.env.PORT ? Number(process.env.PORT) : 3001; | ||
|
|
@@ -10,6 +12,9 @@ export const server = Fastify({ | |
| logger, | ||
| }); | ||
|
|
||
| const tokenCacheRepository = getTokenCacheRepository(); | ||
| setTokenCacheRepository(tokenCacheRepository); | ||
|
|
||
|
Comment on lines
+15
to
+17
Contributor
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. 🛠️ Refactor suggestion Unconditional Redis dependency here can crash API startup; remove wiring from main or guard it. Recommended (remove from main; let inversify.config.ts own lifecycle): -import { getTokenCacheRepository } from '@cowprotocol/services';
-import { setTokenCacheRepository } from '@cowprotocol/repositories';
...
-const tokenCacheRepository = getTokenCacheRepository();
-setTokenCacheRepository(tokenCacheRepository);If you must keep it in main, at least guard: +import { getTokenCacheRepository } from '@cowprotocol/services';
+import { setTokenCacheRepository } from '@cowprotocol/repositories';
...
-const tokenCacheRepository = getTokenCacheRepository();
-setTokenCacheRepository(tokenCacheRepository);
+try {
+ const tokenCacheRepository = getTokenCacheRepository();
+ setTokenCacheRepository(tokenCacheRepository);
+ server.log.info('Token cache repository initialized');
+} catch (err) {
+ server.log.warn({ err }, 'Token cache repository not initialized; proceeding without Redis');
+}Also applies to: 4-5 🤖 Prompt for AI Agents |
||
| // Register your application as a normal plugin. | ||
| server.register(app); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "extends": ["../../.eslintrc.json"], | ||
| "ignorePatterns": ["!**/*"], | ||
| "overrides": [ | ||
| { | ||
| "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], | ||
| "rules": {} | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| /* eslint-disable */ | ||
| export default { | ||
| displayName: 'token-list-updater-e2e', | ||
| preset: '../..//jest.preset.js', | ||
| setupFiles: ['<rootDir>/src/test-setup.ts'], | ||
| testEnvironment: 'node', | ||
| transform: { | ||
| '^.+\\.[tj]s$': [ | ||
| 'ts-jest', | ||
| { | ||
| tsconfig: '<rootDir>/tsconfig.spec.json', | ||
| }, | ||
| ], | ||
| }, | ||
| moduleFileExtensions: ['ts', 'js', 'html'], | ||
| coverageDirectory: '../..//coverage/token-list-updater-e2e', | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| { | ||
| "name": "token-list-updater-e2e", | ||
| "$schema": "../../node_modules/nx/schemas/project-schema.json", | ||
| "implicitDependencies": ["token-list-updater"], | ||
| "targets": { | ||
| "e2e": { | ||
| "executor": "@nx/jest:jest", | ||
| "outputs": ["{workspaceRoot}/coverage/{e2eProjectRoot}"], | ||
| "options": { | ||
| "jestConfig": "apps/token-list-updater-e2e/jest.config.ts", | ||
| "passWithNoTests": true | ||
| } | ||
| }, | ||
| "lint": { | ||
| "executor": "@nx/linter:eslint", | ||
| "outputs": ["{options.outputFile}"], | ||
| "options": { | ||
| "lintFilePatterns": ["apps/token-list-updater-e2e/**/*.{js,ts}"] | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| // |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,12 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { execSync } from 'child_process'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { join } from 'path'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| describe('CLI tests', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| it('should print a message', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const cliPath = join(process.cwd(), 'dist/apps/token-list-updater'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const output = execSync(`node ${cliPath}`).toString(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(output).toMatch(/Hello World/); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+12
Contributor
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. 🛠️ Refactor suggestion Point to the built entry file and use execFileSync with a timeout The current test runs a directory path via a shell. Use the compiled main file and avoid shell interpolation; add a timeout to prevent hangs. -import { execSync } from 'child_process';
+import { execFileSync } from 'child_process';
import { join } from 'path';
describe('CLI tests', () => {
it('should print a message', () => {
- const cliPath = join(process.cwd(), 'dist/apps/token-list-updater');
-
- const output = execSync(`node ${cliPath}`).toString();
+ const cliPath = join(process.cwd(), 'dist/apps/token-list-updater/main.js');
+ const output = execFileSync('node', [cliPath], {
+ encoding: 'utf8',
+ timeout: 15_000,
+ });
expect(output).toMatch(/Hello World/);
});
});📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "extends": "../../tsconfig.base.json", | ||
| "files": [], | ||
| "include": [], | ||
| "references": [ | ||
| { | ||
| "path": "./tsconfig.spec.json" | ||
| } | ||
| ], | ||
| "compilerOptions": { | ||
| "esModuleInterop": true | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "extends": "./tsconfig.json", | ||
| "compilerOptions": { | ||
| "outDir": "../..//dist/out-tsc", | ||
| "module": "commonjs", | ||
| "types": ["jest", "node"] | ||
| }, | ||
| "include": ["jest.config.ts", "src/**/*.ts"] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "extends": ["../../.eslintrc.json"], | ||
| "ignorePatterns": ["!**/*"], | ||
| "overrides": [ | ||
| { | ||
| "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], | ||
| "rules": {} | ||
| }, | ||
| { | ||
| "files": ["*.ts", "*.tsx"], | ||
| "rules": {} | ||
| }, | ||
| { | ||
| "files": ["*.js", "*.jsx"], | ||
| "rules": {} | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,24 @@ | ||||||||||||||||||||||||||
| # This file is generated by Nx. | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Build the docker image with `npx nx docker-build token-list-updater`. | ||||||||||||||||||||||||||
| # Tip: Modify "docker-build" options in project.json to change docker build args. | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # Run the container with `docker run -p 3000:3000 -t token-list-updater`. | ||||||||||||||||||||||||||
| FROM docker.io/node:lts-alpine | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| ENV HOST=0.0.0.0 | ||||||||||||||||||||||||||
| ENV PORT=3000 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| WORKDIR /app | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| RUN addgroup --system token-list-updater && \ | ||||||||||||||||||||||||||
| adduser --system -G token-list-updater token-list-updater | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| COPY dist/apps/token-list-updater token-list-updater | ||||||||||||||||||||||||||
| RUN chown -R token-list-updater:token-list-updater . | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
|
Comment on lines
+14
to
+19
Contributor
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. 🛠️ Refactor suggestion Run as non-root for container hardening. You create the user and chown but never switch to it. Apply: RUN addgroup --system token-list-updater && \
adduser --system -G token-list-updater token-list-updater
@@
RUN chown -R token-list-updater:token-list-updater .
+
+USER token-list-updater:token-list-updater📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| # You can remove this install step if you build with `--bundle` option. | ||||||||||||||||||||||||||
| # The bundled output will include external dependencies. | ||||||||||||||||||||||||||
| RUN npm --prefix token-list-updater --omit=dev -f install | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| CMD [ "node", "token-list-updater/main.js" ] | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| /* eslint-disable */ | ||
| export default { | ||
| displayName: 'token-list-updater', | ||
| preset: '../../jest.preset.js', | ||
| testEnvironment: 'node', | ||
| transform: { | ||
| '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }], | ||
| }, | ||
| moduleFileExtensions: ['ts', 'js', 'html'], | ||
| coverageDirectory: '../../coverage/apps/token-list-updater', | ||
| }; |
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.
Guard token-cache DI binding; don’t crash when Redis is absent
getTokenCacheRepository() throws without Redis. Wrap creation, bind only if available, and keep startup consistent with main.ts’ graceful path.
Longer-term, consider sourcing the single instance from DI and calling setTokenCacheRepository() with that instance to avoid duplication and ensure consistent lifecycle.
Also applies to: 92-95
🤖 Prompt for AI Agents