diff --git a/frontend/src/components/Editor/EditorComponents/GenerateCode.jsx b/frontend/src/components/Editor/EditorComponents/GenerateCode.jsx
index c83d8bcd..dedc6f63 100644
--- a/frontend/src/components/Editor/EditorComponents/GenerateCode.jsx
+++ b/frontend/src/components/Editor/EditorComponents/GenerateCode.jsx
@@ -2,71 +2,71 @@
import { useState } from 'react';
const GenerateCode = () => {
- const [contractFilter, setContractFilter] = useState('');
- const [selectedMethods, setSelectedMethods] = useState([]);
- const [selectedEvents, setSelectedEvents] = useState([]);
- const [generatedCode, setGeneratedCode] = useState({ jsCode: '', sqlCode: '' });
+ const [contractFilter, setContractFilter] = useState('');
+ const [selectedMethods, setSelectedMethods] = useState([]);
+ const [selectedEvents, setSelectedEvents] = useState([]);
+ const [generatedCode, setGeneratedCode] = useState({ jsCode: '', sqlCode: '' });
- const handleGenerateCode = async () => {
- const response = await fetch('/api/generateCode', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ contractFilter, selectedMethods, selectedEvents }),
- });
- const data = await response.json();
- setGeneratedCode(data);
- };
+ const handleGenerateCode = async () => {
+ const response = await fetch('/api/generateCode', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ contractFilter, selectedMethods, selectedEvents }),
+ });
+ const data = await response.json();
+ setGeneratedCode(data);
+ };
- return (
-
-
-
Generate Code
-
- setContractFilter(e.target.value)}
- className="w-full p-3 border border-gray-300 rounded-md"
- />
-
-
- setSelectedMethods(e.target.value.split(','))}
- className="w-full p-3 border border-gray-300 rounded-md"
- />
-
-
- setSelectedEvents(e.target.value.split(','))}
- className="w-full p-3 border border-gray-300 rounded-md"
- />
-
-
-
-
Generated JavaScript Code
-
{generatedCode.jsCode}
-
-
-
Generated SQL Code
-
{generatedCode.sqlCode}
-
-
+ return (
+
+
+
Generate Code
+
+ setContractFilter(e.target.value)}
+ className="w-full p-3 border border-gray-300 rounded-md"
+ />
- );
+
+ setSelectedMethods(e.target.value.split(','))}
+ className="w-full p-3 border border-gray-300 rounded-md"
+ />
+
+
+ setSelectedEvents(e.target.value.split(','))}
+ className="w-full p-3 border border-gray-300 rounded-md"
+ />
+
+
+
+
Generated JavaScript Code
+
{generatedCode.jsCode}
+
+
+
Generated SQL Code
+
{generatedCode.sqlCode}
+
+
+
+ );
};
export default GenerateCode;
diff --git a/frontend/src/pages/api/generateCode.js b/frontend/src/pages/api/generateCode.js
deleted file mode 100644
index 44d121ce..00000000
--- a/frontend/src/pages/api/generateCode.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import { defaultCode, defaultSchema, } from '../../utils/formatters';
-
-export default function handler(req, res) {
- res.setHeader('Access-Control-Allow-Origin', '*');
- res.setHeader('Access-Control-Allow-Methods', 'POST');
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
-
- if (req.method === 'OPTIONS') {
- res.status(200).end();
- return;
- }
-
- const { contractFilter, selectedMethods, selectedEvents } = req.body;
-
- if (!contractFilter || !selectedMethods || !selectedEvents) {
- return res.status(400).json({ error: 'Missing required fields' });
- }
-
- if (!Array.isArray(selectedMethods) || !Array.isArray(selectedEvents)) {
- return res.status(400).json({ error: 'selectedMethods and selectedEvents must be arrays' });
- }
-
- const jsCode = generateDummyJSCode(contractFilter, selectedMethods, selectedEvents);
- const sqlCode = generateDummySQLCode(contractFilter, selectedMethods, selectedEvents);
-
- res.status(200).json({ jsCode, sqlCode });
-}
-
-function generateDummyJSCode(contractFilter, selectedMethods, selectedEvents) {
- let jsCode = `// JavaScript Code\n\n`;
- jsCode += `-- Contract Filter: ${contractFilter}\n\n`;
- jsCode += `-- Selected Methods: ${selectedMethods}\n\n`;
- jsCode += `-- Selected Events: ${selectedEvents}\n\n`;
-
- jsCode += defaultCode;
-
- selectedMethods.forEach(method => {
- jsCode += `function ${method}() {\n`;
- jsCode += ` console.log('Executing ${method}');\n`;
- jsCode += `}\n\n`;
- });
-
- selectedEvents.forEach(event => {
- jsCode += `function handle${event}() {\n`;
- jsCode += ` console.log('Handling event ${event}');\n`;
- jsCode += `}\n\n`;
- });
-
- return jsCode;
-}
-
-function generateDummySQLCode(contractFilter, selectedMethods, selectedEvents) {
- let sqlCode = `-- SQL Code\n\n`;
- sqlCode += `-- Contract Filter: ${contractFilter}\n\n`;
- sqlCode += `-- Selected Methods: ${selectedMethods}\n\n`;
- sqlCode += `-- Selected Events: ${selectedEvents}\n\n`;
-
- sqlCode += defaultSchema;
-
- selectedMethods.forEach(method => {
- sqlCode += `-- Method: ${method}\n`;
- sqlCode += `INSERT INTO methods (name) VALUES ('${method}');\n\n`;
- });
-
- selectedEvents.forEach(event => {
- sqlCode += `-- Event: ${event}\n`;
- sqlCode += `INSERT INTO events (name) VALUES ('${event}');\n\n`;
- });
-
- return sqlCode;
-}
diff --git a/frontend/src/pages/api/generateCode.ts b/frontend/src/pages/api/generateCode.ts
new file mode 100644
index 00000000..50c0d8bd
--- /dev/null
+++ b/frontend/src/pages/api/generateCode.ts
@@ -0,0 +1,98 @@
+import type { NextApiRequest, NextApiResponse } from 'next';
+
+import { defaultCode, defaultSchema } from '../../utils/formatters';
+
+interface RequestBody {
+ contractFilter: string | string[];
+ selectedMethods: string[];
+ selectedEvents: string[];
+}
+
+const validateRequestBody = (body: any): body is RequestBody => {
+ const isStringOrArray = (value: any): value is string | string[] =>
+ typeof value === 'string' || (Array.isArray(value) && value.every((item) => typeof item === 'string'));
+
+ return (
+ isStringOrArray(body.contractFilter) &&
+ Array.isArray(body.selectedMethods) &&
+ body.selectedMethods.every((method: any) => typeof method === 'string') &&
+ Array.isArray(body.selectedEvents) &&
+ body.selectedEvents.every((event: any) => typeof event === 'string')
+ );
+};
+
+const generateDummyJSCode = (
+ contractFilter: string | string[],
+ selectedMethods: string[],
+ selectedEvents: string[],
+): string => {
+ const filterString = Array.isArray(contractFilter) ? contractFilter.join(', ') : contractFilter;
+ const jsCodeHeader =
+ `// JavaScript Code\n\n` +
+ `-- Contract Filter: ${filterString}\n\n` +
+ `-- Selected Methods: ${selectedMethods.join(', ')}\n\n` +
+ `-- Selected Events: ${selectedEvents.join(', ')}\n\n`;
+
+ const methodsJS = selectedMethods
+ .map((method) => `function ${method}() {\n console.log('Executing ${method}');\n}\n\n`)
+ .join('');
+
+ const eventsJS = selectedEvents
+ .map((event) => `function handle${event}() {\n console.log('Handling event ${event}');\n}\n\n`)
+ .join('');
+
+ return jsCodeHeader + defaultCode + methodsJS + eventsJS;
+};
+
+const generateDummySQLCode = (
+ contractFilter: string | string[],
+ selectedMethods: string[],
+ selectedEvents: string[],
+): string => {
+ const filterString = Array.isArray(contractFilter) ? contractFilter.join(', ') : contractFilter;
+ const sqlCodeHeader =
+ `-- SQL Code\n\n` +
+ `-- Contract Filter: ${filterString}\n\n` +
+ `-- Selected Methods: ${selectedMethods.join(', ')}\n\n` +
+ `-- Selected Events: ${selectedEvents.join(', ')}\n\n`;
+
+ const methodsSQL = selectedMethods
+ .map((method) => `-- Method: ${method}\nINSERT INTO methods (name) VALUES ('${method}');\n\n`)
+ .join('');
+
+ const eventsSQL = selectedEvents
+ .map((event) => `-- Event: ${event}\nINSERT INTO events (name) VALUES ('${event}');\n\n`)
+ .join('');
+
+ return sqlCodeHeader + defaultSchema + methodsSQL + eventsSQL;
+};
+
+export default function handler(req: NextApiRequest, res: NextApiResponse): void {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ res.setHeader('Access-Control-Allow-Methods', 'POST');
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
+
+ if (req.method === 'OPTIONS') {
+ res.status(200).end();
+ return;
+ }
+ if (req.method !== 'POST') {
+ res.status(405).json({ error: 'Method Not Allowed' });
+ return;
+ }
+
+ if (!validateRequestBody(req.body)) {
+ res.status(400).json({
+ error:
+ 'Invalid request body: contractFilter must be a string or an array of strings, and selectedMethods and selectedEvents must be arrays of strings',
+ });
+ return;
+ }
+
+ const { contractFilter, selectedMethods, selectedEvents } = req.body;
+
+ const jsCode = generateDummyJSCode(contractFilter, selectedMethods, selectedEvents);
+ const sqlCode = generateDummySQLCode(contractFilter, selectedMethods, selectedEvents);
+
+ res.status(200).json({ jsCode, sqlCode });
+}
diff --git a/frontend/src/pages/query-api-editor/index.js b/frontend/src/pages/query-api-editor/index.js
index 32e2d2d1..87750c60 100644
--- a/frontend/src/pages/query-api-editor/index.js
+++ b/frontend/src/pages/query-api-editor/index.js
@@ -3,9 +3,9 @@ import React, { useContext, useEffect } from 'react';
import { Alert } from 'react-bootstrap';
import Editor from '@/components/Editor/EditorComponents/Editor';
+import GenerateCode from '@/components/Editor/EditorComponents/GenerateCode';
import IndexerLogsContainer from '@/components/Logs/LogsViewContainer/IndexerLogsContainer';
import { IndexerDetailsContext } from '@/contexts/IndexerDetailsContext';
-import GenerateCode from '@/components/Editor/EditorComponents/GenerateCode';
const QueryApiEditorPage = ({ router }) => {
const { accountId, indexerName } = router.query;
@@ -26,9 +26,7 @@ const QueryApiEditorPage = ({ router }) => {
}
if (accountId == 'test' || indexerName == 'test') {
- return (
-
- );
+ return
;
}
return showLogsView ?
:
;
diff --git a/frontend/src/test/api/generateCode.test.js b/frontend/src/test/api/generateCode.test.js
index ff3156f0..4fa52f28 100644
--- a/frontend/src/test/api/generateCode.test.js
+++ b/frontend/src/test/api/generateCode.test.js
@@ -1,66 +1,189 @@
-import handler from '../../pages/api/generateCode';
import { createMocks } from 'node-mocks-http';
-jest.mock('../../utils/formatters', () => ({
- defaultCode: '// Default JS Code',
- defaultSchema: '-- Default SQL Schema',
-}));
+import handler from '../../pages/api/generateCode';
describe('API Handler', () => {
- it('should return generated JS and SQL code for valid input', async () => {
- const { req, res } = createMocks({
- method: 'POST',
- body: {
- contractFilter: 'filter',
- selectedMethods: ['method1', 'method2'],
- selectedEvents: ['event1', 'event2'],
- },
- });
+ it('should return generated JS and SQL code for valid input', async () => {
+ const { req, res } = createMocks({
+ method: 'POST',
+ body: {
+ contractFilter: 'filter',
+ selectedMethods: ['method1', 'method2'],
+ selectedEvents: ['event1', 'event2'],
+ },
+ });
+
+ await handler(req, res);
+
+ expect(res._getStatusCode()).toBe(200);
+ const responseData = JSON.parse(res._getData());
+ expect(responseData).toHaveProperty('jsCode');
+ expect(responseData).toHaveProperty('sqlCode');
+ });
+
+ it('should return 400 if required fields are missing', async () => {
+ const { req, res } = createMocks({
+ method: 'POST',
+ body: {},
+ });
+
+ await handler(req, res);
+
+ expect(res._getStatusCode()).toBe(400);
+ expect(JSON.parse(res._getData())).toEqual({
+ error:
+ 'Invalid request body: contractFilter must be a string or an array of strings, and selectedMethods and selectedEvents must be arrays of strings',
+ });
+ });
+
+ it('should return 400 if selectedMethods or selectedEvents are not arrays', async () => {
+ const { req, res } = createMocks({
+ method: 'POST',
+ body: {
+ contractFilter: 'filter',
+ selectedMethods: 'not-an-array',
+ selectedEvents: [],
+ },
+ });
+
+ await handler(req, res);
+
+ expect(res._getStatusCode()).toBe(400);
+ expect(JSON.parse(res._getData())).toEqual({
+ error:
+ 'Invalid request body: contractFilter must be a string or an array of strings, and selectedMethods and selectedEvents must be arrays of strings',
+ });
+
+ const { req: req2, res: res2 } = createMocks({
+ method: 'POST',
+ body: {
+ contractFilter: 'filter',
+ selectedMethods: [],
+ selectedEvents: 'not-an-array',
+ },
+ });
+
+ await handler(req2, res2);
- await handler(req, res);
+ expect(res2._getStatusCode()).toBe(400);
+ expect(JSON.parse(res2._getData())).toEqual({
+ error:
+ 'Invalid request body: contractFilter must be a string or an array of strings, and selectedMethods and selectedEvents must be arrays of strings',
+ });
+ });
+
+ it('should handle OPTIONS request correctly', async () => {
+ const { req, res } = createMocks({
+ method: 'OPTIONS',
+ });
+
+ await handler(req, res);
+
+ expect(res._getStatusCode()).toBe(200);
+ });
+
+ it('should handle empty arrays correctly', async () => {
+ const { req, res } = createMocks({
+ method: 'POST',
+ body: {
+ contractFilter: 'filter',
+ selectedMethods: [],
+ selectedEvents: [],
+ },
+ });
+
+ await handler(req, res);
+
+ expect(res._getStatusCode()).toBe(200);
+ const responseData = JSON.parse(res._getData());
+ expect(responseData).toHaveProperty('jsCode');
+ expect(responseData).toHaveProperty('sqlCode');
+ expect(responseData.jsCode).toContain('// JavaScript Code');
+ expect(responseData.sqlCode).toContain('-- SQL Code');
+ });
- expect(res._getStatusCode()).toBe(200);
+ it('should return 400 for invalid contractFilter data type', async () => {
+ const { req, res } = createMocks({
+ method: 'POST',
+ body: {
+ contractFilter: 123,
+ selectedMethods: ['method1'],
+ selectedEvents: ['event1'],
+ },
});
- it('should return 400 if required fields are missing', async () => {
- const { req, res } = createMocks({
- method: 'POST',
- body: {},
- });
+ await handler(req, res);
- await handler(req, res);
+ expect(res._getStatusCode()).toBe(400);
+ expect(JSON.parse(res._getData())).toEqual({
+ error:
+ 'Invalid request body: contractFilter must be a string or an array of strings, and selectedMethods and selectedEvents must be arrays of strings',
+ });
+ });
- expect(res._getStatusCode()).toBe(400);
- expect(JSON.parse(res._getData())).toEqual({ error: 'Missing required fields' });
+ it('should return 400 for invalid method or event data types', async () => {
+ const { req, res } = createMocks({
+ method: 'POST',
+ body: {
+ contractFilter: 'filter',
+ selectedMethods: [123],
+ selectedEvents: ['event1'],
+ },
});
- it('should return 400 if selectedMethods or selectedEvents are not arrays', async () => {
- const { req, res } = createMocks({
- method: 'POST',
- body: {
- contractFilter: 'filter',
- selectedMethods: 'not-an-array',
- selectedEvents: [],
- },
- });
+ await handler(req, res);
- await handler(req, res);
+ expect(res._getStatusCode()).toBe(400);
+ expect(JSON.parse(res._getData())).toEqual({
+ error:
+ 'Invalid request body: contractFilter must be a string or an array of strings, and selectedMethods and selectedEvents must be arrays of strings',
+ });
+ });
- expect(res._getStatusCode()).toBe(400);
- expect(JSON.parse(res._getData())).toEqual({ error: 'selectedMethods and selectedEvents must be arrays' });
+ it('should handle large inputs correctly', async () => {
+ const largeArray = Array.from({ length: 1000 }, (_, i) => `method${i}`);
+ const { req, res } = createMocks({
+ method: 'POST',
+ body: {
+ contractFilter: 'filter',
+ selectedMethods: largeArray,
+ selectedEvents: largeArray,
+ },
+ });
- const { req: req2, res: res2 } = createMocks({
- method: 'POST',
- body: {
- contractFilter: 'filter',
- selectedMethods: [],
- selectedEvents: 'not-an-array',
- },
- });
+ await handler(req, res);
- await handler(req2, res2);
+ expect(res._getStatusCode()).toBe(200);
+ const responseData = JSON.parse(res._getData());
+ expect(responseData).toHaveProperty('jsCode');
+ expect(responseData).toHaveProperty('sqlCode');
+ });
- expect(res2._getStatusCode()).toBe(400);
- expect(JSON.parse(res2._getData())).toEqual({ error: 'selectedMethods and selectedEvents must be arrays' });
+ it('should return 405 for unsupported HTTP methods', async () => {
+ const { req, res } = createMocks({
+ method: 'GET',
});
+
+ await handler(req, res);
+
+ expect(res._getStatusCode()).toBe(405);
+ expect(JSON.parse(res._getData())).toEqual({ error: 'Method Not Allowed' });
+ });
+
+ it('should have correct CORS headers', async () => {
+ const { req, res } = createMocks({
+ method: 'POST',
+ body: {
+ contractFilter: 'filter',
+ selectedMethods: ['method1'],
+ selectedEvents: ['event1'],
+ },
+ });
+
+ await handler(req, res);
+
+ expect(res._getHeaders()).toHaveProperty('access-control-allow-origin', '*');
+ expect(res._getHeaders()).toHaveProperty('access-control-allow-methods', 'POST');
+ expect(res._getHeaders()).toHaveProperty('access-control-allow-headers', 'Content-Type');
+ });
});
diff --git a/frontend/src/utils/pgSchemaTypeGen.js b/frontend/src/utils/pgSchemaTypeGen.js
index a53df000..d4e4a4fb 100644
--- a/frontend/src/utils/pgSchemaTypeGen.js
+++ b/frontend/src/utils/pgSchemaTypeGen.js
@@ -103,9 +103,7 @@ export class PgSchemaTypeGen {
Object.prototype.hasOwnProperty.call(columnSpec, 'definition')
) {
this.addColumn(columnSpec, columns);
- } else if (
- columnSpec.constraint_type === 'primary key'
- ) {
+ } else if (columnSpec.constraint_type === 'primary key') {
for (const foreignKeyDef of columnSpec.definition) {
columns[foreignKeyDef.column.expr.value].nullable = false;
}
@@ -211,7 +209,7 @@ export class PgSchemaTypeGen {
const tsType = columnDetails.nullable ? columnDetails.type + ' | null' : columnDetails.type;
const optional = columnDetails.required ? '' : '?';
- queryDefinition += ` ${columnName}?: ${tsType} | ${tsType}[];\n`
+ queryDefinition += ` ${columnName}?: ${tsType} | ${tsType}[];\n`;
itemDefinition += ` ${columnName}?: ${tsType};\n`;
inputDefinition += ` ${columnName}${optional}: ${tsType};\n`;
}